@fraczak/k 7.2.0 → 8.1.0

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.
Files changed (46) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/DOCS/BIG_PICTURE.md +54 -7
  3. package/DOCS/CODECS.md +21 -13
  4. package/DOCS/CODEC_ARCHITECTURE.md +1 -1
  5. package/DOCS/DICTIONARY.md +13 -4
  6. package/DOCS/FILE_FORMATS.md +3 -2
  7. package/DOCS/JSON_APPROACHES_COMPARISON.md +302 -0
  8. package/DOCS/NAMED_PATTERNS.md +317 -0
  9. package/DOCS/PATTERNS.md +59 -11
  10. package/DOCS/REPL.md +13 -2
  11. package/DOCS/WEB_REPL_REQUIREMENTS.md +3 -2
  12. package/Examples/arithmetics.k +22 -17
  13. package/Examples/bnat.k +21 -11
  14. package/Examples/buda.k +37 -20
  15. package/Examples/byte.k +3 -1
  16. package/Examples/code.k +38 -39
  17. package/Examples/djson.k +789 -0
  18. package/Examples/esc.k +1 -1
  19. package/Examples/karatsuba-mult.k +16 -13
  20. package/Examples/list.k +8 -4
  21. package/Examples/nat.k +17 -11
  22. package/Examples/poly.k +11 -3
  23. package/Examples/vhdl_to_k.k +43 -31
  24. package/README.md +40 -16
  25. package/browser/repl-browser.mjs +2 -0
  26. package/browser/tree-view.mjs +35 -16
  27. package/browser/vfs-data.mjs +10 -8
  28. package/codecs/README.md +1 -1
  29. package/codecs/djson.mjs +375 -84
  30. package/core.k +79 -115
  31. package/index.mjs +3 -0
  32. package/named-patterns.mjs +267 -0
  33. package/object.mjs +31 -3
  34. package/package.json +1 -1
  35. package/parser.jison +68 -5
  36. package/parser.mjs +111 -60
  37. package/pretty.mjs +5 -0
  38. package/repl.mjs +188 -22
  39. package/scripts/build-repl-html.mjs +58 -4
  40. package/scripts/run-tests.mjs +10 -0
  41. package/symbol-table.mjs +20 -2
  42. package/tests/test-codecs.mjs +30 -17
  43. package/tests/test-djson-methods.mjs +285 -0
  44. package/tests/test-named-patterns.mjs +189 -0
  45. package/tests/test-repl-html.mjs +47 -4
  46. package/tests/test-repl.mjs +149 -0
package/core.k CHANGED
@@ -9,9 +9,11 @@
9
9
  -- §1. Bits
10
10
  ---------------------------------------------------------------
11
11
 
12
- bit? = ?< {} 0, {} 1 >;
12
+ bit[] = < {} 0, {} 1 >;
13
+ bits[] = < {} _, B 0, B 1 > = B;
13
14
 
14
- bits? = ?< {} _, bits? 0, bits? 1 > = bits?;
15
+ bit? = ? bit[];
16
+ bits? = ? bits[];
15
17
 
16
18
  _ = {}|_;
17
19
 
@@ -22,52 +24,66 @@ _ = {}|_;
22
24
  -- §2. Unicode and String
23
25
  ---------------------------------------------------------------
24
26
 
