@weborigami/language 0.3.2 → 0.3.3-jse.2

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.
@@ -204,7 +204,8 @@ export function makeCall(target, args) {
204
204
  }
205
205
 
206
206
  let fnCall;
207
- if (args[0] === ops.traverse) {
207
+ const op = args[0];
208
+ if (op === ops.traverse || op === ops.optionalTraverse) {
208
209
  let tree = target;
209
210
 
210
211
  if (tree[0] === undetermined) {
@@ -219,12 +220,12 @@ export function makeCall(target, args) {
219
220
  if (args.length > 1) {
220
221
  // Regular traverse
221
222
  const keys = args.slice(1);
222
- fnCall = [ops.traverse, tree, ...keys];
223
+ fnCall = [op, tree, ...keys];
223
224
  } else {
224
225
  // Traverse without arguments equates to unpack
225
226
  fnCall = [ops.unpack, tree];
226
227
  }
227
- } else if (args[0] === ops.template) {
228
+ } else if (op === ops.templateStandard || op === ops.templateTree) {
228
229
  // Tagged template
229
230
  const strings = args[1];
230
231
  const values = args.slice(2);
@@ -275,6 +276,15 @@ export function makeDeferredArguments(args) {
275
276
  });
276
277
  }
277
278
 
279
+ export function makeJsPropertyAccess(expression, property) {
280
+ const location = {
281
+ source: expression.location.source,
282
+ start: expression.location.start,
283
+ end: property.location.end,
284
+ };
285
+ return annotate([expression, property], location);
286
+ }
287
+
278
288
  /**
279
289
  * Make an object.
280
290
  *
@@ -364,15 +374,6 @@ export function makeProperty(key, value) {
364
374
  return [key, modified];
365
375
  }
366
376
 
367
- export function makeJsPropertyAccess(expression, property) {
368
- const location = {
369
- source: expression.location.source,
370
- start: expression.location.start,
371
- end: property.location.end,
372
- };
373
- return annotate([expression, property], location);
374
- }
375
-
376
377
  export function makeReference(identifier) {
377
378
  // We can't know for sure that an identifier is a builtin reference until we
378
379
  // see whether it's being called as a function.
@@ -74,9 +74,12 @@ export async function handleExtension(parent, value, key) {
74
74
  key = trailingSlash.remove(key);
75
75
  }
76
76
 
77
- // Special case: `.ori.<ext>` extensions are Origami documents.
77
+ // Special cases: `.ori.<ext>` extensions are Origami documents,
78
+ // `.jse.<ext>` are JSE documents.
78
79
  const extname = key.match(/\.ori\.\S+$/)
79
80
  ? ".oridocument"
81
+ : key.match(/\.jse\.\S+$/)
82
+ ? ".jsedocument"
80
83
  : extension.extname(key);
81
84
  if (extname) {
82
85
  const handler = await getExtensionHandler(parent, extname);
@@ -12,16 +12,17 @@ import {
12
12
  isUnpackable,
13
13
  scope as scopeFn,
14
14
  setParent,
15
- concat as treeConcat,
15
+ text as templateFunctionTree,
16
16
  } from "@weborigami/async-tree";
17
17
  import os from "node:os";
18
- import taggedTemplateIndent from "../../src/runtime/taggedTemplateIndent.js";
19
18
  import { builtinReferenceError, scopeReferenceError } from "./errors.js";
20
19
  import expressionObject from "./expressionObject.js";
21
20
  import { evaluate } from "./internal.js";
22
21
  import mergeTrees from "./mergeTrees.js";
23
22
  import OrigamiFiles from "./OrigamiFiles.js";
24
23
  import { codeSymbol } from "./symbols.js";
24
+ import templateFunctionIndent from "./templateIndent.js";
25
+ import templateFunctionStandard from "./templateStandard.js";
25
26
 
26
27
  function addOpLabel(op, label) {
27
28
  Object.defineProperty(op, "toString", {
@@ -105,7 +106,7 @@ addOpLabel(comma, "«ops.comma»");
105
106
  * @param {any[]} args
106
107
  */
107
108
  export async function concat(...args) {
108
- return treeConcat.call(this, args);
109
+ return deepText.call(this, args);
109
110
  }
110
111
  addOpLabel(concat, "«ops.concat»");
111
112
 
@@ -114,6 +115,13 @@ export async function conditional(condition, truthy, falsy) {
114
115
  return value instanceof Function ? await value() : value;
115
116
  }
116
117
 
118
+ export async function construct(constructor, ...args) {
119
+ if (isUnpackable(constructor)) {
120
+ constructor = await constructor.unpack();
121
+ }
122
+ return Reflect.construct(constructor, args);
123
+ }
124
+
117
125
  /**
118
126
  * Construct a document object by invoking the body code (a lambda) and adding
119
127
  * the resulting text to the front data.
@@ -472,6 +480,14 @@ export async function object(...entries) {
472
480
  addOpLabel(object, "«ops.object»");
473
481
  object.unevaluatedArgs = true;
474
482
 
483
+ export function optionalTraverse(treelike, key) {
484
+ if (!treelike) {
485
+ return undefined;
486
+ }
487
+ return Tree.traverseOrThrow(treelike, key);
488
+ }
489
+ addOpLabel(optionalTraverse, "«ops.optionalTraverse");
490
+
475
491
  export function remainder(a, b) {
476
492
  return a % b;
477
493
  }
@@ -546,21 +562,29 @@ export function subtraction(a, b) {
546
562
  }
547
563
  addOpLabel(subtraction, "«ops.subtraction»");
548
564
 
565
+ /**
566
+ * Apply the tree indent tagged template function.
567
+ */
568
+ export async function templateIndent(strings, ...values) {
569
+ return templateFunctionIndent(strings, ...values);
570
+ }
571
+ addOpLabel(templateIndent, "«ops.templateIndent»");
572
+
549
573
  /**
550
574
  * Apply the default tagged template function.
551
575
  */
552
- export async function template(strings, ...values) {
553
- return deepText(strings, ...values);
576
+ export function templateStandard(strings, ...values) {
577
+ return templateFunctionStandard(strings, ...values);
554
578
  }
555
- addOpLabel(template, "«ops.template»");
579
+ addOpLabel(templateStandard, "«ops.templateStandard»");
556
580
 
557
581
  /**
558
- * Apply the tagged template indent function.
582
+ * Apply the tree tagged template function.
559
583
  */
560
- export async function templateIndent(strings, ...values) {
561
- return taggedTemplateIndent(strings, ...values);
584
+ export async function templateTree(strings, ...values) {
585
+ return templateFunctionTree(strings, ...values);
562
586
  }
563
- addOpLabel(templateIndent, "«ops.templateIndent");
587
+ addOpLabel(templateTree, "«ops.templateTree»");
564
588
 
565
589
  /**
566
590
  * Traverse a path of keys through a tree.
@@ -1,4 +1,4 @@
1
- import { concat, toString, Tree } from "@weborigami/async-tree";
1
+ import { deepText, toString, Tree } from "@weborigami/async-tree";
2
2
 
3
3
  const lastLineWhitespaceRegex = /\n(?<indent>[ \t]*)$/;
4
4
 
@@ -19,7 +19,7 @@ export default async function indent(strings, ...values) {
19
19
  }
20
20
  const { blockIndentations, strings: modifiedStrings } = modified;
21
21
  const valueTexts = await Promise.all(
22
- values.map((value) => (Tree.isTreelike(value) ? concat(value) : value))
22
+ values.map((value) => (Tree.isTreelike(value) ? deepText(value) : value))
23
23
  );
24
24
  return joinBlocks(modifiedStrings, valueTexts, blockIndentations);
25
25
  }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Concatenate the strings with a standard tagged template function that works
3
+ * just like normal JavaScript templates. This is: a) synchronous, b) does not
4
+ * convert treelike objects to strings.
5
+ */
6
+ export default function standardTemplate(strings, ...values) {
7
+ let result = strings[0];
8
+ for (let i = 0; i < values.length; i++) {
9
+ result += values[i];
10
+ result += strings[i + 1];
11
+ }
12
+ return result;
13
+ }
@@ -19,6 +19,46 @@ describe("Origami parser", () => {
19
19
  ]);
20
20
  });
21
21
 
22
+ test("angleBracketLiteral", () => {
23
+ assertParse(
24
+ "angleBracketLiteral",
25
+ "<index.html>",
26
+ [ops.scope, "index.html"],
27
+ "jse",
28
+ false
29
+ );
30
+ assertParse(
31
+ "angleBracketLiteral",
32
+ "<foo/bar/baz>",
33
+ [
34
+ ops.traverse,
35
+ [ops.scope, "foo/"],
36
+ [ops.literal, "bar/"],
37
+ [ops.literal, "baz"],
38
+ ],
39
+ "jse",
40
+ false
41
+ );
42
+ assertParse("angleBracketLiteral", "<files:src/assets>", [
43
+ ops.traverse,
44
+ [
45
+ [ops.builtin, "files:"],
46
+ [ops.literal, "src/"],
47
+ ],
48
+ [ops.literal, "assets"],
49
+ ]);
50
+ assertParse(
51
+ "angleBracketLiteral",
52
+ "<https://example.com/>",
53
+ [
54
+ [ops.builtin, "https:"],
55
+ [ops.literal, "example.com/"],
56
+ ],
57
+ "jse",
58
+ false
59
+ );
60
+ });
61
+
22
62
  test("arrayLiteral", () => {
23
63
  assertParse("arrayLiteral", "[]", [ops.array]);
24
64
  assertParse("arrayLiteral", "[1, 2, 3]", [
@@ -399,7 +439,8 @@ Body`,
399
439
  ]);
400
440
 
401
441
  // Consecutive slashes at start of something = comment
402
- assertParse("expression", "path //comment", [ops.scope, "path"], false);
442
+ assertParse("expression", "x //comment", [ops.scope, "x"], "jse", false);
443
+
403
444
  assertParse("expression", "page.ori(mdHtml:(about.md))", [
404
445
  [ops.scope, "page.ori"],
405
446
  [
@@ -446,7 +487,11 @@ Body`,
446
487
  ]);
447
488
  assertParse("expression", "fn =`x`", [
448
489
  [ops.builtin, "fn"],
449
- [ops.lambda, [[ops.literal, "_"]], [ops.template, [ops.literal, ["x"]]]],
490
+ [
491
+ ops.lambda,
492
+ [[ops.literal, "_"]],
493
+ [ops.templateTree, [ops.literal, ["x"]]],
494
+ ],
450
495
  ]);
451
496
  assertParse("expression", "copy app.js(formulas), files:snapshot", [
452
497
  [ops.builtin, "copy"],
@@ -464,7 +509,7 @@ Body`,
464
509
  [
465
510
  ops.lambda,
466
511
  [[ops.literal, "_"]],
467
- [ops.template, [ops.literal, ["<li>", "</li>"]], [ops.scope, "_"]],
512
+ [ops.templateTree, [ops.literal, ["<li>", "</li>"]], [ops.scope, "_"]],
468
513
  ],
469
514
  ]);
470
515
  assertParse("expression", `https://example.com/about/`, [
@@ -523,6 +568,23 @@ Body`,
523
568
  ]);
524
569
  });
525
570
 
571
+ test("frontMatterExpression", () => {
572
+ assertParse(
573
+ "frontMatterExpression",
574
+ `---
575
+ (name) => _template()
576
+ ---
577
+ `,
578
+ [
579
+ ops.lambda,
580
+ [[ops.literal, "name"]],
581
+ [[ops.scope, "_template"], undefined],
582
+ ],
583
+ "jse",
584
+ false
585
+ );
586
+ });
587
+
526
588
  test("group", () => {
527
589
  assertParse("group", "(hello)", [ops.scope, "hello"]);
528
590
  assertParse("group", "(((nested)))", [ops.scope, "nested"]);
@@ -544,10 +606,10 @@ Body`,
544
606
  });
545
607
 
546
608
  test("identifier", () => {
547
- assertParse("identifier", "abc", "abc", false);
548
- assertParse("identifier", "index.html", "index.html", false);
549
- assertParse("identifier", "foo\\ bar", "foo bar", false);
550
- assertParse("identifier", "x-y-z", "x-y-z", false);
609
+ assertParse("identifier", "abc", "abc", "shell", false);
610
+ assertParse("identifier", "index.html", "index.html", "shell", false);
611
+ assertParse("identifier", "foo\\ bar", "foo bar", "shell", false);
612
+ assertParse("identifier", "x-y-z", "x-y-z", "shell", false);
551
613
  });
552
614
 
553
615
  test("implicitParenthesesCallExpression", () => {
@@ -586,8 +648,8 @@ Body`,
586
648
  });
587
649
 
588
650
  test("jsIdentifier", () => {
589
- assertParse("jsIdentifier", "foo", "foo", false);
590
- assertParse("jsIdentifier", "$Δelta", "$Δelta", false);
651
+ assertParse("jsIdentifier", "foo", "foo", "jse", false);
652
+ assertParse("jsIdentifier", "$Δelta", "$Δelta", "jse", false);
591
653
  assertThrows(
592
654
  "jsIdentifier",
593
655
  "1stCharacterIsNumber",
@@ -627,6 +689,7 @@ Body`,
627
689
  [ops.literal, 2],
628
690
  [ops.literal, 3],
629
691
  ]);
692
+ assertThrows("list", "1\n2\n3", `but "\\n" found`, 0, "jse");
630
693
  assertParse("list", "'a' , 'b' , 'c'", [
631
694
  [ops.literal, "a"],
632
695
  [ops.literal, "b"],
@@ -662,7 +725,13 @@ Body`,
662
725
  });
663
726
 
664
727
  test("multiLineComment", () => {
665
- assertParse("multiLineComment", "/*\nHello, world!\n*/", null, false);
728
+ assertParse(
729
+ "multiLineComment",
730
+ "/*\nHello, world!\n*/",
731
+ null,
732
+ "jse",
733
+ false
734
+ );
666
735
  });
667
736
 
668
737
  test("multiplicativeExpression", () => {
@@ -687,6 +756,13 @@ Body`,
687
756
  assertParse("namespace", "js:", [ops.builtin, "js:"]);
688
757
  });
689
758
 
759
+ test("newExpression", () => {
760
+ assertParse("newExpression", "new Foo()", [
761
+ ops.construct,
762
+ [ops.scope, "Foo"],
763
+ ]);
764
+ });
765
+
690
766
  test("nullishCoalescingExpression", () => {
691
767
  assertParse("nullishCoalescingExpression", "a ?? b", [
692
768
  ops.nullishCoalescing,
@@ -839,9 +915,16 @@ Body`,
839
915
  });
840
916
 
841
917
  test("objectPublicKey", () => {
842
- assertParse("objectPublicKey", "a", "a", false);
843
- assertParse("objectPublicKey", "markdown/", "markdown/", false);
844
- assertParse("objectPublicKey", "foo\\ bar", "foo bar", false);
918
+ assertParse("objectPublicKey", "a", "a", "jse", false);
919
+ assertParse("objectPublicKey", "markdown/", "markdown/", "jse", false);
920
+ assertParse("objectPublicKey", "foo\\ bar", "foo bar", "jse", false);
921
+ });
922
+
923
+ test("optionalChaining", () => {
924
+ assertParse("optionalChaining", "?.key", [
925
+ ops.optionalTraverse,
926
+ [ops.literal, "key"],
927
+ ]);
845
928
  });
846
929
 
847
930
  test("parenthesesArguments", () => {
@@ -911,6 +994,15 @@ Body`,
911
994
  [ops.literal, 1],
912
995
  [ops.literal, 2],
913
996
  ]);
997
+ // Only in JSE
998
+ assertParse(
999
+ "primary",
1000
+ "<index.html>",
1001
+ [ops.scope, "index.html"],
1002
+ "jse",
1003
+ false
1004
+ );
1005
+ assertThrows("primary", "<index.html>", `but "<" found`, 0, "shell");
914
1006
  });
915
1007
 
916
1008
  test("program", () => {
@@ -920,6 +1012,7 @@ Body`,
920
1012
  'Hello'
921
1013
  `,
922
1014
  [ops.literal, "Hello"],
1015
+ "jse",
923
1016
  false
924
1017
  );
925
1018
  });
@@ -964,6 +1057,10 @@ Body`,
964
1057
  ]);
965
1058
  });
966
1059
 
1060
+ test("regexLiteral", () => {
1061
+ assertParse("regexLiteral", "/abc+/g", [ops.literal, /abc+/g]);
1062
+ });
1063
+
967
1064
  test("relationalExpression", () => {
968
1065
  assertParse("relationalExpression", "1 < 2", [
969
1066
  ops.lessThan,
@@ -1025,7 +1122,7 @@ Body`,
1025
1122
  assertParse("shorthandFunction", "=`Hello, ${name}.`", [
1026
1123
  ops.lambda,
1027
1124
  [[ops.literal, "_"]],
1028
- [ops.template, [ops.literal, ["Hello, ", "."]], [ops.scope, "name"]],
1125
+ [ops.templateTree, [ops.literal, ["Hello, ", "."]], [ops.scope, "name"]],
1029
1126
  ]);
1030
1127
  assertParse("shorthandFunction", "=indent`hello`", [
1031
1128
  ops.lambda,
@@ -1038,7 +1135,7 @@ Body`,
1038
1135
  });
1039
1136
 
1040
1137
  test("singleLineComment", () => {
1041
- assertParse("singleLineComment", "// Hello, world!", null, false);
1138
+ assertParse("singleLineComment", "// Hello, world!", null, "jse", false);
1042
1139
  });
1043
1140
 
1044
1141
  test("spreadElement", () => {
@@ -1142,21 +1239,27 @@ Body text`,
1142
1239
 
1143
1240
  test("templateLiteral", () => {
1144
1241
  assertParse("templateLiteral", "`Hello, world.`", [
1145
- ops.template,
1242
+ ops.templateTree,
1146
1243
  [ops.literal, ["Hello, world."]],
1147
1244
  ]);
1245
+ assertParse(
1246
+ "templateLiteral",
1247
+ "`Hello, world.`",
1248
+ [ops.templateStandard, [ops.literal, ["Hello, world."]]],
1249
+ "jse"
1250
+ );
1148
1251
  assertParse("templateLiteral", "`foo ${x} bar`", [
1149
- ops.template,
1252
+ ops.templateTree,
1150
1253
  [ops.literal, ["foo ", " bar"]],
1151
1254
  [ops.scope, "x"],
1152
1255
  ]);
1153
1256
  assertParse("templateLiteral", "`${`nested`}`", [
1154
- ops.template,
1257
+ ops.templateTree,
1155
1258
  [ops.literal, ["", ""]],
1156
- [ops.template, [ops.literal, ["nested"]]],
1259
+ [ops.templateTree, [ops.literal, ["nested"]]],
1157
1260
  ]);
1158
1261
  assertParse("templateLiteral", "`${ map:(people, =`${name}`) }`", [
1159
- ops.template,
1262
+ ops.templateTree,
1160
1263
  [ops.literal, ["", ""]],
1161
1264
  [
1162
1265
  [ops.builtin, "map:"],
@@ -1164,14 +1267,20 @@ Body text`,
1164
1267
  [
1165
1268
  ops.lambda,
1166
1269
  [[ops.literal, "_"]],
1167
- [ops.template, [ops.literal, ["", ""]], [ops.scope, "name"]],
1270
+ [ops.templateTree, [ops.literal, ["", ""]], [ops.scope, "name"]],
1168
1271
  ],
1169
1272
  ],
1170
1273
  ]);
1171
1274
  });
1172
1275
 
1173
1276
  test("templateSubtitution", () => {
1174
- assertParse("templateSubstitution", "${foo}", [ops.scope, "foo"], false);
1277
+ assertParse(
1278
+ "templateSubstitution",
1279
+ "${foo}",
1280
+ [ops.scope, "foo"],
1281
+ "shell",
1282
+ false
1283
+ );
1175
1284
  });
1176
1285
 
1177
1286
  test("whitespace block", () => {
@@ -1182,6 +1291,7 @@ Body text`,
1182
1291
  // Second comment
1183
1292
  `,
1184
1293
  null,
1294
+ "jse",
1185
1295
  false
1186
1296
  );
1187
1297
  });
@@ -1197,9 +1307,16 @@ Body text`,
1197
1307
  });
1198
1308
  });
1199
1309
 
1200
- function assertParse(startRule, source, expected, checkLocation = true) {
1310
+ function assertParse(
1311
+ startRule,
1312
+ source,
1313
+ expected,
1314
+ mode = "shell",
1315
+ checkLocation = true
1316
+ ) {
1201
1317
  const code = parse(source, {
1202
1318
  grammarSource: { text: source },
1319
+ mode,
1203
1320
  startRule,
1204
1321
  });
1205
1322
 
@@ -1227,10 +1344,11 @@ function assertCodeLocations(code) {
1227
1344
  }
1228
1345
  }
1229
1346
 
1230
- function assertThrows(startRule, source, message, position) {
1347
+ function assertThrows(startRule, source, message, position, mode = "shell") {
1231
1348
  try {
1232
1349
  parse(source, {
1233
1350
  grammarSource: { text: source },
1351
+ mode,
1234
1352
  startRule,
1235
1353
  });
1236
1354
  } catch (/** @type {any} */ error) {
@@ -80,7 +80,11 @@ describe("ops", () => {
80
80
  assert.strictEqual(await ops.conditional(false, errorFn, trueFn), true);
81
81
  });
82
82
 
83
- test("ops.documentFunction", async () => {
83
+ test("ops.construct", async () => {
84
+ assert.equal(await ops.construct(String, "hello"), "hello");
85
+ });
86
+
87
+ test("ops.document", async () => {
84
88
  const code = createCode([
85
89
  ops.document,
86
90
  {
@@ -90,7 +94,7 @@ describe("ops", () => {
90
94
  ops.lambda,
91
95
  [["_"]],
92
96
  [
93
- ops.template,
97
+ ops.templateIndent,
94
98
  [ops.literal, ["a = ", ""]],
95
99
  [ops.concat, [ops.scope, "a"]],
96
100
  ],
@@ -293,6 +297,11 @@ describe("ops", () => {
293
297
  assert.strictEqual(ops.multiplication("foo", 2), NaN);
294
298
  });
295
299
 
300
+ test("ops.optionalTraverse", async () => {
301
+ assert.equal(await ops.optionalTraverse(null, "a"), undefined);
302
+ assert.equal(await ops.optionalTraverse({ a: 1 }, "a"), 1);
303
+ });
304
+
296
305
  test("ops.notEqual", () => {
297
306
  assert(!ops.notEqual(1, 1));
298
307
  assert(ops.notEqual(1, 2));
@@ -1,6 +1,6 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
- import indent from "../../src/runtime/taggedTemplateIndent.js";
3
+ import indent from "../../src/runtime/templateIndent.js";
4
4
 
5
5
  describe("taggedTemplateIndent", () => {
6
6
  test("joins strings and values together if template isn't a block template", async () => {
@@ -0,0 +1,18 @@
1
+ import assert from "node:assert";
2
+ import { describe, test } from "node:test";
3
+ import templateText from "../../src/runtime/templateStandard.js";
4
+
5
+ describe("templateText", () => {
6
+ test("joins strings and values together like JavaScript", async () => {
7
+ const a = 1;
8
+ const b = 2;
9
+ const result = await templateText`-${a} ${b}-`;
10
+ assert.equal(result, "-1 2-");
11
+ });
12
+
13
+ test("renders an object like JavaScript", async () => {
14
+ const object = { a: 1 };
15
+ const result = await templateText`-${object}-`;
16
+ assert.equal(result, "-[object Object]-");
17
+ });
18
+ });