functionalscript 0.3.12 → 0.3.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,10 +2,76 @@
2
2
  * Rules for serializing and deserializing the BNF grammar.
3
3
  *
4
4
  * @module
5
+ *
6
+ * @example
7
+ *
8
+ * ```ts
9
+ * // Define a simple grammar
10
+ * const grammar: Rule = () => [
11
+ * [range('AZ')], // 'A-Z'
12
+ * [range('az'), grammar], // 'a-z' followed by more grammar
13
+ * ]
14
+ *
15
+ * const parse = parser(toRuleMap(grammar))
16
+ *
17
+ * // Parse an input
18
+ * const input = toArray(stringToCodePointList('abcdefgA'))
19
+ * const result = parse(grammar.name, input)
20
+ * if (result === null) { throw result }
21
+ * const [, b] = result
22
+ * if (b?.length !== 0) { throw b }
23
+ * ```
24
+ */
25
+ import type { CodePoint } from '../../text/utf16/module.f.ts';
26
+ import type { TerminalRange, Rule as FRule } from '../func/module.f.ts';
27
+ /**
28
+ * Represents a sequence of grammar rules, including terminal ranges or rule identifiers.
29
+ * @template Id - The type for rule identifiers.
5
30
  */
6
- import type { TerminalRange } from '../module.f.ts';
7
31
  export type Sequence<Id> = readonly (TerminalRange | Id)[];
32
+ /**
33
+ * Represents a grammar rule as a collection of sequences.
34
+ * @template Id - The type for rule identifiers.
35
+ */
8
36
  export type Rule<Id> = readonly Sequence<Id>[];
37
+ /**
38
+ * Represents a map of grammar rules, where each rule is identified by a unique string identifier.
39
+ * @template Id - The type for rule identifiers.
40
+ */
9
41
  export type RuleMap<Id extends string> = {
10
42
  readonly [k in Id]: Rule<Id>;
11
43
  };
44
+ /**
45
+ * Transforming functional rule to a serializable rule map.
46
+ *
47
+ * @param src a functional rule.
48
+ * @returns a serializable rule map.
49
+ */
50
+ export declare const toRuleMap: (src: FRule) => RuleMap<string>;
51
+ /**
52
+ * Represents a parsed Abstract Syntax Tree (AST) sequence.
53
+ */
54
+ export type AstSequence = readonly (AstRule | CodePoint)[];
55
+ /**
56
+ * Represents a parsed AST rule, consisting of a rule name and its parsed sequence.
57
+ */
58
+ export type AstRule = readonly [string, AstSequence];
59
+ /**
60
+ * Represents the remaining input after a match attempt, or `null` if no match is possible.
61
+ */
62
+ export type Remainder = readonly CodePoint[] | null;
63
+ /**
64
+ * Represents the result of a match operation, including the parsed AST rule and the remainder of the input.
65
+ */
66
+ export type MatchResult = readonly [AstRule, Remainder];
67
+ /**
68
+ * Represents an LL(1) parser function for matching input against grammar rules.
69
+ */
70
+ export type Match = (name: string, s: readonly CodePoint[]) => MatchResult;
71
+ /**
72
+ * Creates a simple LL1 parser for the given map.
73
+ *
74
+ * @param map a prepared rule map.
75
+ * @returns a parser.
76
+ */
77
+ export declare const parser: (rm: RuleMap<string>) => Match;
@@ -2,5 +2,206 @@
2
2
  * Rules for serializing and deserializing the BNF grammar.
3
3
  *
4
4
  * @module
