Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,37 @@ history.

## Unreleased

- **BREAKING CHANGES:** `fjs/effects/list` migrates from authored
TypeScript (`.f.ts`) to JSDoc-typed JavaScript (`.f.mjs`), splitting
its type-level API into a sibling `types.ts` — importers must use
the `.f.mjs` specifier for runtime values and the `types.ts`
specifier for types
[#1487](https://github.com/functionalscript/functionalscript/pull/1487)
- **BREAKING CHANGES:** `fjs/effects/module.f.ts` migrates from
authored TypeScript (`.f.ts`) to JSDoc-typed JavaScript (`.f.mjs`),
splitting its type-level API into a sibling `types.ts` — importers
must use the `.f.mjs` specifier for runtime values and the
`types.ts` specifier for types. Updates all 30+ dependents across
the repo; `proof.f.ts` stays TypeScript for now
[#1487](https://github.com/functionalscript/functionalscript/pull/1487)
- **BREAKING CHANGES:** `fjs/bnf/descent` migrates from authored
TypeScript (`.f.ts`) to JSDoc-typed JavaScript (`.f.mjs`), splitting
its type-level API into a sibling `types.ts` — importers must use
the `.f.mjs` specifier for runtime values and the `types.ts`
specifier for types. `proof.f.ts` stays TypeScript for now
[#1487](https://github.com/functionalscript/functionalscript/pull/1487)
- **BREAKING CHANGES:** `fjs/bnf/ll1` migrates from authored TypeScript
(`.f.ts`) to JSDoc-typed JavaScript (`.f.mjs`), splitting its
type-level API into a sibling `types.ts` — importers must use the
`.f.mjs` specifier for runtime values and the `types.ts` specifier
for types. `proof.f.ts` stays TypeScript for now
[#1487](https://github.com/functionalscript/functionalscript/pull/1487)
- **BREAKING CHANGES:** `fjs/bnf/data` migrates from authored TypeScript
(`.f.ts`) to JSDoc-typed JavaScript (`.f.mjs`), splitting its
type-level API into a sibling `types.ts` — importers must use the
`.f.mjs` specifier for runtime values and the `types.ts` specifier
for types. `proof.f.ts` stays TypeScript for now
[#1487](https://github.com/functionalscript/functionalscript/pull/1487)
- **BREAKING CHANGES:** `fjs/bnf/token_symbol` migrates from authored
TypeScript (`.f.ts`) to JSDoc-typed JavaScript (`.f.mjs`), splitting
the `Encoding<T>` type into a sibling `types.ts` — importers must use
Expand Down
103 changes: 43 additions & 60 deletions fjs/bnf/data/module.f.ts → fjs/bnf/data/module.f.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,65 +8,31 @@
* {@link RuleSet} live in their own sibling modules (`fjs/bnf/ll1`,
* `fjs/bnf/descent`, …), so the IR stays free of any one parser's machinery.
*
* See `./types.ts` for the type-level API.
*
* @module
*/
import { stringToCodePointList } from '../../text/utf16/module.f.mjs'
import { map, toArray } from '../../types/list/module.f.mjs'
import {
oneEncode,
} from '../module.f.mjs'
import type {
DataRule,
Rule as FRule,
Sequence as FSequence,
} from '../types.ts'
/** @import { DataRule, Rule as FRule, Sequence as FSequence } from '../types.ts' */
import { definedEntries } from '../../types/object/module.f.mjs'
import type { StringMap } from '../../types/object/types.ts'

/**
* Encoded terminal range value used by BNF data rules.
*
* The same as the functional TerminalRange.
*/
export type TerminalRange = number

/**
* Ordered list of grammar rule names.
*/
export type Sequence = readonly string[]

/** A variant of rule names. */
export type Variant = StringMap<string>

/**
* Grammar rule definition.
*
* It can be one of:
* - a tagged variant map,
* - a sequence of referenced rule names,
* - an encoded terminal range.
*/
export type Rule = Variant | Sequence | TerminalRange

/** The full grammar */
export type RuleSet = Readonly<Record<string, Rule>>

/**
* Whether a rule can match empty input: `undefined` if it never can, `true`
* if it can with no tag (a nullable sequence), or the tag of the nullable
* variant branch.
*/
export type EmptyTag = string | true | undefined
/** @import { StringMap } from '../../types/object/types.ts' */
/** @import { EmptyTag, Rule, RuleSet, Sequence, Variant } from './types.ts' */

type _EmptyTagMap = StringMap<EmptyTag>
/** @typedef {StringMap<EmptyTag>} _EmptyTagMap */

const emptyTagOf = (map: _EmptyTagMap) => (rule: Rule): EmptyTag => {
/** @type {(map: _EmptyTagMap) => (rule: Rule) => EmptyTag} */
const emptyTagOf = map => rule => {
if (typeof rule === 'number') {
return undefined
} else if (rule instanceof Array) {
return rule.every(item => map[item] !== undefined) ? true : undefined
} else {
let tag: EmptyTag = undefined
/** @type {EmptyTag} */
let tag = undefined
for (const [k, item] of definedEntries(rule)) {
if (map[item] !== undefined) {
tag = k
Expand All @@ -76,7 +42,8 @@ const emptyTagOf = (map: _EmptyTagMap) => (rule: Rule): EmptyTag => {
}
}

const emptyTagStep = (ruleSet: RuleSet) => (map: _EmptyTagMap): readonly [_EmptyTagMap, boolean] => {
/** @type {(ruleSet: RuleSet) => (map: _EmptyTagMap) => readonly [_EmptyTagMap, boolean]} */
const emptyTagStep = ruleSet => map => {
let next = map
let changed = false
for (const name in ruleSet) {
Expand All @@ -101,10 +68,13 @@ const emptyTagStep = (ruleSet: RuleSet) => (map: _EmptyTagMap): readonly [_Empty
* terminates — but a rule's tag can still change for rounds *after* its own
* nullable/non-nullable status has already settled, while a cyclic
* dependency's tag catches up, so a fixed round count isn't enough.
*
* @type {(ruleSet: RuleSet) => _EmptyTagMap}
*/
export const emptyTagMap = (ruleSet: RuleSet): _EmptyTagMap => {
export const emptyTagMap = ruleSet => {
const step = emptyTagStep(ruleSet)
const relax = (map: _EmptyTagMap): _EmptyTagMap => {
/** @type {(map: _EmptyTagMap) => _EmptyTagMap} */
const relax = map => {
const [next, changed] = step(map)
return changed ? relax(next) : next
}
Expand All @@ -113,11 +83,12 @@ export const emptyTagMap = (ruleSet: RuleSet): _EmptyTagMap => {

//

type _FRuleMap = StringMap<FRule>
/** @typedef {StringMap<FRule>} _FRuleMap */

const { entries } = Object

const find = (map: _FRuleMap) => (fr: FRule): string | undefined => {
/** @type {(map: _FRuleMap) => (fr: FRule) => string | undefined} */
const find = map => fr => {
for (const [k, v] of entries(map)) {
if (v === fr) {
return k
Expand All @@ -126,7 +97,8 @@ const find = (map: _FRuleMap) => (fr: FRule): string | undefined => {
return undefined
}

const newName = (map: _FRuleMap, name: string) => {
/** @type {(map: _FRuleMap, name: string) => string} */
const newName = (map, name) => {
let i = 0
let result = name
while (result in map) {
Expand All @@ -136,10 +108,13 @@ const newName = (map: _FRuleMap, name: string) => {
return result
}

type _NewRule = (m: _FRuleMap) => readonly [_FRuleMap, RuleSet, Rule]
/** @typedef {(m: _FRuleMap) => readonly [_FRuleMap, RuleSet, Rule]} _NewRule */

const sequence = (list: FSequence): _NewRule => map => {
let result: Sequence = []
/** @type {(list: FSequence) => _NewRule} */
const sequence = list => map => {
/** @type {Sequence} */
let result = []
/** @type {RuleSet} */
let set = {}
for (const fr of list) {
const [map1, set1, id] = toDataAdd(map)(fr)
Expand All @@ -150,9 +125,12 @@ const sequence = (list: FSequence): _NewRule => map => {
return [map, set, result]
}

const variant = (fr: FRule): _NewRule => map => {
let set: RuleSet = {}
let rule: Variant = {}
/** @type {(fr: FRule) => _NewRule} */
const variant = fr => map => {
/** @type {RuleSet} */
let set = {}
/** @type {Variant} */
let rule = {}
for (const [k, v] of entries(fr)) {
const [m1, s, id] = toDataAdd(map)(v)
map = m1
Expand All @@ -164,7 +142,8 @@ const variant = (fr: FRule): _NewRule => map => {

const mapOneEncode = map(oneEncode)

const data = (dr: DataRule): _NewRule => {
/** @type {(dr: DataRule) => _NewRule} */
const data = dr => {
switch (typeof dr) {
case 'string': {
return sequence(toArray(mapOneEncode(stringToCodePointList(dr))))
Expand All @@ -179,14 +158,16 @@ const data = (dr: DataRule): _NewRule => {
}
}

const toDataAdd = (map: _FRuleMap) => (fr: FRule): readonly [_FRuleMap, RuleSet, string] => {
/** @type {(map: _FRuleMap) => (fr: FRule) => readonly [_FRuleMap, RuleSet, string]} */
const toDataAdd = map => fr => {
{
const id = find(map)(fr)
if (id !== undefined) {
return [map, {}, id]
}
}
const [dr, tmpId]: readonly [DataRule, string] =
/** @type {readonly [DataRule, string]} */
const [dr, tmpId] =
typeof fr === 'function' ? [fr(), fr.name] : [fr, '']
const newRule = data(dr)
const id = newName(map, tmpId)
Expand All @@ -198,8 +179,10 @@ const toDataAdd = (map: _FRuleMap) => (fr: FRule): readonly [_FRuleMap, RuleSet,
/**
* Converts a functional grammar rule into serializable BNF data and returns
* the generated rule set with the entry rule identifier.
*
* @type {(fr: FRule) => readonly [RuleSet, string]}
*/
export const toData = (fr: FRule): readonly [RuleSet, string] => {
export const toData = fr => {
const [, ruleSet, id] = toDataAdd({})(fr)
return [ruleSet, id]
}
3 changes: 2 additions & 1 deletion fjs/bnf/data/proof.f.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { identity } from '../../types/function/module.f.mjs'
import { sort } from '../../types/object/module.f.mjs'
import { oneEncode, option, range, rangeDecode, repeat0Plus, set } from '../module.f.mjs'
import { classic, deterministic } from '../testlib.f.ts'
import { emptyTagMap, type RuleSet, toData } from './module.f.ts'
import { emptyTagMap, toData } from './module.f.mjs'
import type { RuleSet } from './types.ts'
import { assertEq } from '../../asserts/module.f.mjs'

export const proof = {
Expand Down
42 changes: 42 additions & 0 deletions fjs/bnf/data/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* Types for the serializable BNF intermediate representation (IR).
*
* @module
*/

import type { StringMap } from '../../types/object/types.ts'

/**
* Encoded terminal range value used by BNF data rules.
*
* The same as the functional TerminalRange.
*/
export type TerminalRange = number

/**
* Ordered list of grammar rule names.
*/
export type Sequence = readonly string[]

/** A variant of rule names. */
export type Variant = StringMap<string>

/**
* Grammar rule definition.
*
* It can be one of:
* - a tagged variant map,
* - a sequence of referenced rule names,
* - an encoded terminal range.
*/
export type Rule = Variant | Sequence | TerminalRange

/** The full grammar */
export type RuleSet = Readonly<Record<string, Rule>>

/**
* Whether a rule can match empty input: `undefined` if it never can, `true`
* if it can with no tag (a nullable sequence), or the tag of the nullable
* variant branch.
*/
export type EmptyTag = string | true | undefined
Loading
Loading