Completed
Push — master ( d064ac...09268d )
by P.R.
02:30
created

SDoc1Visitor.visitCmd_error()   A

Complexity

Conditions 1

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1.6296

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 14
ccs 1
cts 7
cp 0.1429
rs 9.4285
cc 1
crap 1.6296
1
"""
2
SDoc
3
4
Copyright 2016 Set Based IT Consultancy
5
6
Licence MIT
7
"""
8
# ----------------------------------------------------------------------------------------------------------------------
9 1
import os
10
11 1
import antlr4
12
13 1
from sdoc.antlr.sdoc1Lexer import sdoc1Lexer
14 1
from sdoc.antlr.sdoc1Parser import sdoc1Parser
15 1
from sdoc.antlr.sdoc1ParserVisitor import sdoc1ParserVisitor
16 1
from sdoc.helper.SDoc import SDoc
17 1
from sdoc.sdoc.SDocVisitor import SDocVisitor
18 1
from sdoc.sdoc1.data_type.ArrayDataType import ArrayDataType
19 1
from sdoc.sdoc1.data_type.IdentifierDataType import IdentifierDataType
20 1
from sdoc.sdoc1.data_type.IntegerDataType import IntegerDataType
21 1
from sdoc.sdoc1.data_type.StringDataType import StringDataType
22 1
from sdoc.sdoc1.error import DataTypeError
23
24
25 1
class SDoc1Visitor(sdoc1ParserVisitor, SDocVisitor):
1 ignored issue
show
best-practice introduced by
Too many public methods (24/20)
Loading history...
26
    """
27
    Visitor for SDoc level 1.
28
    """
29
30
    # ------------------------------------------------------------------------------------------------------------------
31 1
    def __init__(self, io, root_dir=os.getcwd()):
32
        """
33
        Object constructor.
34
35
        :param str root_dir: The root directory for including sub-documents.
36
        """
37 1
        SDocVisitor.__init__(self, io)
38
39 1
        self._io = io
40
        """
41
        Styled output formatter.
42
43
        :type: sdoc.style.SdocStyle.SdocStyle
44
        """
45
46 1
        self._output = None
47
        """
48
        Object for streaming the generated output. This object MUST implement the write method.
49
        """
50
51 1
        self._global_scope = ArrayDataType()
52
        """
53
        All defined variables at global scope.
54
55
        :type: sdoc.sdoc1.data_type.ArrayDataType.ArrayDataType
56
        """
57
58 1
        self._include_level = 0
59
        """
60
        The level of including other SDoc documents.
61
62
        :type: int
63
        """
64
65 1
        self._options = {'max_include_level': 100}
66
        """
67
        The options.
68
69
        :type: dict
70
        """
71
72 1
        self._root_dir = root_dir
73 1
        """
74
        The root directory for including sub-documents.
75
76
        :type: str
77
        """
78
79
    # ------------------------------------------------------------------------------------------------------------------
80 1
    @property
81
    def output(self):
82
        """
83
        Getter for output.
84
85
        :rtype: T
86
        """
87
        return self._output
88
89
    # ------------------------------------------------------------------------------------------------------------------
90 1
    @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 1
        self._output = output
98
99
    # ------------------------------------------------------------------------------------------------------------------
100 1
    @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 1
    @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 1
    @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 1
    @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 1
    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 1
        if snippet is not None:
147 1
            self._output.write(snippet)
148
149
    # ------------------------------------------------------------------------------------------------------------------
150 1
    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 1
        if position == 'start':
158 1
            token = ctx.start
159
        else:
160 1
            token = ctx.stop
161
162 1
        stream = token.getInputStream()
163 1
        if hasattr(stream, 'fileName'):
164
            # antlr4.FileStream.FileStream
165 1
            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 1
        line_number = token.line
171 1
        column = token.column
172
173 1
        if position == 'stop':
174 1
            column += len(token.text)
175
176 1
        self.stream('\\position{{{0!s}:{1:d}.{2:d}}}'.format(SDoc.escape(filename), line_number, column))
177
178
    # ------------------------------------------------------------------------------------------------------------------
179 1
    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 1
    def visit(self, tree):
196
        """
197
        Visits a parse tree produced by sdoc1
198
199
        :param ParserRuleContext tree: The context tree.
200
        """
