Completed
Push — master ( 57e7bf...8a74a7 )
by P.R.
01:36
created

SDoc1Visitor.visitLogicalOrExpressionLogicalOr()   A

Complexity

Conditions 2

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 12
rs 9.4285
cc 2
1
"""
2
SDoc
3
4
Copyright 2016 Set Based IT Consultancy
5
6
Licence MIT
7
"""
8
# ----------------------------------------------------------------------------------------------------------------------
9
import os
10
11
import antlr4
0 ignored issues
show
Configuration introduced by
The import antlr4 could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
12
13
import sdoc
14
from sdoc.antlr.sdoc1Lexer import sdoc1Lexer
15
from sdoc.antlr.sdoc1Parser import sdoc1Parser
16
from sdoc.antlr.sdoc1ParserVisitor import sdoc1ParserVisitor
17
from sdoc.sdoc1.data_type.ArrayDataType import ArrayDataType
18
from sdoc.sdoc1.data_type.IdentifierDataType import IdentifierDataType
19
from sdoc.sdoc1.data_type.IntegerDataType import IntegerDataType
20
from sdoc.sdoc1.data_type.StringDataType import StringDataType
21
22
23
class SDoc1Visitor(sdoc1ParserVisitor):
0 ignored issues
show
best-practice introduced by
Too many public methods (22/20)
Loading history...
24
    """
25
    Visitor for SDoc level 1.
26
    """
27
28
    # ------------------------------------------------------------------------------------------------------------------
29
    def __init__(self, root_dir=os.getcwd()):
30
        """
31
        Object constructor.
32
33
        :param str root_dir: The root directory for including sub-documents.
34
        """
35
        self._output = None
36
        """
37
        Object for streaming the generated output. This object MUST implement the write method.
38
        """
39
40
        self._global_scope = ArrayDataType()
41
        """
42
        All defined variables at global scope.
43
44
        :type: sdoc.sdoc1.data_type.ArrayDataType.ArrayDataType
45
        """
46
47
        self._include_level = 0
48
        """
49
        The level of including other SDoc documents.
50
51
        :type: int
52
        """
53
54
        self._options = {'max_include_level': 100}
55
        """
56
        The options.
57
58
        :type: dict
59
        """
60
61
        self._root_dir = root_dir
62
        """
63
        The root directory for including sub-documents.
64
65
        :type: str
66
        """
67
68
    # ------------------------------------------------------------------------------------------------------------------
69
    @property
70
    def output(self):
71
        """
72
        Getter for output.
73
74
        :rtype: T
75
        """
76
        return self._output
77
78
    # ------------------------------------------------------------------------------------------------------------------
79
    @output.setter
80
    def output(self, output):
81
        """
82
        Setter for output.
83
84
        :param T output: This object MUST implement the write method.
85
        """
86
        self._output = output
87
88
    # ------------------------------------------------------------------------------------------------------------------
89
    @property
90
    def include_level(self):
91
        """
92
        Getter for include_level.
93
94
        :rtype: T
95
        """
96
        return self._include_level
97
98
    # ------------------------------------------------------------------------------------------------------------------
99
    @include_level.setter
100
    def include_level(self, include_level):
101
        """
102
        Setter for include_level.
103
104
        :param int include_level: The include level.
105
        """
106
        self._include_level = include_level
107
108
    # ------------------------------------------------------------------------------------------------------------------
109
    @property
110
    def global_scope(self):
111
        """
112
        Getter for global_scope.
113
114
        :rtype: sdoc.sdoc1.data_type.ArrayDataType.ArrayDataType
115
        """
116
        return self._global_scope
117
118
    # ------------------------------------------------------------------------------------------------------------------
119
    @global_scope.setter
120
    def global_scope(self, scope):
121
        """
122
        Setter for global_scope.
123
124
        :param sdoc.sdoc1.data_type.ArrayDataType.ArrayDataType scope: The global scope.
125
        """
126
        self._global_scope = scope
127
128
    # ------------------------------------------------------------------------------------------------------------------
129
    def stream(self, snippet):
