BREAKING CHANGES:fjs/djs no longer exists: compile, tokenize, parseFromTokens, run and transpile are imported from the same paths under fjs/fsc/ now. fjs/fsc's old init and terminal exports are removed. (#2009, #2042)
BREAKING CHANGES:fjs/djs/serializer and fjs/djs/types.ts are removed. Write a module through fjs/media/datajs/serializer's tryStringify; Primitive, Unknown, Object and Array are fjs/media/datajs/types.ts's. (#2042)
BREAKING CHANGES:fjs/bnf is deleted. The grammar module is fjs/ebnf: a front end over the types/range_set algebra, the LL(1) backend fjs/ebnf/ll1, fjs/ebnf/data and the grammars under fjs/ebnf/lib. (#1882, #1887, #1907, #1941)
BREAKING CHANGES:fjs compile routes its output by extension: FunctionalScript for .js, DataJS for .data.js, the EDAG for .edag.data.js, Rust for .rs and JSON for .json, with .mjs spellings too; anything else is refused. (#2031, #2051, #2084)
BREAKING CHANGES:fjs compile: a .json output is refused, with exit code 1 and no file written, for a value holding undefined, a bigint, NaN, Infinity, -Infinity, or a node shared by more than one reference. (#2031)
BREAKING CHANGES:fsc, fjs compile: an object's members are evaluated and written in the order the object carries them, where they used to be sorted by key — sorting wrote a document denoting a different object. (#2028, #2031)
BREAKING CHANGES:fsc, fjs compile: NaN, Infinity and -Infinity are values, written as those words where a non-finite number was written null; -NaN is refused. They are reserved words, so binding one is refused. (#2013, #2015)
BREAKING CHANGES:fsc/parser requires ; after every statement, export default included; a newline no longer ends one. A JavaScript keyword is refused as a name bound or referenced, reserved word. (#1934, #2056)
BREAKING CHANGES:fsc: a .json file imported without with { type: "json" } is refused by every output, and a module imported with the attribute is refused. _attributeMissing is replaced by _attributeError on fsc/transpiler. (#2056)
BREAKING CHANGES:fsc/tokenizer: a numeric literal never carries a sign — -1 is - and 1, -Infinity is - and Infinity; DjsToken loses its -Infinity kind and gains -. A consumer that read a signed value from one token now reads two. (#2092)
BREAKING CHANGES:fsc/parser: a negative number is an operation, not a literal — parseFromTokens gives ['-', 1] where it gave the primitive -1, and AstConst gains AstNeg. The EDAG is unchanged: the lowering folds a negated literal back. (#2092)
BREAKING CHANGES:fsc/parser: a[-1.5] no longer parses — an index is a constant key, and a negative key is written as the string it names, a["-1.5"]. (#2092)
BREAKING CHANGES:fsc/ast: AstObject is ['object', readonly AstMember[]], not a plain object keyed by name, and AstMember is [string, AstConst] — spell one ['object', [['a', 1]]]. AstModule[0] is readonly AstImport[]. (#2044, #2056)
BREAKING CHANGES:fsc/ast: run returns Result<Unknown, string> — a property read on null or undefined is the failure — and values returns every entry's value the same way; sharing takes the entries' values as a third argument. (#2054)
BREAKING CHANGES:fsc/transpiler: transpile returns { value, shared, reaches }, a Denotation, where it returned the value alone; djsResult is removed and ParseContext holds Denotations. (#2031)
BREAKING CHANGES:fsc/parser: a const whose value names the const itself is refused, const not found; it used to be accepted and denote the previous const's value, or null for the first. (#2031)
BREAKING CHANGES:js/tokenizer: the scanner is retired for the grammar's token stream. A token's metadata is its start, where the scanner reported one past its end, and a string or comment keeps a character beyond U+FFFF. (#2035)
BREAKING CHANGES:js/tokenizer: tokenize reads its whole input before yielding, so an unbounded input never yields, and malformed input is one error token and a stop. fjs/js/tokenizer/types.ts is gone. (#2035)
BREAKING CHANGES:js/tokenizer: the token types — JsToken, TokenMetadata, TriviaKind, ErrorToken — and mergeTrivia are exported from fjs/ebnf/lib/js/; isKeywordToken is removed, test kind against fjs/js/keywords. (#2012)
BREAKING CHANGES:js/tokenizer, js/keywords: NaN and Infinity tokenize as keyword tokens, { kind: 'NaN' } and { kind: 'Infinity' }, where they were { kind: 'id', value: … }. Match on the kind instead. (#2013)
BREAKING CHANGES:media/json/parser: parse(policy) takes the text rather than a token list, and NumberPolicy<P> is (lexeme: string) => Result<P, string>; both codecs read through ebnf/lib/json's grammar. (#1910, #1924)
BREAKING CHANGES:media/json: fjs/media/json/tokenizer is removed — tokenize, JsonToken and _ScanState. parse and both codecs are unchanged by it. (#1894, #2035)
BREAKING CHANGES:effects/node: NodeOp gains Inflate and ReadWhole, so NodeOperationMap gains required inflate and readWhole handlers. A runner built on that type outside this repository must add them. (#1944, #2024)
BREAKING CHANGES:types/array: Tuple is renamed to FixedArray and isTuple to isFixedArray, and the unused Array1_5 is removed in favour of BoundedArray<1, 5, T>. (#1865)
BREAKING CHANGES:types/range_set: the representation is a toggle list of half-open boundaries (RangeSet) rather than a RangeMap<boolean>; merge and get are replaced by union, intersection, difference, complement and contains. (#1874)
BREAKING CHANGES:ci: NixJob.shellHook is gone — a shell hook is declared per system, as NixJob.perSystem[system].shellHook. CI generation is not stable for third-party consumption and owes no migration. (#1892)
BREAKING CHANGES:ci: fjs/ci/rust's i686NixJob, i686JobId and i686Steps are gone; i686System, i686PerSystem and shellRustCommands replace them. A consumer regenerating with fjs ci deletes nix/ubuntu-intel32/. (#1892)
BREAKING CHANGES:edag: negation is ['-', exp], not ['neg', exp]; Op1Id loses neg, Op2Id loses + and -, and both join Op12Id, legal at one or two operands. Respell neg as -; read the node's length for the arity. (#1954)
BREAKING CHANGES:edag: Op3Id and Op3 are added and Exp gains the ['?:', exp, exp, exp] node, so a consumer that dispatches exhaustively over Exp needs a ?: case. (#1963)
BREAKING CHANGES:edag/amnesia: types.ts no longer exports ExpOp, TagMap, Map and Get; ExpOp and TagMap are exported from fjs/edag/types.ts, and the handler map type is Operations in fjs/edag/operations/types.ts. (#2072)
BREAKING CHANGES:nanvm: Operand and isFunctionValue are removed; FunctionValue is a Value and Expectation is Const | Ref | Throws. Replace Operand with Value, and a function-operand check with lowering it. (#1960)
BREAKING CHANGES:nanvm: NonEdagGroup, opId and Lowered are removed; Group12, Group3 and groupKey are added, and caseExp returns Exp. Read caseExp's result as the expression directly, and a group's tag as g.op. (#1954, #1963)
BREAKING CHANGES:nanvm: Eq, EqCase, LoweredEq and lowerEq are removed; Data carries shared instead of eq. Build the shared nodes with sharedExp(data.shared) and pass them to caseExp. (#1974)
BREAKING CHANGES:nanvm-lib: Shr for BigInt<A>'s Output is Result<BigInt<A>, Any<A>> — every call site needs an ?/.unwrap()/match added — and <<'s oversized-shift guard tightened to 2^14 words. (#1872)
BREAKING CHANGES:website: main is a NodeProgram that reads its options, so main() now throws. Pass them: main(options), or main(defaultNodeProgramOptions) from effects/node/virtual. (#2036)
BREAKING CHANGES:emergent_testing/browser: the report is one <details data-test-module> group per module rather than <li> rows, and the counts move to [data-test-counts]. Select rows as [data-test-results] li. (#2037)
BREAKING CHANGES:dev/update: the module is removed with its syncMcp and main exports, and npm run gen no longer writes .vscode/mcp.json; register the MCP server with npx functionalscript mcp instead. (#1863)
BREAKING CHANGES: the DataJS specification no longer recognizes .d.js and .d.mjs as DataJS extensions; .data.js and .data.mjs are the two. (#2061)
fsc: the language grows property access — a.b, a[key], [1].length — non-capturing arrow functions with a block body, calls, and unary -. A module reaching a function or a call compiles to the EDAG output. (#2053, #2058, #2060, #2085, #2087, #2092)
fsc: fjs compile writes the program linked into one EDAG, and the FunctionalScript writer puts a linked EDAG back as source, so compiling the output again yields the same graph. (#2046, #2049, #2057, #2073, #2078, #2086)
media/datajs: new — a DataJS reader and writer behind four try* entry points, reading a document into the graph it denotes and writing normalized form, refusing what the specification puts outside the data model. (#1911, #1982, #2040, #2043, #2045)
spec/datajs/vectors: the DataJS conformance corpus — the reader accept and reject sets, the byte form, the writer-side sets and the class-by-role matrix, each proved against the reader and the writer. (#1956, #1964, #1975, #1978, #2003, #2033)
git: new — reading a Git repository: loose and packed objects, trees, commits, tags, refs and packed-refs, the directories a repository borrows from, and the walk from a name to an object's bytes. (#1926, #1940, #1959, #1962, #2018, #2024, #2041, #2062)
edag: analysis, the table of a program for a writer and a VM; operations, one table per tag parameterized by the operand evaluation; memo, the JavaScript-compatible executor over it; and rust, the EDAG→Rust printer. (#2070, #2072, #2074, #2075)
nanvm-lib: every remaining arithmetic and comparison operator — %, /, **, <, <=, >, >= — the bitwise and shift operators, !, &&, ||, ??, ?:, typeof, own, and ./[] with .length. (#1864, #1867, #1868, #1871, #1879, #2068)
nanvm-lib, nanvm-harness: Any::to_json serializes a VM value, and the harness runs the Rust that fjs compile's code generator writes over its fixtures. (#2071, #2093, #2095)
effects/node: readWhole, readWholeBytes, readChunks, namesNothing, leadsNowhere, isDirectory, notAFileCode and notAFileMessage. readWholeBytes reads a whole file as a byte list, which has no Vec bound. (#2024, #2062, #2079)
effects/node/virtual: reading or writing a memory slot that memCreate never allocated panics with memory key not found: <key>, where the read returned undefined as a success and surfaced as an unrelated TypeError later. (#1899)
types/array: new OptionArray, BoundedArray, OptionalTailArray and OfLength type constructors; text/utf8's Utf8NonEmptyState is BoundedArray<1, 3, number>, the same type it spelled out by hand. (#1865)
crypto: sha1, the hash today's Git names its objects by, proven on the checked-in objects; and hmac takes any Hash whose end answers a Vec, so hmac(sha1) is HMAC-SHA1. (#1958, #1987)
js/prototype: new — objectPrototype, arrayPrototype, stringPrototype, numberPrototype, booleanPrototype, bigintPrototype, functionPrototype and prototypeNames. (#2059)
path: under joins a name below a directory that may already end in a separator, in either spelling, and may be empty — the join for a directory handed in from outside, where join is for segments you wrote yourself. (#2011)
website: every directory gets an index.html, a page runs the proofs of its own subtree and shows a demo, a file opens on GitHub at the commit the site was built from, and every page fits a phone screen. (#1901, #1912, #1939, #2002, #2036, #2063)
emergent_testing/browser: exports groupByModule, groupLabel, formatDuration, and the report's markup as pure media/html views — resultView, groupView, reportView, countsView. A demo shows the report failing. (#2037, #2038)