201 1
        self.put_position(tree, 'start')
202
203 1
        return super().visit(tree)
204
205
    # ------------------------------------------------------------------------------------------------------------------
206 1
    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 1
        right_hand_side = ctx.assignmentExpression().accept(self)
213 1
        left_hand_side = ctx.postfixExpression().accept(self)
214
215
        # Left hand side must be an identifier.
216
        # @todo implement array element.
217 1
        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 1
        try:
223 1
            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 1
        return value
229
230
    # ------------------------------------------------------------------------------------------------------------------
231 1
    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 1
    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 1
    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 1
        expression = ctx.expression().accept(self)
275 1
        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 1
        postfix_expression = ctx.postfixExpression().accept(self)
281 1
        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 1
        return postfix_expression.get_array_element(expression)
287
288
    # ------------------------------------------------------------------------------------------------------------------
289 1
    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 1
        return IdentifierDataType(self._global_scope, ctx.EXPR_IDENTIFIER().getText())
296
297
    # ------------------------------------------------------------------------------------------------------------------
298 1
    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 1
        return IntegerDataType(ctx.EXPR_INTEGER_CONSTANT().getText())
305
306
    # ------------------------------------------------------------------------------------------------------------------
307 1
    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 1
        return StringDataType(ctx.EXPR_STRING_CONSTANT().getText()[1:-1].replace('\\\\', '\\').replace('\\\'', '\''))
314
315
    # ------------------------------------------------------------------------------------------------------------------
316 1
    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 1
    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 1
    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 1
        expression = ctx.expression()
341
342 1
        if expression is not None:
343 1
            self._io.writeln(expression.accept(self).debug())
344
        else:
345
            self._io.writeln(self._global_scope.debug())
346
347 1
        self.put_position(ctx, 'stop')
348
349
    # ------------------------------------------------------------------------------------------------------------------
350 1
    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 1
        self.visitExpression(ctx.expression())
357
358 1
        self.put_position(ctx, 'stop')
359
360
    # ------------------------------------------------------------------------------------------------------------------
361 1
    def visitCmd_error(self, ctx):
362
        """
363
        Visits a parse tree produced by sdoc1Parser#cmd_error.
364
365
        :param sdoc1Parser.Cmd_errorContext ctx: The parse tree.
366
        """
367
        token = ctx.ERROR().getSymbol()
368
        filename = token.getInputStream().fileName  # Replace fileName with get_source_name() when implemented in ANTLR.
369
        line_number = token.line
370
        message = SDoc.unescape(ctx.SIMPLE_ARG().getText())
371
372
        self._io.writeln('<err>Error: {0!s} at {1!s}:{2:d}</err>'.format(message, os.path.relpath(filename), line_number))
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (122/120).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
373
374
        self.put_position(ctx, 'stop')
375
376
    # ------------------------------------------------------------------------------------------------------------------
377 1
    def visitCmd_if(self, ctx):
378
        """
379
        Visits a parse tree produced by sdoc1Parser#cmd_if.
380
381
        :param sdoc1Parser.Cmd_ifContext ctx: The parse tree.
382
        """
383
        n = ctx.getChildCount()
384
        fired = False
385
        i = 0
386
        while i < n and not fired:
387
            child = ctx.getChild(i)
388
            token_text = child.getText()
389
            i += 1
390
            if token_text in ['\\if', '\\elif']:
391
                # Skip {
392
                i += 1
393
394
                # Child is the expression to be evaluated.
395
                child = ctx.getChild(i)
396
                i += 1
397
                data = child.accept(self)
398
399
                # Skip }
400
                i += 1
401
402
                if self._data_is_true(data, child.start):
403
                    # Child is the code inside the if or elif clause.
404
                    child = ctx.getChild(i)
405
                    self.put_position(child, 'start')
406
                    i += 1
407
                    child.accept(self)
408
                    fired = True
409
410
                else:
411
                    # Skip the code inside the if or elif clause.
412
                    i += 1
413
414
            elif token_text == '\\else':
415
                # Child is the code inside the else clause.
416
                child = ctx.getChild(i)
417
                i += 1
418
419
                child.accept(self)
420
                fired = True
421
422
            elif token_text == '\\endif':
423
                pass
