1
1
local lpeg = require (' lulpeg' )
2
+ local utils = require (' graphql.utils' )
3
+
4
+ --- NOTE: Functions that worth moving out into other modules:
5
+ --- 1) Regexp implementation (also lies inside of
6
+ --- accessor_general.lua)
7
+ --- 2) Vararg iterator.
8
+
9
+ --- TODO:
10
+ --- 1) Validation.
11
+ --- 2) Big numbers.
12
+ --- 3) Absence of variable in context.variables is equal
13
+ --- to the situation when context.variables.var == nil.
2
14
3
15
local expressions = {}
4
16
local P , R , S , V = lpeg .P , lpeg .R , lpeg .S , lpeg .V
@@ -28,8 +40,8 @@ local field_path = identifier * ('.' * identifier) ^ 0
28
40
-- Possible logical function patterns:
29
41
-- 1) is_null.
30
42
local is_null = P (' is_null' )
31
- -- 2) not_null .
32
- local not_null = P (' not_null ' )
43
+ -- 2) is_not_null .
44
+ local is_not_null = P (' is_not_null ' )
33
45
-- 3) regexp.
34
46
local regexp = P (' regexp' )
35
47
@@ -63,31 +75,6 @@ local lt = P('<')
63
75
-- 10) <=
64
76
local le = P (' <=' )
65
77
66
- --- Utility functions:
67
- --- 1) Vararg iterator.
68
-
69
-
70
- local function vararg (...)
71
- local i = 0
72
- local t = {}
73
- local limit
74
- local function iter (...)
75
- i = i + 1
76
- if i > limit then return end
77
- return i , t [i ]
78
- end
79
-
80
- i = 0
81
- limit = select (" #" , ... )
82
- for n = 1 , limit do
83
- t [n ] = select (n , ... )
84
- end
85
- for n = limit + 1 , # t do
86
- t [n ] = nil
87
- end
88
- return iter
89
- end
90
-
91
78
-- AST nodes generating functions.
92
79
local function identical (arg )
93
80
return arg
@@ -110,7 +97,8 @@ local function bin_op_node(...)
110
97
end
111
98
local operators = {}
112
99
local operands = {}
113
- for i , v in vararg (... ) do
100
+ for i = 1 , select (' #' , ... ) do
101
+ local v = select (i , ... )
114
102
if i % 2 == 0 then
115
103
table.insert (operators , v )
116
104
else
@@ -193,7 +181,7 @@ local _logic_and = logic_and / op_name
193
181
local _comparison_op = (eq + not_eq + ge + gt + le + lt ) / op_name
194
182
local _arithmetic_op = (addition + subtraction ) / op_name
195
183
local _unary_op = (negation + unary_minus + unary_plus ) / op_name
196
- local _functions = (is_null + not_null + regexp ) / identical
184
+ local _functions = (is_null + is_not_null + regexp ) / identical
197
185
198
186
-- Grammar rules for C-style expressions positioned ascending in
199
187
-- terms of priority.
@@ -220,14 +208,218 @@ local expression_grammar = P {
220
208
value_terminal = (_literal + _variable + _field_path ) / identical_node
221
209
}
222
210
211
+ -- Add one number to another.
212
+ --
213
+ -- It may be changed after introduction of "big ints".
214
+ --
215
+ -- @param operand_1
216
+ -- @param operand_2
217
+ local function sum (operand_1 , operand_2 )
218
+ return operand_1 + operand_2
219
+ end
220
+
221
+ -- Subtract one number from another.
222
+ --
223
+ -- It may be changed after introduction of "big ints".
224
+ --
225
+ -- @param operand_1
226
+ -- @param operand_2
227
+ local function subtract (operand_1 , operand_2 )
228
+ return operand_1 - operand_2
229
+ end
230
+
223
231
--- Parse given string which supposed to be a c-style expression.
224
232
---
225
- --- @tparam str string representation of expression.
233
+ --- @tparam string str string representation of expression
226
234
---
227
- --- @treturn syntax tree.
228
- function expressions . parse (str )
235
+ --- @treturn table syntax tree
236
+ local function parse (str )
229
237
assert (type (str ) == ' string' , ' parser expects a string' )
230
238
return expression_grammar :match (str ) or error (' syntax error' )
231
239
end
232
240
241
+ -- local function validate(context)
242
+ --
243
+ -- end
244
+
245
+ --- Recursively execute the syntax subtree. Of course it can be
246
+ --- syntax tree itself.
247
+ ---
248
+ --- @tparam table node node to be executed
249
+ ---
250
+ --- @tparam table context table containing information useful for
251
+ --- execution (see @{expressions.new})
252
+ ---
253
+ --- @return subtree value
254
+ local function execute_node (node , context )
255
+ if node .kind == ' const' then
256
+ if node .value_class == ' string' then
257
+ return node .value
258
+ end
259
+
260
+ if node .value_class == ' bool' then
261
+ if node .value == ' false' then
262
+ return false
263
+ end
264
+ return true
265
+ end
266
+
267
+ if node .value_class == ' number' then
268
+ return tonumber (node .value )
269
+ end
270
+ end
271
+
272
+ if node .kind == ' variable' then
273
+ local name = node .name
274
+ return context .variables [name ]
275
+ end
276
+
277
+ if node .kind == ' object_field' then
278
+ local path = node .path
279
+ local field = context .object
280
+ local table_path = (path :split (' .' ))
281
+ for i = 1 , # table_path do
282
+ field = field [table_path [i ]]
283
+ end
284
+ return field
285
+ end
286
+
287
+ if node .kind == ' func' then
288
+ -- regexp() implementation.
289
+ if node .name == ' regexp' then
290
+ return utils .regexp (execute_node (node .args [1 ], context ),
291
+ execute_node (node .args [2 ], context ))
292
+ end
293
+
294
+ -- is_null() implementation.
295
+ if node .name == ' is_null' then
296
+ return execute_node (node .args [1 ], context ) == nil
297
+ end
298
+
299
+ -- is_not_null() implementation.
300
+ if node .name == ' is_not_null' then
301
+ return execute_node (node .args [1 ], context ) ~= nil
302
+ end
303
+ end
304
+
305
+ if node .kind == ' unary_operation' then
306
+ -- Negation.
307
+ if node .op == ' !' then
308
+ return not execute_node (node .node , context )
309
+ end
310
+
311
+ -- Unary '+'.
312
+ if node .op == ' +' then
313
+ return execute_node (node .node , context )
314
+ end
315
+
316
+ -- Unary '-'.
317
+ if node .op == ' -' then
318
+ return - execute_node (node .node , context )
319
+ end
320
+ end
321
+
322
+ if node .kind == ' binary_operations' then
323
+ local prev = execute_node (node .operands [1 ], context )
324
+ for i , op in ipairs (node .operators ) do
325
+ local second_operand = execute_node (node .operands [i + 1 ],
326
+ context )
327
+ -- Sum.
328
+ if op == ' +' then
329
+ prev = sum (prev , second_operand )
330
+ end
331
+
332
+ -- Subtraction.
333
+ if op == ' -' then
334
+ prev = subtract (prev , second_operand )
335
+ end
336
+
337
+ -- Logical and.
338
+ if op == ' &&' then
339
+ prev = prev and second_operand
340
+ end
341
+
342
+ -- Logical or.
343
+ if op == ' ||' then
344
+ prev = prev or second_operand
345
+ end
346
+
347
+ -- Equal.
348
+ if op == ' ==' then
349
+ prev = prev == second_operand
350
+ end
351
+
352
+ -- Not equal.
353
+ if op == ' !=' then
354
+ prev = prev ~= second_operand
355
+ end
356
+
357
+ -- Greater than.
358
+ if op == ' >' then
359
+ prev = prev > second_operand
360
+ end
361
+
362
+ -- Greater or equal.
363
+ if op == ' >=' then
364
+ prev = prev >= second_operand
365
+ end
366
+
367
+ -- Lower than.
368
+ if op == ' <' then
369
+ prev = prev < second_operand
370
+ end
371
+
372
+ -- Lower or equal.
373
+ if op == ' &&' then
374
+ prev = prev and second_operand
375
+ end
376
+ end
377
+ return prev
378
+ end
379
+
380
+ if node .kind == ' root_expression' then
381
+ return execute_node (node .expr , context )
382
+ end
383
+ end
384
+
385
+ local function expr_execute (self , object , variables )
386
+ local context = {
387
+ object = object ,
388
+ variables = variables ,
389
+ }
390
+ return execute_node (self .ast , context )
391
+ end
392
+
393
+ --- Compile and execute given string that represents a c-style
394
+ --- expression.
395
+ ---
396
+ --- @tparam string str string representation of expression
397
+ --- @tparam table object object considered inside of an
398
+ --- expression
399
+ --- @tparam table variables list of variables
400
+ ---
401
+ --- @return expression value
402
+ function expressions .execute (str , object , variables )
403
+ local expr = expressions .new (str )
404
+ return expr :execute (object , variables )
405
+ end
406
+
407
+ --- Create a new c-style expression object.
408
+ ---
409
+ --- @tparam string str string representation of expression
410
+ ---
411
+ --- @treturn table expression object
412
+ function expressions .new (str )
413
+ local ast = parse (str )
414
+
415
+ return setmetatable ({
416
+ raw = str ,
417
+ ast = ast ,
418
+ }, {
419
+ __index = {
420
+ execute = expr_execute ,
421
+ }
422
+ })
423
+ end
424
+
233
425
return expressions
0 commit comments