@fraczak/k 7.1.0 → 8.0.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 (64) hide show
  1. package/CHANGELOG.md +99 -0
  2. package/DOCS/BIG_PICTURE.md +54 -7
  3. package/DOCS/CODECS.md +47 -2
  4. package/DOCS/CODEC_ARCHITECTURE.md +14 -5
  5. package/DOCS/DICTIONARY.md +13 -4
  6. package/DOCS/FILE_FORMATS.md +3 -2
  7. package/DOCS/JSON_APPROACHES_COMPARISON.md +306 -0
  8. package/DOCS/NAMED_PATTERNS.md +317 -0
  9. package/DOCS/PATTERNS.md +59 -11
  10. package/DOCS/REPL.md +24 -5
  11. package/DOCS/WEB_REPL_REQUIREMENTS.md +4 -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-btrie.k +414 -0
  18. package/Examples/djson-ptrie.k +663 -0
  19. package/Examples/djson.k +323 -0
  20. package/Examples/esc.k +1 -1
  21. package/Examples/karatsuba-mult.k +16 -13
  22. package/Examples/list.k +8 -4
  23. package/Examples/nat.k +17 -11
  24. package/Examples/poly.k +11 -3
  25. package/Examples/vhdl_to_k.k +43 -31
  26. package/README.md +42 -15
  27. package/Value.mjs +28 -3
  28. package/backend-api.mjs +5 -1
  29. package/browser/repl-browser.mjs +6 -0
  30. package/browser/tree-view.mjs +82 -24
  31. package/browser/vfs-data.mjs +21 -12
  32. package/codecs/README.md +3 -0
  33. package/codecs/djson-btrie.mjs +560 -0
  34. package/codecs/djson-ptrie.mjs +587 -0
  35. package/codecs/djson.mjs +270 -0
  36. package/codecs/int.mjs +2 -1
  37. package/codecs/json.mjs +7 -2
  38. package/codecs/k-parse.mjs +7 -1
  39. package/codecs/k-print.mjs +4 -0
  40. package/codecs/runtime/codec-sdk.mjs +16 -2
  41. package/codecs/runtime/pattern-json.mjs +80 -1
  42. package/codecs/runtime/prefix-codec.mjs +629 -67
  43. package/codecs/runtime/show-value.mjs +93 -51
  44. package/codecs/runtime/unicode-string.mjs +29 -6
  45. package/codecs/show.mjs +4 -0
  46. package/codecs/utf16.mjs +2 -1
  47. package/core.k +79 -115
  48. package/index.mjs +3 -0
  49. package/named-patterns.mjs +267 -0
  50. package/object.mjs +31 -3
  51. package/package.json +5 -1
  52. package/parser.jison +68 -5
  53. package/parser.mjs +111 -60
  54. package/pretty.mjs +5 -0
  55. package/repl.mjs +245 -28
  56. package/scripts/build-repl-html.mjs +76 -5
  57. package/scripts/perf-djson.mjs +1142 -0
  58. package/scripts/run-tests.mjs +5 -0
  59. package/symbol-table.mjs +20 -2
  60. package/tests/bench-djson-tries.mjs +340 -0
  61. package/tests/test-codecs.mjs +211 -0
  62. package/tests/test-named-patterns.mjs +189 -0
  63. package/tests/test-repl-html.mjs +52 -4
  64. package/tests/test-repl.mjs +223 -0
@@ -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.1.0",
3
+ "version": "8.0.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"
@@ -33,6 +33,7 @@
33
33
  "perf:poly:pkir": "BACKENDS=pkir node scripts/perf-poly.mjs",
34
34
  "perf:poly:llvm-pkir": "BACKENDS=llvm-pkir node scripts/perf-poly.mjs",
35
35
  "perf:poly:trace": "node scripts/perf-poly.mjs --trace",
36
+ "perf:djson": "node scripts/perf-djson.mjs",
36
37
  "perf:idempotency": "node scripts/benchmark-idempotency.mjs",
37
38
  "deps:graph": "node scripts/generate-dependency-graph.mjs",
38
39
  "compare": "node harness/cli.mjs",
@@ -58,6 +59,9 @@
58
59
  "k-wasm": "backends/wasm/bin/k-wasm.mjs",
59
60
  "k-wasm-compile": "backends/wasm/bin/k-wasm-compile.mjs",
60
61
  "k-wasm-run": "backends/wasm/bin/k-wasm-run.mjs",
62
+ "k-djson": "codecs/djson.mjs",
63
+ "k-djson-btrie": "codecs/djson-btrie.mjs",
64
+ "k-djson-ptrie": "codecs/djson-ptrie.mjs",
61
65
  "k-ieee": "codecs/ieee.mjs",
62
66
  "k-int": "codecs/int.mjs",
63
67
  "k-json": "codecs/json.mjs",
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