5
+ *
6
+ * @example
7
+ *
8
+ * ```ts
9
+ * // Define a simple grammar
10
+ * const grammar: Rule = () => [
11
+ * [range('AZ')], // 'A-Z'
12
+ * [range('az'), grammar], // 'a-z' followed by more grammar
13
+ * ]
14
+ *
15
+ * const parse = parser(toRuleMap(grammar))
16
+ *
17
+ * // Parse an input
18
+ * const input = toArray(stringToCodePointList('abcdefgA'))
19
+ * const result = parse(grammar.name, input)
20
+ * if (result === null) { throw result }
21
+ * const [, b] = result
22
+ * if (b?.length !== 0) { throw b }
23
+ * ```
24
+ */
25
+ import { empty as emptyArray } from "../../types/array/module.f.js";
26
+ import { strictEqual } from "../../types/function/operator/module.f.js";
27
+ import { toArray } from "../../types/list/module.f.js";
28
+ import { contains } from "../../types/range/module.f.js";
29
+ import { rangeMap } from "../../types/range_map/module.f.js";
30
+ const findName = (map, rule) => {
31
+ const { name } = rule;
32
+ let result = name;
33
+ {
34
+ let i = 0;
35
+ while (result in map) {
36
+ result = name + i;
37
+ ++i;
38
+ }
39
+ }
40
+ return result;
41
+ };
42
+ /**
43
+ * Add a new rule to the temporary map.
44
+ */
45
+ const tmpAdd = ({ queue, result }) => (src) => {
46
+ // find a name for the item.
47
+ const name = findName(result, src);
48
+ return [{
49
+ // add the item to a queue under the name.
50
+ queue: [...queue, [src, name]],
51
+ // add the name to the result and fill the rule later.
52
+ result: { ...result, [name]: emptyArray },
53
+ }, name];
54
+ };
55
+ const tmpNew = tmpAdd({
56
+ queue: emptyArray,
57
+ result: {},
58
+ });
59
+ const tmpItem = (tmp, src) => {
60
+ const found = tmp.queue.find(([f]) => f === src);
61
+ return found !== undefined ? [tmp, found[1]] : tmpAdd(tmp)(src);
62
+ };
63
+ /**
64
+ * Transforming functional rule to a serializable rule map.
65
+ *
66
+ * @param src a functional rule.
67
+ * @returns a serializable rule map.
68
+ */
69
+ export const toRuleMap = (src) => {
70
+ let [tmp] = tmpNew(src);
71
+ let i = 0;
72
+ do {
73
+ const [srcOr, name] = tmp.queue[i];
74
+ let rule = emptyArray;
75
+ // iterate all sequences of the `Or` rule.
76
+ for (const srcSeq of srcOr()) {
77
+ let s = emptyArray;
78
+ // iterate all items of the sequence.
79
+ for (const srcItem of srcSeq) {
80
+ let item;
81
+ if (srcItem instanceof Array) {
82
+ item = srcItem;
83
+ }
84
+ else {
85
+ [tmp, item] = tmpItem(tmp, srcItem);
86
+ }
87
+ s = [...s, item];
88
+ }
89
+ rule = [...rule, s];
90
+ }
91
+ // fix the rule in the result.
92
+ tmp = {
93
+ queue: tmp.queue,
94
+ result: { ...tmp.result, [name]: rule },
95
+ };
96
+ ++i;
97
+ } while (i !== tmp.queue.length);
98
+ return tmp.result;
99
+ };
100
+ const dispatchOp = rangeMap({
101
+ union: a => b => {
102
+ if (a === null) {
103
+ return b;
104
+ }
105
+ if (b === null) {
106
+ return a;
107
+ }
108
+ throw ['can not merge [', a, '][', b, ']'];
109
+ },
110
+ equal: strictEqual,
111
+ def: null,
112
+ });
113
+ /**
114
+ * Creates a dispatch map for LL1 parser.
115
+ *
116
+ * @param ruleMap a serializable rule map.
117
+ * @returns A dispatch map
118
+ */
119
+ const dispatchMap = (ruleMap) => {
120
+ const dispatchSequence = (dm, sequence) => {
121
+ let empty = true;
122
+ let result = [];
123
+ for (const item of sequence) {
124
+ if (typeof item === 'string') {
125
+ dm = dispatchRule(dm, item);
126
+ const [e, dispatch] = dm[item];
127
+ result = toArray(dispatchOp.merge(result)(dispatch.map(x => [x[0] === null ? null : sequence, x[1]])));
128
+ if (e) {
129
+ continue;
130
+ }
131
+ }
132
+ else {
133
+ const dispatch = dispatchOp.fromRange(item)(sequence);
134
+ result = toArray(dispatchOp.merge(result)(dispatch));
135
+ }
136
+ empty = false;
137
+ break;
138
+ }
139
+ return [dm, [empty, result]];
140
+ };
141
+ const dispatchRule = (dm, name) => {
142
+ if (name in dm) {
143
+ return dm;
144
+ }
145
+ let empty = false;
146
+ let dispatch = [];
147
+ for (const sequence of ruleMap[name]) {
148
+ const [newDm, [e, d]] = dispatchSequence(dm, sequence);
149
+ dm = newDm;
150
+ empty ||= e;
151
+ dispatch = toArray(dispatchOp.merge(dispatch)(d));
152
+ }
153
+ return { ...dm, [name]: [empty, dispatch] };
154
+ };
155
+ let result = {};
156
+ for (const k in ruleMap) {
157
+ result = dispatchRule(result, k);
158
+ }
159
+ // TODO: validate all sequences if they are deterministic
160
+ return result;
161
+ };
162
+ /**
163
+ * Creates a simple LL1 parser for the given map.
164
+ *
165
+ * @param map a prepared rule map.
166
+ * @returns a parser.
5
167
  */