25
- string? = ? <
26
- {} nil,
27
- {
28
- <
29
- {
30
- < {} 0, {} 1 >=Bit 0, Bit 1, Bit 2, Bit 3, Bit 4, Bit 5, Bit 6
31
- }=Bits7 ascii,
32
- {
33
- <
34
- { Bit 0, Bit 1, Bit 2 }=Bits3 h08_0F,
35
- Bits7 h10_7F,
36
- Bits7 h80_CF,
37
- Bits3 hD0_D7,
38
- {} hF9,
39
- { Bit 0 }=Bits1 hFA_FB,
40
- Bits1 hFC_FD,
41
- Bits1 hFE_FF
42
- > hi,
43
- { Bit 0, Bit 1, Bit 2, Bit 3, Bit 4, Bit 5, Bit 6, Bit 7 }=Bits8 lo
44
- } bmp_common,
45
- {
46
- <
47
- { Bit 0, Bit 1, Bit 2, Bit 3 } hE0_EF,
48
- Bits3 hF0_F7,
49
- {} hF8
50
- > hi,
51
- Bits8 lo
52
- } bmp_private_use,
53
- {
54
- Bit 0, Bit 1, Bit 2, Bit 3, Bit 4, Bit 5, Bit 6, Bit 7, Bit 8, Bit 9, Bit 10
55
- } plane0,
56
- { Bits8 mid, Bits8 lo } supplementary_plane1,
57
- {
58
- <
59
- Bits1 p02_03,
60
- { Bit 0, Bit 1 } p04_07,
61
- Bits3 p08_0F,
62
- {} p10
63
- > plane,
64
- Bits8 mid,
65
- Bits8 lo
66
- } supplementary_planes2_16
67
- > car,
68
- String cdr
69
- } cons
70
- > = String;
27
+ -- 8-bit octet used as a building block for UTF-8 payload pieces.
28
+ byte[] = {
29
+ bit[] 0, bit[] 1, bit[] 2, bit[] 3,
30
+ bit[] 4, bit[] 5, bit[] 6, bit[] 7
31
+ };
32
+
33
+ -- 7-bit ASCII code point payload (U+0000..U+007F).
34
+ ascii[] = {
35
+ bit[] 0, bit[] 1, bit[] 2, bit[] 3,
36
+ bit[] 4, bit[] 5, bit[] 6
37
+ };
38
+
39
+ -- High-byte classes for common BMP ranges, excluding surrogate
40
+ -- and private-use partitions.
41
+ bmp_hi_common[] = <
42
+ { bit[] 0, bit[] 1, bit[] 2 } h08_0F,
43
+ ascii[] h10_7F,
44
+ ascii[] h80_CF,
45
+ { bit[] 0, bit[] 1, bit[] 2 } hD0_D7,
46
+ {} hF9,
47
+ { bit[] 0 } hFA_FB,
48
+ { bit[] 0 } hFC_FD,
49
+ { bit[] 0 } hFE_FF
50
+ >;
51
+
52
+ -- High-byte classes for BMP private-use/high-reserved slices.
53
+ bmp_hi_private_use[] = <
54
+ { bit[] 0, bit[] 1, bit[] 2, bit[] 3 } hE0_EF,
55
+ { bit[] 0, bit[] 1, bit[] 2 } hF0_F7,
56
+ {} hF8
57
+ >;
58
+
59
+ -- 11-bit payload used for Plane 0 code points U+0080..U+07FF.
60
+ plane0[] = {
61
+ bit[] 0, bit[] 1, bit[] 2, bit[] 3, bit[] 4,
62
+ bit[] 5, bit[] 6, bit[] 7, bit[] 8, bit[] 9, bit[] 10
63
+ };
64
+
65
+ -- Plane selector for supplementary code points in Planes 2..16.
66
+ plane_02_10[] = <
67
+ { bit[] 0 } p02_03,
68
+ { bit[] 0, bit[] 1 } p04_07,
69
+ { bit[] 0, bit[] 1, bit[] 2 } p08_0F,
70
+ {} p10
71
+ >;
72
+
73
+ -- Canonical Unicode scalar-value partition used by string encoding.
74
+ unicode[] = <
75
+ ascii[] ascii,
76
+ { bmp_hi_common[] hi, byte[] lo } bmp_common,
77
+ { bmp_hi_private_use[] hi, byte[] lo } bmp_private_use,
78
+ plane0[] plane0,
79
+ { byte[] mid, byte[] lo } supplementary_plane1,
80
+ { plane_02_10[] plane, byte[] mid, byte[] lo } supplementary_planes2_16
81
+ >;
82
+
83
+ -- Linked-list string: nil or (unicode car, string cdr).
84
+ string[] = < {} nil, { unicode[] car, S cdr } cons > = S;
85
+
86
+ string? = ? string[];
71
87
 
72
88
  ---------------------------------------------------------------
73
89
  -- §3. Pattern
@@ -82,72 +98,20 @@ string? = ? <
82
98
  -- to decode any value, first decode a pattern under that constant,
83
99
  -- then decode the value under the pattern just obtained.
84
100
 
