@graffiticode/basis 1.0.10 → 1.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 (3) hide show
  1. package/package.json +6 -3
  2. package/src/compiler.js +154 -23
  3. package/src/parse.js +2426 -0
package/package.json CHANGED
@@ -1,10 +1,12 @@
1
1
  {
2
2
  "name": "@graffiticode/basis",
3
3
  "type": "module",
4
- "version": "1.0.10",
4
+ "version": "1.1.0",
5
5
  "description": "The basis library for creating Graffiticode languages",
6
- "main": "compiler.js",
7
- "scripts": {},
6
+ "main": "index.js",
7
+ "scripts": {
8
+ "test": "jest"
9
+ },
8
10
  "repository": {
9
11
  "type": "git",
10
12
  "url": "git+https://github.com/graffiticode/basis.git"
@@ -19,6 +21,7 @@
19
21
  "d3": "^6.6.2",
20
22
  "hashids": "^2.2.8",
21
23
  "https": "^1.0.0",
24
+ "jest": "^27.0.1",
22
25
  "react": "^17.0.2"
23
26
  }
24
27
  }
package/src/compiler.js CHANGED
@@ -6,11 +6,23 @@ messages[1002] = "Invalid tag in node with Node ID %1.";
6
6
  messages[1003] = "No async callback provided.";
7
7
  messages[1004] = "No visitor method defined for '%1'.";
8
8
 
9
+ function error(msg, arg) {
10
+ return msg + arg;
11
+ }
12
+
13
+ function newNode(tag, elts) {
14
+ return {
15
+ tag: tag,
16
+ elts: elts,
17
+ };
18
+ }
19
+
9
20
  const ASYNC = true;
10
21
 
