- Improve interactive view error/warning capture
- Update capitalization cleanup postprocessing to support sentences
ending before quotation marks and other matching delimiters like brackets.
For instance,
"Test." test
will now correct to"Test." Test
where previously it would not. - Fix visual line breaks when backslash is followed by whitespace
- Update punctuation cleanup postprocessing to shift punctuation to the
right of quotes (single, double, or underscores). For example
"test" .
will now correct to"test".
. Note that this does not shift punctuation to the inside of quotes, as this is context and style dependent. - Move working directory from top-level call argument to render settings.
- Update experimental in-eval ref lookup. Now exposed by two
functions,
bml.ref(id)
(returns rendered output of ref) andbml.refDetail(id)
(returns ref output, index, and original fork object). The new functions should be lazily evaluated so they act on the fork map state at the time of function use, not function declaration. - In interactive mode, make spacebar also force refresh
- Update punctuation cleanup to work around more script types (like Chinese)
- Make interactive view support unicode
- Fix interactive view confusingly suppressing error messages, for example when trying to read a non-existent file.
- Provide better error messages when including non-existent files.
- Fix bug where files included from included files were being done so relative to the working directory of the top-level script. Includes should now always be relative to the containing file.
- Change include behavior so duplicate references and eval bindings are now allowed, and silently overwrite previous bindings. This is needed to support repeated includes (caused by diamond-like include graphs) without using namespacing.
- Support set forks with
{$id: (foo), (bar)}
syntax. Silent set forks are also supported.
- Fix bug introduced in 0.1.6 article correction
- Fix bug in relative path includes on non-interactive execution. (Yet another bug caused by the very confusing way cli.ts is written.)
- Make punctuation cleanup include dashes (hyphens, en-dash, and em-dash)
- Support re-executing forks with the syntax
{@!foo}
, which is the equivalent of rewriting the original{@foo}
fork: re-executing it, inserting its result, and updating the executed fork map accordingly.
- Add and enable by default correction of English indefinite articles (a / an).
This can be disabled using the new document setting
indefiniteArticleCleanup: false
. - Make static analysis handle unmapped refs more gracefully, since these are expected while includes are not analyzed.
- Make
include
command paths relative to parent script's location - Fix bug with plaintext parentheses
- Experimentally expose fork results in eval blocks
- Support including other BML scripts using the function
include
exposed in eval blocks.
- Interactive view improvements
- Support copying last render to clipboard
- Display analysis statistics
- Support non-ascii characters in fork ids
- Add an interactive mode in the CLI with
--interactive
.
- Add support for static branch-counting analysis. This can be done
with the cli using
--analyze
and through the library withbml.analyze
In this release, the language and parser have been largely rewritten, with substantial improvements and feature changes. The most commonly used features are unchanged, and many old programs should continue to work in this version.
- Support for replacer rules and modes has been removed. This includes
things like the
mode
anduse
keywords. Users who want this functionality should instead do this with a custom post-processor. - The document prelude section has been removed.
- The language nomenclature has been changed to improve clarity:
- "Fork" refers to any curly-braces
{}
block, including common replacers and references (formerly called "back references"). - "Branch" refers to any possible execution path a fork can go down. This includes text blocks, eval blocks, and nested forks.
- "Reference" and "Ref" refer to what used to be called "back
reference" blocks, e.g.
{@foo: 0 -> (bar)}
and{@foo}
. - The labels that identify forks are now called "Ids" or "ForkIds".
- "Fork" refers to any curly-braces
- Eval blocks have been completely overhauled
- The
eval
block has been changed to an eval directive usable only as branches in forks, marked with single square brackets, for example{[js code]}
- The
provide()
function has been replaced with abind()
function that accepts any object where its keys are valid javascript identifiers. Bound values are made available as local variables in the scope of subsequent eval blocks, and mutations to these values are persisted in the bound context. - Document settings are now set through this
bind()
function using the reserved key namesettings
. This can be bound anywhere, though it is recommended to do so at the top of the document in a bare choice block, e.g.{[bind(settings: {...})]}
- The
call
keyword syntax has been replaced with eval blocks. To insert text from an eval block, use the newinsert()
function, e.g.{[insert('foo')]}
. - Attempting to bind the same field more than once will result in an error.
- The
- Reference blocks can now include multiple fallback branches, including those with weights, which are grouped together and used to make a fallback fork.
- For more convenient nesting, forks can be used directly themselves
as branches. What used to be
{@foo: 0 -> ({(bar), (biz)})}
can now be written{@foo: 0 -> {(bar), (biz)}}
, omitting the branch parentheses. - The interpreter has been divided cleanly into a separate parser and renderer, allowing proper static analysis. This will allow fairly straightforward branch counting in the future.
- Comments should now properly be well-supported in all contexts
- Make line comments emit a single newline. This fixes the behavior of
lines which end in line comments. like
foo // comment\n
- Require line comments to be preceded or followed by a whitespace or
beginning/end of input. This is needed to allow writing things like
URLs which use the
//
sequence.
-
Remove built-in markdown support. This has long been an outlier feature that needlessly bloats the library bundle size for users which don't need it. Users needing markdown should now pull in a markdown rendering library themselves and manually run it on BML's output. This results in a web bundle size reduction of ~16kb, nearly a third.
- To exactly replicate old behavior, use
[email protected]
and manually plug in any markdown settings needed. - The CLI
--render-markdown
flag has been removed - The BML document setting
markdownSettings
has been removed
- To exactly replicate old behavior, use
-
Change the signature for custom JS functions. The new signature is:
(match: RegExpMatchArray | null, inlineCall: { input: string, index: number } | null) -> string
This corrects old awkwardness in the signatures by making it unclear from which context functions were being called. It also corrects an old redundancy where the
match
object was always a regexp match array, which already includes the input and match index. Furthermore, this provides a natural location for potential future arguments that could be applied to inline calls. -
Make mode changes inside recursively rendered text bubble up.
-
Allow deactivating the active mode using
{use none}
. The mode namenone
is now reserved and BML will throw aModeNameError
if a document tries to shadow it. -
Fix bug breaking regexp matchers ending with asterisks.
-
Fix bug where line comments ending with whitespace didn't terminate
-
Fix bugs with line comments ending with backslashes, escaped (literal) and not (visual line breaks.)
- Change the
as
keyword used in mode rules to the arrow->
used in reference mappings.
- Add validations to eval-proided fields.
- Fix several bugs around escape sequences, including escaped braces and square brackets.
- Improve whitespace cleanup by making it collapse runs of spaces in
the middle of lines, for example
·foo···bar·
is cleaned to·foo·bar\n
- Remove support for the long-deprecated
using
alias of theuse
keyword. - Move
whitespaceCleanup
setting from cli andrenderSettings
to document-definedsettings
provided througheval
. - Add new post-processing step for correcting position of some
punctuation marks according to English grammar rules. This is
enabled by default and can be disabled using the document-defined
setting
punctuationCleanup: false
. - Add a new post-processing step for correcting capitalization of the
first words of sentences. This is enabled by default and can be
disabled using the document-defined setting
capitalizationCleanup: false
.
- Internal change: the repo has been migrated to Typescript. All
commands like
npm run build
andnpm run test
should still work just as before. - Fix bug where
UnknownModeError
incorrectly called itself aJavascriptSyntaxError
. - Fix bug causing literal blocks to not be properly treated literally
- Fix several small parser bugs unearthed by Typescript migration.
- Add basic safety checks to eval blocks - logging warnings when
Math.random()
is used and whenprovide()
is omitted.
skipped due to bad release from Typescript build headaches
- Fix bug causing comments to not be stripped out in many situations
- Line and block comments are now supported in plain body text, including text in choice branches. Line comments emit no output (including their terminating newlines), while block comments emit a single whitespace.
-
Rule replacers must now be surrounded by curly braces
Rule replacers are now defined using the same syntax as anonymous inline choices, harmonizing the syntax.
mode example { (foo) as (bar), (biz) // is now (foo) as {(bar), (biz)} }
-
Rules no longer automatically insert an implicit no-op replacement branch. Users must now specify no-op replacement branches explicitly using the new
match
keyword.Where
(foo) as {(bar)}
used to be interpreted as "foo
50% of the time andbar
50% of the time," this code is now interpreted as "bar
100% of the time." To replicate the old behavior, use the newmatch
explicitly like so:(foo) as {(bar), match}
.
- No real library or language changes. This is a stub release to start uploading bundles to jsdelivr.
- Make all render settings available to the CLI
- Make CLI errors from invalid arguments log more useful messages to stderr and give exit code 1.
- Support trailing commas in inline choices
- Support visual line breaks, marked by ending a line with a backslash.
- Support grouping backrefs using
{@ref: 0, 1 -> (foo)}
syntax
- Remove accidentally-left-in debug log on regex matcher parsing
- Fix bug preventing backref mappings pointing to empty strings from being matched.
- Fix bug introduced in 0.0.20 which prevented user-defined markdown settings from being passed to the markdown processor.
- Change the syntax for regex matchers from
r(foo)
to/foo/
. This is necessary because the old syntax used parens for its delimiter, which is a special character in regex, meaning it was impossible to match a regex like/\)/
. This change also makes syntax highlighting simpler.
- Overhaul the
eval
system:eval
blocks are now executed withnew Function(...)
instead of raw (evil) eval.- User-defined functions and settings are now explicitly passed to
the BML interpreter using the
provide
function. - The provided eval API (micro-stdlib) is now scoped to a
bml
namespace available ineval
blocks.
- Set a render recursion sanity limit of 1000
- Move
renderMarkdown
setting from BML document settings (defined in documenteval
blocks) to therenderSettings
passed into the BML render call. To set this going forward, usebml(src, {renderMarkdown: true, ...})
. - Support a new additional
renderSettings
field,whitespaceCleanup
, which performs typically desirable whitespace cleanup after rendering. This new field is enabled by default. - Remove the top-level API field
defaultDocumentSettings
argument. This behavior is no longer supported.
- Fix markdown rendering bug by only rendering markdown at the topmost render pass.
-
Fix bug causing the active mode to not be passed down into recursively rendered text.
-
Fix bug causing named choices executed inside recursively rendered text to not propagate upward.
-
Fix a CLI bug where passed-in seeds were interpreted as strings, not integers. This caused discrepancies between generated text with the same fixed seed when BML was invoked from the CLI vs the API. The CLI has been updated to require that seeds are integers and it now casts to integers as expected, aligning it with expected outputs as seen in the wild.
This change breaks breaks fixed-seed reproducibility on the CLI. Texts generated with fixed seeds on the CLI prior to
0.0.17
will differ from newly reproducibly outputs.
- Support copy back-references
{Name: (Alice), (Bob)} {@Name} // results in "Alice Alice" or "Bob Bob"
- Support silent named choices
silent {#Name: (Alice), (Bob)} then referenced {@Name} // results in "silent then referenced Alice" or "silent then referenced Bob"
- No longer log warnings when no bml version is present in settings. While this is probably a good idea, in practice it's pretty annoying.
- Add experimental support for references and back-references
{Name: (Alice), (Bob)} went to the store. {@Name: 0 -> (She), 1 -> (He)} bought some tofu.
-
Replaces double-brace syntax with single braces. This affects inline choice blocks and mode switch blocks. Also replaces single/double quoted string syntax with parentheses so that all string literals outside
eval
blocks are now simply surrounded with parentheses. This helps simplify natural language (where quotation marks are commonly used) and allows the syntax for nested replacements to be much more elegant. | before | after | |------------------------------|------------------------------| |{{'a' 10, 'b'}}
|{(a) 10, (b)}
| |{{use anotherMode}}
|{use anotherMode}
| |'foo' as 'bar' 5, call foo
|(foo) as (bar) 5, call foo
|For migrating existing BML text, the following emacs regexps (in this order) have proven helpful:
{{ -> {
}} -> }
'\([^{"\\]*?\)' -> (\1)
"\([^{\\]*?\)" -> (\1)
-
Remove the
begin
statement; instead the first non-prelude-y text will cause the prelude to end. To start with an active mode, simply call the{use someMode}
command. -
Remove the
using
variant of theuse
keyword -
Support recursive rendering within replacements, both in inline choices and in rule replacements. For instance:
mode main { (recurse!) as (just kidding), (outer {(inner 1), (inner 2)}) } {use main} recurse! a {(simple inline), ({(complex), (fancy)} recursive)} inline choice
-
Add new render setting (passed in
bml()
call) forallowEval
which defualts totrue
and allows ignoringeval
blocks, mostly for security purposes. -
Add new
defaultDocumentSettings
argument to mainbml()
function which overrides the global default document settings before applying any settings defined in the document itself.
- Expose
randomInt
andrandomFloat
to eval blocks.
- Fix bug where random float generation was rounding results to integers, causing incorrect behavior when using floating-point or small values in replacement choice weights.
- Add full support for random seed pinning for fully reproducible bml render artifacts
- Document some of the API provided to
eval
blocks
- Added experimental support for built-in javascript utils,
starting with exposing
weightedChoose()
andWeightedChoice
. - Fixed a bug causing version checks to emit a warning when a correct version was provided.
- Changed
evaluate
keyword toeval
- Added experimental support for syntax highlighting in browsers using a custom language definition in highlightjs
- Added a command line interface and man page
(requires a global install with
npm install -g bml
)
- Support double quotes in inline replacement options.
hello {{"double" 60, 'single'}} quoted world!
- Support bml documents which do not have preludes.
Note that this changes the default behavior around malformed preludes;
while previously a missing prelude or a prelude whose ending cannot be
found would trigger a
BMLSyntaxError
, the behavior now is to consider it to not be a prelude at all, but normal bml text. - Add
settings.version
: an optional setting to specify a bml version for a document. If the specified setting does not match the running bml version, a warning is logged. If no version number is specified, a warning is logged informing that unexpected behavior may occur.
- Fix regression breaking regex matchers
- Support double-quotes in addition to single quotes for all string literals
- support escaping special tokens in regex matchers without
needing a double-backslash. e.g.
r'\\\\s+' -> r'\\s+'
- a major internal refactor of all parsers increases long-term stability and flexibility of the language implementation.
- Fix silly bug causing no-op options to never occur
- Remove the explicit bml.renderBML function - to render a string of bml, simply call the package as a function.
- Implement automatic no-op options in choice rules.
Rules now have a default chance to not do anything.
A no-op option is automatically inserted for all choice rules
with a weight of
null
, to share an equal probability as all other options without explicit weights. - Fix bug in renderer causing halt after first match found
- Add settings.markdownSettings. Allows users to specify settings to pass to marked.js at render time.
- Add WeightedChoice class and use it in place of weight objects.
{option: ___, weight: ___} -> new WeightedChoice(choice, weight)
The new class has achoice
property in place of anoption
one. - rename
rand.weightedChoice() -> rand.weightedChoose()
- Implement toString() methods in all classes
- Fix rand.normalizeWeights and rand.weightedChoose not correctly calculating values.
- Regex matchers can be specified with the character
r
immediately before the opening quote of a matcher string. Internally, strings not prepended with anr
are still stored as regexps, but they are fully escaped. Regex flags cannot be set by users - they will always be the single sticky-y flag. For example,r'.*'
gives the RegExp/.*/y
, while'.*'
gives the RegExp/\.\*/y
. - Transform/replacer functions now take a RegExp match array.
- [[ Double square brackets ]] are now used for marking blocks of literal text in order to prevent collisions with HTML in the previous << double angle bracket >> marker
- Fixed a bug with backslash escapes inside literal blocks so literal double square brackets can be used [[ inside blocks like this \]] ]]