85
- pattern? = ? <
86
- {} nil,
87
- {
88
- <
89
- {} any,
90
- <
91
- {} nil,
92
- {
93
- {
94
- <
95
- {} nil,
96
- {
97
- <
98
- {
99
- < {} 0, {} 1 >=Bit 0, Bit 1, Bit 2, Bit 3, Bit 4, Bit 5, Bit 6
100
- }=Bits7 ascii,
101
- {
102
- <
103
- { Bit 0, Bit 1, Bit 2 }=Bits3 h08_0F,
104
- Bits7 h10_7F,
105
- Bits7 h80_CF,
106
- Bits3 hD0_D7,
107
- {} hF9,
108
- { Bit 0 }=Bits1 hFA_FB,
109
- Bits1 hFC_FD,
110
- Bits1 hFE_FF
111
- > hi,
112
- { Bit 0, Bit 1, Bit 2, Bit 3, Bit 4, Bit 5, Bit 6, Bit 7 }=Bits8 lo
113
- } bmp_common,
114
- {
115
- <
116
- { Bit 0, Bit 1, Bit 2, Bit 3 } hE0_EF,
117
- Bits3 hF0_F7,
118
- {} hF8
119
- > hi,
120
- Bits8 lo
121
- } bmp_private_use,
122
- {
123
- Bit 0, Bit 1, Bit 2, Bit 3, Bit 4, Bit 5, Bit 6, Bit 7, Bit 8, Bit 9, Bit 10
124
- } plane0,
125
- { Bits8 mid, Bits8 lo } supplementary_plane1,
126
- {
127
- <
128
- Bits1 p02_03,
129
- { Bit 0, Bit 1 } p04_07,
130
- Bits3 p08_0F,
131
- {} p10
132
- > plane,
133
- Bits8 mid,
134
- Bits8 lo
135
- } supplementary_planes2_16
136
- > car,
137
- String cdr
138
- } cons
139
- > = String label,
140
- < Bits 0, Bits 1, {} _ > = Bits target
141
- } car,
142
- Edges cdr
143
- } cons
144
- > = Edges open-product,
145
- Edges open-union,
146
- Edges closed-product,
147
- Edges closed-union
148
- > car,
149
- Pattern cdr
150
- } cons
151
- > = Pattern;
101
+ edge[] = { string[] label, bits[] target };
102
+
103
+ edges[] = < {} nil, { edge[] car, E cdr } cons > = E;
104
+
105
+ node[] = <
106
+ {} any,
107
+ edges[] open-product,
108
+ edges[] open-union,
109
+ edges[] closed-product,
110
+ edges[] closed-union
111
+ >;
112
+
113
+ pattern[] = < {} nil, { node[] car, P cdr } cons > = P;
114
+
115
+ pattern? = ? pattern[];
152
116
 
153
117
  pattern?
package/index.mjs CHANGED
@@ -2,6 +2,7 @@ import { parse } from "./parser.mjs";
2
2
  import { fromObject, isValue } from "./Value.mjs";
3
3
  import { parseValue } from "./valueIO.mjs";
4
4
  import { compileTypes } from "./compiler.mjs";
5
+ import { expandPatternsInRels } from "./named-patterns.mjs";
5
6
  import run from "./run.mjs";
6
7
  import codes from "./codes.mjs";
7
8
 
