@weborigami/language 0.0.69 → 0.0.70

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.
@@ -153,7 +153,7 @@ export function makeObject(entries, op) {
153
153
  value instanceof Array &&
154
154
  value[0] === ops.getter &&
155
155
  value[1] instanceof Array &&
156
- value[1][0] === ops.primitive
156
+ value[1][0] === ops.literal
157
157
  ) {
158
158
  // Simplify a getter for a primitive value to a regular property
159
159
  value = value[1];
@@ -197,16 +197,12 @@ export function makeProperty(key, value) {
197
197
  return [key, modified];
198
198
  }
199
199
 
200
- export function makeTemplate(parts) {
201
- // Drop empty/null strings.
202
- const filtered = parts.filter((part) => part);
203
- // Return a concatenation of the parts. If there are no parts, return the
204
- // empty string. If there's just one string, return that directly.
205
- return filtered.length === 0
206
- ? ""
207
- : filtered.length === 1 &&
208
- filtered[0][0] === ops.primitive &&
209
- typeof filtered[0][1] === "string"
210
- ? filtered[0]
211
- : [ops.concat, ...filtered];
200
+ export function makeTemplate(op, head, tail) {
201
+ const strings = [head];
202
+ const values = [];
203
+ for (const [value, string] of tail) {
204
+ values.push([ops.concat, value]);
205
+ strings.push(string);
206
+ }
207
+ return [op, [ops.literal, strings], ...values];
212
208
  }
@@ -43,6 +43,7 @@ export default function WatchFilesMixin(Base) {
43
43
  this.watcher = new Watcher(this.dirname, {
44
44
  ignoreInitial: true,
45
45
  persistent: false,
46
+ recursive: true,
46
47
  });
47
48
  this.watcher.on("all", (event, filePath) => {
48
49
  const key = path.basename(filePath);
@@ -1,15 +1,7 @@
1
- import {
2
- Tree,
3
- isPlainObject,
4
- isUnpackable,
5
- scope,
6
- } from "@weborigami/async-tree";
1
+ import { Tree, isUnpackable, scope } from "@weborigami/async-tree";
7
2
  import codeFragment from "./codeFragment.js";
8
3
  import { ops } from "./internal.js";
9
-
10
- const codeSymbol = Symbol("code");
11
- const scopeSymbol = Symbol("scope");
12
- const sourceSymbol = Symbol("source");
4
+ import { codeSymbol, scopeSymbol, sourceSymbol } from "./symbols.js";
13
5
 
14
6
  /**
15
7
  * Evaluate the given code and return the result.
@@ -28,7 +20,7 @@ export default async function evaluate(code) {
28
20
  }
29
21
 
30
22
  let evaluated;
31
- const unevaluatedFns = [ops.lambda, ops.object];
23
+ const unevaluatedFns = [ops.lambda, ops.object, ops.literal];
32
24
  if (unevaluatedFns.includes(code[0])) {
33
25
  // Don't evaluate instructions, use as is.
34
26
  evaluated = code;
@@ -93,11 +85,19 @@ export default async function evaluate(code) {
93
85
  }
94
86
 
95
87
  // To aid debugging, add the code to the result.
96
- if (Object.isExtensible(result) && !isPlainObject(result)) {
88
+ if (Object.isExtensible(result) /* && !isPlainObject(result) */) {
97
89
  try {
98
- result[codeSymbol] = code;
99
- if (/** @type {any} */ (code).location) {
100
- result[sourceSymbol] = codeFragment(code.location);
90
+ if (code.location && !result[sourceSymbol]) {
91
+ Object.defineProperty(result, sourceSymbol, {
92
+ value: codeFragment(code.location),
93
+ enumerable: false,
94
+ });
95
+ }
96
+ if (!result[codeSymbol]) {
97
+ Object.defineProperty(result, codeSymbol, {
98
+ value: code,
99
+ enumerable: false,
100
+ });
101
101
  }
102
102
  if (!result[scopeSymbol]) {
103
103
  Object.defineProperty(result, scopeSymbol, {
@@ -20,10 +20,18 @@ import HandleExtensionsTransform from "./HandleExtensionsTransform.js";
20
20
  import { evaluate } from "./internal.js";
21
21
  import mergeTrees from "./mergeTrees.js";
22
22
  import OrigamiFiles from "./OrigamiFiles.js";
23
+ import taggedTemplate from "./taggedTemplate.js";
23
24
 
24
25
  // For memoizing lambda functions
25
26
  const lambdaFnMap = new Map();
26
27
 
28
+ function addOpLabel(op, label) {
29
+ Object.defineProperty(op, "toString", {
30
+ value: () => label,
31
+ enumerable: false,
32
+ });
33
+ }
34
+
27
35
  /**
28
36
  * Construct an array.
29
37
  *
@@ -33,7 +41,7 @@ const lambdaFnMap = new Map();
33
41
  export async function array(...items) {
34
42
  return Array(...items);
35
43
  }
36
- array.toString = () => "«ops.array»";
44
+ addOpLabel(array, "«ops.array»");
37
45
 
38
46
  // The assign op is a placeholder for an assignment declaration.
39
47
  // It is only used during parsing -- it shouldn't be executed.
@@ -48,7 +56,7 @@ export const assign = "«ops.assign»";
48
56
  export async function concat(...args) {
49
57
  return treeConcat.call(this, args);
50
58
  }
51
- concat.toString = () => "«ops.concat»";
59
+ addOpLabel(concat, "«ops.concat»");
52
60
 
53
61
  /**
54
62
  * Find the indicated constructor in scope, then return a function which invokes
@@ -71,7 +79,7 @@ export async function constructor(...keys) {
71
79
  ? new constructor()
72
80
  : new constructor(...args);
73
81
  }
74
- constructor.toString = () => "«ops.constructor»";
82
+ addOpLabel(constructor, "«ops.constructor»");
75
83
 
76
84
  /**
77
85
  * Given a protocol, a host, and a list of keys, construct an href.
@@ -144,6 +152,18 @@ async function fetchResponse(href) {
144
152
  */
145
153
  export const getter = new String("«ops.getter»");
146
154
 
155
+ /**
156
+ * A site tree with JSON Keys via HTTPS.
157
+ *
158
+ * @this {AsyncTree|null}
159
+ * @param {string} host
160
+ * @param {...string} keys
161
+ */
162
+ export function explorableSite(host, ...keys) {
163
+ return constructSiteTree("https:", ExplorableSiteTree, this, host, ...keys);
164
+ }
165
+ addOpLabel(explorableSite, "«ops.explorableSite»");
166
+
147
167
  /**
148
168
  * Construct a files tree for the filesystem root.
149
169
  *
@@ -171,7 +191,7 @@ export async function http(host, ...keys) {
171
191
  const href = constructHref("http:", host, ...keys);
172
192
  return fetchResponse.call(this, href);
173
193
  }
174
- http.toString = () => "«ops.http»";
194
+ addOpLabel(http, "«ops.http»");
175
195
 
176
196
  /**
177
197
  * Retrieve a web resource via HTTPS.
@@ -184,7 +204,7 @@ export function https(host, ...keys) {
184
204
  const href = constructHref("https:", host, ...keys);
185
205
  return fetchResponse.call(this, href);
186
206
  }
187
- https.toString = () => "«ops.https»";
207
+ addOpLabel(https, "«ops.https»");
188
208
 
189
209
  /**
190
210
  * Search the parent's scope -- i.e., exclude the current tree -- for the given
@@ -200,7 +220,7 @@ export async function inherited(key) {
200
220
  const parentScope = scopeFn(this.parent);
201
221
  return parentScope.get(key);
202
222
  }
203
- inherited.toString = () => "«ops.inherited»";
223
+ addOpLabel(inherited, "«ops.inherited»");
204
224
 
205
225
  /**
206
226
  * Return a function that will invoke the given code.
@@ -258,7 +278,15 @@ export function lambda(parameters, code) {
258
278
  lambdaFnMap.set(code, invoke);
259
279
  return invoke;
260
280
  }
261
- lambda.toString = () => "«ops.lambda";
281
+ addOpLabel(lambda, "«ops.lambda");
282
+
283
+ /**
284
+ * Return a primitive value
285
+ */
286
+ export async function literal(value) {
287
+ return value;
288
+ }
289
+ addOpLabel(literal, "«ops.literal»");
262
290
 
263
291
  /**
264
292
  * Merge the given trees. If they are all plain objects, return a plain object.
@@ -269,7 +297,7 @@ lambda.toString = () => "«ops.lambda";
269
297
  export async function merge(...trees) {
270
298
  return mergeTrees.call(this, ...trees);
271
299
  }
272
- merge.toString = () => "«ops.merge»";
300
+ addOpLabel(merge, "«ops.merge»");
273
301
 
274
302
  /**
275
303
  * Construct an object. The keys will be the same as the given `obj`
@@ -282,19 +310,7 @@ merge.toString = () => "«ops.merge»";
282
310
  export async function object(...entries) {
283
311
  return expressionObject(entries, this);
284
312
  }
285
- object.toString = () => "«ops.object»";
286
-
287
- /**
288
- * A site tree with JSON Keys via HTTPS.
289
- *
290
- * @this {AsyncTree|null}
291
- * @param {string} host
292
- * @param {...string} keys
293
- */
294
- export function explorableSite(host, ...keys) {
295
- return constructSiteTree("https:", ExplorableSiteTree, this, host, ...keys);
296
- }
297
- explorableSite.toString = () => "«ops.explorableSite»";
313
+ addOpLabel(object, "«ops.object»");
298
314
 
299
315
  /**
300
316
  * Look up the given key in the scope for the current tree.
@@ -308,7 +324,7 @@ export async function scope(key) {
308
324
  const scope = scopeFn(this);
309
325
  return scope.get(key);
310
326
  }
311
- scope.toString = () => "«ops.scope»";
327
+ addOpLabel(scope, "«ops.scope»");
312
328
 
313
329
  /**
314
330
  * The spread operator is a placeholder during parsing. It should be replaced
@@ -319,15 +335,15 @@ export function spread(...args) {
319
335
  "A compile-time spread operator wasn't converted to an object merge."
320
336
  );
321
337
  }
322
- spread.toString = () => "«ops.spread»";
338
+ addOpLabel(spread, "«ops.spread»");
323
339
 
324
340
  /**
325
- * Return a primitive value
341
+ * Apply the default tagged template function.
326
342
  */
327
- export async function primitive(value) {
328
- return value;
343
+ export function template(strings, ...values) {
344
+ return taggedTemplate(strings, values);
329
345
  }
330
- primitive.toString = () => "«ops.primitive»";
346
+ addOpLabel(template, "«ops.template»");
331
347
 
332
348
  /**
333
349
  * Traverse a path of keys through a tree.
@@ -344,7 +360,7 @@ export const traverse = Tree.traverseOrThrow;
344
360
  export function treeHttp(host, ...keys) {
345
361
  return constructSiteTree("http:", SiteTree, this, host, ...keys);
346
362
  }
347
- treeHttp.toString = () => "«ops.treeHttp»";
363
+ addOpLabel(treeHttp, "«ops.treeHttp»");
348
364
 
349
365
  /**
350
366
  * A website tree via HTTPS.
@@ -356,7 +372,7 @@ treeHttp.toString = () => "«ops.treeHttp»";
356
372
  export function treeHttps(host, ...keys) {
357
373
  return constructSiteTree("https:", SiteTree, this, host, ...keys);
358
374
  }
359
- treeHttps.toString = () => "«ops.treeHttps»";
375
+ addOpLabel(treeHttps, "«ops.treeHttps»");
360
376
 
361
377
  /**
362
378
  * If the value is packed but has an unpack method, call it and return that as
@@ -0,0 +1,3 @@
1
+ export const codeSymbol = Symbol("code");
2
+ export const scopeSymbol = Symbol("scope");
3
+ export const sourceSymbol = Symbol("source");
@@ -0,0 +1,9 @@
1
+ // Default JavaScript tagged template function splices strings and values
2
+ // together.
3
+ export default function defaultTemplateJoin(strings, values) {
4
+ let result = strings[0];
5
+ for (let i = 0; i < values.length; i++) {
6
+ result += values[i] + strings[i + 1];
7
+ }
8
+ return result;
9
+ }
@@ -61,6 +61,27 @@ describe("compile", () => {
61
61
  "escape characters with `backslash`"
62
62
  );
63
63
  });
64
+
65
+ test("tagged template string array is identical across calls", async () => {
66
+ let saved;
67
+ const scope = new ObjectTree({
68
+ tag: (strings, ...values) => {
69
+ assert.deepEqual(strings, ["Hello, ", "!"]);
70
+ if (saved) {
71
+ assert.equal(strings, saved);
72
+ } else {
73
+ saved = strings;
74
+ }
75
+ return strings[0] + values[0] + strings[1];
76
+ },
77
+ });
78
+ const fn = compile.expression("=tag`Hello, ${_}!`");
79
+ const templateFn = await fn.call(null);
80
+ const alice = await templateFn.call(scope, "Alice");
81
+ assert.equal(alice, "Hello, Alice!");
82
+ const bob = await templateFn.call(scope, "Bob");
83
+ assert.equal(bob, "Hello, Bob!");
84
+ });
64
85
  });
65
86
 
66
87
  async function assertCompile(text, expected) {