@weborigami/language 0.0.42 → 0.0.44

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.
@@ -1,6 +1,7 @@
1
1
  import { Tree, isStringLike } from "@weborigami/async-tree";
2
2
  import Scope from "./Scope.js";
3
3
  import extname from "./extname.js";
4
+ import * as symbols from "./symbols.js";
4
5
 
5
6
  /**
6
7
  * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
@@ -14,25 +15,31 @@ export default function FileLoadersTransform(Base) {
14
15
  async get(key) {
15
16
  let value = await super.get(key);
16
17
 
17
- // If the value is string-like and the key has an extension, look for a
18
- // loader that handles that extension and call it. The value will
19
- // typically be a Buffer loaded from the file system, but could also be a
20
- // string-like object defined by a user function.
21
- if (isStringLike(value) && isStringLike(key)) {
18
+ // If the key is string-like and has an extension, look for a loader that
19
+ // handles that extension.
20
+ if (value && isStringLike(key)) {
22
21
  const extension = extname(String(key)).toLowerCase().slice(1);
23
22
  if (extension) {
24
23
  /** @type {any} */
25
24
  const scope = Scope.getScope(this);
26
- /** @type {FileUnpackFunction} */
27
- const unpackFn = await Tree.traverse(scope, "@loaders", extension);
28
- if (unpackFn) {
25
+ const loader = await Tree.traverse(scope, "@loaders", extension);
26
+ if (loader) {
29
27
  const input = value;
30
28
  // If the input is a plain string, convert it to a String so we can
31
29
  // attach data to it.
32
- value = new String(input);
30
+ if (typeof input === "string") {
31
+ value = new String(input);
32
+ }
33
33
  const parent = this;
34
- value.parent = parent;
35
- value.unpack = unpackFn.bind(null, input, { key, parent });
34
+ value[symbols.parent] = parent;
35
+
36
+ // Wrap the loader with a function that will only be called once per
37
+ // value.
38
+ let loaded;
39
+ value.unpack = async () => {
40
+ loaded ??= await loader(input, { key, parent });
41
+ return loaded;
42
+ };
36
43
  }
37
44
  }
38
45
  }
