Completed
Push — master ( 260382...690f4b )
by P.R.
01:53
created

SDoc1Visitor.visitText()   A

Complexity

Conditions 1

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 7
rs 9.4285
cc 1
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, 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 _data_is_true(self, data, token=None):
173
        """
174
        Returns True if a data type evaluates to True, False if a data type evaluates to False, and None if an error
175
        occurs.
176
177
        :param sdoc.sdoc1.data_type.DataType.DataType data: The data.
178
        :param antlr4.Token.CommonToken token: The token where data type is been used.
179
        """
180
        try:
181
            return data.is_true()
182
183
        except DataTypeError as e:
184
            self._error(str(e), token)
185
            return None
186
187
    # ------------------------------------------------------------------------------------------------------------------
188
    def visit(self, tree):
189
        """
190
        Visits a parse tree produced by sdoc1
191
192
        :param ParserRuleContext tree: The context tree.
193
        """
194
        self.put_position(tree, 'start')
195
196
        return super().visit(tree)
197
198
    # ------------------------------------------------------------------------------------------------------------------
199
    def visitAssignmentExpressionAssignment(self, ctx):
200
        """
201
        Visit a parse tree for expression like a = b.
202
203
        :param sdoc1Parser.AssignmentExpressionAssignmentContext ctx: The context tree.
204
        """
205
        right_hand_side = ctx.assignmentExpression().accept(self)
206
        left_hand_side = ctx.postfixExpression().accept(self)
207
208
        # Left hand side must be an identifier.
209
        # @todo implement array element.
210
        if not isinstance(left_hand_side, IdentifierDataType):
211
            message = "Left hand side '{0!s}' is not an identifier.".format(str(left_hand_side))
212
            self._error(message, ctx.postfixExpression().start)
213
            return
214
215
        try:
216
            value = left_hand_side.set_value(right_hand_side)
217
        except DataTypeError as e:
218
            self._error(str(e), ctx.assignmentExpression().start)
219
            return None
220
221
        return value
222
223
    # ------------------------------------------------------------------------------------------------------------------
224
    def visitLogicalAndExpressionAnd(self, ctx):
225
        """
226
        Visits a parse tree for expressions like 'a && b'.
227
228
        :param sdoc1Parser.LogicalAndExpressionAndContext ctx: The context tree.
229
        """
230
        a_ctx = ctx.logicalAndExpression()
231
        b_ctx = ctx.equalityExpression()
232
233
        a = a_ctx.accept(self)
234
        b = b_ctx.accept(self)
235
236
        a_is_true = self._data_is_true(a, a_ctx.start)
237
        b_is_true = self._data_is_true(b, b_ctx.start)
238
239
        return IntegerDataType(1 if a_is_true and b_is_true else 0)
240
241
    # ------------------------------------------------------------------------------------------------------------------
242
    def visitLogicalOrExpressionLogicalOr(self, ctx):
243
        """
244
        Visits a parse tree for expressions like 'a || b'.
245
246
        :param sdoc1Parser.LogicalOrExpressionLogicalOrContext ctx: The context tree.
247
        """
248
        a_ctx = ctx.logicalOrExpression()
249
        b_ctx = ctx.logicalAndExpression()
250
251
        a = a_ctx.accept(self)
252
        b = b_ctx.accept(self)
253
254
        a_is_true = self._data_is_true(a, a_ctx.start)
255
        b_is_true = self._data_is_true(b, b_ctx.start)
256
257
        return IntegerDataType(1 if a_is_true or b_is_true else 0)
258
259
    # ------------------------------------------------------------------------------------------------------------------
260
    def visitPostfixExpressionExpression(self, ctx):
261
        """
262
        Visits a parse tree for expressions like 'a[1]'.
263
264
        :param sdoc1Parser.PostfixExpressionExpressionContext ctx: The context tree.
265
        """
266
        # First get the value of key.
267
        expression = ctx.expression().accept(self)
268
        if not expression.is_defined():
269
            message = '{0!s} is not defined.'.format(ctx.expression().getSymbol())
270
            self._error(message, ctx.expression().start)
271
            return
272
273
        postfix_expression = ctx.postfixExpression().accept(self)
274
        if not isinstance(postfix_expression, IdentifierDataType):
