Skip to content

Commit be2d819

Browse files
committed
macros; macro definitions for the short syntax of $exec and $call instructions
1 parent 03938a2 commit be2d819

File tree

12 files changed

+399
-15
lines changed

12 files changed

+399
-15
lines changed

LANGUAGE.md

Lines changed: 152 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ In the example above it is an object with the name `"router"` (the value of the
1818

1919
When this simple script is evaluated its value will be the resolved value that the executor returns. See [Synchronous and asynchronous values](#synchronous-and-asynchronous-values).
2020

21+
The full syntax above allows all properties to be evaluated - executor name and methods can be the result of some other scripts. For the majority of cases when these are constants the short syntax can be used:
22+
23+
```json
24+
{ "$$router.get": { "path": "/resource/1" } }
25+
```
26+
27+
This is achieved via [macros](#macros) support.
28+
2129
JSONScript core includes other instructions that will be shown later and the interpreter may allow to define your own custom instructions.
2230

2331
With JSONScript you can combine instructions in three ways:
@@ -50,11 +58,20 @@ JSONScript can include several instructions that will be executed sequentially:
5058
{
5159
"$exec": "router",
5260
"$method": "put",
53-
"$args": { "path": "/resource/1", "body": { "test": "test" } }
61+
"$args": { "path": "/resource/1", "body": "test" }
5462
}
5563
]
5664
```
5765

66+
or with short syntax:
67+
68+
```json
69+
[
70+
{ "$$router.get": { "path": "/resource/1" } },
71+
{ "$$router.put": { "path": "/resource/1", "body": "test" } }
72+
]
73+
```
74+
5875
"Sequential" means that the next script begins evaluating only after the script in the previous item has evaluated (and if the result is an asynchronous value this value has resolved into synchronous).
5976

6077
The example above will retrive the value of some resource and once it was retrieved it will update it. The sequential execution guarantees that it will be the value before the update.
@@ -76,7 +93,7 @@ For example, this script does the same as the script above for two resources:
7693
{
7794
"$exec": "router",
7895
"$method": "put",
79-
"$args": { "path": "/resource/1", "body": { "test": "test" } }
96+
"$args": { "path": "/resource/1", "body": "test" }
8097
}
8198
],
8299
[
@@ -88,12 +105,28 @@ For example, this script does the same as the script above for two resources:
88105
{
89106
"$exec": "router",
90107
"$method": "put",
91-
"$args": { "path": "/resource/2", "body": { "test": "test" } }
108+
"$args": { "path": "/resource/2", "body": "test" }
92109
}
93110
]
94111
]
95112
```
96113

114+
or with short syntax:
115+
116+
```json
117+
[
118+
[
119+
{ "$$router.get": { "path": "/resource/1" } },
120+
{ "$$router.put": { "path": "/resource/1", "body": "test" } }
121+
],
122+
[
123+
{ "$$router.get": { "path": "/resource/2" } },
124+
{ "$$router.put": { "path": "/resource/2", "body": "test" } }
125+
]
126+
]
127+
```
128+
129+
97130
The result of this script evaluation is the array of two arrays containing two items each, the items being the results of evaluation of individual instructions.
98131

99132

@@ -116,6 +149,15 @@ JSONScript can include several instructions that will be executed in parallel:
116149
}
117150
```
118151

152+
or with short syntax:
153+
154+
```json
155+
{
156+
"res1": { "$$router.get": { "path": "/resource/1" } },
157+
"res2": { "$$router.get": { "path": "/resource/2" } }
158+
}
159+
```
160+
119161
The meaning of "parallel" depends on the environment in which the script executes and on the interpreter implementation. The implementation should not guarantee any order in which evaluation of individual scripts will start, and instructions don't wait until another instruction evaluates (and resolves) to begin executing.
120162

121163
The example above retrieves the values fo two resources. The result of the evaluation of the whole script above is a single asynchronous value (assuming that instructions return asynchronous values) that resolves into the object with the results of both instructions available in properties `"res1"` and `"res2"`.
@@ -137,7 +179,7 @@ For example, the script below is similar to the example in the previous section
137179
{
138180
"$exec": "router",
139181
"$method": "put",
140-
"$args": { "path": "/resource/1", "body": { "test": "test" } }
182+
"$args": { "path": "/resource/1", "body": "test" }
141183
}
142184
],
143185
"res2": [
@@ -149,12 +191,27 @@ For example, the script below is similar to the example in the previous section
149191
{
150192
"$exec": "router",
151193
"$method": "put",
152-
"$args": { "path": "/resource/2", "body": { "test": "test" } }
194+
"$args": { "path": "/resource/2", "body": "test" }
153195
}
154196
]
155197
}
156198
```
157199

200+
or with short syntax:
201+
202+
```json
203+
{
204+
"res1": [
205+
{ "$$router.get": { "path": "/resource/1" } },
206+
{ "$$router.put": { "path": "/resource/1", "body": "test" } }
207+
],
208+
"res2": [
209+
{ "$$router.get": { "path": "/resource/2" } },
210+
{ "$$router.put": { "path": "/resource/2", "body": "test" } }
211+
]
212+
}
213+
```
214+
158215
This example combines parallel and sequential evaluation. Each resource update only starts after the current value is retrieved, but the update of `"/resource/2"` does not need to wait until the `"/resource/1"` finished updating.
159216

160217
The result of this script evaluation is the object with two properties `"res1"` and `"res2"` each containing two items with the results of individual instructions.
@@ -183,6 +240,15 @@ During the evaluation the script can use the data instance passed to the interpe
183240
]
184241
```
185242

243+
or with short syntax:
244+
245+
```json
246+
[
247+
{ "$$router.get: { "path": { "$data": "/path" } } },
248+
{ "$$router.put": { "$data": "" } }
249+
]
250+
```
251+
186252
Data instance:
187253

188254
```json
@@ -264,6 +330,16 @@ JSONScript interpreters should both try to determine such situations as early as
264330
}
265331
```
266332

333+
or with short syntax:
334+
335+
```json
336+
{
337+
"$if": { "$$checkAvailability": "router1" },
338+
"$then": { "$$router1.get": { "path": "/resource/1" } },
339+
"$else": { "$$router2.get": { "path": "/resource/1" } }
340+
}
341+
```
342+
267343
The result of the evaluation of the script in `$if` keyword should be a boolean value, otherwise the whole script will fail to evailuate (no type coercion is made).
268344

269345
If the condition is `true` then the script in `$then` keyword will be evaluted and its result will be the result of `$if` instruction, otherwise the script in `$else` will be evaluated and `$if` evaluate to its result.
@@ -328,6 +404,18 @@ or using reference:
328404
}
329405
```
330406

