forked from schibsted/jslt
-
Notifications
You must be signed in to change notification settings - Fork 0
/
language-design.txt
331 lines (249 loc) · 8.98 KB
/
language-design.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
---------------------------------------------------------------------------
OPEN QUESTIONS
.key into a literal or array -> what should result be?
jq = error
jsonpath = error
jsonata = nothing
jstl now = null
---------------------------------------------------------------------------
LANGUAGE DESIGN
. -> input
.foo -> key in object
.[2] -> index in array
.[1:2] -> array slicing (just copy from Python)
==, !=, ... -> comparison
and, or, not(...)
+ * % / - -> numeric operators
string + string -> concat
string * number -> repeat
$foo -> variable reference
foo( ... ) -> function call
if (condition) <expr> else <expr>
let ...
ISSUE: must be terminated somehow because with chainable expressions,
how do you make clear that in
let foo = $foo.bar
.baz
.baz is the actual expression and not part of the let?
(this is an issue inside if and at toplevel, will also be inside functions)
possible solutions:
let var = ( expr )
let var = expr;
expr as $var <-- how is this now recognizable as let rather than expr?
could require per let, or just at the end of the let block
for (<expr>) <expr>
[...] -> array literal
{...} -> object literal
"..." -> string literal
34834 -> number literal
true|false -> boolean literal
null -> what it says
* -> matcher
----- PROBABLY NECESSARY
..foo -> recursive descent matching all keys named 'foo'
result is an array of all matches
leads to need for filtering predicate support
also, filtering in for loops will become necessary quite soon
FOR (...) ... IF (...) ?
FOR (...) ... WHERE ... ?
implicit not(is-value()) removal ?
----- ADD LATER, if at foo
.all [ condition ] -> FIXME: multiple results? how to deal with?
inside condition we change the context node
.foo .[*] .bar -> traverse array? how to deal with this?
can we do this by supporting
for (<expr>) <expr> if <expr> ?
alternative:
.[*] returns entire array
.foo on array of objects = for (...) .foo
FOR (...)
...
IF (...) <-- filter the array
----- OPEN QUESTIONS
termination of expression in LET: how?
let foo = (...) <- ugly to require the parens?
let foo = ... ;
let ... as $foo
set ... as $foo
details of matcher semantics
what happens if you declare same variable twice in same block?
how strict should we be on type errors?
anything + null -> null
"foo" + 5 -> "foo5"
"foo" + true -> "footrue"
"foo" + [1,2,3] -> ???
5 + [1,2,3] -> error
5 + false -> error
[1,2,3] + {...} -> error
how to support?
.object."spt:custom" | with_entries(.key |= "spt:custom_" + .)
{for (.object."spt:custom")
"spt:custom_" + .key : .value
if ()}
for loops: when iteration produces null, should it be added to the
array?
how do {} and [] convert to strings?
more detail on implicit type conversion rules
allow .foo on array by applying to each element in array?
-> raises need for [ ...boolean expr... ]
-> if we want this extension . inside [] has to refer to the node
support ..foo operator?
-> also raises need for [ filters ]
contains() vs 'in'?
----- FUNCTION LIBRARY
NECESSARY CHANGE:
type conversion functions: extra, optional default fallback value
if fallback: return that
if no fallback: error
parse-json( ... ): same solution: fallback object to which function adds
error information. allows client to recognize failed parses without
building error handling into the language
don't actually need parse-json handling for CSP, since filtering failed
events is sufficient
OK is-array()
OK is-object()
OK string(.)
OK is-string()
OK number(.)
OK is-number()
OK round()
OK floor()
OK ceiling()
OK random() <- random number 0.0 - 1.0
OK boolean(.)
OK is-boolean()
OK not(.)
OK test(., regexp)
OK capture(., regexp)
OK split(., splitter)
OK join(., joiner)
OK starts-with()
OK ends-with()
OK lowercase()
OK uppercase()
OK fallback(...)
OK size(.) <- string, object, array
parse-json(.) <- parses json argument
to-json(.) <- serialize to json
=== POTENTIALS
is-value <- same as boolean except for 0 and 0.0
filter(seq, func) <- macro to filter a sequence
array(.) <- turn {} into [{"key":, "value":}] ?
object(.) <- reverse: turn [{"key":, "value":}] into {...}
match(., regexp)
normalize-whitespace
format("...${ jstl expr }...")
run-template()
=== AREAS NOT DESIGNED YET
datetime parsing
---------------------------------------------------------------------------
NAME
jstl Oracle JavaServer Pages Standard Tag Library
jslt XSLT in JSON alternative, now dead
jsonator ditto
j2j already exists, but marginal
argonator kind of weird
jsonify not really what it does
jtransform an FFT thing, might work
jquery taken by the frontend thing
colchis cryptic
boreas cryptic
-------------------------------------------------------------------------------
USE CASES
--- #1: TRUE/FALSE if .object.items contains {} with @Type==Jobs
{"object":{"items":[
{"@Type" : "George"},
{"@Type" : "Jobs"},
{"@Type" : "Fnupp"}
]}}
solutions:
WORKS NOW contains("Jobs", for (.object.items) ."@Type")
MIGHT DO contains("Jobs", .object.items."@Type")
requires .foo on arrays: apply to each object in array
MIGHT DO "Jobs" in (for (.object.items) ."@Type")
turn 'contains' function into 'in' operator
---------------------------------------------------------------------------
THE LET ISSUE
===== #1 PARENS
{
"actor" : {
// first find the user ID value
let userid = (if ( test(.actor."@id", "^(sd|u)rn:[^:]+:(user|person):.*") )
.actor."@id"
else
.actor."spt:userId")
let good_user_id = (
if ( test($userid, "^(u|sd)rn:[^:]+:user:null") )
null // user modeling complains about these fake IDs
else if ( test($userid, "(u|sd)rn:[^:]+:(person|user|account):.*") )
// :person: -> :user: (and urn: -> sdrn:)
let parts = (capture($userid, "(u|sd)rn:(?<site>[^:]+):(person|user|account):(?<id>.*)"))
let site = (if ($parts.site == "spid.se") "schibsted.com" else $parts.site)
if ( $parts.id )
// If we have an id, split the ID by : and pick the last element
("sdrn:" + $site + ":user:" + split($parts.id, ":")[-1])
)
"@id" : $good_user_id,
"spt:userId" : $good_user_id,
* - "@type", "spt:acceptLanguage", "spt:screenSize", "spt:environmentId",
"spt:userAgent", "spt:viewportSize", "spt:userId" : .
},
===== #2 ;
"actor" : {
// first find the user ID value
let userid = if ( test(.actor."@id", "^(sd|u)rn:[^:]+:(user|person):.*") )
.actor."@id"
else
.actor."spt:userId";
let good_user_id =
if ( test($userid, "^(u|sd)rn:[^:]+:user:null") )
null // user modeling complains about these fake IDs
else if ( test($userid, "(u|sd)rn:[^:]+:(person|user|account):.*") )
// :person: -> :user: (and urn: -> sdrn:)
let parts = capture($userid, "(u|sd)rn:(?<site>[^:]+):(person|user|account):(?<id>.*)");
let site = if ($parts.site == "spid.se") "schibsted.com" else $parts.site;
if ( $parts.id )
// If we have an id, split the ID by : and pick the last element
("sdrn:" + $site + ":user:" + split($parts.id, ":")[-1])
;
"@id" : $good_user_id,
"spt:userId" : $good_user_id,
* - "@type", "spt:acceptLanguage", "spt:screenSize", "spt:environmentId",
"spt:userAgent", "spt:viewportSize", "spt:userId" : .
},
===== #3 LET AS
"actor" : {
// first find the user ID value
let if ( test(.actor."@id", "^(sd|u)rn:[^:]+:(user|person):.*") )
.actor."@id"
else
.actor."spt:userId" as $userid
let
if ( test($userid, "^(u|sd)rn:[^:]+:user:null") )
null // user modeling complains about these fake IDs
else if ( test($userid, "(u|sd)rn:[^:]+:(person|user|account):.*") )
// :person: -> :user: (and urn: -> sdrn:)
let capture($userid, "(u|sd)rn:(?<site>[^:]+):(person|user|account):(?<id>.*)") as $parts
let if ($parts.site == "spid.se") "schibsted.com" else $parts.site as $site
if ( $parts.id )
// If we have an id, split the ID by : and pick the last element
("sdrn:" + $site + ":user:" + split($parts.id, ":")[-1])
as $good_user_id
"@id" : $good_user_id,
"spt:userId" : $good_user_id,
* - "@type", "spt:acceptLanguage", "spt:screenSize", "spt:environmentId",
"spt:userAgent", "spt:viewportSize", "spt:userId" : .
},
---------------------------------------------------------------------------
BUILD ERROR HANDLING INTO LANGUAGE?
try ( <expr> ) else <expr>
try <expr> else <expr>
try string(.location.accuracy) else null
try
parse-json( .Report )
else
... how about access to error information here?
privileged variable $error?
get into exception matching?
only allow function calls inside try/else?
how complex can the expression inside try/else be?