Skip to content

Commit 5b9dca8

Browse files
committed
graphql: implement c-style expressions parser
Needed for tarantool#13
1 parent 2ce2d88 commit 5b9dca8

File tree

3 files changed

+624
-0
lines changed

3 files changed

+624
-0
lines changed

graphql/expressions.lua

+233
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
local lpeg = require('lulpeg')
2+
3+
local expressions = {}
4+
local P, R, S, V = lpeg.P, lpeg.R, lpeg.S, lpeg.V
5+
local C = lpeg.C
6+
7+
-- Some special symbols.
8+
local space_symbol = S(' \t\r\n')
9+
local eof = P(-1)
10+
local spaces = space_symbol ^ 0
11+
12+
-- Possible identifier patterns:
13+
-- 1) Number.
14+
local digit = R('09')
15+
local integer = R('19') * digit ^ 0
16+
local decimal = (digit ^ 1) * '.' * (digit ^ 1)
17+
local number = decimal + integer
18+
-- 2) Boolean.
19+
local bool = P('false') + P('true')
20+
-- 3) String.
21+
local string = P('"') * C((P('\\"') + 1 - P('"')) ^ 0) * P('"')
22+
-- 4) Variable.
23+
local identifier = ('_' + R('az', 'AZ')) * ('_' + R('09', 'az', 'AZ')) ^ 0
24+
local variable_name = identifier
25+
-- 5) Object's field path.
26+
local field_path = identifier * ('.' * identifier) ^ 0
27+
28+
-- Possible logical function patterns:
29+
-- 1) is_null.
30+
local is_null = P('is_null')
31+
-- 2) not_null.
32+
local not_null = P('not_null')
33+
-- 3) regexp.
34+
local regexp = P('regexp')
35+
36+
-- Possible unary operator patterns:
37+
-- 1) Logical negation.
38+
local negation = P('!')
39+
-- 2) Unary minus.
40+
local unary_minus = P('-')
41+
-- 3) Unary plus.
42+
local unary_plus = P('+')
43+
44+
-- Possible binary operator patterns:
45+
-- 1) Logical and.
46+
local logic_and = P('&&')
47+
-- 2) logical or.
48+
local logic_or = P('||')
49+
-- 3) +
50+
local addition = P('+')
51+
-- 4) -
52+
local subtraction = P('-')
53+
-- 5) ==
54+
local eq = P('==')
55+
-- 6) !=
56+
local not_eq = P('!=')
57+
-- 7) >
58+
local gt = P('>')
59+
-- 8) >=
60+
local ge = P('>=')
61+
-- 9) <
62+
local lt = P('<')
63+
-- 10) <=
64+
local le = P('<=')
65+
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+
-- AST nodes generating functions.
92+
local function identical(arg)
93+
return arg
94+
end
95+
96+
local identical_node = identical
97+
98+
local op_name = identical
99+
100+
local function root_expr_node(expr)
101+
return {
102+
kind = 'root_expression',
103+
expr = expr
104+
}
105+
end
106+
107+
local function bin_op_node(...)
108+
if select('#', ...) == 1 then
109+
return select(1, ...)
110+
end
111+
local operators = {}
112+
local operands = {}
113+
for i, v in vararg(...) do
114+
if i % 2 == 0 then
115+
table.insert(operators, v)
116+
else
117+
table.insert(operands, v)
118+
end
119+
end
120+
return {
121+
kind = 'binary_operations',
122+
operators = operators,
123+
operands = operands
124+
}
125+
end
126+
127+
local function unary_op_node(unary_operator, operand_1)
128+
return {
129+
kind = 'unary_operation',
130+
op = unary_operator,
131+
node = operand_1
132+
}
133+
end
134+
135+
local function func_node(name, ...)
136+
local args = {...}
137+
return {
138+
kind = 'func',
139+
name = name,
140+
args = args
141+
}
142+
end
143+
144+
local function number_node(value)
145+
return {
146+
kind = 'const',
147+
value_class = 'number',
148+
value = value
149+
}
150+
end
151+
152+
local function string_node(value)
153+
return {
154+
kind = 'const',
155+
value_class = 'string',
156+
value = value
157+
}
158+
end
159+
160+
local function bool_node(value)
161+
return {
162+
kind = 'const',
163+
value_class = 'bool',
164+
value = value
165+
}
166+
end
167+
168+
local function variable_node(name)
169+
return {
170+
kind = 'variable',
171+
name = name
172+
}
173+
end
174+
175+
local function path_node(path)
176+
return {
177+
kind = 'object_field',
178+
path = path
179+
}
180+
end
181+
182+
-- Patterns returning corresponding nodes (note that all of them
183+
-- start with '_').
184+
local _number = number / number_node
185+
local _bool = bool / bool_node
186+
local _string = string / string_node
187+
local _variable = '$' * C(variable_name) / variable_node
188+
local _field_path = field_path / path_node
189+
local _literal = _bool + _number + _string
190+
191+
local _logic_or = logic_or / op_name
192+
local _logic_and = logic_and / op_name
193+
local _comparison_op = (eq + not_eq + ge + gt + le + lt) / op_name
194+
local _arithmetic_op = (addition + subtraction) / op_name
195+
local _unary_op = (negation + unary_minus + unary_plus) / op_name
196+
local _functions = (is_null + not_null + regexp) / identical
197+
198+
-- Grammar rules for C-style expressions positioned ascending in
199+
-- terms of priority.
200+
local expression_grammar = P {
201+
'init_expr',
202+
init_expr = V('expr') * eof / root_expr_node,
203+
expr = spaces * V('log_expr_or') * spaces / identical_node,
204+
205+
log_expr_or = V('log_expr_and') * (spaces * _logic_or *
206+
spaces * V('log_expr_and')) ^ 0 / bin_op_node,
207+
log_expr_and = V('comparison') * (spaces * _logic_and * spaces *
208+
V('comparison')) ^ 0 / bin_op_node,
209+
comparison = V('arithmetic_expr') * (spaces * _comparison_op * spaces *
210+
V('arithmetic_expr')) ^ 0 / bin_op_node,
211+
arithmetic_expr = V('unary_expr') * (spaces * _arithmetic_op * spaces *
212+
V('unary_expr')) ^ 0 / bin_op_node,
213+
214+
unary_expr = (_unary_op * V('first_prio') / unary_op_node) +
215+
(V('first_prio') / identical_node),
216+
first_prio = (V('func') + V('value_terminal') + '(' * spaces * V('expr') *
217+
spaces * ')') / identical_node,
218+
func = _functions * '(' * spaces * V('value_terminal') * (spaces * ',' *
219+
spaces * V('value_terminal')) ^ 0 * spaces * ')' / func_node,
220+
value_terminal = (_literal + _variable + _field_path) / identical_node
221+
}
222+
223+
--- Parse given string which supposed to be a c-style expression.
224+
---
225+
--- @tparam str string representation of expression.
226+
---
227+
--- @treturn syntax tree.
228+
function expressions.parse(str)
229+
assert(type(str) == 'string', 'parser expects a string')
230+
return expression_grammar:match(str) or error('syntax error')
231+
end
232+
233+
return expressions

0 commit comments

Comments
 (0)