Passed
Branch master (0442a2)
by P.R.
02:00
created

SDoc1Visitor.visitCmd_if()   C

Complexity

Conditions 7

Size

Total Lines 49

Duplication

Lines 0
Ratio 0 %

Importance

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