@weborigami/language 0.0.72 → 0.1.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.
package/main.js CHANGED
@@ -1,13 +1,13 @@
1
1
  export * from "./src/runtime/internal.js";
2
2
 
3
3
  export * as compile from "./src/compiler/compile.js";
4
+ export * from "./src/runtime/errors.js";
4
5
  export { default as evaluate } from "./src/runtime/evaluate.js";
5
6
  export { default as EventTargetMixin } from "./src/runtime/EventTargetMixin.js";
6
7
  export * as expressionFunction from "./src/runtime/expressionFunction.js";
7
- export * from "./src/runtime/extensions.js";
8
- export { default as formatError } from "./src/runtime/formatError.js";
9
8
  export { default as functionResultsMap } from "./src/runtime/functionResultsMap.js";
10
9
  export { default as HandleExtensionsTransform } from "./src/runtime/HandleExtensionsTransform.js";
10
+ export * from "./src/runtime/handlers.js";
11
11
  export { default as ImportModulesMixin } from "./src/runtime/ImportModulesMixin.js";
12
12
  export { default as InvokeFunctionsTransform } from "./src/runtime/InvokeFunctionsTransform.js";
13
13
  export { default as OrigamiFiles } from "./src/runtime/OrigamiFiles.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weborigami/language",
3
- "version": "0.0.72",
3
+ "version": "0.1.0",
4
4
  "description": "Web Origami expression language compiler and runtime",
5
5
  "type": "module",
6
6
  "main": "./main.js",
@@ -11,8 +11,8 @@
11
11
  "typescript": "5.6.2"
12
12
  },
13
13
  "dependencies": {
14
- "@weborigami/async-tree": "0.0.72",
15
- "@weborigami/types": "0.0.72",
14
+ "@weborigami/async-tree": "0.1.0",
15
+ "@weborigami/types": "0.1.0",
16
16
  "watcher": "2.3.1"
17
17
  },