130
        """
131
        Puts an output snippet on the output stream.
132
133
        :param str snippet: The snippet to be appended to the output stream of this parser.
134
        """
135
        if snippet is not None:
136
            self._output.write(snippet)
137
138
    # ------------------------------------------------------------------------------------------------------------------
139
    def put_position(self, ctx, position):
140
        """
141
        Puts a position SDoc2 command on the output stream.
142
143
        :param ParserRuleContext ctx: The context tree.
144
        :param str position: Either start or stop.
145
        """
146
        if position == 'start':
147
            token = ctx.start
148
        else:
149
            token = ctx.stop
150
151
        stream = token.getInputStream()
152
        if hasattr(stream, 'fileName'):
153
            # antlr4.FileStream.FileStream
154
            filename = stream.fileName  # Replace fileName with get_source_name() when implemented in ANTLR.
155
        else:
156
            # Input stream is a antlr4.InputStream.InputStream.
157
            filename = ''
158
159
        line_number = token.line
160
        column = token.column
161
162
        if position == 'stop':
163
            column += len(token.text)
164
165
        self.stream('\\position{{{0!s}:{1:d}.{2:d}}}'.format(sdoc.escape(filename), line_number, column))
166
167
    # ------------------------------------------------------------------------------------------------------------------
168
    def visit(self, tree):
169
        """
170
        Visits a parse tree produced by sdoc1
171
172
        :param ParserRuleContext tree: The context tree.
173
        """
174
        self.put_position(tree, 'start')
175
176
        return super().visit(tree)
177
178
    # ------------------------------------------------------------------------------------------------------------------
179
    def visitAssignmentExpressionAssignment(self, ctx):
180
        """
181
        Visit a parse tree for expression like a = b.
182
183
        :param sdoc1Parser.AssignmentExpressionAssignmentContext ctx: The context tree.
184
        """
185
        right_hand_side = ctx.assignmentExpression().accept(self)
186
        left_hand_side = ctx.postfixExpression().accept(self)
187
188
        # Left hand side must be an identifier.
189
        # @todo implement array element.
190
        if not isinstance(left_hand_side, IdentifierDataType):
191
            raise RuntimeError("Left hand side '{0!s}' is not an identifier.".format(str(left_hand_side)))
192
            # @todo more verbose logging, own exception class
193
194
        return left_hand_side.set_value(right_hand_side)
195
196
    # ------------------------------------------------------------------------------------------------------------------
197
    def visitLogicalAndExpressionAnd(self, ctx):
198
        """
199
        Visits a parse tree for expressions like 'a && b'.
200
201
        :param sdoc1Parser.LogicalAndExpressionAndContext ctx: The context tree.
202
        """
203
        a = ctx.logicalAndExpression().accept(self)
204
        b = ctx.equalityExpression().accept(self)
205
206
        # @todo test a and b are defined
207
208
        return IntegerDataType(1 if a.is_true() and b.is_true() else 0)
209
210
    # ------------------------------------------------------------------------------------------------------------------
211
    def visitLogicalOrExpressionLogicalOr(self, ctx):
212
        """
213
        Visits a parse tree for expressions like 'a || b'.
214
215
        :param sdoc1Parser.LogicalOrExpressionLogicalOrContext ctx: The context tree.
216
        """
217
        a = ctx.logicalOrExpression().accept(self)
218
        b = ctx.logicalAndExpression().accept(self)
219
220
        # @todo test a and b are defined
221
222
        return IntegerDataType(1 if a.is_true() or b.is_true() else 0)
223
224
    # ------------------------------------------------------------------------------------------------------------------
225
    def visitPostfixExpressionExpression(self, ctx):
226
        """
227
        Visits a parse tree for expressions like 'a[1]'.
228
229
        :param sdoc1Parser.PostfixExpressionExpressionContext ctx: The context tree.
230
        """
231
        # First get the value of key.
232
        expression = ctx.expression().accept(self)
233
        if not expression.is_defined():
234
            raise RuntimeError('{0!s} is not defined.'.format(ctx.expression().getText()))
235
236
        postfix_expression = ctx.postfixExpression().accept(self)
