Completed
Push — master ( ffbd85...1e6578 )
by P.R.
01:48
created

SDoc1Visitor.visitAssignmentExpressionAssignment()   A

Complexity

Conditions 3

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

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