@panagos/jojoscript 0.9.0 → 0.9.1

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/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # JojoScript 0.9.0
1
+ # JojoScript 0.9.1
2
2
 
3
3
  JojoScript is a small JavaScript-compatible language and runtime for
4
4
  expressive, composable pipelines. It adds lightweight syntax for declarations,
@@ -237,6 +237,26 @@ becomes:
237
237
  divide(100, value)
238
238
  ```
239
239
 
240
+ #### Property access stages
241
+
242
+ A pipeline stage may also start with a dot to read a field or invoke a method
243
+ on the piped value directly:
244
+
245
+ ```jojo
246
+ users |> .length
247
+ name |> .trim() |> .toUpperCase()
248
+ ```
249
+
250
+ becomes:
251
+
252
+ ```js
253
+ users.length
254
+ name.trim().toUpperCase()
255
+ ```
256
+
257
+ This is handy for compact pipelines where the previous value is the receiver of
258
+ an ordinary JavaScript property or method call.
259
+
240
260
  #### `catch` / `orElse` control stages
241
261
 
242
262
  `catch` and `orElse` provide inline error handling without leaving pipeline
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@panagos/jojoscript",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "A tiny JavaScript-compatible language layer with Jojo syntax and pipelines.",
5
5
  "license": "Unlicense",
6
6
  "type": "module",
package/src/index.js CHANGED
@@ -1,33 +1,33 @@
1
- export {
2
- compile,
3
- compileFile,
4
- compileDirectory
5
- } from './compiler.js'
6
-
7
- export {
8
- transformPipelines,
9
- emitPipeline,
10
- findPipelineStdlibFunctions
11
- } from './pipeline-parser.js'
12
-
13
- export {
14
- transformMatchExpressions
15
- } from './match-parser.js'
16
-
17
- export {
18
- transformTypeAnnotations
19
- } from './type-annotations.js'
20
-
21
- export {
22
- analyzePipelines,
23
- buildGraph,
24
- renderGraphText,
25
- renderGraphDot
26
- } from './pipeline-ir.js'
27
-
28
- export {
29
- transformEffectDeclarations,
30
- transformHandleBlocks,
31
- findEffectDeclarations,
32
- findHandleBlocks
33
- } from './effect-parser.js'
1
+ export {
2
+ compile,
3
+ compileFile,
4
+ compileDirectory
5
+ } from './compiler.js'
6
+
7
+ export {
8
+ transformPipelines,
9
+ emitPipeline,
10
+ findPipelineStdlibFunctions
11
+ } from './pipeline-parser.js'
12
+
13
+ export {
14
+ transformMatchExpressions
15
+ } from './match-parser.js'
16
+
17
+ export {
18
+ transformTypeAnnotations
19
+ } from './type-annotations.js'
20
+
21
+ export {
22
+ analyzePipelines,
23
+ buildGraph,
24
+ renderGraphText,
25
+ renderGraphDot
26
+ } from './pipeline-ir.js'
27
+
28
+ export {
29
+ transformEffectDeclarations,
30
+ transformHandleBlocks,
31
+ findEffectDeclarations,
32
+ findHandleBlocks
33
+ } from './effect-parser.js'
@@ -112,9 +112,18 @@ function splitArguments(tokens) {
112
112
  return result
113
113
  }
114
114
 
115
+ function isPropertyAccessName(name) {
116
+ return /^\.[A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)*$/.test(name)
117
+ }
118
+
115
119
  function parseStage(source, tokens) {
116
120
  if (!tokens.length) throw new SyntaxError('Empty pipeline stage')
117
121
 
122
+ const bareName = text(source, tokens).trim()
123
+ if (isPropertyAccessName(bareName)) {
124
+ return { name: bareName, args: [], propertyCall: false }
125
+ }
126
+
118
127
  if (tokens.every((token, index) =>
119
128
  index % 2 === 0
120
129
  ? token.type === 'identifier'
@@ -145,6 +154,14 @@ function parseStage(source, tokens) {
145
154
 
146
155
  const name = text(source, tokens.slice(0, openIndex)).trim()
147
156
 
157
+ if (isPropertyAccessName(name)) {
158
+ const args = splitArguments(
159
+ tokens.slice(openIndex + 1, closeIndex)
160
+ ).map(argument => text(source, argument).trim())
161
+
162
+ return { name, args, propertyCall: true }
163
+ }
164
+
148
165
  if (!/^[A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)*$/.test(name)) {
149
166
  throw new SyntaxError(`Invalid pipeline function: ${name}`)
150
167
  }
@@ -310,6 +327,25 @@ export function emitPipeline(pipeline, options = {}) {
310
327
  continue
311
328
  }
312
329
 
330
+ if (stage.name.startsWith('.')) {
331
+ const args = stage.args.slice()
332
+ const placeholderIndex = args.indexOf('_')
333
+ if (placeholderIndex !== -1) {
334
+ args[placeholderIndex] = expression
335
+ }
336
+
337
+ if (stage.propertyCall || args.length > 0) {
338
+ expression = `${expression}${stage.name}(${args.join(', ')})`
339
+ } else {
340
+ expression = `${expression}${stage.name}`
341
+ }
342
+
343
+ if (options.profile) {
344
+ expression = `traceNode(${expression}, ${metaLiteral(pipelineId, stage.nodeId, stage.name)})`
345
+ }
346
+ continue
347
+ }
348
+
313
349
  if (stage.name === 'retry' && stage.standaloneRetry) {
314
350
  const meta = metaLiteral(pipelineId, stage.nodeId, 'retry')
315
351
  expression = `(await retry(async () => (${expression}), ${stage.args[0]}, ${meta})())`