237
        if not isinstance(postfix_expression, IdentifierDataType):
238
            raise RuntimeError("'{0!s}' is not an identifier.".format(ctx.postfixExpression().getText()))
239
            # @todo more verbose logging, own exception class
240
241
        return postfix_expression.get_array_element(expression)
242
243
    # ------------------------------------------------------------------------------------------------------------------
244
    def visitPrimaryExpressionIdentifier(self, ctx):
245
        """
246
        Visits a parse tree produced by sdoc1Parser#primaryExpressionIdentifier.
247
248
        :param sdoc1Parser.PrimaryExpressionIdentifierContext ctx: The context tree.
249
        """
250
        return IdentifierDataType(self._global_scope, ctx.EXPR_IDENTIFIER().getText())
251
252
    # ------------------------------------------------------------------------------------------------------------------
253
    def visitPrimaryExpressionIntegerConstant(self, ctx):
254
        """
255
        Visits a parse tree produced by sdoc1Parser#PrimaryExpressionIntegerConstantContext.
256
257
        :param sdoc1Parser.PrimaryExpressionIntegerConstantContext ctx: The context tree.
258
        """
259
        return IntegerDataType(ctx.EXPR_INTEGER_CONSTANT().getText())
260
261
    # ------------------------------------------------------------------------------------------------------------------
262
    def visitPrimaryExpressionStringConstant(self, ctx):
263
        """
264
        Visits a parse tree produced by sdoc1Parser#PrimaryExpressionStringConstantContext.
265
266
        :param sdoc1Parser.PrimaryExpressionStringConstantContext ctx: The context tree.
267
        """
268
        return StringDataType(ctx.EXPR_STRING_CONSTANT().getText()[1:-1])
269
270
    # ------------------------------------------------------------------------------------------------------------------
271
    def visitPrimaryExpressionSubExpression(self, ctx):
272
        """
273
        Visits a parse tree for sub-expressions like (a && b).
274
275
        :param sdoc1Parser.primaryExpressionSubExpression ctx: The context tree.
276
        """
277
        return ctx.expression().accept(self)
278
279
    # ------------------------------------------------------------------------------------------------------------------
280
    def visitCmd_comment(self, ctx):
281
        """
282
        Visits a parse tree produced by sdoc1Parser#cmd_comment.
283
284
        :param sdoc1Parser.Cmd_commentContext ctx: The context tree.
285
        """
286
        self.put_position(ctx, 'stop')
287
288
    # ------------------------------------------------------------------------------------------------------------------
289
    def visitCmd_debug(self, ctx):
290
        """
291
        Visits a parse tree produced by sdoc1Parser#cmd_debug.
292
293
        :param sdoc1Parser.Cmd_debugContext ctx: The context tree.
294
        """
295
        expression = ctx.expression()
296
297
        if expression is not None:
298
            print(expression.accept(self).debug())
299
        else:
300
            print(self._global_scope.debug())
301
302
        self.put_position(ctx, 'stop')
303
304
    # ------------------------------------------------------------------------------------------------------------------
305
    def visitCmd_expression(self, ctx):
306
        """
307
        Visits a parse tree produced by sdoc1Parser#cmd_expression.
308
309
        :param sdoc1Parser.Cmd_expressionContext ctx: The context tree.
310
        """
311
        self.visitExpression(ctx.expression())
312
313
        self.put_position(ctx, 'stop')
314
315
    # ------------------------------------------------------------------------------------------------------------------
316
    def visitCmd_if(self, ctx):
317
        """
318
        Visits a parse tree produced by sdoc1Parser#cmd_if.
319
320
        :param sdoc1Parser.Cmd_ifContext ctx: The parse tree.
321
        """
322
        n = ctx.getChildCount()
323
        fired = False
324
        i = 0
325
        while i < n and not fired:
326
            child = ctx.getChild(i)
327
            token_text = child.getText()
328
            i += 1
329
            if token_text in ['\\if', '\\elif']:
330
                # Skip {
331
                i += 1
332
333
                # Child is the expression to be evaluated.
334
                child = ctx.getChild(i)