275
            message = "'{0!s}' is not an identifier.".format(ctx.postfixExpression().getSymbol())
276
            self._error(message, ctx.postfixExpression().start)
277
            return
278
279
        return postfix_expression.get_array_element(expression)
280
281
    # ------------------------------------------------------------------------------------------------------------------
282
    def visitPrimaryExpressionIdentifier(self, ctx):
283
        """
284
        Visits a parse tree produced by sdoc1Parser#primaryExpressionIdentifier.
285
286
        :param sdoc1Parser.PrimaryExpressionIdentifierContext ctx: The context tree.
287
        """
288
        return IdentifierDataType(self._global_scope, ctx.EXPR_IDENTIFIER().getText())
289
290
    # ------------------------------------------------------------------------------------------------------------------
291
    def visitPrimaryExpressionIntegerConstant(self, ctx):
292
        """
293
        Visits a parse tree produced by sdoc1Parser#PrimaryExpressionIntegerConstantContext.
294
295
        :param sdoc1Parser.PrimaryExpressionIntegerConstantContext ctx: The context tree.
296
        """
297
        return IntegerDataType(ctx.EXPR_INTEGER_CONSTANT().getText())
298
299
    # ------------------------------------------------------------------------------------------------------------------
300
    def visitPrimaryExpressionStringConstant(self, ctx):
301
        """
302
        Visits a parse tree produced by sdoc1Parser#PrimaryExpressionStringConstantContext.
303
304
        :param sdoc1Parser.PrimaryExpressionStringConstantContext ctx: The context tree.
305
        """
306
        return StringDataType(ctx.EXPR_STRING_CONSTANT().getText()[1:-1].replace('\\\\', '\\').replace('\\\'', '\''))
307
308
    # ------------------------------------------------------------------------------------------------------------------
309
    def visitPrimaryExpressionSubExpression(self, ctx):
310
        """
311
        Visits a parse tree for sub-expressions like (a && b).
312
313
        :param sdoc1Parser.primaryExpressionSubExpression ctx: The context tree.
314
        """
315
        return ctx.expression().accept(self)
316
317
    # ------------------------------------------------------------------------------------------------------------------
318
    def visitCmd_comment(self, ctx):
319
        """
320
        Visits a parse tree produced by sdoc1Parser#cmd_comment.
321
322
        :param sdoc1Parser.Cmd_commentContext ctx: The context tree.
323
        """
324
        self.put_position(ctx, 'stop')
325
326
    # ------------------------------------------------------------------------------------------------------------------
327
    def visitCmd_debug(self, ctx):
328
        """
329
        Visits a parse tree produced by sdoc1Parser#cmd_debug.
330
331
        :param sdoc1Parser.Cmd_debugContext ctx: The context tree.
332
        """
333
        expression = ctx.expression()
334
335
        if expression is not None:
336
            print(expression.accept(self).debug())
337
        else:
338
            print(self._global_scope.debug())
339
340
        self.put_position(ctx, 'stop')
341
342
    # ------------------------------------------------------------------------------------------------------------------
343
    def visitCmd_expression(self, ctx):
344
        """
345
        Visits a parse tree produced by sdoc1Parser#cmd_expression.
346
347
        :param sdoc1Parser.Cmd_expressionContext ctx: The context tree.
348
        """
349
        self.visitExpression(ctx.expression())
350
351
        self.put_position(ctx, 'stop')
352
353
    # ------------------------------------------------------------------------------------------------------------------
354
    def visitCmd_if(self, ctx):
355
        """
356
        Visits a parse tree produced by sdoc1Parser#cmd_if.
357
358
        :param sdoc1Parser.Cmd_ifContext ctx: The parse tree.
359
        """
360
        n = ctx.getChildCount()
361
        fired = False
362
        i = 0
363
        while i < n and not fired:
364
            child = ctx.getChild(i)
365
            token_text = child.getText()
366
            i += 1
367
            if token_text in ['\\if', '\\elif']:
368
                # Skip {
369
                i += 1
370
371
                # Child is the expression to be evaluated.
372
                child = ctx.getChild(i)
373
                i += 1
374
                data = child.accept(self)
375
376
                # Skip }
377
                i += 1
378
379
                if self._data_is_true(data, child.start):