407+
or with short syntax:
408+
409+
```json
410+
{
411+
"res1": { "$$router.get": { "path": "/resource/1" } },
412+
"res2": {
413+
"$delay": { "$$router.get": { "path": "/resource/2" } },
414+
"$wait": 50
415+
}
416+
}
417+
```
418+
331419
The evaluation result will be the same as without `$delay` istruction, but the second "$exec" instruction will start executing at least 50 milliseconds later than the first.
332420

333421
This instruction can also be used to create asynchronous value from synchronous value. For example if some executor expects an asynchronous value as an argument and you want to pass a constant, you can use `$delay`:
@@ -343,6 +431,17 @@ This instruction can also be used to create asynchronous value from synchronous
343431
}
344432
```
345433

434+
or with short syntax:
435+
436+
```json
437+
{
438+
"$$logger.resolve": {
439+
"message": "Resolved",
440+
"asyncValue": { "$delay": "test", "$wait": 1000 }
441+
}
442+
}
443+
```
444+
346445
In the example above a hypothetical logger logs message when asynchronous value is resolved. `$delay` instruction result is an asynchrnous value that resolves 1 second after its evaluation with the value `"test"`.
347446

348447
`$wait` keyword is optional, the default is 0. It means that the interpreter should schedule the script evaluation as soon as possible but do not execute it immediately.
@@ -378,6 +477,27 @@ Anonymous or named function can be defined in the script to be passed to executo
378477
]
379478
```
380479

480+
or with short syntax:
481+
482+
```json
483+
[
484+
{
485+
"$func": { "$$router.get" { "path": { "$data": "/path" } } },
486+
"$name": "getRes",
487+
"$args": [ "path" ]
488+
},
489+
{ "$#getRes": [ "/resource/1" ] },
490+
{
491+
"$call": { "$ref": "/0" },
492+
"$args": { "path": "/resource/2" }
493+
},
494+
{
495+
"$call": { "$ref": "1/0" },
496+
"$args": "/resource/3"
497+
}
498+
]
499+
```
500+
381501
In the example above the same function `getRes` is used three times, being called by name and using $ref with absolute and relative JSON-pointers. Arguments can be passed to function as array, as an object (property names should match parameters declarations, otherwise an exception will be thrown) and as a scalar value if there is only one parameter.
382502

383503
Functions can be used as parameters in the executors:
@@ -406,6 +526,26 @@ Functions can be used as parameters in the executors:
406526
}
407527
```
408528

529+
or with short syntax:
530+
531+
```json
532+
{
533+
"$$array.map": {
534+
"data": [
535+
"/resource/1",
536+
"/resource/2",
537+
"/resource/3"
538+
],
539+
"iterator": {
540+
"$func": {
541+
"$$router1.get": { "path": { "$data": "/path" } }
542+
},
543+
"$args": ["path"]
544+
}
545+
}
546+
}
547+
```
548+
409549
If the function was previously defined it can be passed either using `"$ref"` with an absolute or relative JSON-pointer or `{ "$func": "myfunc" }. The latter always evaluates as the reference to the existing function rather than the function that always returns string "myfunc", to define the function that always returns the same string you can use "$quote".
410550

