aboutsummaryrefslogtreecommitdiff
path: root/ql/parser/parser.py
blob: d962a0fa952d735b570d5456fd1b81714093d3f6 (plain)
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
from ply.yacc import yacc

from ql.ast.nodes.expressions.binary_operators.addition_operator import AdditionOperatorNode
from ql.ast.nodes.expressions.binary_operators.and_operator import AndOperatorNode
from ql.ast.nodes.expressions.binary_operators.division_operator import DivisionOperatorNode
from ql.ast.nodes.expressions.binary_operators.equals_operator import EqualsOperatorNode
from ql.ast.nodes.expressions.binary_operators.greater_equals_operator import GreaterEqualsOperatorNode
from ql.ast.nodes.expressions.binary_operators.greater_than_operator import GreaterThanOperatorNode
from ql.ast.nodes.expressions.binary_operators.less_equals_operator import LessEqualsOperatorNode
from ql.ast.nodes.expressions.binary_operators.less_than_operator import LessThanOperatorNode
from ql.ast.nodes.expressions.binary_operators.multiplication_operator import MultiplicationOperatorNode
from ql.ast.nodes.expressions.binary_operators.not_equals_operator import NotEqualsOperatorNode
from ql.ast.nodes.expressions.binary_operators.or_operator import OrOperatorNode
from ql.ast.nodes.expressions.binary_operators.subtraction_operator import SubtractionOperatorNode
from ql.ast.nodes.expressions.literals.boolean_literal import BooleanNode
from ql.ast.nodes.expressions.literals.date_literal import DateNode
from ql.ast.nodes.expressions.literals.decimal_literal import DecimalNode
from ql.ast.nodes.expressions.literals.integer_literal import IntegerNode
from ql.ast.nodes.expressions.literals.money_literal import MoneyNode
from ql.ast.nodes.expressions.literals.string_literal import StringNode
from ql.ast.nodes.expressions.unary_operators.negation_operator import NegationOperatorNode
from ql.ast.nodes.expressions.unary_operators.negative_operator import NegativeOperatorNode
from ql.ast.nodes.expressions.variable import VariableNode
from ql.ast.nodes.statements.form_statement import FormNode
from ql.ast.nodes.statements.if_statement import IfNode
from ql.ast.nodes.statements.question_statement import QuestionNode
from ql.parser.lexer import QLLexer
from ql.parser.metadata import Metadata
from ql.types.boolean import QLBoolean
from ql.types.date import QLDate
from ql.types.decimal import QLDecimal
from ql.types.integer import QLInteger
from ql.types.money import QLMoney
from ql.types.string import QLString
from ql.types.undefined import QLUndefined