424
425
        self.put_position(ctx, 'stop')
426
427
    # ------------------------------------------------------------------------------------------------------------------
428 1
    def visitCmd_include(self, ctx):
429
        """
430
        Includes another SDoc into this SDoc.
431
432
        :param sdoc1Parser.Cmd_includeContext ctx: The parse tree.
433
        """
434
        # Test the maximum include level.
435
        if self._include_level >= self._options['max_include_level']:
436
            message = 'Maximum include level exceeded'
437
            self._error(message, ctx.INCLUDE().getSymbol())
438
            return
439
440
        # Open a stream for the sub-document.
441
        file_name = SDoc.unescape(ctx.SIMPLE_ARG().getText())
442
        if not os.path.isabs(file_name):
443
            file_name = os.path.join(self._root_dir, file_name + '.sdoc')
444
        real_path = os.path.relpath(file_name)
445
        self._io.writeln("Including <fso>{0!s}</fso>".format(real_path))
446
        try:
447
            stream = antlr4.FileStream(file_name, 'utf-8')
448
449
            # root_dir
450
451
            # Create a new lexer and parser for the sub-document.
452
            lexer = sdoc1Lexer(stream)
453
            tokens = antlr4.CommonTokenStream(lexer)
454
            parser = sdoc1Parser(tokens)
455
            tree = parser.sdoc()
456
457
            # Create a visitor.
458
            visitor = SDoc1Visitor(self._io, root_dir=os.path.dirname(os.path.realpath(file_name)))
459
460
            # Set or inherit properties from the parser of the parent document.
461
            visitor.include_level = self._include_level + 1
462
            visitor.output = self._output
463
            visitor.global_scope = self._global_scope
464
465
            # Run the visitor on the parse tree.
466
            visitor.visit(tree)
467
468
            # Copy  properties from the child document.
469
            self._errors += visitor.errors
470
471
            self.put_position(ctx, 'stop')
472
        except FileNotFoundError as e:
473
            message = 'Unable to open file {0!s}.\nCause: {1!s}'.format(real_path, e)
474
            self._error(message, ctx.INCLUDE().getSymbol())
475
476
    # ------------------------------------------------------------------------------------------------------------------
477 1
    def visitCmd_notice(self, ctx):
478
        """
479
        Visits a parse tree produced by sdoc1Parser#cmd_notice.
480
481
        :param sdoc1Parser.Cmd_noticeContext ctx: The parse tree.
482
        """
483
        token = ctx.NOTICE().getSymbol()
484
        filename = token.getInputStream().fileName  # Replace fileName with get_source_name() when implemented in ANTLR.
485
        line_number = token.line
486
        message = SDoc.unescape(ctx.SIMPLE_ARG().getText())
487
488
        self._io.writeln('<notice>Notice: {0!s} at {1!s}:{2:d}</notice>'.format(message, os.path.relpath(filename), line_number))
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (129/120).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
489
490
        self.put_position(ctx, 'stop')
491
492
    # ------------------------------------------------------------------------------------------------------------------
493 1
    def visitCmd_substitute(self, ctx):
494
        """
495
        Visit a parse tree produced by sdoc1Parser#cmd_substitute.
496
497
        :param sdoc1Parser.Cmd_substituteContext ctx:  The parse tree.
498
        """
499 1
        expression = ctx.expression()
500 1
        self.stream(expression.accept(self).get_value())
501
502 1
        self.put_position(ctx, 'stop')
503
504
    # ------------------------------------------------------------------------------------------------------------------
505 1
    def visitCmd_sdoc2(self, ctx):
506
        """
507
        Visits a parse tree produced by sdoc1Parser#sdoc2_cmd.
508
509
        :param sdoc1Parser.Cmd_sdoc2Context ctx: The parse tree.
510
        """
511 1
        self.stream(ctx.SDOC2_COMMAND().getText())
512
513
    # ------------------------------------------------------------------------------------------------------------------
514 1
    def visitText(self, ctx):
515
        """
516
        Visits a parse tree produced by sdoc1Parser#text.
517
518
        :param sdoc1Parser.TextContext ctx: The parse tree.
519
        """
520 1
        self.stream(ctx.TEXT().getText())
521
522
# ----------------------------------------------------------------------------------------------------------------------
523