411551

@@ -421,7 +561,7 @@ To insert an object that contains properties that start with `"$"` that normally
421561
}
422562
```
423563

424-
evaluates as: `{ "$exec": "myExec" }`.
564+
evaluates as: `{ "$exec": "myExec" }` and the executor is not called.
425565

426566
`$quote` can also be used to define the function that always returns the same string:
427567

@@ -431,4 +571,9 @@ evaluates as: `{ "$exec": "myExec" }`.
431571
}
432572
```
433573

434-
The anonymous function defined above always returns the string `"foo"`. Without `$quote` it would be the reference to the function with the name `foo`.
574+
The anonymous function defined above always returns the string `"foo"`. Without `$quote` it would have been the reference to the function with the name `foo`.
575+
576+
577+
## Macros
578+
579+
JSONScript defines several core macros to support short syntax for executor and function calls and for calculations. The interpreter may allow to define your own custom macros.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ JSONScript is created to manage scripted execution in remote systems to avoid th
1212

1313
JSONScript:
1414

15-
- uses JSON as its representaion format for both data and control structures, being similar to lisp (homoiconic)
15+
- uses JSON as its representaion format for both data and control structures, being similar to lisp (homoiconic).
1616
- defines simple control structures.
17-
- is asynchronous and concurrent without the need to use any special keywords
17+
- is asynchronous and concurrent without the need to use any special keywords.
1818
- actual processing in the remote system is done synchronously or asynchronously by the functions and objects supplied to JSONScript interpreter by the host environment.
1919

2020

SCHEMA.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
# JSONScript Schema
22

3-
JSONScript uses JSON-Schema standard both for the validation schemas and for the schema that defines evaluation process.
3+
JSONScript uses JSON-Schema standard both for the validation schemas and for the schemas that define macro expansion and evaluation process.
44

5-
[JSONScript schema](http://www.json-script.com/schema/schema.json#) the schema that does not validate scalar keywords in instructions (keyword values can be scripts and have to be validated when the script is evaluated).
5+
[JSONScript schema](http://www.json-script.com/schema/schema.json#) - the schema for JSONScript that does not validate scalar keywords in instructions (keyword values can be scripts and have to be validated when the script is evaluated).
66

7-
[JSONScript strict schema](http://www.json-script.com/schema/schema_strict.json#) the schema that validates scalar keywords in instructions.
7+
[JSONScript strict schema](http://www.json-script.com/schema/schema_strict.json#) - the schema for JSONScript that validates scalar keywords in instructions.
88

9-
[JSONScript evaluation schema](http://www.json-script.com/schema/evaluate.json#) this schema defines evalution process. It can be used by implementations to evaluate scripts. It contains non-standard keywords.
9+
[Macro expansion schema](http://www.json-script.com/schema/expand_macros.json#) - this schema defines macro expansion process. It can be used by implementations to expand macros in the scripts before their evaluation. It contains non-standard keyword `expandJsMacro`.
1010

11-
[Instruction definition schema](http://www.json-script.com/schema/instruction.json#) the schema for instruction defnitions. The definitions of both standard and user-defined instructions should be valid according to this schema.
11+
[Evaluation schema](http://www.json-script.com/schema/evaluate.json#) - this schema defines evalution process. It can be used by implementations to evaluate scripts. It contains non-standard keywords.
12+
13+
[Instruction definition schema](http://www.json-script.com/schema/instruction.json#) - the schema for instruction defnitions. The definitions of both standard and user-defined instructions should be valid according to this schema.
14+
15+
[Macro definition schema](http://www.json-script.com/schema/macro.json#) - the schema for macro definition. The definitions of both standard and user-defined macros should be valid according to this schema.

macros/call.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"name": "call",
3+
"description": "function call",
4+
"rules": [
5+
{
6+
"description": "call named function with arguments",
7+
"rule": {
8+
"^\\$\\#(.+)$": 1
9+
},
10+
"script": {
11+
"$call": "$1",
12+
"$args": 1
13+
}
14+
}
15+
]
16+
}

macros/exec.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "exec",
3+
"description": "executor call",
4+
"rules": [
5+
{
6+
"description": "executor call without method",
7+
"rule": {
8+
"^\\$\\$([^\\.]+)$": 1
9+
},
10+
"script": {
11+
"$exec": "$1",
12+
"$args": 1
13+
}
14+
},
15+
{
16+
"description": "executor call with method",
17+
"rule": {
18+
"^\\$\\$([^\\.]+)\\.([^\\.]+)$": 1
19+
},
20+
"script": {
21+
"$exec": "$1",
22+
"$method": "$2",
23+
"$args": 1
24+
}
25+
}
26+
]
27+
}

macros/index.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
'use strict';
2+
3+
module.exports = [
4+
require('./exec.json'),
5+
require('./call.json')
6+
];

0 commit comments

Comments
 (0)