test_ebnf.py 8.06 KB
Newer Older
1
2
#!/usr/bin/python3

3
"""test_ebnf.py - tests of the EBNFcompiler-module of DHParser 
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
                             

Author: Eckhart Arnold <arnold@badw.de>

Copyright 2017 Bavarian Academy of Sciences and Humanities

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""

23
from functools import partial
24
25
26
import os
import sys
sys.path.append(os.path.abspath('../../'))
27
from DHParser.syntaxtree import traverse
28
from DHParser.parsers import full_compilation, Retrieve, WHITESPACE_KEYWORD
29
from DHParser.ebnf import EBNFGrammar, EBNFTransform, EBNFCompiler
30
from DHParser.dsl import compileEBNF
31
32
33
34
35


WRITE_LOGS = True


36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class TestDirectives:
    mini_language = """
        expression =  term  { ("+" | "-") term }
        term       =  factor  { ("*" | "/") factor }
        factor     =  constant | "("  expression  ")"
        constant   =  digit { digit } [ //~ ]
        digit      = /0/ | /1/ | /2/ | /3/ | /4/ | /5/ | /6/ | /7/ | /8/ | /9/ 
        """

    def test_whitespace_linefeed(self):
        lang = "@ whitespace = linefeed\n" + self.mini_language
        MinilangParser = compileEBNF(lang)
        parser = MinilangParser()
        assert parser
        syntax_tree = parser.parse("3 + 4 * 12")
51
        # parser.log_parsing_history("WSP")
52
53
        assert not syntax_tree.collect_errors()
        syntax_tree = parser.parse("3 + 4 \n * 12")
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
        # parser.log_parsing_history("WSPLF")
        assert not syntax_tree.collect_errors()
        syntax_tree = parser.parse("3 + 4 \n \n * 12")
        assert syntax_tree.collect_errors()
        syntax_tree = parser.parse("3 + 4 \n\n * 12")
        assert syntax_tree.collect_errors()

    def test_whitespace_vertical(self):
        lang = "@ whitespace = vertical\n" + self.mini_language
        parser = compileEBNF(lang)()
        assert parser
        syntax_tree = parser.parse("3 + 4 * 12")
        assert not syntax_tree.collect_errors()
        syntax_tree = parser.parse("3 + 4 \n * 12")
        assert not syntax_tree.collect_errors()
        syntax_tree = parser.parse("3 + 4 \n \n * 12")
        assert not syntax_tree.collect_errors()
        syntax_tree = parser.parse("3 + 4 \n\n * 12")
72
73
        assert not syntax_tree.collect_errors()

74
75
    def test_whitespace_horizontal(self):
        lang = "@ whitespace = horizontal\n" + self.mini_language
76
77
78
79
80
81
82
        parser = compileEBNF(lang)()
        assert parser
        syntax_tree = parser.parse("3 + 4 * 12")
        assert not syntax_tree.collect_errors()
        syntax_tree = parser.parse("3 + 4 \n * 12")
        assert syntax_tree.collect_errors()

83

84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
class TestEBNFParser:
    def setup(self):
        self.EBNF = EBNFGrammar()

    def test_literal(self):
        snippet = '"literal" '
        result = self.EBNF.parse(snippet, 'literal')
        assert not result.error_flag
        assert str(result) == snippet
        assert result.find(lambda node: str(node) == WHITESPACE_KEYWORD)

        result = self.EBNF.parse(' "literal"', 'literal')
        assert result.error_flag  # literals catch following, but not leading whitespace


99
100
101
102
class TestPopRetrieve:
    mini_language = """
        document       = { text | codeblock }
        codeblock      = delimiter { text | (!:delimiter delimiter_sign) } ::delimiter
103
        delimiter      = delimiter_sign  # never use delimiter between capture and retrieve!!!
104
105
106
        delimiter_sign = /`+/
        text           = /[^`]+/ 
        """
107
    mini_lang2 = """
108
        @retrieve_counterpart = braces
109
        document       = { text | codeblock }
110
111
112
        codeblock      = braces { text | opening_braces | (!:braces closing_braces) } ::braces
        braces         = opening_braces
        opening_braces = /\{+/
113
        closing_braces = /\}+/
114
        text           = /[^{}]+/