11
22
  class Visitor {
12
- constructor(nodePool) {
13
- this.nodePool = nodePool;
23
+ constructor(code) {
24
+ this.nodePool = code;
25
+ this.root = code.root;
14
26
  }
15
27
  visit(nid, options, resume) {
16
28
  assert(nid);
@@ -29,6 +41,34 @@ class Visitor {
29
41
  this[node.tag](node, options, resume);
30
42
  }
31
43
  }
44
+ node(nid) {
45
+ var n = this.nodePool[nid];
46
+ if (!nid) {
47
+ return null;
48
+ } else if (!n) {
49
+ return {};
50
+ }
51
+ var elts = [];
52
+ switch (n.tag) {
53
+ case "NULL":
54
+ break;
55
+ case "NUM":
56
+ case "STR":
57
+ case "IDENT":
58
+ case "BOOL":
59
+ elts[0] = n.elts[0];
60
+ break;
61
+ default:
62
+ for (var i=0; i < n.elts.length; i++) {
63
+ elts[i] = this.node(n.elts[i]);
64
+ }
65
+ break;
66
+ }
67
+ return {
68
+ tag: n.tag,
69
+ elts: elts,
70
+ };
71
+ }
32
72
  }
33
73
 
34
74
  export class Checker extends Visitor {
@@ -36,7 +76,7 @@ export class Checker extends Visitor {
36
76
  super(nodePool);
37
77
  }
38
78
  check(options, resume) {
39
- const nid = this.nodePool.root;
79
+ const nid = this.root;
40
80
  this.visit(nid, options, (err, data) => {
41
81
  resume(err, data);
42
82
  });
@@ -102,8 +142,8 @@ export class Checker extends Visitor {
102
142
  });
103
143
  }
104
144
  ADD(node, options, resume) {
105
- this.visit(node.elts[0], options, function (err1, val1) {
106
- this.visit(node.elts[1], options, function (err2, val2) {
145
+ this.visit(node.elts[0], options, (err1, val1) => {
146
+ this.visit(node.elts[1], options, (err2, val2) => {
107
147
  if (isNaN(+val1)) {
108
148
  err1 = err1.concat(error("Argument must be a number.", node.elts[0]));
109
149
  }
@@ -121,6 +161,11 @@ export class Checker extends Visitor {
121
161
  const val = node;
122
162
  resume(err, val);
123
163
  }
164
+ NULL(node, options, resume) {
165
+ const err = [];
166
+ const val = node;
167
+ resume(err, val);
168
+ }
124
169
  RECORD(node, options, resume) {
125
170
  const err = [];
126
171
  const val = node;
@@ -132,8 +177,8 @@ export class Checker extends Visitor {
132
177
  resume(err, val);
133
178
  }
134
179
  MUL(node, options, resume) {
135
- this.visit(node.elts[0], options, function (err1, val1) {
136
- this.visit(node.elts[1], options, function (err2, val2) {
180
+ this.visit(node.elts[0], options, (err1, val1) => {
181
+ this.visit(node.elts[1], options, (err2, val2) => {
137
182
  if (isNaN(+val1)) {
138
183
  err1 = err1.concat(error("Argument must be a number.", node.elts[0]));
139
184
  }
@@ -147,8 +192,8 @@ export class Checker extends Visitor {
147
192
  });
148
193
  }
149
194
  POW(node, options, resume) {
150
- this.visit(node.elts[0], options, function (err1, val1) {
151
- this.visit(node.elts[1], options, function (err2, val2) {
195
+ this.visit(node.elts[0], options, (err1, val1) => {
196
+ this.visit(node.elts[1], options, (err2, val2) => {
152
197
  if (isNaN(+val1)) {
153
198
  err1 = err1.concat(error("Argument must be a number.", node.elts[0]));
154
199
  }
@@ -257,17 +302,96 @@ function addWord(ctx, lexeme, entry) {
257
302
  function topEnv(ctx) {
258
303
  return ctx.env[ctx.env.length-1]
259
304
  }
260
-
261
305
  export class Transformer extends Visitor {
262
306
  constructor(nodePool) {
263
307
  super(nodePool);
308
+ this.patternNodePool = ['unused'];
309
+ this.patternNodeMap = {};
264
310
  }
265
311
  transform(options, resume) {
266
- const nid = this.nodePool.root;
312
+ const nid = this.root;
267
313
  this.visit(nid, options, (err, data) => {
268
314
  resume(err, data);
269
315
  });
270
316
  }
317
+ internPattern(n) {
318
+ if (!n) {
319
+ return 0;
320
+ }
321
+ const nodeMap = this.patternNodeMap;
322
+ const nodePool = this.patternNodePool;
323
+ const tag = n.tag;
324
+ const elts_nids = [];
325
+ const count = n.elts.length;
326
+ let elts = "";
327
+ for (let i = 0; i < count; i++) {
328
+ if (typeof n.elts[i] === "object") {
329
+ n.elts[i] = this.internPattern(n.elts[i]);
330
+ }
331
+ elts += n.elts[i];
332
+ }
333
+ const key = tag+count+elts;
334
+ let nid = nodeMap[key];
335
+ if (nid === void 0) {
336
+ nodePool.push({tag: tag, elts: n.elts});
337
+ nid = nodePool.length - 1;
338
+ nodeMap[key] = nid;
339
+ if (n.coord) {
340
+ ctx.state.coords[nid] = n.coord;
341
+ }
342
+ }
343
+ return nid;
344
+ }
345
+ match(options, patterns, node) {
346
+ if (patterns.size === 0 || node === undefined) {
347
+ return false;
348
+ }
349
+ let matches = patterns.filter((pattern) => {
350
+ if (pattern.tag === undefined || node.tag === undefined) {
351
+ return false;
352
+ }
353
+ const patternNid = this.internPattern(pattern);
354
+ if (patternNid === this.internPattern(node) ||
355
+ patternNid === this.internPattern(newNode('IDENT', ['_']))) {
356
+ return true;
357
+ }
358
+ if (pattern.tag === node.tag) {
359
+ if (pattern.elts.length === node.elts.length) {
360
+ // Same number of args, so see if each matches.
361
+ return pattern.elts.every((arg, i) => {
362
+ if (pattern.tag === 'VAR') {
363
+ if (arg === node.elts[i]) {
364
+ return true;
365
+ }
366
+ return false;
367
+ }
368
+ let result = this.match(options, [arg], node.elts[i]);
369
+ return result.length === 1;
370
+ });
371
+ } else if (pattern.elts.length < node.elts.length) {
372
+ // Different number of args, then see if there is a wildcard match.
373
+ let nargs = node.elts.slice(1);
374
+ if (pattern.elts.length === 2) {
375
+ // Binary node pattern
376
+ let result = (
377
+ this.match(options, [pattern.elts[0]], node.elts[0]).length > 0 &&
378
+ this.match(options, [pattern.elts[1]], newNode(node.tag, nargs)).length > 0
379
+ // Match rest of the node against the second pattern argument.
380
+ );
381
+ return result;
382
+ }
383
+ }
384
+ }
385
+ return false;
386
+ });
387
+ // if (true || matches.length > 0) {
388
+ // console.log("match() node: " + JSON.stringify(node, null, 2));
389
+ // console.log("match() matches: " + JSON.stringify(matches, null, 2));
390
+ // }
391
+ return matches;
392
+ }
393
+
394
+
271
395
  PROG(node, options, resume) {
272
396
  if (!options) {
273
397
  options = {};
@@ -319,7 +443,7 @@ export class Transformer extends Visitor {
319
443
  // });
320
444
  }
321
445
  });
322
- this.visit(node.elts[1], options, function (err, val) {
446
+ this.visit(node.elts[1], options, (err, val) => {
323
447
  exitEnv(options);
324
448
  resume([].concat(err0).concat(err).concat(err), val)
325
449
  });
@@ -374,6 +498,11 @@ export class Transformer extends Visitor {
374
498
  const val = node;
375
499
  resume(err, val);
376
500
  }
501
+ NULL(node, options, resume) {
502
+ const err = [];
503
+ const val = null;
504
+ resume(err, val);
505
+ }
377
506
  BINDING(node, options, resume) {
378
507
  const err = [];
379
508
  const val = node;
@@ -403,8 +532,8 @@ export class Transformer extends Visitor {
403
532
  }
404
533
  }
405
534
  MUL(node, options, resume) {
406
- this.visit(node.elts[0], options, function (err1, val1) {
407
- this.visit(node.elts[1], options, function (err2, val2) {
535
+ this.visit(node.elts[0], options, (err1, val1) => {
536
+ this.visit(node.elts[1], options, (err2, val2) => {
408
537
  if (isNaN(+val1)) {
409
538
  err1 = err1.concat(error("Argument must be a number.", node.elts[0]));
410
539
  }
@@ -412,14 +541,14 @@ export class Transformer extends Visitor {
412
541
  err2 = err2.concat(error("Argument must be a number.", node.elts[1]));
413
542
  }
414
543
  const err = [].concat(err1).concat(err2);
415
- const val = node;
544
+ const val = +val1 * +val2;
416
545
  resume(err, val);
417
546
  });
418
547
  });
419
548
  }
420
549
  POW(node, options, resume) {
421
- this.visit(node.elts[0], options, function (err1, val1) {
422
- this.visit(node.elts[1], options, function (err2, val2) {
550
+ this.visit(node.elts[0], options, (err1, val1) => {
551
+ this.visit(node.elts[1], options, (err2, val2) => {
423
552
  if (isNaN(+val1)) {
424
553
  err1 = err1.concat(error("Argument must be a number.", node.elts[0]));
425
554
  }
@@ -464,7 +593,7 @@ export class Transformer extends Visitor {
464
593
  resume(err, val);
465
594
  } else {
466
595
  // Otherwise, use the default data.
467
- this.visit(node.elts[0], options, function (e0, v0) {
596
+ this.visit(node.elts[0], options, (e0, v0) => {
468
597
  const err = e0;
469
598
  const val = v0;
470
599
  resume(err, val);
@@ -472,7 +601,7 @@ export class Transformer extends Visitor {
472
601
  }
473
602
  }
474
603
  PAREN(node, options, resume) {
475
- this.visit(node.elts[0], options, function (e0, v0) {
604
+ this.visit(node.elts[0], options, (e0, v0) => {
476
605
  const err = [].concat(e0);
477
606
  const val = v0;
478
607
  resume(err, val);
@@ -515,11 +644,14 @@ export class Transformer extends Visitor {
515
644
  CASE(node, options, resume) {
516
645
  // FIXME this isn't ASYNC compatible
517
646
  options.SYNC = true;
518
- this.visit(node.elts[0], options, (err, expr) => {
647
+ this.visit(node.elts[0], options, (err, e0) => {
648
+ const e0Node = this.node(node.elts[0]);
649
+ const expr = (e0Node.tag === 'NUM' || e0Node.tag === 'NUM') && e0Node || {tag: 'STR', elts: [`${e0}`]};
519
650
  let foundMatch = false;
651
+ const patterns = [];
520
652
  for (var i = 1; i < node.elts.length; i++) {
521
653
  this.visit(node.elts[i], options, (err, val) => {
522
- if (expr === val.pattern) {
654
+ if (this.match(options, [this.node(node.elts[i]).elts[0]], expr).length) {
523
655
  this.visit(val.exprElt, options, resume);
524
656
  foundMatch = true;
525
657
  }
@@ -535,7 +667,7 @@ export class Transformer extends Visitor {
535
667
  options.SYNC = false;
536
668
  }
537
669
  OF(node, options, resume) {
538
- this.visit(node.elts[0], options, function (err0, pattern) {
670
+ this.visit(node.elts[0], options, (err0, pattern) => {
539
671
  resume([].concat(err0), {
540
672
  pattern: pattern,
541
673
  exprElt: node.elts[1],
@@ -583,7 +715,6 @@ export class Compiler {
583
715
  } else {
584
716
  const renderer = new this.Renderer(val);
585
717
  renderer.render(options, (err, val) => {
586
- val = !(val instanceof Array) && [val] || val;
587
718
  resume(err, val);
588
719
  });
589
720
  }