@weborigami/language 0.4.1 → 0.5.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.
@@ -177,8 +177,8 @@ export function makeCall(target, args, location) {
177
177
  } else if (op === markers.property) {
178
178
  // Property access
179
179
  const property = args[1];
180
- fnCall = [target, property];
181
- } else if (op === ops.templateTree) {
180
+ fnCall = [ops.property, target, property];
181
+ } else if (op === ops.templateText) {
182
182
  // Tagged template
183
183
  const strings = args[1];
184
184
  const values = args.slice(2);
@@ -218,8 +218,6 @@ export function makeDocument(front, body, location) {
218
218
  );
219
219
 
220
220
  // Add an entry for the body
221
- // TODO: Deprecate @text
222
- entries.push(annotate(["(@text)", body], location));
223
221
  entries.push(annotate(["_body", body], location));
224
222
 
225
223
  // Return the code for the document object
@@ -233,7 +231,7 @@ export function makeDocument(front, body, location) {
233
231
  *
234
232
  * {
235
233
  * x = { a: 1 }
236
- * x
234
+ * ...x
237
235
  * y = x
238
236
  * }
239
237
  *
@@ -244,7 +242,7 @@ export function makeDocument(front, body, location) {
244
242
  * y = x
245
243
  * _result: {
246
244
  * x
247
- * x
245
+ * ...x
248
246
  * y
249
247
  * }
250
248
  * }.result
@@ -348,8 +346,12 @@ export function makeObject(entries, location) {
348
346
  // Merge multiple spreads
349
347
  code = makeMerge(spreads, location);
350
348
  } else if (spreads.length === 1) {
351
- // A single spread can just be the object
352
- code = spreads[0];
349
+ // Spreading a single reference equates to an unpack
350
+ if (spreads[0][0] === markers.traverse) {
351
+ code = annotate([ops.unpack, spreads[0]], location);
352
+ } else {
353
+ code = spreads[0];
354
+ }
353
355
  } else {
354
356
  // Empty object
355
357
  code = [ops.object];
@@ -23,6 +23,10 @@ const origamiSourceSignals = [
23
23
  const displayedWarnings = new Set();
24
24
 
25
25
  export function attachWarning(value, message) {
26
+ if (value == null) {
27
+ return value;
28
+ }
29
+
26
30
  if (typeof value === "object" && value?.[symbols.warningSymbol]) {
27
31
  // Already has a warning, don't overwrite it
28
32
  return value;
@@ -156,14 +156,29 @@ export function entryKey(entry, object = null, eagerProperties = []) {
156
156
  return trailingSlash.add(key);
157
157
  }
158
158
 
159
+ if (!(value instanceof Array)) {
160
+ // Can't be a subtree
161
+ return trailingSlash.remove(key);
162
+ }
163
+
164
+ // If we're dealing with a getter, work with what that gets
165
+ if (value[0] === ops.getter) {
166
+ value = value[1];
167
+ }
168
+
159
169
  // If entry will definitely create a subtree, add a trailing slash
160
- const entryCreatesSubtree =
161
- value instanceof Array &&
162
- (value[0] === ops.object ||
163
- (value[0] === ops.getter &&
164
- value[1] instanceof Array &&
165
- (value[1][0] === ops.object || value[1][0] === ops.merge)));
166
- return trailingSlash.toggle(key, entryCreatesSubtree);
170
+ if (value[0] === ops.object) {
171
+ // Subtree
172
+ return trailingSlash.add(key);
173
+ }
174
+
175
+ // See if it looks a merged object
176
+ if (value[1] === "_result" && value[0][0] === ops.object) {
177
+ // Merge
178
+ return trailingSlash.add(key);
179
+ }
180
+
181
+ return key;
167
182
  }
168
183
 
169
184
  function keys(object, eagerProperties, propertyIsEnumerable, entries) {
@@ -31,6 +31,7 @@ export async function handleExtension(parent, value, key, handlers) {
31
31
 
32
32
  // Special cases: `.ori.<ext>` extensions are Origami documents,
33
33
  // `.jse.<ext>` are JSE documents.
34
+ // TODO: Remove .jse.<ext>
34
35
  const extname = key.match(/\.ori\.\S+$/)
35
36
  ? ".oridocument"
36
37
  : key.match(/\.jse\.\S+$/)
@@ -163,6 +163,9 @@ Object.defineProperty(globals, "globalThis", {
163
163
  export default globals;
164
164
 
165
165
  async function fetchWrapper(resource, options) {
166
+ console.warn(
167
+ "Warning: A plain `fetch` reference will eventually call the standard JavaScript fetch() function. For Origami's fetch behavior, update your code to call Origami.fetch()."
168
+ );
166
169
  const response = await fetch(resource, options);
167
170
  return response.ok ? await response.arrayBuffer() : undefined;
168
171
  }
@@ -6,12 +6,13 @@
6
6
  */
7
7
 
8
8
  import {
9
- ObjectTree,
10
- Tree,
11
9
  deepText,
10
+ indent,
12
11
  isUnpackable,
12
+ ObjectTree,
13
13
  scope as scopeFn,
14
- text as templateFunctionTree,
14
+ text,
15
+ Tree,
15
16
  } from "@weborigami/async-tree";
16
17
  import os from "node:os";
17
18
  import expressionObject from "./expressionObject.js";
@@ -20,7 +21,6 @@ import { evaluate } from "./internal.js";
20
21
  import mergeTrees from "./mergeTrees.js";
21
22
  import OrigamiFiles from "./OrigamiFiles.js";
22
23
  import { codeSymbol } from "./symbols.js";
23
- import templateFunctionIndent from "./templateIndent.js";
24
24
 
25
25
  function addOpLabel(op, label) {
26
26
  Object.defineProperty(op, "toString", {
@@ -273,7 +273,7 @@ export function lambda(parameters, code) {
273
273
  invoke.code = code;
274
274
  return invoke;
275
275
  }
276
- addOpLabel(lambda, "«ops.lambda");
276
+ addOpLabel(lambda, "«ops.lambda»");
277
277
  lambda.unevaluatedArgs = true;
278
278
 
279
279
  export function lessThan(a, b) {
@@ -293,7 +293,12 @@ addOpLabel(lessThanOrEqual, "«ops.lessThanOrEqual»");
293
293
  * literals.
294
294
  */
295
295
  export async function literal(value) {
296
- return value;
296
+ if (value instanceof Array) {
297
+ // Strip code properties like `source`
298
+ return [...value];
299
+ } else {
300
+ return value;
301
+ }
297
302
  }
298
303
  addOpLabel(literal, "«ops.literal»");
299
304
  literal.unevaluatedArgs = true;
@@ -416,6 +421,45 @@ export function optionalTraverse(treelike, key) {
416
421
  }
417
422
  addOpLabel(optionalTraverse, "«ops.optionalTraverse");
418
423
 
424
+ /**
425
+ * Return the indicated property
426
+ *
427
+ * @param {any} obj
428
+ * @param {string} key
429
+ */
430
+ export async function property(obj, key) {
431
+ if (obj == null) {
432
+ throw new ReferenceError();
433
+ }
434
+
435
+ if (isUnpackable(obj)) {
436
+ obj = await obj.unpack();
437
+ } else if (typeof obj === "string") {
438
+ obj = new String(obj);
439
+ } else if (typeof obj === "number") {
440
+ obj = new Number(obj);
441
+ }
442
+
443
+ if (key in obj) {
444
+ // Object defines the property, get it
445
+ let value = obj[key];
446
+ // Is value an instance method? Copied from ObjectTree.
447
+ const isInstanceMethod =
448
+ !(obj instanceof Function) &&
449
+ value instanceof Function &&
450
+ !Object.hasOwn(obj, key);
451
+ if (isInstanceMethod) {
452
+ // Bind it to the object
453
+ value = value.bind(obj);
454
+ }
455
+ return value;
456
+ }
457
+
458
+ // Handle as tree traversal
459
+ return Tree.traverseOrThrow(obj, key);
460
+ }
461
+ addOpLabel(property, "«ops.property»");
462
+
419
463
  export function remainder(a, b) {
420
464
  return a % b;
421
465
  }
@@ -491,17 +535,17 @@ addOpLabel(subtraction, "«ops.subtraction»");
491
535
  * Apply the tree indent tagged template function.
492
536
  */
493
537
  export async function templateIndent(strings, ...values) {
494
- return templateFunctionIndent(strings, ...values);
538
+ return indent(strings, ...values);
495
539
  }
496
540
  addOpLabel(templateIndent, "«ops.templateIndent»");
497
541
 
498
542
  /**
499
543
  * Apply the tree tagged template function.
500
544
  */
501
- export async function templateTree(strings, ...values) {
502
- return templateFunctionTree(strings, ...values);
545
+ export async function templateText(strings, ...values) {
546
+ return text(strings, ...values);
503
547
  }
504
- addOpLabel(templateTree, "«ops.templateTree»");
548
+ addOpLabel(templateText, "«ops.templateText»");
505
549
 
506
550
  export function unaryMinus(a) {
507
551
  return -a;
@@ -19,8 +19,7 @@ describe("compile", () => {
19
19
  test("functionComposition", async () => {
20
20
  await assertCompile("greet()", "Hello, undefined!");
21
21
  await assertCompile("greet(name)", "Hello, Alice!");
22
- await assertCompile("greet(name)", "Hello, Alice!", { mode: "jse" });
23
- await assertCompile("greet 'world'", "Hello, world!");
22
+ await assertCompile("greet 'world'", "Hello, world!", { mode: "shell" });
24
23
  });
25
24
 
26
25
  test("angle bracket path", async () => {
@@ -40,7 +39,7 @@ describe("compile", () => {
40
39
  {
41
40
  message: "Hello, Alice!",
42
41
  },
43
- { mode: "jse" }
42
+ { mode: "shell" }
44
43
  );
45
44
  });
46
45
 
@@ -109,7 +108,7 @@ describe("compile", () => {
109
108
  tag: (strings, ...values) => {
110
109
  assertCodeEqual(strings, ["Hello, ", "!"]);
111
110
  if (saved) {
112
- assert.equal(strings, saved);
111
+ assert.deepEqual(strings, saved);
113
112
  } else {
114
113
  saved = strings;
115
114
  }
@@ -126,7 +125,7 @@ describe("compile", () => {
126
125
  });
127
126
 
128
127
  async function assertCompile(text, expected, options = {}) {
129
- const mode = options.mode ?? "shell";
128
+ const mode = options.mode ?? "program";
130
129
  const fn = compile.expression(text, { globals: sharedGlobals, mode });
131
130
  const target = options.target ?? null;
132
131
  let result = await fn.call(target);
@@ -89,7 +89,7 @@ describe("optimize", () => {
89
89
  ],
90
90
  ];
91
91
  assertCompile(expression, expected);
92
- assertCompile(expression, expected, "jse");
92
+ assertCompile(expression, expected, "shell");
93
93
  });
94
94
 
95
95
  describe("resolve reference", () => {
@@ -171,7 +171,7 @@ describe("optimize", () => {
171
171
  [markers.reference, "Math.PI"],
172
172
  ]);
173
173
  const globals = { Math: { PI: null } }; // value doesn't matter
174
- const expected = [globals.Math, "PI"];
174
+ const expected = [ops.property, globals.Math, "PI"];
175
175
  assertCodeEqual(optimize(code, { globals }), expected);
176
176
  });
177
177
 
@@ -195,7 +195,8 @@ describe("optimize", () => {
195
195
  const locals = [["post"]];
196
196
  const actual = optimize(code, { globals, locals });
197
197
  const expected = [
198
- [[[ops.context], [ops.literal, "post"]], "author"],
198
+ ops.property,
199
+ [ops.property, [[ops.context], [ops.literal, "post"]], "author"],
199
200
  "name",
200
201
  ];
201
202
  assertCodeEqual(actual, expected);