18
18
  "scripts": {
@@ -2,8 +2,11 @@ import { trailingSlash } from "@weborigami/async-tree";
2
2
  import { createExpressionFunction } from "../runtime/expressionFunction.js";
3
3
  import { ops } from "../runtime/internal.js";
4
4
  import { parse } from "./parse.js";
5
+ import { annotate } from "./parserHelpers.js";
5
6
 
6
- function compile(source, startRule) {
7
+ function compile(source, options) {
8
+ const { startRule } = options;
9
+ const scopeCaching = options.scopeCaching ?? true;
7
10
  if (typeof source === "string") {
8
11
  source = { text: source };
9
12
  }
@@ -12,20 +15,24 @@ function compile(source, startRule) {
12
15
  startRule,
13
16
  });
14
17
  const cache = {};
15
- const modified = cacheNonLocalScopeReferences(code, cache);
16
- // const modified = code;
18
+ const modified = scopeCaching
19
+ ? cacheExternalScopeReferences(code, cache)
20
+ : code;
17
21
  const fn = createExpressionFunction(modified);
18
22
  return fn;
19
23
  }
20
24
 
21
- export function expression(source) {
22
- return compile(source, "expression");
25
+ export function expression(source, options = {}) {
26
+ return compile(source, {
27
+ ...options,
28
+ startRule: "expression",
29
+ });
23
30
  }
24
31
 
25
32
  // Given code containing ops.scope calls, upgrade them to ops.cache calls unless
26
33
  // they refer to local variables: variables defined by object literals or lambda
27
34
  // parameters.
28
- export function cacheNonLocalScopeReferences(code, cache, locals = {}) {
35
+ export function cacheExternalScopeReferences(code, cache, locals = {}) {
29
36
  const [fn, ...args] = code;
30
37
 
31
38
  let additionalLocalNames;
@@ -38,7 +45,7 @@ export function cacheNonLocalScopeReferences(code, cache, locals = {}) {
38
45
  } else {
39
46
  // Upgrade to cached scope lookup
40
47
  const modified = [ops.cache, key, cache];
41
- /** @type {any} */ (modified).location = code.location;
48
+ annotate(modified, code.location);
42
49
  return modified;
43
50
  }
44
51
 
@@ -67,18 +74,28 @@ export function cacheNonLocalScopeReferences(code, cache, locals = {}) {
67
74
  // be preferable to only descend into instructions. This would require
68
75
  // surrounding ops.lambda parameters with ops.literal, and ops.object
69
76
  // entries with ops.array.
70
- return cacheNonLocalScopeReferences(child, cache, updatedLocals);
77
+ return cacheExternalScopeReferences(child, cache, updatedLocals);
71
78
  } else {
72
79
  return child;
73
80
  }
74
81
  });
75
82
 
76
83
  if (code.location) {
77
- modified.location = code.location;
84
+ annotate(modified, code.location);
78
85
  }
79
86
  return modified;
80
87
  }
81
88
 
82
- export function templateDocument(source) {
83
- return compile(source, "templateDocument");
89
+ export function program(source, options = {}) {
90
+ return compile(source, {
91
+ ...options,
92
+ startRule: "program",
93
+ });
94
+ }
95
+
96
+ export function templateDocument(source, options = {}) {
97
+ return compile(source, {
98
+ ...options,
99
+ startRule: "templateDocument",
100
+ });
84
101
  }
@@ -52,20 +52,19 @@ arrayEntries
52
52
 
53
53
  arrayEntry
54
54
  = spread
55
- / expr
55
+ / pipeline
56
56
 
57
- // Something that can be called. This is more restrictive than the `expr`
57
+ // Something that can be called. This is more restrictive than the `value`
58
58
  // parser; it doesn't accept regular function calls.
59
59
  callTarget "function call"
60
60
  = absoluteFilePath
61
+ / scopeTraverse
61
62
  / array
62
63
  / object
63
- / lambda
64
- / parameterizedLambda
65
- / protocolCall
66
64
  / group
67
- / scopeTraverse
68
- / scopeReference
65
+ / namespacePath
66
+ / namespace
67
+ / functionReference
69
68
 
70
69
  // Required closing curly brace. We use this for the `object` term: if the
71
70
  // parser sees a left curly brace, here we must see a right curly brace.
@@ -109,6 +108,12 @@ doubleQuoteString "double quote string"
109
108
  doubleQuoteStringChar
110
109
  = !('"' / newLine) @textChar
111
110
 
111
+ // Path that follows a builtin reference in a URL: `//example.com/index.html`
112
+ doubleSlashPath
113
+ = "//" host:host path:path? {
114
+ return annotate([host, ...(path ?? [])], location());
115
+ }
116
+
112
117
  ellipsis = "..." / "…" // Unicode ellipsis
113
118
 
114
119
  escapedChar "backslash-escaped character"
@@ -121,14 +126,9 @@ escapedChar "backslash-escaped character"
121
126
  / "\\v" { return "\v"; }
122
127
  / "\\" @.
123
128
 
124
- // An Origami expression, no leading/trailing whitespace
125
- expr
126
- = pipeline
127
-
128
- // Top-level Origami expression, possible shebang directive and leading/trailing
129
- // whitepsace.
130
- expression "Origami expression"
131
- = shebang? __ @expr __
129
+ // A top-level expression, possibly with leading/trailing whitespace
130
+ expression
131
+ = __ @pipeline __
132
132
 
133
133
  float "floating-point number"
134
134
  = sign? digits? "." digits {
@@ -153,11 +153,23 @@ functionComposition "function composition"
153
153
  return annotate(makeFunctionCall(target, chain, location()), location());
154
154
  }
155
155
 
156
+ // A reference to a function in scope: `fn` or `fn.js`.
157
+ functionReference
158
+ = ref:scopeReference {
159
+ // If the reference looks like a builtin name, we treat it as a builtin
160
+ // reference, otherwise it's a regular scope reference. We can't make this
161
+ // distinction in the grammar.
162
+ const name = ref[1];
163
+ const builtinRegex = /^[A-Za-z][A-Za-z0-9]*$/;
164
+ const op = builtinRegex.test(name) ? ops.builtin : ops.scope;
165
+ return annotate([op, name], location());
166
+ }
167
+
156
168
  // An expression in parentheses: `(foo)`
157
169
  group "parenthetical group"
158
- = "(" __ expr:expr __ closingParen {
159
- return annotate(expr, location());
160
- }
170
+ = "(" __ pipeline:pipeline __ closingParen {
171
+ return annotate(pipeline, location());
172
+ }
161
173
 
162
174
  guillemetString "guillemet string"
163
175
  = '«' chars:guillemetStringChar* '»' {
@@ -167,13 +179,19 @@ guillemetString "guillemet string"
167
179
  guillemetStringChar
168
180
  = !('»' / newLine) @textChar
169
181
 
182
+ homeTree
183
+ = "~" {
184
+ return annotate([ops.homeTree], location());
185
+ }
186
+
170
187
  // A host identifier that may include a colon and port number: `example.com:80`.
171
188
  // This is used as a special case at the head of a path, where we want to
172
189
  // interpret a colon as part of a text identifier.
173
190
  host "HTTP/HTTPS host"
174
- = identifier:identifier port:(":" @number)? {
191
+ = identifier:identifier port:(":" @number)? slash:"/"? {
175
192
  const portText = port ? `:${port[1]}` : "";
176
- const hostText = identifier + portText;
193
+ const slashText = slash ? "/" : "";
194
+ const hostText = identifier + portText + slashText;
177
195
  return annotate([ops.literal, hostText], location());
178
196
  }
179
197
 
@@ -191,12 +209,7 @@ identifierList
191
209
  }
192
210
 
193
211
  implicitParensArgs "arguments with implicit parentheses"
194
- // Implicit parens args are a separate list of `step`, not `expr`, because
195
- // they can't contain a pipeline.
196
- = inlineSpace+ args:step|1.., separator| separator? {
197
- /* Stuff */
198
- return annotate(args, location());
199
- }
212
+ = inlineSpace+ @list
200
213
 
201
214
  inlineSpace
202
215
  = [ \t]
@@ -208,8 +221,8 @@ integer "integer"
208
221
 
209
222
  // A lambda expression: `=foo()`
210
223
  lambda "lambda function"
211
- = "=" __ expr:expr {
212
- return annotate([ops.lambda, ["_"], expr], location());
224
+ = "=" __ pipeline:pipeline {
225
+ return annotate([ops.lambda, ["_"], pipeline], location());
213
226
  }
214
227
 
215
228
  // A path that begins with a slash: `/foo/bar`
@@ -218,15 +231,31 @@ leadingSlashPath "path with a leading slash"
218
231
  return annotate(path ?? [], location());
219
232
  }
220
233
 
221
- // A separated list of expressions
234
+ // A separated list of values
222
235
  list "list"
223
- = list:expr|1.., separator| separator? {
224
- return annotate(list, location());
236
+ = values:value|1.., separator| separator? {
237
+ return annotate(values, location());
225
238
  }
226
239
 
227
240
  multiLineComment
228
241
  = "/*" (!"*/" .)* "*/" { return null; }
229
242
 
243
+ // A namespace reference is a string of letters only, followed by a colon.
244
+ // For the time being, we also allow a leading `@`, which is deprecated.
245
+ namespace
246
+ = at:"@"? chars:[A-Za-z]+ ":" {
247
+ return annotate([ops.builtin, (at ?? "") + chars.join("") + ":"], location());
248
+ }
249
+
250
+ // A namespace followed by a path: `fn:a/b/c`
251
+ namespacePath
252
+ = fn:namespace path:doubleSlashPath {
253
+ return annotate(makeFunctionCall(fn, [path], location()), location());
254
+ }
255
+ / fn:namespace path:path {
256
+ return annotate(makeFunctionCall(fn, [path], location()), location());
257
+ }
258
+
230
259
  newLine
231
260
  = "\n"
232
261
  / "\r\n"
@@ -257,8 +286,11 @@ objectEntry
257
286
 
258
287
  // A getter definition inside an object literal: `foo = 1`
259
288
  objectGetter "object getter"
260
- = key:objectKey __ "=" __ value:expr {
261
- return annotate(makeProperty(key, [ops.getter, value]), location());
289
+ = key:objectKey __ "=" __ pipeline:pipeline {
290
+ return annotate(
291
+ makeProperty(key, annotate([ops.getter, pipeline], location())),
292
+ location()
293
+ );
262
294
  }
263
295
 
264
296
  objectHiddenKey
@@ -270,8 +302,8 @@ objectKey "object key"
270
302
 
271
303
  // A property definition in an object literal: `x: 1`
272
304
  objectProperty "object property"
273
- = key:objectKey __ ":" __ value:expr {
274
- return annotate(makeProperty(key, value), location());
305
+ = key:objectKey __ ":" __ pipeline:pipeline {
306
+ return annotate(makeProperty(key, pipeline), location());
275
307
  }
276
308
 
277
309
  // A shorthand reference inside an object literal: `foo`
@@ -290,8 +322,8 @@ objectPublicKey
290
322
  }
291
323
 
292
324
  parameterizedLambda
293
- = "(" __ parameters:identifierList? __ ")" __ doubleArrow __ expr:expr {
294
- return annotate([ops.lambda, parameters ?? [], expr], location());
325
+ = "(" __ parameters:identifierList? __ ")" __ doubleArrow __ pipeline:pipeline {
326
+ return annotate([ops.lambda, parameters ?? [], pipeline], location());
295
327
  }
296
328
 
297
329
  // Function arguments in parentheses
@@ -300,19 +332,22 @@ parensArgs "function arguments in parentheses"
300
332
  return annotate(list ?? [undefined], location());
301
333
  }
302
334
 
303
- pipeline
304
- = steps:(@step|1.., __ singleArrow __ |) {
305
- return annotate(makePipeline(steps), location());
306
- }
307
-
308
335
  // A slash-separated path of keys
309
336
  path "slash-separated path"
310
- = head:pathElement|0..| tail:pathTail? {
337
+ // Path with at least a tail
338
+ = head:pathElement|0..| tail:pathTail {
311
339
  let path = tail ? [...head, tail] : head;
312
340
  // Remove parts for consecutive slashes
313
341
  path = path.filter((part) => part[1] !== "/");
314
342
  return annotate(path, location());
315
343
  }
344
+ // Path with slashes, maybe no tail
345
+ / head:pathElement|1..| tail:pathTail? {
346
+ let path = tail ? [...head, tail] : head;
347
+ // Remove parts for consecutive slashes
348
+ path = path.filter((part) => part[1] !== "/");
349
+ return annotate(path, location());
350
+ }
316
351
 
317
352
  // A path key followed by a slash
318
353
  pathElement
@@ -333,26 +368,24 @@ pathTail
333
368
  return annotate([ops.literal, chars.join("")], location());
334
369
  }
335
370
 
336
- // Parse a protocol call like `fn://foo/bar`.
337
- // There can be zero, one, or two slashes after the colon.
338
- protocolCall "function call using protocol: syntax"
339
- = protocol:protocol ":" "/"|0..2| host:host path:leadingSlashPath? {
340
- return annotate([protocol, host, ...(path ?? [])], location());
371
+ // A pipeline that starts with a value and optionally applies a series of
372
+ // functions to it.
373
+ pipeline
374
+ = head:value tail:(__ singleArrow __ @pipelineStep)* {
375
+ return tail.length === 0
376
+ ? head
377
+ : annotate(makePipeline([head, ...tail]), location());
341
378
  }
342
379
 
343
- protocol "protocol"
344
- = reservedProtocol
345
- / scopeReference
380
+ // A step in a pipeline
381
+ pipelineStep
382
+ = lambda
383
+ / parameterizedLambda
384
+ / callTarget
346
385
 
347
- reservedProtocol "reserved protocol"
348
- = "explore" { return ops.explorableSite; }
349
- / "https" { return ops.https; } // Must come before "http"
350
- / "http" { return ops.http; }
351
- / "new" { return ops.constructor; }
352
- / "package" { return [ops.scope, "@package"] } // Alias
353
- / "treehttps" { return ops.treeHttps; } // Must come before `treehttp`
354
- / "treehttp" { return ops.treeHttp; } // Must come before `tree`
355
- / "tree" { return ops.treeHttps; }
386
+ // Top-level Origami progam with possible shebang directive (which is ignored)
387
+ program "Origami program"
388
+ = shebang? @expression
356
389
 
357
390
  scopeReference "scope reference"
358
391
  = key:identifier {
@@ -360,10 +393,10 @@ scopeReference "scope reference"
360
393
  }
361
394
 
362
395
  scopeTraverse
363
- = ref:scopeReference "/" path:path {
396
+ = ref:scopeReference "/" path:path? {
364
397
  const head = [ops.scope, `${ ref[1] }/`];
365
398
  head.location = ref.location;
366
- return annotate([ops.traverse, head, ...path], location());
399
+ return annotate([ops.traverse, head, ...(path ?? [])], location());
367
400
  }
368
401
 
369
402
  separator
@@ -376,7 +409,9 @@ shebang
376
409
  sign
377
410
  = [+\-]
378
411
 
379
- singleArrow = "→" / "->"
412
+ singleArrow
413
+ = "→"
414
+ / "->"
380
415
 
381
416
  singleLineComment
382
417
  = "//" [^\n\r]* { return null; }
@@ -390,34 +425,10 @@ singleQuoteStringChar
390
425
  = !("'" / newLine) @textChar
391
426
 
392
427
  spread
393
- = ellipsis expr:expr {
394
- return annotate([ops.spread, expr], location());
428
+ = ellipsis value:value {
429
+ return annotate([ops.spread, value], location());
395
430
  }
396
431
 
397
- // A single step in a pipeline, or a top-level expression
398
- step
399
- // Literals that can't start a function call
400
- = number
401
- // Try functions next; they can start with expression types that follow
402
- // (array, object, etc.), and we want to parse the larger thing first.
403
- / functionComposition
404
- // Then try parsers that look for a distinctive token at the start: an opening
405
- // slash, bracket, curly brace, etc.
406
- / absoluteFilePath
407
- / array
408
- / object
409
- / lambda
410
- / parameterizedLambda
411
- / templateLiteral
412
- / string
413
- / group
414
- // Things that have a distinctive character, but not at the start
415
- / protocolCall
416
- / taggedTemplate
417
- / scopeTraverse
418
- // Least distinctive option is a simple scope reference, so it comes last.
419
- / scopeReference
420
-
421
432
  start
422
433
  = number
423
434
 
@@ -474,11 +485,37 @@ templateLiteralText
474
485
 
475
486
  // A substitution in a template literal: `${x}`
476
487
  templateSubstitution "template substitution"
477
- = "${" __ @expr __ "}"
488
+ = "${" @expression "}"
478
489
 
479
490
  textChar
480
491
  = escapedChar
481
492
  / .
482
493
 
494
+ // An Origami expression that produces a value, no leading/trailing whitespace
495
+ value
496
+ // Literals that can't start a function call
497
+ = number
498
+ // Try functions next; they can start with expression types that follow
499
+ // (array, object, etc.), and we want to parse the larger thing first.
500
+ / parameterizedLambda
501
+ / functionComposition
502
+ / taggedTemplate
503
+ / namespacePath
504
+ // Then try parsers that look for a distinctive token at the start: an opening
505
+ // slash, bracket, curly brace, etc.
506
+ / absoluteFilePath
507
+ / array
508
+ / object
509
+ / lambda
510
+ / templateLiteral
511
+ / string
512
+ / group
513
+ / homeTree
514
+ // Things that have a distinctive character, but not at the start
515
+ / scopeTraverse
516
+ / namespace
517
+ // Least distinctive option is a simple scope reference, so it comes last.
518
+ / scopeReference
519
+
483
520
  whitespaceWithNewLine
484
521
  = inlineSpace* comment? newLine __