380
                    # Child is the code inside the if or elif clause.
381
                    child = ctx.getChild(i)
382
                    self.put_position(child, 'start')
383
                    i += 1
384
                    child.accept(self)
385
                    fired = True
386
387
                else:
388
                    # Skip the code inside the if or elif clause.
389
                    i += 1
390
391
            elif token_text == '\\else':
392
                # Child is the code inside the else clause.
393
                child = ctx.getChild(i)
394
                i += 1
395
396
                child.accept(self)
397
                fired = True
398
399
            elif token_text == '\\endif':
400
                pass
401
402
        self.put_position(ctx, 'stop')
403
404
    # ------------------------------------------------------------------------------------------------------------------
405
    def visitCmd_include(self, ctx):
406
        """
407
        Includes another SDoc into this SDoc.
408
409
        :param sdoc1Parser.Cmd_includeContext ctx: The parse tree.
410
        """
411
        # Test the maximum include level.
412
        if self._include_level >= self._options['max_include_level']:
413
            message = 'Maximum include level exceeded'
414
            self._error(message, ctx.INCLUDE().getSymbol())
415
            return
416
417
        # Open a stream for the sub-document.
418
        file_name = SDoc.unescape(ctx.SIMPLE_ARG().getText())
419
        if not os.path.isabs(file_name):
420
            file_name = os.path.join(self._root_dir, file_name + '.sdoc')
421
        real_path = os.path.relpath(file_name)
422
        print("Including {0!s}".format(real_path))
423
        try:
424
            stream = antlr4.FileStream(file_name, 'utf-8')
425
426
            # root_dir
427
428
            # Create a new lexer and parser for the sub-document.
429
            lexer = sdoc1Lexer(stream)
430
            tokens = antlr4.CommonTokenStream(lexer)
431
            parser = sdoc1Parser(tokens)
432
            tree = parser.sdoc()
433
434
            # Create a visitor.
435
            visitor = SDoc1Visitor(root_dir=os.path.dirname(os.path.realpath(file_name)))
436
437
            # Set or inherit properties from the parser of the parent document.
438
            visitor.include_level = self._include_level + 1
439
            visitor.output = self._output
440
            visitor.global_scope = self._global_scope
441
442
            # Run the visitor on the parse tree.
443
            visitor.visit(tree)
444
445
            # Copy  properties from the child document.
446
            self._errors += visitor.errors
447
448
            self.put_position(ctx, 'stop')
449
        except FileNotFoundError as e:
450
            message = 'Unable to open file {0!s}.\nCause: {1!s}'.format(real_path, e)
451
            self._error(message, ctx.INCLUDE().getSymbol())
452
453
    # ------------------------------------------------------------------------------------------------------------------
454
    def visitCmd_notice(self, ctx):
455
        """
456
        Visits a parse tree produced by sdoc1Parser#cmd_notice.
457
458
        :param sdoc1Parser.Cmd_noticeContext ctx: The parse tree.
459
        """
460
        token = ctx.NOTICE().getSymbol()
461
        filename = token.getInputStream().fileName  # Replace fileName with get_source_name() when implemented in ANTLR.
462
        line_number = token.line
463
        message = SDoc.unescape(ctx.SIMPLE_ARG().getText())
464
465
        print('Notice: {0!s} at {1!s}:{2:d}'.format(message, os.path.relpath(filename), line_number))
466
467
        self.put_position(ctx, 'stop')
468
469
    # ------------------------------------------------------------------------------------------------------------------
470
    def visitCmd_sdoc2(self, ctx):
471
        """
472
        Visits a parse tree produced by sdoc1Parser#sdoc2_cmd.
473
474
        :param sdoc1Parser.Cmd_sdoc2Context ctx: The parse tree.
475
        """
476
        self.stream(ctx.SDOC2_COMMAND().getText())
477
478
    # ------------------------------------------------------------------------------------------------------------------
479
    def visitText(self, ctx):
480
        """
481
        Visits a parse tree produced by sdoc1Parser#text.
482
483
        :param sdoc1Parser.TextContext ctx: The parse tree.
484
        """
485
        self.stream(ctx.TEXT().getText())
486
487
# ----------------------------------------------------------------------------------------------------------------------
488