335
                i += 1
336
                data = child.accept(self)
337
338
                # Skip }
339
                i += 1
340
341
                if data.is_true():
342
                    # Child is the code inside the if or elif clause.
343
                    child = ctx.getChild(i)
344
                    self.put_position(child, 'start')
345
                    i += 1
346
                    child.accept(self)
347
                    fired = True
348
349
                else:
350
                    # Skip the code inside the if or elif clause.
351
                    i += 1
352
353
            elif token_text == '\\else':
354
                # Child is the code inside the else clause.
355
                child = ctx.getChild(i)
356
                i += 1
357
358
                child.accept(self)
359
                fired = True
360
361
            elif token_text == '\\endif':
362
                pass
363
364
        self.put_position(ctx, 'stop')
365
366
    # ------------------------------------------------------------------------------------------------------------------
367
    def visitCmd_include(self, ctx):
368
        """
369
        Includes another SDoc into this SDoc.
370
371
        :param sdoc1Parser.Cmd_includeContext ctx: The parse tree.
372
        """
373
        # Test the maximum include level.
374
        if self._include_level >= self._options['max_include_level']:
375
            raise RuntimeError("Maximum include level exceeded.")  # @todo More verbose logging, own exception class.
376
377
        # Open a stream for the sub-document.
378
        file_name = sdoc.unescape(ctx.SIMPLE_ARG().getText())
379
        if not os.path.isabs(file_name):
380
            file_name = os.path.join(self._root_dir, file_name + '.sdoc')
381
        print("Including {0!s}".format(os.path.relpath(file_name)))
382
        stream = antlr4.FileStream(file_name, 'utf-8')
383
384
        # root_dir
385
386
        # Create a new lexer and parser for the sub-document.
387
        lexer = sdoc1Lexer(stream)
388
        tokens = antlr4.CommonTokenStream(lexer)
389
        parser = sdoc1Parser(tokens)
390
        tree = parser.sdoc()
391
392
        # Create a visitor.
393
        visitor = SDoc1Visitor(root_dir=os.path.dirname(os.path.realpath(file_name)))
394
395
        # Set or inherit properties from the parser of the parent document.
396
        visitor.include_level = self._include_level + 1
397
        visitor.output = self._output
398
        visitor.global_scope = self._global_scope
399
400
        # Run the visitor on the parse tree.
401
        visitor.visit(tree)
402
403
        self.put_position(ctx, 'stop')
404
405
        # @todo test on errors and warnings from parser and pass back to parent parser
406
407
    # ------------------------------------------------------------------------------------------------------------------
408
    def visitCmd_notice(self, ctx):
409
        """
410
        Visits a parse tree produced by sdoc1Parser#cmd_notice.
411
412
        :param sdoc1Parser.Cmd_noticeContext ctx: The parse tree.
413
        """
414
        token = ctx.NOTICE().getSymbol()
415
        filename = token.getInputStream().fileName  # Replace fileName with get_source_name() when implemented in ANTLR.
416
        line_number = token.line
417
        message = sdoc.unescape(ctx.SIMPLE_ARG().getText())
418
419
        print('Notice: {0!s} at {1!s}:{2:d}'.format(message, os.path.relpath(filename), line_number))
420
421
        self.put_position(ctx, 'stop')
422
423
    # ------------------------------------------------------------------------------------------------------------------
424
    def visitCmd_sdoc2(self, ctx):
425
        """
426
        Visits a parse tree produced by sdoc1Parser#sdoc2_cmd.
427
428
        :param sdoc1Parser.Cmd_sdoc2Context ctx: The parse tree.
429
        """
430
        self.stream(ctx.SDOC2_COMMAND().getText())
431
432
    # ------------------------------------------------------------------------------------------------------------------
433
    def visitText(self, ctx):
434
        """
435
        Visits a parse tree produced by sdoc1Parser#text.
436
437
        :param sdoc1Parser.TextContext ctx: The parse tree.
438
        """
439
        self.stream(ctx.TEXT().getText())
440
441
# ----------------------------------------------------------------------------------------------------------------------
442