@graffiticode/basis 1.5.18 → 1.5.20

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.
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "@graffiticode/basis",
3
3
  "type": "module",
4
- "version": "1.5.18",
4
+ "version": "1.5.20",
5
5
  "description": "The basis library for creating Graffiticode languages",
6
6
  "main": "index.js",
7
7
  "scripts": {
8
8
  "test": "jest",
9
9
  "build-spec": "npx spec-md ./spec/spec.md > ./spec/spec.html",
10
- "watch-spec": "npx nodemon --exec 'npx spec-md > ./spec/spec.html' ./spec/spec.md"
10
+ "watch-spec": "npx nodemon --exec 'npx spec-md > ./spec/spec.html' ./spec/spec.md",
11
+ "publish-spec": "npm run build-spec && for dir in ../l0*/packages/api/public; do cp ./spec/spec.html \"$dir/graffiticode-language-spec.html\"; echo \"Copied to $dir/graffiticode-language-spec.html\"; done"
11
12
  },
12
13
  "engines": {
13
14
  "node": "22.x"
package/spec/spec.html CHANGED
@@ -174,6 +174,9 @@ end
174
174
  <li><strong>Strict evaluation</strong>: arguments evaluated before function application</li>
175
175
  <li><strong>Immutable data</strong>: all values are immutable</li>
176
176
  </ul>
177
+ <p>Many built-in functions in Graffiticode follow a model-threading pattern. In this pattern, functions are defined to take one or more arguments followed by a model, which represents the current state of the program or view. The function uses the earlier arguments to compute an update to the model and returns a new model as its result.</p>
178
+ <p>This style enables a declarative and order-independent composition of functions. Since each function call returns a new model, multiple calls can be reordered without changing the final result, provided the functional dependencies are preserved.</p>
179
+ <p>This approach draws inspiration from <strong>Model-View-Update</strong> (MVU) architectures, in which the model represents the application state and functions describe pure, deterministic transformations of that state.</p>
177
180
  </section>
178
181
  <section id="sec-Functions" secid="4.2">
179
182
  <h2><span class="spec-secid" title="link to this section"><a href="#sec-Functions">4.2</a></span>Functions</h2>
@@ -305,12 +308,12 @@ end
305
308
  </tr>
306
309
  <tr>
307
310
  <td align="left"><code>get</code></td>
308
- <td align="left"><code>&lt;record string: any&gt;</code></td>
311
+ <td align="left"><code>&lt;string record: any&gt;</code></td>
309
312
  <td align="left">Retrieves a value from a record by key</td>
310
313
  </tr>
311
314
  <tr>
312
315
  <td align="left"><code>set</code></td>
313
- <td align="left"><code>&lt;record string any: record&gt;</code></td>
316
+ <td align="left"><code>&lt;string any record: record&gt;</code></td>
314
317
  <td align="left">Returns a new record with a key set to a value</td>
315
318
  </tr>
316
319
  </tbody>
@@ -414,13 +417,13 @@ end
414
417
  <section id="sec-get" secid="5.2.17">
415
418
  <h3><span class="spec-secid" title="link to this section"><a href="#sec-get">5.2.17</a></span>get</h3>
416
419
  <p>Retrieve a record field</p>
417
- <pre><code>get {a: 1, b: 2} "b" | returns 2
420
+ <pre><code>get "b" {a: 1, b: 2} | returns 2
418
421
  </code></pre>
419
422
  </section>
420
423
  <section id="sec-set" secid="5.2.18">
421
424
  <h3><span class="spec-secid" title="link to this section"><a href="#sec-set">5.2.18</a></span>set</h3>
422
425
  <p>Return a new record with an updated field</p>
423
- <pre><code>set {a: 1} "a" 2 | returns {a: 2}
426
+ <pre><code>set "a" 2 {a: 1} | returns {a: 2}
424
427
  </code></pre>
425
428
  </section>
426
429
  </section>
package/spec/spec.md CHANGED
@@ -119,6 +119,14 @@ Tags are resolved as special constants with symbolic identity. They are case-sen
119
119
  - **Strict evaluation**: arguments evaluated before function application
120
120
  - **Immutable data**: all values are immutable
121
121
 
122
+ Many built-in functions in Graffiticode follow a model-threading pattern. In this pattern, functions are defined to take one or more arguments followed by a model, which represents the current state of the program or view. The function uses the earlier arguments to compute an update to the model and returns a new model as its result.
123
+
124
+ This style enables a declarative and order-independent composition of functions. Since each function call returns a new model, multiple calls can be reordered without changing the final result, provided the functional dependencies are preserved.
125
+
126
+ This approach draws inspiration from **Model-View-Update** (MVU) architectures, in which the model represents the application state and functions describe pure, deterministic transformations of that state.
127
+
128
+
129
+
122
130
  ## Functions
123
131
 
124
132
  - **Fixed arity**: every function has a known number of parameters
@@ -167,8 +175,8 @@ Tags are resolved as special constants with symbolic identity. They are case-sen
167
175
  | `nth` | `<number list: any>` | Nth element of list |
168
176
  | `apply` | `<function list: any>` | Applies a function to a list of arguments |
169
177
  | `isEmpty` | `<list: bool>` | Returns true if the list is empty |
170
- | `get` | `<record string: any>` | Retrieves a value from a record by key |
171
- | `set` | `<record string any: record>` | Returns a new record with a key set to a value |
178
+ | `get` | `<string record: any>` | Retrieves a value from a record by key |
179
+ | `set` | `<string any record: record>` | Returns a new record with a key set to a value |
172
180
 
173
181
  ### add
174
182
 
@@ -303,7 +311,7 @@ isEmpty [] | returns true
303
311
  Retrieve a record field
304
312
 
305
313
  ```
306
- get {a: 1, b: 2} "b" | returns 2
314
+ get "b" {a: 1, b: 2} | returns 2
307
315
  ```
308
316
 
309
317
  ### set
@@ -311,7 +319,7 @@ get {a: 1, b: 2} "b" | returns 2
311
319
  Return a new record with an updated field
312
320
 
313
321
  ```
314
- set {a: 1} "a" 2 | returns {a: 2}
322
+ set "a" 2 {a: 1} | returns {a: 2}
315
323
  ```
316
324
 
317
325
  # Program Examples
package/src/compiler.js CHANGED
@@ -27,7 +27,7 @@ class Visitor {
27
27
  }
28
28
  visit(nid, options, resume) {
29
29
  try {
30
- assert(nid);
30
+ assert(nid, "Invalid nid=" + nid);
31
31
  let node;
32
32
  if (typeof nid === "object") {
33
33
  node = nid;
@@ -425,6 +425,62 @@ export class Checker extends Visitor {
425
425
  });
426
426
  });
427
427
  }
