7267
|
1 # testyacc.py
|
|
2
|
|
3 import unittest
|
|
4 try:
|
|
5 import StringIO
|
|
6 except ImportError:
|
|
7 import io as StringIO
|
|
8
|
|
9 import sys
|
|
10 import os
|
|
11 import warnings
|
|
12 import re
|
|
13 import platform
|
|
14
|
|
15 sys.path.insert(0,"..")
|
|
16 sys.tracebacklimit = 0
|
|
17
|
|
18 import ply.yacc
|
|
19
|
|
20 def make_pymodule_path(filename):
|
|
21 path = os.path.dirname(filename)
|
|
22 file = os.path.basename(filename)
|
|
23 mod, ext = os.path.splitext(file)
|
|
24
|
|
25 if sys.hexversion >= 0x3040000:
|
|
26 import importlib.util
|
|
27 fullpath = importlib.util.cache_from_source(filename, ext=='.pyc')
|
|
28 elif sys.hexversion >= 0x3020000:
|
|
29 import imp
|
|
30 modname = mod+"."+imp.get_tag()+ext
|
|
31 fullpath = os.path.join(path,'__pycache__',modname)
|
|
32 else:
|
|
33 fullpath = filename
|
|
34 return fullpath
|
|
35
|
|
36 def pymodule_out_exists(filename):
|
|
37 return os.path.exists(make_pymodule_path(filename))
|
|
38
|
|
39 def pymodule_out_remove(filename):
|
|
40 os.remove(make_pymodule_path(filename))
|
|
41
|
|
42 def implementation():
|
|
43 if platform.system().startswith("Java"):
|
|
44 return "Jython"
|
|
45 elif hasattr(sys, "pypy_version_info"):
|
|
46 return "PyPy"
|
|
47 else:
|
|
48 return "CPython"
|
|
49
|
|
50 # Check the output to see if it contains all of a set of expected output lines.
|
|
51 # This alternate implementation looks weird, but is needed to properly handle
|
|
52 # some variations in error message order that occurs due to dict hash table
|
|
53 # randomization that was introduced in Python 3.3
|
|
54 def check_expected(result, expected):
|
|
55 # Normalize 'state n' text to account for randomization effects in Python 3.3
|
|
56 expected = re.sub(r' state \d+', 'state <n>', expected)
|
|
57 result = re.sub(r' state \d+', 'state <n>', result)
|
|
58
|
|
59 resultlines = set()
|
|
60 for line in result.splitlines():
|
|
61 if line.startswith("WARNING: "):
|
|
62 line = line[9:]
|
|
63 elif line.startswith("ERROR: "):
|
|
64 line = line[7:]
|
|
65 resultlines.add(line)
|
|
66
|
|
67 # Selectively remove expected lines from the output
|
|
68 for eline in expected.splitlines():
|
|
69 resultlines = set(line for line in resultlines if not line.endswith(eline))
|
|
70
|
|
71 # Return True if no result lines remain
|
|
72 return not bool(resultlines)
|
|
73
|
|
74 def run_import(module):
|
|
75 code = "import "+module
|
|
76 exec(code)
|
|
77 del sys.modules[module]
|
|
78
|
|
79 # Tests related to errors and warnings when building parsers
|
|
80 class YaccErrorWarningTests(unittest.TestCase):
|
|
81 def setUp(self):
|
|
82 sys.stderr = StringIO.StringIO()
|
|
83 sys.stdout = StringIO.StringIO()
|
|
84 try:
|
|
85 os.remove("parsetab.py")
|
|
86 pymodule_out_remove("parsetab.pyc")
|
|
87 except OSError:
|
|
88 pass
|
|
89
|
|
90 if sys.hexversion >= 0x3020000:
|
|
91 warnings.filterwarnings('ignore', category=ResourceWarning)
|
|
92 warnings.filterwarnings('ignore', category=DeprecationWarning)
|
|
93
|
|
94 def tearDown(self):
|
|
95 sys.stderr = sys.__stderr__
|
|
96 sys.stdout = sys.__stdout__
|
|
97 def test_yacc_badargs(self):
|
|
98 self.assertRaises(ply.yacc.YaccError,run_import,"yacc_badargs")
|
|
99 result = sys.stderr.getvalue()
|
|
100 self.assert_(check_expected(result,
|
|
101 "yacc_badargs.py:23: Rule 'p_statement_assign' has too many arguments\n"
|
|
102 "yacc_badargs.py:27: Rule 'p_statement_expr' requires an argument\n"
|
|
103 ))
|
|
104 def test_yacc_badid(self):
|
|
105 self.assertRaises(ply.yacc.YaccError,run_import,"yacc_badid")
|
|
106 result = sys.stderr.getvalue()
|
|
107 self.assert_(check_expected(result,
|
|
108 "yacc_badid.py:32: Illegal name 'bad&rule' in rule 'statement'\n"
|
|
109 "yacc_badid.py:36: Illegal rule name 'bad&rule'\n"
|
|
110 ))
|
|
111
|
|
112 def test_yacc_badprec(self):
|
|
113 try:
|
|
114 run_import("yacc_badprec")
|
|
115 except ply.yacc.YaccError:
|
|
116 result = sys.stderr.getvalue()
|
|
117 self.assert_(check_expected(result,
|
|
118 "precedence must be a list or tuple\n"
|
|
119 ))
|
|
120 def test_yacc_badprec2(self):
|
|
121 self.assertRaises(ply.yacc.YaccError,run_import,"yacc_badprec2")
|
|
122 result = sys.stderr.getvalue()
|
|
123 self.assert_(check_expected(result,
|
|
124 "Bad precedence table\n"
|
|
125 ))
|
|
126
|
|
127 def test_yacc_badprec3(self):
|
|
128 run_import("yacc_badprec3")
|
|
129 result = sys.stderr.getvalue()
|
|
130 self.assert_(check_expected(result,
|
|
131 "Precedence already specified for terminal 'MINUS'\n"
|
|
132 "Generating LALR tables\n"
|
|
133
|
|
134 ))
|
|
135
|
|
136 def test_yacc_badrule(self):
|
|
137 self.assertRaises(ply.yacc.YaccError,run_import,"yacc_badrule")
|
|
138 result = sys.stderr.getvalue()
|
|
139 self.assert_(check_expected(result,
|
|
140 "yacc_badrule.py:24: Syntax error. Expected ':'\n"
|
|
141 "yacc_badrule.py:28: Syntax error in rule 'statement'\n"
|
|
142 "yacc_badrule.py:33: Syntax error. Expected ':'\n"
|
|
143 "yacc_badrule.py:42: Syntax error. Expected ':'\n"
|
|
144 ))
|
|
145
|
|
146 def test_yacc_badtok(self):
|
|
147 try:
|
|
148 run_import("yacc_badtok")
|
|
149 except ply.yacc.YaccError:
|
|
150 result = sys.stderr.getvalue()
|
|
151 self.assert_(check_expected(result,
|
|
152 "tokens must be a list or tuple\n"))
|
|
153
|
|
154 def test_yacc_dup(self):
|
|
155 run_import("yacc_dup")
|
|
156 result = sys.stderr.getvalue()
|
|
157 self.assert_(check_expected(result,
|
|
158 "yacc_dup.py:27: Function p_statement redefined. Previously defined on line 23\n"
|
|
159 "Token 'EQUALS' defined, but not used\n"
|
|
160 "There is 1 unused token\n"
|
|
161 "Generating LALR tables\n"
|
|
162
|
|
163 ))
|
|
164 def test_yacc_error1(self):
|
|
165 try:
|
|
166 run_import("yacc_error1")
|
|
167 except ply.yacc.YaccError:
|
|
168 result = sys.stderr.getvalue()
|
|
169 self.assert_(check_expected(result,
|
|
170 "yacc_error1.py:61: p_error() requires 1 argument\n"))
|
|
171
|
|
172 def test_yacc_error2(self):
|
|
173 try:
|
|
174 run_import("yacc_error2")
|
|
175 except ply.yacc.YaccError:
|
|
176 result = sys.stderr.getvalue()
|
|
177 self.assert_(check_expected(result,
|
|
178 "yacc_error2.py:61: p_error() requires 1 argument\n"))
|
|
179
|
|
180 def test_yacc_error3(self):
|
|
181 try:
|
|
182 run_import("yacc_error3")
|
|
183 except ply.yacc.YaccError:
|
|
184 e = sys.exc_info()[1]
|
|
185 result = sys.stderr.getvalue()
|
|
186 self.assert_(check_expected(result,
|
|
187 "'p_error' defined, but is not a function or method\n"))
|
|
188
|
|
189 def test_yacc_error4(self):
|
|
190 self.assertRaises(ply.yacc.YaccError,run_import,"yacc_error4")
|
|
191 result = sys.stderr.getvalue()
|
|
192 self.assert_(check_expected(result,
|
|
193 "yacc_error4.py:62: Illegal rule name 'error'. Already defined as a token\n"
|
|
194 ))
|
|
195
|
|
196
|
|
197 def test_yacc_error5(self):
|
|
198 run_import("yacc_error5")
|
|
199 result = sys.stdout.getvalue()
|
|
200 self.assert_(check_expected(result,
|
|
201 "Group at 3:10 to 3:12\n"
|
|
202 "Undefined name 'a'\n"
|
|
203 "Syntax error at 'b'\n"
|
|
204 "Syntax error at 4:18 to 4:22\n"
|
|
205 "Assignment Error at 2:5 to 5:27\n"
|
|
206 "13\n"
|
|
207 ))
|
|
208
|
|
209 def test_yacc_error6(self):
|
|
210 run_import("yacc_error6")
|
|
211 result = sys.stdout.getvalue()
|
|
212 self.assert_(check_expected(result,
|
|
213 "a=7\n"
|
|
214 "Line 3: Syntax error at '*'\n"
|
|
215 "c=21\n"
|
|
216 ))
|
|
217
|
|
218 def test_yacc_error7(self):
|
|
219 run_import("yacc_error7")
|
|
220 result = sys.stdout.getvalue()
|
|
221 self.assert_(check_expected(result,
|
|
222 "a=7\n"
|
|
223 "Line 3: Syntax error at '*'\n"
|
|
224 "c=21\n"
|
|
225 ))
|
|
226
|
|
227 def test_yacc_inf(self):
|
|
228 self.assertRaises(ply.yacc.YaccError,run_import,"yacc_inf")
|
|
229 result = sys.stderr.getvalue()
|
|
230 self.assert_(check_expected(result,
|
|
231 "Token 'NUMBER' defined, but not used\n"
|
|
232 "There is 1 unused token\n"
|
|
233 "Infinite recursion detected for symbol 'statement'\n"
|
|
234 "Infinite recursion detected for symbol 'expression'\n"
|
|
235 ))
|
|
236 def test_yacc_literal(self):
|
|
237 self.assertRaises(ply.yacc.YaccError,run_import,"yacc_literal")
|
|
238 result = sys.stderr.getvalue()
|
|
239 self.assert_(check_expected(result,
|
|
240 "yacc_literal.py:36: Literal token '**' in rule 'expression' may only be a single character\n"
|
|
241 ))
|
|
242 def test_yacc_misplaced(self):
|
|
243 self.assertRaises(ply.yacc.YaccError,run_import,"yacc_misplaced")
|
|
244 result = sys.stderr.getvalue()
|
|
245 self.assert_(check_expected(result,
|
|
246 "yacc_misplaced.py:32: Misplaced '|'\n"
|
|
247 ))
|
|
248
|
|
249 def test_yacc_missing1(self):
|
|
250 self.assertRaises(ply.yacc.YaccError,run_import,"yacc_missing1")
|
|
251 result = sys.stderr.getvalue()
|
|
252 self.assert_(check_expected(result,
|
|
253 "yacc_missing1.py:24: Symbol 'location' used, but not defined as a token or a rule\n"
|
|
254 ))
|
|
255
|
|
256 def test_yacc_nested(self):
|
|
257 run_import("yacc_nested")
|
|
258 result = sys.stdout.getvalue()
|
|
259 self.assert_(check_expected(result,
|
|
260 "A\n"
|
|
261 "A\n"
|
|
262 "A\n",
|
|
263 ))
|
|
264
|
|
265 def test_yacc_nodoc(self):
|
|
266 run_import("yacc_nodoc")
|
|
267 result = sys.stderr.getvalue()
|
|
268 self.assert_(check_expected(result,
|
|
269 "yacc_nodoc.py:27: No documentation string specified in function 'p_statement_expr' (ignored)\n"
|
|
270 "Generating LALR tables\n"
|
|
271 ))
|
|
272
|
|
273 def test_yacc_noerror(self):
|
|
274 run_import("yacc_noerror")
|
|
275 result = sys.stderr.getvalue()
|
|
276 self.assert_(check_expected(result,
|
|
277 "no p_error() function is defined\n"
|
|
278 "Generating LALR tables\n"
|
|
279 ))
|
|
280
|
|
281 def test_yacc_nop(self):
|
|
282 run_import("yacc_nop")
|
|
283 result = sys.stderr.getvalue()
|
|
284 self.assert_(check_expected(result,
|
|
285 "yacc_nop.py:27: Possible grammar rule 'statement_expr' defined without p_ prefix\n"
|
|
286 "Generating LALR tables\n"
|
|
287 ))
|
|
288
|
|
289 def test_yacc_notfunc(self):
|
|
290 run_import("yacc_notfunc")
|
|
291 result = sys.stderr.getvalue()
|
|
292 self.assert_(check_expected(result,
|
|
293 "'p_statement_assign' not defined as a function\n"
|
|
294 "Token 'EQUALS' defined, but not used\n"
|
|
295 "There is 1 unused token\n"
|
|
296 "Generating LALR tables\n"
|
|
297 ))
|
|
298 def test_yacc_notok(self):
|
|
299 try:
|
|
300 run_import("yacc_notok")
|
|
301 except ply.yacc.YaccError:
|
|
302 result = sys.stderr.getvalue()
|
|
303 self.assert_(check_expected(result,
|
|
304 "No token list is defined\n"))
|
|
305
|
|
306 def test_yacc_rr(self):
|
|
307 run_import("yacc_rr")
|
|
308 result = sys.stderr.getvalue()
|
|
309 self.assert_(check_expected(result,
|
|
310 "Generating LALR tables\n"
|
|
311 "1 reduce/reduce conflict\n"
|
|
312 "reduce/reduce conflict in state 15 resolved using rule (statement -> NAME EQUALS NUMBER)\n"
|
|
313 "rejected rule (expression -> NUMBER) in state 15\n"
|
|
314
|
|
315 ))
|
|
316
|
|
317 def test_yacc_rr_unused(self):
|
|
318 run_import("yacc_rr_unused")
|
|
319 result = sys.stderr.getvalue()
|
|
320 self.assert_(check_expected(result,
|
|
321 "no p_error() function is defined\n"
|
|
322 "Generating LALR tables\n"
|
|
323 "3 reduce/reduce conflicts\n"
|
|
324 "reduce/reduce conflict in state 1 resolved using rule (rule3 -> A)\n"
|
|
325 "rejected rule (rule4 -> A) in state 1\n"
|
|
326 "reduce/reduce conflict in state 1 resolved using rule (rule3 -> A)\n"
|
|
327 "rejected rule (rule5 -> A) in state 1\n"
|
|
328 "reduce/reduce conflict in state 1 resolved using rule (rule4 -> A)\n"
|
|
329 "rejected rule (rule5 -> A) in state 1\n"
|
|
330 "Rule (rule5 -> A) is never reduced\n"
|
|
331 ))
|
|
332
|
|
333 def test_yacc_simple(self):
|
|
334 run_import("yacc_simple")
|
|
335 result = sys.stderr.getvalue()
|
|
336 self.assert_(check_expected(result,
|
|
337 "Generating LALR tables\n"
|
|
338 ))
|
|
339
|
|
340 def test_yacc_sr(self):
|
|
341 run_import("yacc_sr")
|
|
342 result = sys.stderr.getvalue()
|
|
343 self.assert_(check_expected(result,
|
|
344 "Generating LALR tables\n"
|
|
345 "20 shift/reduce conflicts\n"
|
|
346 ))
|
|
347
|
|
348 def test_yacc_term1(self):
|
|
349 self.assertRaises(ply.yacc.YaccError,run_import,"yacc_term1")
|
|
350 result = sys.stderr.getvalue()
|
|
351 self.assert_(check_expected(result,
|
|
352 "yacc_term1.py:24: Illegal rule name 'NUMBER'. Already defined as a token\n"
|
|
353 ))
|
|
354
|
|
355 def test_yacc_unicode_literals(self):
|
|
356 run_import("yacc_unicode_literals")
|
|
357 result = sys.stderr.getvalue()
|
|
358 self.assert_(check_expected(result,
|
|
359 "Generating LALR tables\n"
|
|
360 ))
|
|
361
|
|
362 def test_yacc_unused(self):
|
|
363 self.assertRaises(ply.yacc.YaccError,run_import,"yacc_unused")
|
|
364 result = sys.stderr.getvalue()
|
|
365 self.assert_(check_expected(result,
|
|
366 "yacc_unused.py:62: Symbol 'COMMA' used, but not defined as a token or a rule\n"
|
|
367 "Symbol 'COMMA' is unreachable\n"
|
|
368 "Symbol 'exprlist' is unreachable\n"
|
|
369 ))
|
|
370 def test_yacc_unused_rule(self):
|
|
371 run_import("yacc_unused_rule")
|
|
372 result = sys.stderr.getvalue()
|
|
373 self.assert_(check_expected(result,
|
|
374 "yacc_unused_rule.py:62: Rule 'integer' defined, but not used\n"
|
|
375 "There is 1 unused rule\n"
|
|
376 "Symbol 'integer' is unreachable\n"
|
|
377 "Generating LALR tables\n"
|
|
378 ))
|
|
379
|
|
380 def test_yacc_uprec(self):
|
|
381 self.assertRaises(ply.yacc.YaccError,run_import,"yacc_uprec")
|
|
382 result = sys.stderr.getvalue()
|
|
383 self.assert_(check_expected(result,
|
|
384 "yacc_uprec.py:37: Nothing known about the precedence of 'UMINUS'\n"
|
|
385 ))
|
|
386
|
|
387 def test_yacc_uprec2(self):
|
|
388 self.assertRaises(ply.yacc.YaccError,run_import,"yacc_uprec2")
|
|
389 result = sys.stderr.getvalue()
|
|
390 self.assert_(check_expected(result,
|
|
391 "yacc_uprec2.py:37: Syntax error. Nothing follows %prec\n"
|
|
392 ))
|
|
393
|
|
394 def test_yacc_prec1(self):
|
|
395 self.assertRaises(ply.yacc.YaccError,run_import,"yacc_prec1")
|
|
396 result = sys.stderr.getvalue()
|
|
397 self.assert_(check_expected(result,
|
|
398 "Precedence rule 'left' defined for unknown symbol '+'\n"
|
|
399 "Precedence rule 'left' defined for unknown symbol '*'\n"
|
|
400 "Precedence rule 'left' defined for unknown symbol '-'\n"
|
|
401 "Precedence rule 'left' defined for unknown symbol '/'\n"
|
|
402 ))
|
|
403
|
|
404 def test_pkg_test1(self):
|
|
405 from pkg_test1 import parser
|
|
406 self.assertTrue(os.path.exists('pkg_test1/parsing/parsetab.py'))
|
|
407 self.assertTrue(os.path.exists('pkg_test1/parsing/lextab.py'))
|
|
408 self.assertTrue(os.path.exists('pkg_test1/parsing/parser.out'))
|
|
409 r = parser.parse('3+4+5')
|
|
410 self.assertEqual(r, 12)
|
|
411
|
|
412 def test_pkg_test2(self):
|
|
413 from pkg_test2 import parser
|
|
414 self.assertTrue(os.path.exists('pkg_test2/parsing/calcparsetab.py'))
|
|
415 self.assertTrue(os.path.exists('pkg_test2/parsing/calclextab.py'))
|
|
416 self.assertTrue(os.path.exists('pkg_test2/parsing/parser.out'))
|
|
417 r = parser.parse('3+4+5')
|
|
418 self.assertEqual(r, 12)
|
|
419
|
|
420 def test_pkg_test3(self):
|
|
421 from pkg_test3 import parser
|
|
422 self.assertTrue(os.path.exists('pkg_test3/generated/parsetab.py'))
|
|
423 self.assertTrue(os.path.exists('pkg_test3/generated/lextab.py'))
|
|
424 self.assertTrue(os.path.exists('pkg_test3/generated/parser.out'))
|
|
425 r = parser.parse('3+4+5')
|
|
426 self.assertEqual(r, 12)
|
|
427
|
|
428 def test_pkg_test4(self):
|
|
429 from pkg_test4 import parser
|
|
430 self.assertFalse(os.path.exists('pkg_test4/parsing/parsetab.py'))
|
|
431 self.assertFalse(os.path.exists('pkg_test4/parsing/lextab.py'))
|
|
432 self.assertFalse(os.path.exists('pkg_test4/parsing/parser.out'))
|
|
433 r = parser.parse('3+4+5')
|
|
434 self.assertEqual(r, 12)
|
|
435
|
|
436 def test_pkg_test5(self):
|
|
437 from pkg_test5 import parser
|
|
438 self.assertTrue(os.path.exists('pkg_test5/parsing/parsetab.py'))
|
|
439 self.assertTrue(os.path.exists('pkg_test5/parsing/lextab.py'))
|
|
440 self.assertTrue(os.path.exists('pkg_test5/parsing/parser.out'))
|
|
441 r = parser.parse('3+4+5')
|
|
442 self.assertEqual(r, 12)
|
|
443
|
|
444 def test_pkg_test6(self):
|
|
445 from pkg_test6 import parser
|
|
446 self.assertTrue(os.path.exists('pkg_test6/parsing/parsetab.py'))
|
|
447 self.assertTrue(os.path.exists('pkg_test6/parsing/lextab.py'))
|
|
448 self.assertTrue(os.path.exists('pkg_test6/parsing/parser.out'))
|
|
449 r = parser.parse('3+4+5')
|
|
450 self.assertEqual(r, 12)
|
|
451
|
|
452 unittest.main()
|