@@ -81,9 +81,13 @@ export default async function evaluate(code) {
81
81
  Object.isExtensible(result) &&
82
82
  !isPlainObject(result)
83
83
  ) {
84
- result[codeSymbol] = code;
85
- if (/** @type {any} */ (code).location) {
86
- result[sourceSymbol] = codeFragment(code);
84
+ try {
85
+ result[codeSymbol] = code;
86
+ if (/** @type {any} */ (code).location) {
87
+ result[sourceSymbol] = codeFragment(code);
88
+ }
89
+ } catch (/** @type {any} */ error) {
90
+ // Ignore errors.
87
91
  }
88
92
  }
89
93
 
@@ -0,0 +1 @@
1
+ export const parent = Symbol("parent");
@@ -49,7 +49,7 @@ describe("compile", () => {
49
49
  });
50
50
 
51
51
  test("templateLiteral", async () => {
52
- await assertCompile("`Hello, {{name}}!`", "Hello, Alice!");
52
+ await assertCompile("`Hello, ${name}!`", "Hello, Alice!");
53
53
  await assertCompile(
54
54
  "`escape characters with \\`backslash\\``",
55
55
  "escape characters with `backslash`"
@@ -70,7 +70,7 @@ describe("Origami parser", () => {
70
70
  ],
71
71
  [[ops.scope, "files"], "snapshot"],
72
72
  ]);
73
- assertParse("expr", "@map =`<li>{{_}}</li>`", [
73
+ assertParse("expr", "@map =`<li>${_}</li>`", [
74
74
  [ops.scope, "@map"],
75
75
  [ops.lambda, null, [ops.concat, "<li>", [ops.scope, "_"], "</li>"]],
76
76
  ]);
@@ -109,6 +109,18 @@ describe("Origami parser", () => {
109
109
  ],
110
110
  ]
111
111
  );
112
+
113
+ // Consecutive slahes inside a path = empty string key
114
+ assertParse("expression", "path//key", [
115
+ ops.traverse,
116
+ [ops.scope, "path"],
117
+ "",
118
+ "key",
119
+ ]);
120
+ // Single slash at start of something = absolute file path
121
+ assertParse("expression", "/path", [[ops.filesRoot], "path"]);
122
+ // Consecutive slashes at start of something = comment
123
+ assertParse("expression", "path //comment", [ops.scope, "path"]);
112
124
  });
113
125
 
114
126
  test("functionComposition", () => {
@@ -237,7 +249,7 @@ describe("Origami parser", () => {
237
249
  null,
238
250
  [ops.scope, "message"],
239
251
  ]);
240
- assertParse("lambda", "=`Hello, {{name}}.`", [
252
+ assertParse("lambda", "=`Hello, ${name}.`", [
241
253
  ops.lambda,
242
254
  null,
243
255
  [ops.concat, "Hello, ", [ops.scope, "name"], "."],
@@ -257,6 +269,10 @@ describe("Origami parser", () => {
257
269
  assertParse("list", "'a' , 'b' , 'c'", ["a", "b", "c"]);
258
270
  });
259
271
 
272
+ test("multiLineComment", () => {
273
+ assertParse("multiLineComment", "/*\nHello, world!\n*/", null);
274
+ });
275
+
260
276
  test("number", () => {
261
277
  assertParse("number", "123", 123);
262
278
  assertParse("number", "-456", -456);
@@ -369,6 +385,14 @@ describe("Origami parser", () => {
369
385
  ]);
370
386
  });
371
387
 
388
+ test("singleLineComment", () => {
389
+ assertParse("singleLineComment", "# Hello, world!", null);
390
+ });
391
+
392
+ test("singleLineComment (JS)", () => {
393
+ assertParse("singleLineComment", "// Hello, world!", null);
394
+ });
395
+
372
396
  test("scopeReference", () => {
373
397
  assertParse("scopeReference", "x", [ops.scope, "x"]);
374
398
  });
@@ -383,7 +407,7 @@ describe("Origami parser", () => {
383
407
  });
384
408
 
385
409
  test("templateDocument", () => {
386
- assertParse("templateDocument", "hello{{foo}}world", [
410
+ assertParse("templateDocument", "hello${foo}world", [
387
411
  ops.lambda,
388
412
  null,
389
413
  [ops.concat, "hello", [ops.scope, "foo"], "world"],
@@ -414,10 +438,33 @@ describe("Origami parser", () => {
414
438
  ]);
415
439
  });
416
440
 
441
+ test("templateLiteral (JS)", () => {
442
+ assertParse("templateLiteral", "`Hello, world.`", "Hello, world.");
443
+ assertParse("templateLiteral", "`foo ${x} bar`", [
444
+ ops.concat,
445
+ "foo ",
446
+ [ops.scope, "x"],
447
+ " bar",
448
+ ]);
449
+ assertParse("templateLiteral", "`${`nested`}`", "nested");
450
+ assertParse("templateLiteral", "`${map(people, =`${name}`)}`", [
451
+ ops.concat,
452
+ [
453
+ [ops.scope, "map"],
454
+ [ops.scope, "people"],
455
+ [ops.lambda, null, [ops.concat, [ops.scope, "name"]]],
456
+ ],
457
+ ]);
458
+ });
459
+
417
460
  test("templateSubstitution", () => {
418
461
  assertParse("templateSubstitution", "{{foo}}", [ops.scope, "foo"]);
419
462
  });
420
463
 
464
+ test("templateSubtitution (JS)", () => {
465
+ assertParse("templateSubstitution", "${foo}", [ops.scope, "foo"]);
466
+ });
467
+
421
468
  test("tree", () => {
422
469
  assertParse("tree", "{}", [ops.tree]);
423
470
  assertParse("tree", "{ a = 1, b }", [
@@ -33,7 +33,7 @@ function createFixture() {
33
33
  /** @type {any} */
34
34
  const scope = {
35
35
  "@loaders": {
36
- json: (input) => JSON.parse(input),
36
+ json: (buffer) => JSON.parse(String(buffer)),
37
37
  },
38
38
  };
39
39
  tree = Scope.treeWithScope(tree, scope);