428
+ NOT(node, options, resume) {
429
+ this.visit(node.elts[0], options, (err1, val1) => {
430
+ let err = [].concat(err1);
431
+ if (typeof val1 !== "boolean" && val1 !== null && val1 !== undefined && val1 !== 0 && val1 !== "" && val1 !== false) {
432
+ err.push(`NOT operation requires a boolean argument, got ${typeof val1}`);
433
+ }
434
+ const val = node;
435
+ resume(err, val);
436
+ });
437
+ }
438
+ EQUIV(node, options, resume) {
439
+ this.visit(node.elts[0], options, (err1, val1) => {
440
+ this.visit(node.elts[1], options, (err2, val2) => {
441
+ let err = [].concat(err1).concat(err2);
442
+ const validTypes = ["boolean", "string", "number"];
443
+ if (!validTypes.includes(typeof val1) && val1 !== null) {
444
+ err.push(`EQUIV operation requires primitive arguments, got ${typeof val1} for first argument`);
445
+ }
446
+ if (!validTypes.includes(typeof val2) && val2 !== null) {
447
+ err.push(`EQUIV operation requires primitive arguments, got ${typeof val2} for second argument`);
448
+ }
449
+ const val = node;
450
+ resume(err, val);
451
+ });
452
+ });
453
+ }
454
+ OR(node, options, resume) {
455
+ this.visit(node.elts[0], options, (err1, val1) => {
456
+ this.visit(node.elts[1], options, (err2, val2) => {
457
+ let err = [].concat(err1).concat(err2);
458
+ if (typeof val1 !== "boolean" && val1 !== null && val1 !== undefined && val1 !== 0 && val1 !== "" && val1 !== false) {
459
+ err.push(`OR operation requires boolean arguments, got ${typeof val1} for first argument`);
460
+ }
461
+ if (typeof val2 !== "boolean" && val2 !== null && val2 !== undefined && val2 !== 0 && val2 !== "" && val2 !== false) {
462
+ err.push(`OR operation requires boolean arguments, got ${typeof val2} for second argument`);
463
+ }
464
+ const val = node;
465
+ resume(err, val);
466
+ });
467
+ });
468
+ }
469
+ AND(node, options, resume) {
470
+ this.visit(node.elts[0], options, (err1, val1) => {
471
+ this.visit(node.elts[1], options, (err2, val2) => {
472
+ let err = [].concat(err1).concat(err2);
473
+ if (typeof val1 !== "boolean" && val1 !== null && val1 !== undefined && val1 !== 0 && val1 !== "" && val1 !== false) {
474
+ err.push(`AND operation requires boolean arguments, got ${typeof val1} for first argument`);
475
+ }
476
+ if (typeof val2 !== "boolean" && val2 !== null && val2 !== undefined && val2 !== 0 && val2 !== "" && val2 !== false) {
477
+ err.push(`AND operation requires boolean arguments, got ${typeof val2} for second argument`);
478
+ }
479
+ const val = node;
480
+ resume(err, val);
481
+ });
482
+ });
483
+ }
428
484
  }
