diff --git a/CHANGELOG.md b/CHANGELOG.md index 431c59afde..8d29ae3c3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,11 @@ history. ## Unreleased +- `media/json/parser`: `endArray`/`endObject` no longer branch on `state.top` + and `tokenToValue` drops its defensive default arm — the parser's state + machine already guarantees these invariants, so the dead branches are gone + instead of tested + [#1536](https://github.com/functionalscript/functionalscript/pull/1536) - `text/code_point`: new `eofFlush` factory builds the end-of-input step `decoder` takes. The UTF-8 and UTF-16 decoders derive their eof ops from it instead of each writing the flush out, so "leftover state becomes exactly one diff --git a/fjs/media/json/parser/module.f.mjs b/fjs/media/json/parser/module.f.mjs index 449e835a08..ba028811ed 100644 --- a/fjs/media/json/parser/module.f.mjs +++ b/fjs/media/json/parser/module.f.mjs @@ -8,7 +8,7 @@ * @import { List } from '../../../types/list/types.ts' * @import { Fold } from '../../../types/function/operator/types.ts' * @import { JsonToken } from '../tokenizer/types.ts' - * @import { _JsonObject, _JsonArray, _StateParse, _JsonState, _JsonStack } from './types.ts' + * @import { _JsonObject, _JsonArray, _StateParse, _JsonState, _JsonStack, _ValueToken } from './types.ts' */ import { error, ok } from '../../../types/result/module.f.mjs' @@ -63,9 +63,14 @@ const popStack = stack => { : { status: '', top: ne.first, stack: ne.tail } } -/** @type {(state: _StateParse) => _JsonState} */ +/** + * `endArray` only ever runs while parsing the array `startArray` opened + * (status `'['`/`'[v'`), so `state.top` is always that array here. + * + * @type {(state: _StateParse) => _JsonState} + */ const endArray = state => { - const array = state.top !== null ? toArray(state.top.values) : null + const array = toArray(/** @type {_JsonArray} */ (state.top).values) const newState = popStack(state.stack) return pushValue(newState)(array) } @@ -76,14 +81,24 @@ const startObject = state => { return { status: '{', top: { kind: 'object', values: null, key: '' }, stack: newStack } } -/** @type {(state: _StateParse) => _JsonState} */ +/** + * `endObject` only ever runs while parsing the object `startObject` opened + * (status `'{'`/`'{v'`), so `state.top` is always that object here. + * + * @type {(state: _StateParse) => _JsonState} + */ const endObject = state => { - const obj = state.top?.kind === 'object' ? fromMap(state.top.values) : null + const obj = fromMap(/** @type {_JsonObject} */ (state.top).values) const newState = popStack(state.stack) return pushValue(newState)(obj) } -/** @type {(token: JsonToken) => Unknown} */ +/** + * Only ever called on a token `isValueToken` has already confirmed carries a + * value, so the switch covers every `_ValueToken` case with no fallback arm. + * + * @type {(token: _ValueToken) => Unknown} + */ const tokenToValue = token => { switch (token.kind) { case 'null': return null @@ -91,11 +106,13 @@ const tokenToValue = token => { case 'true': return true case 'number': return parseFloat(token.value) case 'string': return token.value - default: return null } } -/** @type {(token: JsonToken) => boolean} */ +/** + * @param {JsonToken} token + * @returns {token is _ValueToken} + */ const isValueToken = token => { switch (token.kind) { case 'null': diff --git a/fjs/media/json/parser/types.ts b/fjs/media/json/parser/types.ts index 7bae291f38..4c1a92c5bf 100644 --- a/fjs/media/json/parser/types.ts +++ b/fjs/media/json/parser/types.ts @@ -7,6 +7,10 @@ import type { Unknown } from '../types.ts' import type { OrderedMap } from '../../../types/ordered_map/types.ts' import type { List } from '../../../types/list/types.ts' +import type { JsonToken } from '../tokenizer/types.ts' + +/** JSON tokens that carry a directly-usable value. */ +export type _ValueToken = Extract export type _JsonObject = { readonly kind: 'object'