class QLParser:
    def __init__(self):
        self.__errors = []
        self.__tokens = QLLexer.tokens
        self.__parser = None
        self.__precedence = (
            ('left', 'OR'),
            ('left', 'AND'),
            ('nonassoc', 'EQUALS', 'NOT_EQUALS'),
            ('nonassoc', 'LESS_EQUALS', 'LESS_THAN', 'GREATER_EQUALS', 'GREATER_THAN'),
            ('left', 'PLUS', 'MINUS'),
            ('left', 'TIMES', 'DIVIDE'),
            ('right', 'NOT'),
        )

    @property
    def errors(self):
        return self.__errors

    @property
    def tokens(self):
        return self.__tokens

    @property
    def parser(self):
        return self.__parser

    @property
    def precedence(self):
        return self.__precedence

    def build(self):
        self.__parser = yacc(module=self)

    def parse(self, data, lexer):
        self.__errors = []
        return self.parser.parse(data, lexer)

    # Grammar
    @staticmethod
    def p_form(production):
        """form : FORM IDENTIFIER block"""
        production[0] = FormNode(Metadata(production.lineno(1)), production[3], production[2])

    @staticmethod
    def p_block(production):
        """block : LEFT_BRACE statements RIGHT_BRACE"""
        production[0] = production[2]

    # Statements
    @staticmethod
    def p_statements(production):
        """statements   : statement statements
                        | statement"""
        if len(production) == 3:
            production[0] = [production[1]] + production[2]
        elif len(production) == 2:
            production[0] = [production[1]]

    @staticmethod
    def p_statement(production):
        """statement    : if
                        | question"""
        production[0] = production[1]

    # Questions
    @staticmethod
    def p_question(production):
        """question : STRING_LITERAL IDENTIFIER COLON type"""
        production[0] = QuestionNode(Metadata(production.lineno(1)), production[1], production[2],
                                     production[4], production[4].get_literal_node(production[4]()), False)

    @staticmethod
    def p_question_computed(production):
        """question : STRING_LITERAL IDENTIFIER COLON type ASSIGN expression"""
        production[0] = QuestionNode(Metadata(production.lineno(1)), production[1], production[2],
                                     production[4], production[6], True)

    # Control flow
    @staticmethod
    def p_if(production):
        """if : IF condition block"""
        production[0] = IfNode(Metadata(production.lineno(1)), production[3], production[2])

    @staticmethod
    def p_condition(production):
        """condition : LEFT_BRACKET expression RIGHT_BRACKET"""
        production[0] = production[2]

    # Expressions
    @staticmethod
    def p_parenthesis(production):
        """expression : LEFT_BRACKET expression RIGHT_BRACKET"""
        production[0] = production[2]

    @staticmethod
    def p_variable(production):
        """expression : IDENTIFIER"""
        production[0] = VariableNode(Metadata(production.lineno(1)), QLUndefined, production[1],
                                     QLUndefined())

    # Unary operators
    @staticmethod
    def p_not(production):
        """expression : NOT expression"""
        production[0] = NegationOperatorNode(Metadata(production.lineno(1)), QLBoolean,
                                             production[2], QLUndefined())

    @staticmethod
    def p_negative(production):
        """expression : MINUS expression"""
        production[0] = NegativeOperatorNode(Metadata(production.lineno(1)), QLUndefined,
                                             production[2], QLUndefined())

    # Binary operators
    @staticmethod
    def p_and(production):
        """expression : expression AND expression"""
        production[0] = AndOperatorNode(Metadata(production.lineno(2)), QLBoolean, production[1],
                                        production[3], QLUndefined())

    @staticmethod
    def p_or(production):
        """expression : expression OR expression"""
        production[0] = OrOperatorNode(Metadata(production.lineno(2)), QLBoolean, production[1],
                                       production[3], QLUndefined())

    @staticmethod
    def p_plus(production):
        """expression : expression PLUS expression"""
        production[0] = AdditionOperatorNode(Metadata(production.lineno(2)), QLUndefined,
                                             production[1], production[3], QLUndefined())

    @staticmethod
    def p_minus(production):
        """expression : expression MINUS expression"""
        production[0] = SubtractionOperatorNode(Metadata(production.lineno(2)), QLUndefined,
                                                production[1], production[3], QLUndefined())

    @staticmethod
    def p_times(production):
        """expression : expression TIMES expression"""
        production[0] = MultiplicationOperatorNode(Metadata(production.lineno(2)), QLUndefined,
                                                   production[1], production[3], QLUndefined())

    @staticmethod
    def p_divide(production):
        """expression : expression DIVIDE expression"""
        production[0] = DivisionOperatorNode(Metadata(production.lineno(2)), QLUndefined,
                                             production[1], production[3], QLUndefined())

    @staticmethod
    def p_equals(production):
        """expression : expression EQUALS expression"""
        production[0] = EqualsOperatorNode(Metadata(production.lineno(2)), QLBoolean,
                                           production[1], production[3], QLUndefined())

    @staticmethod
    def p_not_equals(production):
        """expression : expression NOT_EQUALS expression"""
        production[0] = NotEqualsOperatorNode(Metadata(production.lineno(2)), QLBoolean,
                                              production[1], production[3], QLUndefined())

    @staticmethod
    def p_less_equals(production):
        """expression : expression LESS_EQUALS expression"""
        production[0] = LessEqualsOperatorNode(Metadata(production.lineno(2)), QLBoolean,
                                               production[1], production[3], QLUndefined())

    @staticmethod
    def p_less_than(production):
        """expression : expression LESS_THAN expression"""
        production[0] = LessThanOperatorNode(Metadata(production.lineno(2)), QLBoolean,
                                             production[1], production[3], QLUndefined())

    @staticmethod
    def p_greater_equals(production):
        """expression : expression GREATER_EQUALS expression"""
        production[0] = GreaterEqualsOperatorNode(Metadata(production.lineno(2)), QLBoolean,
                                                  production[1], production[3], QLUndefined())

    @staticmethod
    def p_greater_than(production):
        """expression : expression GREATER_THAN expression"""
        production[0] = GreaterThanOperatorNode(Metadata(production.lineno(2)), QLBoolean,
                                                production[1], production[3], QLUndefined())

    # Literals
    @staticmethod
    def p_boolean_literal(production):
        """expression   : FALSE
                        | TRUE"""
        production[0] = BooleanNode(Metadata(production.lineno(1)), QLBoolean, QLBoolean(production[1]))

    @staticmethod
    def p_date_literal(production):
        """expression : DATE_LITERAL"""
        day, month, year = production[1]
        production[0] = DateNode(Metadata(production.lineno(1)), QLDate, QLDate(day, month, year))

    @staticmethod
    def p_integer_literal(production):
        """expression : INTEGER_LITERAL"""
        production[0] = IntegerNode(Metadata(production.lineno(1)), QLInteger, QLInteger(production[1]))

    @staticmethod
    def p_decimal_literal(production):
        """expression : DECIMAL_LITERAL"""
        production[0] = DecimalNode(Metadata(production.lineno(1)), QLDecimal, QLDecimal(production[1]))

    @staticmethod
    def p_money_literal(production):
        """expression   : currency DECIMAL_LITERAL
                        | currency INTEGER_LITERAL"""
        production[0] = MoneyNode(Metadata(production.lineno(1)), QLMoney,
                                  QLMoney(production[2], production[1]))

    @staticmethod
    def p_string_literal(production):
        """expression : STRING_LITERAL"""
        production[0] = StringNode(Metadata(production.lineno(1)), QLString, QLString([1]))

    # Currencies
    @staticmethod
    def p_currency(production):
        """currency : DOLLAR
                    | RUBLE"""
        production[0] = production[1]

    # Types
    @staticmethod
    def p_boolean(production):
        """type : BOOLEAN"""
        production[0] = QLBoolean

    @staticmethod
    def p_decimal(production):
        """type : DECIMAL"""
        production[0] = QLDecimal

    @staticmethod
    def p_string(production):
        """type : STRING"""
        production[0] = QLString

    @staticmethod
    def p_date(production):
        """type : DATE"""
        production[0] = QLDate

    @staticmethod
    def p_money(production):
        """type : MONEY"""
        production[0] = QLMoney

    @staticmethod
    def p_integer(production):
        """type : INTEGER"""
        production[0] = QLInteger

    # Error
    def p_error(self, production):
        if production is None:
            self.errors.append('Syntax error.')
        else:
            self.errors.append('Syntax error at line {}, token={}.'.format(production.lineno, production.type))

    def p_form_label(self, production):
        """form : FORM LEFT_BRACE"""
        self.errors.append('Missing form identifier at line {}.'.format(production.lineno(1)))

    def p_empty_form(self, production):
        """form : FORM IDENTIFIER LEFT_BRACE RIGHT_BRACE"""
        self.errors.append('Empty form at line {}.'.format(production.lineno(1)))

    def p_empty_if(self, production):
        """statement : IF condition LEFT_BRACE RIGHT_BRACE"""
        self.errors.append('Empty if block at line {}.'.format(production.lineno(1)))

    def p_empty_condition(self, production):
        """condition : LEFT_BRACKET RIGHT_BRACKET"""
        self.errors.append('Empty conditional at line {}.'.format(production.lineno(1)))