429
485
 
430
486
  function enterEnv(ctx, name, paramc) {
@@ -724,7 +780,6 @@ export class Transformer extends Visitor {
724
780
  }
725
781
  BINDING(node, options, resume) {
726
782
  const err = [];
727
- const val = node;
728
783
  this.visit(node.elts[0], options, (err1, val1) => {
729
784
  this.visit(node.elts[1], options, (err2, val2) => {
730
785
  resume([].concat(err1).concat(err2), {key: val1, val: val2});
@@ -825,9 +880,11 @@ export class Transformer extends Visitor {
825
880
  resume(err, val);
826
881
  }
827
882
  LEN(node, options, resume) {
828
- const err = [];
829
- const val = node;
830
- resume(err, val);
883
+ this.visit(node.elts[0], options, (e0, v0) => {
884
+ const err = e0;
885
+ const val = Array.isArray(v0) && v0.length;
886
+ resume(err, val);
887
+ });
831
888
  }
832
889
  ARG(node, options, resume) {
833
890
  const err = [];
@@ -869,11 +926,8 @@ export class Transformer extends Visitor {
869
926
  this.visit(node.elts[1], options, (e1, v1) => {
870
927
  let err = [];
871
928
  let val = [];
872
- console.log(
873
- "MAP()",
874
- "v1=" + JSON.stringify(v1),
875
- );
876
929
  v1.forEach(args => {
930
+ options.SYNC = true;
877
931
  options.args = args;
878
932
  options = JSON.parse(JSON.stringify(options)); // Copy option arg support async.
879
933
  this.visit(node.elts[0], options, (e0, v0) => {
@@ -921,6 +975,7 @@ export class Transformer extends Visitor {
921
975
  let err = [];
922
976
  let val = v1;
923
977
  v2.forEach((args, index) => {
978
+ options.SYNC = true;
924
979
  options.args = [val, args];
925
980
  options = JSON.parse(JSON.stringify(options)); // Copy option arg support async.
926
981
  this.visit(node.elts[0], options, (e0, v0) => {
@@ -1007,9 +1062,9 @@ export class Transformer extends Visitor {
1007
1062
  this.visit(node.elts[0], options, (e0, v0) => {
1008
1063
  this.visit(node.elts[1], options, (e1, v1) => {
1009
1064
  const err = [...e0, ...e1];
1010
- assert(typeof v0 === "object", "Type Error: expected v0 to be an object. Got " + JSON.stringify(v0));
1011
- assert(typeof v1 === "string", "Type Error: expected v1 to be a string.");
1012
- const val = v0[v1];
1065
+ assert(typeof v0 === "string", "Type Error: expected v0 to be a string.");
1066
+ assert(typeof v1 === "object", "Type Error: expected v1 to be an object. Got " + JSON.stringify(v1));
1067
+ const val = v1[v0];
1013
1068
  resume(err, val);
1014
1069
  });
1015
1070
  });
@@ -1018,12 +1073,12 @@ export class Transformer extends Visitor {
1018
1073
  this.visit(node.elts[0], options, (e0, v0) => {
1019
1074
  this.visit(node.elts[1], options, (e1, v1) => {
1020
1075
  this.visit(node.elts[2], options, (e2, v2) => {
1021
- const err = [...e0, ...e1];
1022
- assert(typeof v0 === "object", "Type Error: expected v0 to be an object.");
1023
- assert(typeof v1 === "string", "Type Error: expected v1 to be a string.");
1076
+ const err = [...e0, ...e1, ...e2];
1077
+ assert(typeof v0 === "string", "Type Error: expected v0 to be a string.");
1078
+ assert(typeof v2 === "object", "Type Error: expected v2 to be an object.");
1024
1079
  const val = {
1025
- ...v0,
1026
- [v1]: v2,
1080
+ ...v2,
1081
+ [v0]: v1,
1027
1082
  };
1028
1083
  resume(err, val);
1029
1084
  });
@@ -1150,7 +1205,6 @@ export class Transformer extends Visitor {
1150
1205
  const start = new Decimal(v0);
1151
1206
  const end = new Decimal(v1);
1152
1207
  const step = new Decimal(v2);
1153
-
1154
1208
  if (step.isZero()) {
1155
1209
  resume([...err, 'Error in RANGE operation: step cannot be zero'], []);
1156
1210
  return;
@@ -1176,6 +1230,75 @@ export class Transformer extends Visitor {
1176
1230
  });
1177
1231
  });
1178
1232
  }
1233
+ NOT(node, options, resume) {
1234
+ this.visit(node.elts[0], options, (e0, v0) => {
1235
+ const err = [].concat(e0);
1236
+ try {
1237
+ // Handle various falsy values explicitly
1238
+ if (v0 === null || v0 === undefined || v0 === 0 || v0 === "" || v0 === false) {
1239
+ resume(err, true);
1240
+ } else {
1241
+ resume(err, !v0);
1242
+ }
1243
+ } catch (e) {
1244
+ resume([...err, `Error in NOT operation: ${e.message}`], false);
1245
+ }
1246
+ });
1247
+ }
1248
+ EQUIV(node, options, resume) {
1249
+ this.visit(node.elts[0], options, (e0, v0) => {
1250
+ this.visit(node.elts[1], options, (e1, v1) => {
1251
+ const err = [].concat(e0).concat(e1);
1252
+ try {
1253
+ // Use strict equality for primitive comparison
1254
+ const val = v0 === v1;
1255
+ resume(err, val);
1256
+ } catch (e) {
1257
+ resume([...err, `Error in EQUIV operation: ${e.message}`], false);
1258
+ }
1259
+ });
1260
+ });
1261
+ }
1262
+ OR(node, options, resume) {
1263
+ this.visit(node.elts[0], options, (e0, v0) => {
1264
+ // Short-circuit evaluation - if first argument is truthy, return true immediately
1265
+ if (v0) {
1266
+ resume(e0, true);
1267
+ return;
1268
+ }
1269
+
1270
+ this.visit(node.elts[1], options, (e1, v1) => {
1271
+ const err = [].concat(e0).concat(e1);
1272
+ try {
1273
+ // Standard boolean OR operation
1274
+ const val = Boolean(v0) || Boolean(v1);
1275
+ resume(err, val);
1276
+ } catch (e) {
1277
+ resume([...err, `Error in OR operation: ${e.message}`], false);
1278
+ }
1279
+ });
1280
+ });
1281
+ }
1282
+ AND(node, options, resume) {
1283
+ this.visit(node.elts[0], options, (e0, v0) => {
1284
+ // Short-circuit evaluation - if first argument is falsy, return false immediately
1285
+ if (!v0) {
1286
+ resume(e0, false);
1287
+ return;
1288
+ }
1289
+
1290
+ this.visit(node.elts[1], options, (e1, v1) => {
1291
+ const err = [].concat(e0).concat(e1);
1292
+ try {
1293
+ // Standard boolean AND operation
1294
+ const val = Boolean(v0) && Boolean(v1);
1295
+ resume(err, val);
1296
+ } catch (e) {
1297
+ resume([...err, `Error in AND operation: ${e.message}`], false);
1298
+ }
1299
+ });
1300
+ });
1301
+ }
1179
1302
  }
1180
1303
 
1181
1304
  export class Renderer {
package/src/lexicon.js CHANGED
@@ -11,14 +11,16 @@ export const lexicon = {
11
11
  "name": "GET",
12
12
  "cls": "function",
13
13
  "length": 2,
14
- "arity": 2
14
+ "arity": 2,
15
+ "args": ["key", "record"]
15
16
  },
16
17
  "set" : {
17
18
  "tk": 1,
18
19
  "name": "SET",
19
20
  "cls": "function",
20
21
  "length": 3,
21
- "arity": 3
22
+ "arity": 3,
23
+ "args": ["key", "value", "record"]
22
24
  },
23
25
  "nth" : {
24
26
  "tk": 1,
@@ -187,5 +189,33 @@ export const lexicon = {
187
189
  "cls": "function",
188
190
  "length": 3,
189
191
  "arity": 3
192
+ },
193
+ "not" : {
194
+ "tk": 1,
195
+ "name": "NOT",
196
+ "cls": "function",
197
+ "length": 1,
198
+ "arity": 1
199
+ },
200
+ "equiv" : {
201
+ "tk": 1,
202
+ "name": "EQUIV",
203
+ "cls": "function",
204
+ "length": 2,
205
+ "arity": 2
206
+ },
207
+ "or" : {
208
+ "tk": 1,
209
+ "name": "OR",
210
+ "cls": "function",
211
+ "length": 2,
212
+ "arity": 2
213
+ },
214
+ "and" : {
215
+ "tk": 1,
216
+ "name": "AND",
217
+ "cls": "function",
218
+ "length": 2,
219
+ "arity": 2
190
220
  }
191
221
  }