@@ -37,6 +38,8 @@ function annotate(script, options = {}) {
37
38
  const representatives = codes.register(defs.codes);
38
39
  const rels = {...defs.rels, "__main__": {def: exp}};
39
40
 
41
+ expandPatternsInRels(rels, defs.patterns || {}, options);
42
+
40
43
  // Inject library relations into the rels table so @hash refs resolve
41
44
  if (options.libraries) {
42
45
  for (const lib of options.libraries) {
@@ -0,0 +1,267 @@
1
+ // named-patterns.mjs
2
+ // Expansion of named patterns and parameterized type schemes in k ASTs.
3
+
4
+ function cloneFilter(filter) {
5
+ if (!filter || typeof filter !== "object") return filter;
6
+ const copy = { ...filter };
7
+ if (filter.fields) {
8
+ copy.fields = {};
9
+ for (const [k, v] of Object.entries(filter.fields)) {
10
+ copy.fields[k] = cloneFilter(v);
11
+ }
12
+ }
13
+ if (filter.args) {
14
+ copy.args = filter.args.map(cloneFilter);
15
+ }
16
+ return copy;
17
+ }
18
+
19
+ export function expandFilter(
20
+ filter,
21
+ patterns = {},
22
+ stack = new Set(),
23
+ uniqueIdRef = { val: 0 }
24
+ ) {
25
+ if (!filter || typeof filter !== "object") return filter;
26
+
27
+ if (filter.type === "pattern_apply") {
28
+ const patternName = filter.pattern;
29
+ const template = patterns[patternName];
30
+ if (!template) {
31
+ const pos = filter.start
32
+ ? ` (lines ${filter.start.line}:${filter.start.column})`
33
+ : "";
34
+ throw new Error(
35
+ `Unknown pattern template '${patternName}[]'${pos}`
36
+ );
37
+ }
38
+
39
+ if (stack.has(patternName)) {
40
+ const cycle = [...stack, patternName].join(" -> ");
41
+ throw new Error(
42
+ `Cyclic pattern template dependency detected: ${cycle}`
43
+ );
44
+ }
45
+
46
+ const expectedArity = template.params.length;
47
+ const actualArity = (filter.args || []).length;
48
+ if (expectedArity !== actualArity) {
49
+ const pos = filter.start
50
+ ? ` (lines ${filter.start.line}:${filter.start.column})`
51
+ : "";
52
+ throw new Error(
53
+ `Pattern '${patternName}' expects ${expectedArity} ` +
54
+ `argument${expectedArity === 1 ? "" : "s"}, ` +
55
+ `got ${actualArity}${pos}`
56
+ );
57
+ }
58
+
59
+ // Expand arguments in caller scope first
60
+ const expandedArgs = (filter.args || []).map((arg) =>
61
+ expandFilter(arg, patterns, stack, uniqueIdRef)
62
+ );
63
+
64
+ // Map internal variables and knots to unique fresh names
65
+ const varSubst = {};
66
+ const rootKnot = template.filter?.name;
67
+ if (rootKnot && !template.params.includes(rootKnot)) {
68
+ varSubst[rootKnot] =
69
+ filter.name || `_${rootKnot}_np${++uniqueIdRef.val}`;
70
+ }
71
+
72
+ function collectInternalVars(node) {
73
+ if (!node || typeof node !== "object") return;
74
+ if (node.name && !template.params.includes(node.name)) {
75
+ if (!varSubst[node.name]) {
76
+ varSubst[node.name] = `_${node.name}_np${++uniqueIdRef.val}`;
77
+ }
78
+ }
79
+ if (node.fields) {
80
+ for (const f of Object.values(node.fields)) {
81
+ collectInternalVars(f);
82
+ }
83
+ }
84
+ if (node.args) {
85
+ for (const a of node.args) {
86
+ collectInternalVars(a);
87
+ }
88
+ }
89
+ }
90
+ collectInternalVars(template.filter);
91
+
92
+ const nextStack = new Set(stack);
93
+ nextStack.add(patternName);
94
+
95
+ function substitute(node) {
96
+ if (!node || typeof node !== "object") return node;
97
+
98
+ if (node.type === "name") {
99
+ const paramIdx = template.params.indexOf(node.name);
100
+ if (paramIdx !== -1) {
101
+ const clonedArg = cloneFilter(expandedArgs[paramIdx]);
102
+ if (node.name && node.name !== template.params[paramIdx]) {
103
+ clonedArg.name = varSubst[node.name] || node.name;
104
+ }
105
+ return clonedArg;
106
+ } else {
107
+ const renamed = varSubst[node.name] || node.name;
108
+ return { ...node, name: renamed };
109
+ }
110
+ }
111
+
112
+ let knotName = undefined;
113
+ if (node.name) {
114
+ const paramIdx = template.params.indexOf(node.name);
115
+ if (paramIdx === -1) {
116
+ knotName = varSubst[node.name] || node.name;
117
+ } else {
118
+ knotName = node.name;
119
+ }
120
+ }
121
+
122
+ if (node.type === "union" || node.type === "product") {
123
+ const newFields = {};
124
+ for (const [key, fieldFilter] of Object.entries(
125
+ node.fields || {}
126
+ )) {
127
+ newFields[key] = substitute(fieldFilter);
128
+ }
129
+ const res = { ...node, fields: newFields };
130
+ if (knotName !== undefined) res.name = knotName;
131
+ else delete res.name;
132
+ return res;
133
+ }
134
+
135
+ if (node.type === "pattern_apply") {
136
+ const newArgs = (node.args || []).map(substitute);
137
+ const nestedApply = {
138
+ ...node,
139
+ args: newArgs,
140
+ };
141
+ if (knotName !== undefined) nestedApply.name = knotName;
142
+ else delete nestedApply.name;
143
+ return expandFilter(
144
+ nestedApply,
145
+ patterns,
146
+ nextStack,
147
+ uniqueIdRef
148
+ );
149
+ }
150
+
151
+ const res = { ...node };
152
+ if (knotName !== undefined) res.name = knotName;
153
+ else delete res.name;
154
+ return res;
155
+ }
156
+
157
+ const instantiated = substitute(template.filter);
158
+ if (filter.name) {
159
+ instantiated.name = filter.name;
160
+ }
161
+ return instantiated;
162
+ }
163
+
164
+ if (filter.type === "union" || filter.type === "product") {
165
+ const newFields = {};
166
+ for (const [key, fieldFilter] of Object.entries(
167
+ filter.fields || {}
168
+ )) {
169
+ newFields[key] = expandFilter(
170
+ fieldFilter,
171
+ patterns,
172
+ stack,
173
+ uniqueIdRef
174
+ );
175
+ }
176
+ return { ...filter, fields: newFields };
177
+ }
178
+
179
+ return cloneFilter(filter);
180
+ }
181
+
182
+ export function expandExp(
183
+ exp,
184
+ patterns = {},
185
+ stack = new Set(),
186
+ uniqueIdRef = { val: 0 }
187
+ ) {
188
+ if (!exp || typeof exp !== "object") return exp;
189
+
190
+ switch (exp.op) {
191
+ case "filter":
192
+ return {
193
+ ...exp,
194
+ filter: expandFilter(exp.filter, patterns, stack, uniqueIdRef),
195
+ };
196
+ case "pattern_apply":
197
+ return {
198
+ op: "filter",
199
+ filter: expandFilter(
200
+ { type: "pattern_apply", pattern: exp.pattern, args: exp.args },
201
+ patterns,
202
+ stack,
203
+ uniqueIdRef
204
+ ),
205
+ start: exp.start,
206
+ end: exp.end,
207
+ };
208
+ case "comp":
209
+ return {
210
+ ...exp,
211
+ comp: exp.comp.map((subExp) =>
212
+ expandExp(subExp, patterns, stack, uniqueIdRef)
213
+ ),
214
+ };
215
+ case "union":
216
+ return {
217
+ ...exp,
218
+ union: exp.union.map((subExp) =>
219
+ expandExp(subExp, patterns, stack, uniqueIdRef)
220
+ ),
221
+ };
222
+ case "product":
223
+ return {
224
+ ...exp,
225
+ product: exp.product.map(({ label, exp: subExp }) => ({
226
+ label,
227
+ exp: expandExp(subExp, patterns, stack, uniqueIdRef),
228
+ })),
229
+ };
230
+ default:
231
+ return exp;
232
+ }
233
+ }
234
+
235
+ export function expandPatternsInRels(rels, patterns = {}, options = {}) {
236
+ const allPatterns = { ...(options.patterns || {}), ...patterns };
237
+ if (options.libraries) {
238
+ for (const lib of options.libraries) {
239
+ const libPatterns =
240
+ lib.patterns ||
241
+ lib.defs?.patterns ||
242
+ lib.sourceDefs?.patterns ||
243
+ {};
244
+ for (const [name, pat] of Object.entries(libPatterns)) {
245
+ if (!(name in allPatterns)) {
246
+ allPatterns[name] = pat;
247
+ }
248
+ }
249
+ }
250
+ }
251
+
252
+ const uniqueIdRef = { val: 0 };
253
+ for (const relName of Object.keys(rels)) {
254
+ const rel = rels[relName];
255
+ if (rel?.def) {
256
+ rel.def = expandExp(rel.def, allPatterns, new Set(), uniqueIdRef);
257
+ }
258
+ }
259
+
260
+ return rels;
261
+ }
262
+
263
+ export default {
264
+ expandFilter,
265
+ expandExp,
266
+ expandPatternsInRels,
267
+ };
package/object.mjs CHANGED
@@ -489,6 +489,24 @@ function compileLibrary(script, options = {}) {
489
489
  Object.entries(meta).filter(([hash]) => libraryMetaHashes.has(hash))
490
490
  );
491
491
 
492
+ const libPatterns = {};
493
+ if (options.libraries) {
494
+ for (const lib of options.libraries) {
495
+ const lp =
496
+ lib.patterns ||
497
+ lib.defs?.patterns ||
498
+ lib.sourceDefs?.patterns ||
499
+ {};
500
+ Object.assign(libPatterns, lp);
501
+ }
502
+ }
503
+ if (options.patterns) {
504
+ Object.assign(libPatterns, options.patterns);
505
+ }
506
+ if (annotated.sourceDefs?.patterns) {
507
+ Object.assign(libPatterns, annotated.sourceDefs.patterns);
508
+ }
509
+
492
510
  return {
493
511
  format: "k-object",
494
512
  codes: allCodes,
@@ -496,6 +514,7 @@ function compileLibrary(script, options = {}) {
496
514
  relAlias: serialized.relAlias,
497
515
  compileStats: serialized.compileStats,
498
516
  meta: libMeta,
517
+ patterns: libPatterns,
499
518
  main: null
500
519
  };
501
520
  }
@@ -506,13 +525,22 @@ function hydrateObject(object) {
506
525
  }
507
526
  codes.load(object.codes);
508
527
  const hydratedRels = Object.fromEntries(
509
- Object.entries(object.rels).map(([name, rel]) => [name, hydrateRelation(rel)])
528
+ Object.entries(object.rels).map(([name, rel]) => [
529
+ name,
530
+ hydrateRelation(rel)
531
+ ])
510
532
  );
511
- return { ...object, rels: hydratedRels };
533
+ return {
534
+ ...object,
535
+ rels: hydratedRels,
536
+ patterns: object.patterns || {}
537
+ };
512
538
  }
513
539
 
514
540
  function loadLibrary(object) {
515
- const hydrated = isHydratedObject(object) ? object : hydrateObject(object);
541
+ const hydrated = isHydratedObject(object)
542
+ ? object
543
+ : hydrateObject(object);
516
544
  if (hydrated.main != null) {
517
545
  throw new Error("Expected a library (main: null), got an executable");
518
546
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fraczak/k",
3
- "version": "7.2.0",
3
+ "version": "8.1.0",
4
4
  "description": "k — a language for content-addressable schemas and data transformations over algebraic data types (products and tagged unions); includes runtime, contract derivation, REPL, and codec toolchain",
5
5
  "publishConfig": {
6
6
  "access": "public"
package/parser.jison CHANGED
@@ -49,9 +49,11 @@ function fromEscString(escString) {
49
49
  "<" return 'LA';
50
50
  "{" return 'LC';
51
51
  "(" return 'LP';
52
+ "[" return 'LB';
52
53
  ">" return 'RA';
53
54
  "}" return 'RC';
54
55
  ")" return 'RP';
56
+ "]" return 'RB';
55
57
  "=" return 'EQ';
56
58
  "..." return 'DOTS';
57
59
  "." return 'DOT';
@@ -71,7 +73,7 @@ function fromEscString(escString) {
71
73
  /lex
72
74
 
73
75
  %token NAME STRING
74
- %token LA LC LP RA RP RC EQ DOT COMMA SC COL AT QMARK DOTS DIV VID
76
+ %token LA LC LP LB RA RC RP RB EQ DOT COMMA SC COL AT QMARK DOTS DIV VID
75
77
  %token INCREMENTAL
76
78
  %token EOF
77
79
 
@@ -85,9 +87,11 @@ at: AT { $$ = getToken(yytext,yy,_$); };
85
87
  la: LA { $$ = getToken(yytext,yy,_$); };
86
88
  lc: LC { $$ = getToken(yytext,yy,_$); };
87
89
  lp: LP { $$ = getToken(yytext,yy,_$); };
90
+ lb: LB { $$ = getToken(yytext,yy,_$); };
88
91
  ra: RA { $$ = getToken(yytext,yy,_$); };
89
92
  rc: RC { $$ = getToken(yytext,yy,_$); };
90
93
  rp: RP { $$ = getToken(yytext,yy,_$); };
94
+ rb: RB { $$ = getToken(yytext,yy,_$); };
91
95
  eq: EQ { $$ = getToken(yytext,yy,_$); };
92
96
  dots: DOTS { $$ = getToken(yytext,yy,_$); };
93
97
  dot: DOT { $$ = getToken(yytext,yy,_$); };
@@ -100,9 +104,14 @@ qmark: QMARK { $$ = getToken(yytext,yy,_$); };
100
104
  incremental: INCREMENTAL { $$ = getToken(yytext,yy,_$); };
101
105
 
102
106
  input_with_eof: initialize_symbol_table defs comp EOF {
103
- const result = {defs: {rels: s.rels, codes: s.codes}, exp: $3};
104
- // console.log(JSON.stringify(result, "", 2));
105
- // process.exit(0);
107
+ const result = {
108
+ defs: {
109
+ rels: s.rels,
110
+ codes: s.codes,
111
+ patterns: s.patterns
112
+ },
113
+ exp: $3
114
+ };
106
115
  return result;
107
116
  };
108
117
 
@@ -113,17 +122,71 @@ initialize_symbol_table
113
122
 
114
123
  defs: { }
115
124
  | defs name eq comp sc { s.add_rel($2.value,$4); }
125
+ | defs name lb pattern_params rb eq filter sc
126
+ {
127
+ const seen = new Set();
128
+ for (const p of $4) {
129
+ if (seen.has(p)) {
130
+ const msg =
131
+ `Duplicate parameter '${p}' ` +
132
+ `in pattern '${$2.value}'.`;
133
+ anError(@4, msg);
134
+ }
135
+ seen.add(p);
136
+ }
137
+ s.add_pattern($2.value, $4, $7);
138
+ }
139
+ ;
140
+
141
+ pattern_params
142
+ : /* empty */ { $$ = []; }
143
+ | non_empty_pattern_params { $$ = $1; }
144
+ ;
145
+
146
+ non_empty_pattern_params
147
+ : name { $$ = [$1.value]; }
148
+ | non_empty_pattern_params comma name
149
+ { $$ = [].concat($1, $3.value); }
116
150
  ;
117
151
 
118
152
  filter_
119
153
  : lp dots rp { $$ = { type: null, open: true, fields: {}, start: $1.start, end: $3.end}; }
120
154
  | la labelled_filters ra { $$ = { type: "union", open: $2.open, fields: $2.fields, start: $1.start, end: $3.end}; }
121
155
  | lc labelled_filters rc { $$ = { type: "product", open: $2.open, fields: $2.fields, start: $1.start, end: $3.end}; }
156
+ | name lb pattern_args rb
157
+ {
158
+ $$ = {
159
+ type: "pattern_apply",
160
+ pattern: $1.value,
161
+ args: $3,
162
+ start: $1.start,
163
+ end: $4.end
164
+ };
165
+ }
166
+ ;
167
+
168
+ pattern_args
169
+ : /* empty */ { $$ = []; }
170
+ | non_empty_pattern_args { $$ = $1; }
171
+ ;
172
+
173
+ non_empty_pattern_args
174
+ : filter { $$ = [$1]; }
175
+ | non_empty_pattern_args comma filter
176
+ { $$ = [].concat($1, $3); }
122
177
  ;
123
178
 
124
179
  filter
125
180
  : filter_ { $$ = $1; }
126
- | filter_ EQ name { $$ = { name: $3.value, ...$1, start: $1.start, end: $3.end }; }
181
+ | filter_ EQ name
182
+ {
183
+ $$ = {
184
+ ...$1,
185
+ name: $3.value,
186
+ start: $1.start,
187
+ end: $3.end
188
+ };
189
+ }
127
190
  | name { $$ = { type: 'name', name: $1.value, open: true, start: $1.start, end: $1.end}; }
128
191
  ;
129
192
  labelled_filters