115
        """
116
117
118

    def setup(self):
        self.minilang_parser = compileEBNF(self.mini_language)()
119
        self.minilang_parser2 = compileEBNF(self.mini_lang2)()
120

121
122
123
124
125
126
127
128
    @staticmethod
    def opening_delimiter(node, name):
        return node.tag_name == name and not isinstance(node.parser, Retrieve)

    @staticmethod
    def closing_delimiter(node):
        return isinstance(node.parser, Retrieve)

129
130
    def test_compile_mini_language(self):
        assert self.minilang_parser
131
        assert self.minilang_parser2
132
133
134
135
136

    def test_single_line(self):
        teststr = "Anfang ```code block `` <- keine Ende-Zeichen ! ``` Ende"
        syntax_tree = self.minilang_parser.parse(teststr)
        assert not syntax_tree.collect_errors()
137
138
        delim = str(next(syntax_tree.find(partial(self.opening_delimiter, name="delimiter"))))
        pop = str(next(syntax_tree.find(self.closing_delimiter)))
139
        assert delim == pop
140
141
142
143
144
145
146
147
148
149
150
151
152
153
        if WRITE_LOGS:
            syntax_tree.log("test_PopRetrieve_single_line", '.cst')

    def test_multi_line(self):
        teststr = """
            Anfang ```code block `` <- keine Ende-Zeichen ! ``` Ebde

            Absatz ohne ``` codeblock, aber
            das stellt sich erst am Ende herause...

            Mehrzeliger ```code block
            """
        syntax_tree = self.minilang_parser.parse(teststr)
        assert not syntax_tree.collect_errors()
154
155
        delim = str(next(syntax_tree.find(partial(self.opening_delimiter, name="delimiter"))))
        pop = str(next(syntax_tree.find(self.closing_delimiter)))
156
        assert delim == pop
157
158
        if WRITE_LOGS:
            syntax_tree.log("test_PopRetrieve_multi_line", '.cst')
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185

    def test_single_line_complement(self):
        teststr = "Anfang {{{code block }} <- keine Ende-Zeichen ! }}} Ende"
        syntax_tree = self.minilang_parser2.parse(teststr)
        assert not syntax_tree.collect_errors()
        delim = str(next(syntax_tree.find(partial(self.opening_delimiter, name="braces"))))
        pop = str(next(syntax_tree.find(self.closing_delimiter)))
        assert len(delim) == len(pop) and delim != pop
        if WRITE_LOGS:
            syntax_tree.log("test_PopRetrieve_single_line", '.cst')

    def test_multi_line_complement(self):
        teststr = """
            Anfang {{{code block {{ <- keine Ende-Zeichen ! }}} Ende

            Absatz ohne {{{ codeblock, aber
            das stellt sich erst am Ende heraus...

            Mehrzeliger }}}code block
            """
        syntax_tree = self.minilang_parser2.parse(teststr)
        assert not syntax_tree.collect_errors()
        delim = str(next(syntax_tree.find(partial(self.opening_delimiter, name="braces"))))
        pop = str(next(syntax_tree.find(self.closing_delimiter)))
        assert len(delim) == len(pop) and delim != pop
        if WRITE_LOGS:
            syntax_tree.log("test_PopRetrieve_multi_line", '.cst')
186
187


188
189
190
191
192
class TestSemanticValidation:
    def check(self, minilang, bool_filter=lambda x: x):
        grammar = EBNFGrammar()
        st = grammar.parse(minilang)
        assert not st.collect_errors()
193
        EBNFTransform(st)
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
        assert bool_filter(st.collect_errors())

    def test_illegal_nesting(self):
        self.check('impossible = { [ "an optional requirement" ] }')

    def test_illegal_nesting_option_required(self):
        self.check('impossible = [ §"an optional requirement" ]')

    def test_illegal_nesting_oneormore_option(self):
        self.check('impossible = { [ "no use"] }+')

    def test_legal_nesting(self):
        self.check('possible = { [ "+" ] "1" }', lambda x: not x)


class TestCompilerErrors:
    def test_error_propagation(self):
211
        ebnf = "@ literalws = wrongvalue  # testing error propagation\n"
212
        result, messages, st = full_compilation(ebnf, None, EBNFGrammar(), EBNFTransform,
213
214
215
216
                                                EBNFCompiler('ErrorPropagationTest'))
        assert messages


217
if __name__ == "__main__":
218
    from run import runner
219
    runner("TestPopRetrieve", globals())