6
- export {};
168
+ export const parser = (rm) => {
169
+ const map = dispatchMap(rm);
170
+ const f = (name, s) => {
171
+ const mr = (a, r) => [[name, a], r];
172
+ const mre = (a) => mr(a, null);
173
+ const [empty, sequence] = map[name];
174
+ if (s.length === 0) {
175
+ return mr([], empty ? s : null);
176
+ }
177
+ const cp = s[0];
178
+ const i = dispatchOp.get(cp)(sequence);
179
+ if (i === null) {
180
+ return mr([], empty ? s : null);
181
+ }
182
+ let a = [];
183
+ let si = s;
184
+ for (const c of i) {
185
+ if (typeof c === 'string') {
186
+ // c is a name
187
+ const [astRule, newSi] = f(c, si);
188
+ a = [...a, astRule];
189
+ if (newSi === null) {
190
+ return mre(a);
191
+ }
192
+ si = newSi;
193
+ }
194
+ else {
195
+ // c is TerminalRange
196
+ const [first, ...newSi] = si;
197
+ if (first === undefined || !contains(c)(first)) {
198
+ return mre(a);
199
+ }
200
+ a = [...a, first];
201
+ si = newSi;
202
+ }
203
+ }
204
+ return mr(a, si);
205
+ };
206
+ return f;
207
+ };
@@ -1 +1,8 @@
1
- export {};
1
+ declare const _default: {
2
+ example: {
3
+ module: () => void;
4
+ };
5
+ classic: () => void;
6
+ map: () => void;
7
+ };
8
+ export default _default;
package/bnf/djs/test.f.js CHANGED
@@ -1,8 +1,12 @@
1
- import { one } from "../../types/range/module.f.js";
2
- import { toTerminalRangeSequence } from "../module.f.js";
3
- const s = toTerminalRangeSequence;
4
- const c = (a) => one(a.codePointAt(0));
5
- const classic = () => {
1
+ import { cp, range, remove, str } from "../func/module.f.js";
2
+ import { parser, toRuleMap } from "./module.f.js";
3
+ import { classic } from "../func/testlib.f.js";
4
+ import * as j from "../../json/module.f.js";
5
+ import { sort } from "../../types/object/module.f.js";
6
+ import { stringToCodePointList } from "../../text/utf16/module.f.js";
7
+ import { toArray } from "../../types/list/module.f.js";
8
+ const stringify = j.stringify(sort);
9
+ const classicTest = () => {
6
10
  const map = {
7
11
  json: [
8
12
  ['element']
@@ -12,60 +16,58 @@ const classic = () => {
12
16
  ['array'],
13
17
  ['string'],
14
18
  ['number'],
15
- s('true'),
16
- s('false'),
17
- s('null'),
19
+ str('true'),
20
+ str('false'),
21
+ str('null'),
18
22
  ],
19
23
  object: [
20
- [c('{'), 'ws', c('}')],
21
- [c('{'), 'members', c('}')],
24
+ [cp('{'), 'ws', cp('}')],
25
+ [cp('{'), 'members', cp('}')],
22
26
  ],
23
27
  members: [
24
28
  ['member'],
25
- ['member', c(','), 'members'],
29
+ ['member', cp(','), 'members'],
26
30
  ],
27
31
  member: [
28
- ['ws', 'string', 'ws', c(':'), 'element'],
32
+ ['ws', 'string', 'ws', cp(':'), 'element'],
29
33
  ],
30
34
  array: [
31
- [c('['), 'ws', c(']')],
32
- [c('['), 'elements', c(']')],
35
+ [cp('['), 'ws', cp(']')],
36
+ [cp('['), 'elements', cp(']')],
33
37
  ],
34
38
  elements: [
35
39
  ['element'],
36
- ['element', c(','), 'elements'],
40
+ ['element', cp(','), 'elements'],
37
41
  ],
38
42
  element: [
39
43
  ['ws', 'value', 'ws'],
40
44
  ],
41
45
  string: [
42
- [c('"'), 'characters', c('"')],
46
+ [cp('"'), 'characters', cp('"')],
43
47
  ],
44
48
  characters: [
45
49
  [],
46
50
  ['character', 'characters'],
47
51
  ],
48
52
  character: [
49
- [[0x20, 0x21]], // exclude '"' 0x22
50
- [[0x23, 0x5B]], // exclude '\' 0x5C
51
- [[0x5D, 0x10FFFF]], // 93-1114111
52
- [c('\\'), 'escape'], // 92
53
+ ...remove([0x20, 0x10FFFF], [cp('"'), cp('\\')]),
54
+ [cp('\\'), 'escape'], // 92
53
55
  ],
54
56
  escape: [
55
- [c('"')],
56
- [c('\\')],
57
- [c('/')],
58
- [c('b')],
59
- [c('f')],
60
- [c('n')],
61
- [c('r')],
62
- [c('t')],
63
- [c('u'), 'hex', 'hex', 'hex', 'hex'],
57
+ str('"'),
58
+ str('\\'),
59
+ str('/'),
60
+ str('b'),
61
+ str('f'),
62
+ str('n'),
63
+ str('r'),
64
+ str('t'),
65
+ [cp('u'), 'hex', 'hex', 'hex', 'hex'],
64
66
  ],
65
67
  hex: [
66
68
  ['digit'],
67
- [[0x41, 0x46]], // A-F
68
- [[0x61, 0x66]], // a-f
69
+ [range('AF')], // A-F
70
+ [range('af')], // a-f
69
71
  ],
70
72
  number: [
71
73
  ['integer', 'fraction', 'exponent'],
@@ -73,43 +75,44 @@ const classic = () => {
73
75
  integer: [
74
76
  ['digit'],
75
77
  ['onenine', 'digits'],
76
- [c('-'), 'digit'],
77
- [c('-'), 'onenine', 'digits'],
78
+ [cp('-'), 'digit'],
79
+ [cp('-'), 'onenine', 'digits'],
78
80
  ],
79
81
  digits: [
80
82
  ['digit'],
81
83
  ['digit', 'digits'],
82
84
  ],
83
85
  digit: [
84
- [c('0')],
86
+ [cp('0')],
85
87
  ['onenine'],
86
88
  ],
87
89
  onenine: [
88
- [[0x31, 0x39]], // 1-9
90
+ [range('19')],
89
91
  ],
90
92
  fraction: [
91
93
  [],
92
- [c('.'), 'digits'],
94
+ [cp('.'), 'digits'],
93
95
  ],
94
96
  exponent: [
95
97
  [],
96
- [c('E'), 'sign', 'digits'],
97
- [c('e'), 'sign', 'digits'],
98
+ [cp('E'), 'sign', 'digits'],
99
+ [cp('e'), 'sign', 'digits'],
98
100
  ],
99
101
  sign: [
100
102
  [],
101
- [c('+')],
102
- [c('-')],
103
+ [cp('+')],
104
+ [cp('-')],
103
105
  ],
104
106
  ws: [
105
107
  [],
106
- [c(' '), 'ws'],
107
- [c('\n'), 'ws'],
108
- [c('\r'), 'ws'],
109
- [c('\t'), 'ws'],
108
+ [cp(' '), 'ws'],
109
+ [cp('\n'), 'ws'],
110
+ [cp('\r'), 'ws'],
111
+ [cp('\t'), 'ws'],
110
112
  ],
111
113
  };
112
- const _map = map;
114
+ const result = map;
115
+ return result;
113
116
  };
114
117
  const deterministic = () => {
115
118
  const map = {
@@ -117,13 +120,13 @@ const deterministic = () => {
117
120
  ['ws', 'element']
118
121
  ],
119
122
  value: [
120
- [c('{'), 'ws', 'object', c('}')],
121
- [c('['), 'ws', 'array', c(']')],
123
+ [cp('{'), 'ws', 'object', cp('}')],
124
+ [cp('['), 'ws', 'array', cp(']')],
122
125
  ['string'],
123
126
  ['number'],
124
- s('true'),
125
- s('false'),
126
- s('null'),
127
+ str('true'),
128
+ str('false'),
129
+ str('null'),
127
130
  ],
128
131
  object: [
129
132
  [],
@@ -131,10 +134,10 @@ const deterministic = () => {
131
134
  ],
132
135
  members: [
133
136
  [],
134
- [c(','), 'ws', 'member', 'members'],
137
+ [cp(','), 'ws', 'member', 'members'],
135
138
  ],
136
139
  member: [
137
- ['string', 'ws', c(':'), 'ws', 'element'],
140
+ ['string', 'ws', cp(':'), 'ws', 'element'],
138
141
  ],
139
142
  array: [
140
143
  [],
@@ -142,46 +145,44 @@ const deterministic = () => {
142
145
  ],
143
146
  elements: [
144
147
  [],
145
- [c(','), 'ws', 'element', 'elements'],
148
+ [cp(','), 'ws', 'element', 'elements'],
146
149
  ],
147
150
  element: [
148
151
  ['value', 'ws'],
149
152
  ],
150
153
  string: [
151
- [c('"'), 'characters', c('"')],
154
+ [cp('"'), 'characters', cp('"')],
152
155
  ],
153
156
  characters: [
154
157
  [],
155
158
  ['character', 'characters'],
156
159
  ],
157
160
  character: [
158
- [[0x20, 0x21]], // exclude '"' 0x22
159
- [[0x23, 0x5B]], // exclude '\' 0x5C
160
- [[0x5D, 0x10FFFF]], // 93-1114111
161
- [c('\\'), 'escape'], // 92
161
+ ...remove([0x20, 0x10FFFF], [cp('"'), cp('\\')]),
162
+ [cp('\\'), 'escape'], // 92
162
163
  ],
163
164
  escape: [
164
- [c('"')],
165
- [c('\\')],
166
- [c('/')],
167
- [c('b')],
168
- [c('f')],
169
- [c('n')],
170
- [c('r')],
171
- [c('t')],
172
- [c('u'), 'hex', 'hex', 'hex', 'hex'],
165
+ [cp('"')],
166
+ [cp('\\')],
167
+ [cp('/')],
168
+ [cp('b')],
169
+ [cp('f')],
170
+ [cp('n')],
171
+ [cp('r')],
172
+ [cp('t')],
173
+ [cp('u'), 'hex', 'hex', 'hex', 'hex'],
173
174
  ],
174
175
  hex: [
175
176
  ['digit'],
176
- [[0x41, 0x46]], // A-F
177
- [[0x61, 0x66]], // a-f
177
+ [range('AF')],
178
+ [range('af')],
178
179
  ],
179
180
  number: [
180
181
  ['integer', 'fraction', 'exponent'],
181
- [c('-'), 'integer', 'fraction', 'exponent'],
182
+ [cp('-'), 'integer', 'fraction', 'exponent'],
182
183
  ],
183
184
  integer: [
184
- [c('0')],
185
+ [cp('0')],
185
186
  ['onenine', 'digits'],
186
187
  ],
187
188
  digits: [
@@ -189,33 +190,93 @@ const deterministic = () => {
189
190
  ['digit', 'digits'],
190
191
  ],
191
192
  digit: [
192
- [c('0')],
193
+ [cp('0')],
193
194
  ['onenine'],
194
195
  ],
195
196
  onenine: [
196
- [[0x31, 0x39]], // 1-9
197
+ [range('19')],
197
198
  ],
198
199
  fraction: [
199
200
  [],
200
- [c('.'), 'digit', 'digits'],
201
+ [cp('.'), 'digit', 'digits'],
201
202
  ],
202
203
  exponent: [
203
204
  [],
204
- [c('E'), 'sign', 'digit', 'digits'],
205
- [c('e'), 'sign', 'digit', 'digits'],
205
+ [cp('E'), 'sign', 'digit', 'digits'],
206
+ [cp('e'), 'sign', 'digit', 'digits'],
206
207
  ],
207
208
  sign: [
208
209
  [],
209
- [c('+')],
210
- [c('-')],
210
+ [cp('+')],
211
+ [cp('-')],
211
212
  ],
212
213
  ws: [
213
214
  [],
214
- [c(' '), 'ws'],
215
- [c('\n'), 'ws'],
216
- [c('\r'), 'ws'],
217
- [c('\t'), 'ws'],
215
+ [cp(' '), 'ws'],
216
+ [cp('\n'), 'ws'],
217
+ [cp('\r'), 'ws'],
218
+ [cp('\t'), 'ws'],
218
219
  ],
219
220
  };
220
221
  const _map = map;
222
+ return _map;
223
+ };
224
+ export default {
225
+ example: {
226
+ module: () => {
227
+ // Define a simple grammar
228
+ const grammar = () => [
229
+ [range('AZ')], // 'A-Z'
230
+ [range('az'), grammar], // 'a-z' followed by more grammar
231
+ ];
232
+ const ruleMap = toRuleMap(grammar);
233
+ const parse = parser(ruleMap);
234
+ // Parse an input
235
+ const input = toArray(stringToCodePointList('abcdefgA'));
236
+ const result = parse(grammar.name, input);
237
+ if (result === null) {
238
+ throw result;
239
+ }
240
+ const [, b] = result;
241
+ if (b?.length !== 0) {
242
+ throw b;
243
+ }
244
+ },
245
+ },
246
+ classic: () => {
247
+ const c = classic();
248
+ const json = stringify(toRuleMap(c.json));
249
+ const jsonE = stringify(classicTest());
250
+ if (json !== jsonE) {
251
+ //console.error(json)
252
+ //console.error(jsonE)
253
+ throw [json, jsonE];
254
+ }
255
+ },
256
+ map: () => {
257
+ const f = parser(deterministic());
258
+ // console.error(stringify(x))
259
+ //
260
+ const isSuccess = (s) => s?.length === 0;
261
+ const expect = (s, success) => {
262
+ const [a, r] = f('json', toArray(stringToCodePointList(s)));
263
+ if (isSuccess(r) !== success) {
264
+ throw r;
265
+ }
266
+ };
267
+ //
268
+ expect(' true ', true);
269
+ expect(' tr2ue ', false);
270
+ expect(' true" ', false);
271
+ expect(' "Hello" ', true);
272
+ expect(' "Hello ', false);
273
+ expect(' "Hello\\n\\r\\"" ', true);
274
+ expect(' -56.7e+5 ', true);
275
+ expect(' h-56.7e+5 ', false);
276
+ expect(' -56.7e+5 3', false);
277
+ expect(' [ 12, false, "a"] ', true);
278
+ expect(' [ 12, false2, "a"] ', false);
279
+ expect(' { "q": [ 12, false, [{}], "a"] } ', true);
280
+ expect(' { "q": [ 12, false, [}], "a"] } ', false);
281
+ }
221
282
  };