Skip to content

Commit c5c4a84

Browse files
committed
SMTLIB: Add Row, Column info to parser
These debug information can be disabled by running python with -O flag
1 parent fc467e2 commit c5c4a84

File tree

4 files changed

+104
-35
lines changed

4 files changed

+104
-35
lines changed

SMT-LIB/parse_all.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ def main():
5656
exit(1)
5757

5858
random.seed(42)
59-
6059
p = multiprocessing.Pool()
6160
chunks = multiprocessing.cpu_count()
6261
file_list = list(get_all_smt_files())

pysmt/exceptions.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,16 @@ class PysmtTypeError(PysmtException, TypeError):
134134
pass
135135

136136
class PysmtSyntaxError(PysmtException, SyntaxError):
137-
pass
137+
def __init__(self, message, pos_info=None):
138+
super(PysmtSyntaxError, self).__init__(message)
139+
self.pos_info = pos_info
140+
141+
def __str__(self):
142+
if self.pos_info:
143+
return "Line %d, Col %d: " % self.pos_info + self.message
144+
else:
145+
return self.message
146+
138147

139148
class PysmtIOError(PysmtException, IOError):
140149
pass

pysmt/smtlib/parser.py

Lines changed: 81 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -157,9 +157,18 @@ class Tokenizer(object):
157157
def __init__(self, handle, interactive=False):
158158
if not interactive:
159159
# reads char-by-char
160-
self.reader = itertools.chain.from_iterable(handle)
160+
if __debug__:
161+
self.reader = self.char_iterator(handle)
162+
self.__col_cnt = 0
163+
self.__row_cnt = 0
164+
else:
165+
self.reader = itertools.chain.from_iterable(handle)
166+
self.__col_cnt = None
167+
self.__row_cnt = None
161168
else:
162169
self.reader = interactive_char_iterator(handle)
170+
self.__col_cnt = None
171+
self.__row_cnt = None
163172
self.generator = self.create_generator(self.reader)
164173
self.extra_queue = []
165174
self.consume = self.consume_token
@@ -181,6 +190,12 @@ def consume_token(self):
181190
def raw_read(self):
182191
return next(self.reader)
183192

193+
@property
194+
def pos_info(self):
195+
if self.__row_cnt is not None:
196+
return (self.__row_cnt, self.__col_cnt)
197+
return None
198+
184199
@staticmethod
185200
def create_generator(reader):
186201
"""Takes a file-like object and produces a stream of tokens following
@@ -210,13 +225,14 @@ def create_generator(reader):
210225
c = next(reader)
211226
if c != "|" and c != "\\":
212227
# Only \| and \\ are supported escapings
213-
raise PysmtSyntaxError("Unknown escaping in " \
214-
"quoted symbol: "
215-
"'\\%s'" % c)
228+
raise PysmtSyntaxError(
229+
"Unknown escaping in quoted symbol: "
230+
"'\\%s'" % c, reader.pos_info)
216231
s.append(c)
217232
c = next(reader)
218233
if not c:
219-
raise PysmtSyntaxError("Expected '|'")
234+
raise PysmtSyntaxError("Expected '|'",
235+
reader.pos_info)
220236
yield "".join(s)
221237
c = next(reader)
222238

@@ -236,7 +252,8 @@ def create_generator(reader):
236252
s.append(c)
237253
c = next(reader)
238254
if not c:
239-
raise PysmtSyntaxError("Expected '|'")
255+
raise PysmtSyntaxError("Expected '|'",
256+
reader.pos_info)
240257
yield '"%s"' % ("".join(s)) # string literals maintain their quoting
241258

242259
else:
@@ -259,6 +276,20 @@ def create_generator(reader):
259276
c = next(reader)
260277
yield "".join(tk)
261278

279+
def char_iterator(self, handle):
280+
c = handle.read(1)
281+
while c:
282+
if c == "\n":
283+
self.__row_cnt += 1
284+
self.__col_cnt = 0
285+
elif c == "\r":
286+
pass
287+
else:
288+
self.__col_cnt += 1
289+
yield c
290+
c = handle.read(1)
291+
292+
262293
# EOC Tokenizer
263294

264295

@@ -449,7 +480,8 @@ def _enter_smtlib_as(self, stack, tokens, key):
449480
#pylint: disable=unused-argument
450481
const = self.parse_atom(tokens, "expression")
451482
if const != "const":
452-
raise PysmtSyntaxError("expected 'const' in expression after 'as'")
483+
raise PysmtSyntaxError("expected 'const' in expression after 'as'",
484+
tokens.pos_info)
453485
ty = self.parse_type(tokens, "expression")
454486

455487
def res(expr):
@@ -474,7 +506,7 @@ def _smtlib_underscore(self, stack, tokens, key):
474506
end = int(send)
475507
except ValueError:
476508
raise PysmtSyntaxError("Expected number in '_ extract' "
477-
"expression")
509+
"expression", tokens.pos_info)
478510
fun = lambda x : mgr.BVExtract(x, start, end)
479511

480512
elif op == "zero_extend":
@@ -483,7 +515,7 @@ def _smtlib_underscore(self, stack, tokens, key):
483515
width = int(swidth)
484516
except ValueError:
485517
raise PysmtSyntaxError("Expected number in '_ zero_extend' "
486-
"expression")
518+
"expression", tokens.pos_info)
487519
fun = lambda x: mgr.BVZExt(x, width)
488520

489521
elif op == "repeat":
@@ -492,7 +524,7 @@ def _smtlib_underscore(self, stack, tokens, key):
492524
count = int(scount)
493525
except ValueError:
494526
raise PysmtSyntaxError("Expected number in '_ repeat' "
495-
"expression")
527+
"expression", tokens.pos_info)
496528
fun = lambda x: mgr.BVRepeat(x, count)
497529

498530
elif op == "rotate_left":
@@ -501,7 +533,7 @@ def _smtlib_underscore(self, stack, tokens, key):
501533
step = int(sstep)
502534
except ValueError:
503535
raise PysmtSyntaxError("Expected number in '_ rotate_left' "
504-
"expression")
536+
"expression", tokens.pos_info)
505537
fun = lambda x: mgr.BVRol(x, step)
506538

507539
elif op == "rotate_right":
@@ -510,7 +542,7 @@ def _smtlib_underscore(self, stack, tokens, key):
510542
step = int(sstep)
511543
except ValueError:
512544
raise PysmtSyntaxError("Expected number in '_ rotate_left' "
513-
"expression")
545+
"expression", tokens.pos_info)
514546
fun = lambda x: mgr.BVRor(x, step)
515547

516548
elif op == "sign_extend":
@@ -519,7 +551,7 @@ def _smtlib_underscore(self, stack, tokens, key):
519551
width = int(swidth)
520552
except ValueError:
521553
raise PysmtSyntaxError("Expected number in '(_ sign_extend) "
522-
"expression'")
554+
"expression'", tokens.pos_info)
523555
fun = lambda x: mgr.BVSExt(x, width)
524556

525557
elif op.startswith("bv"):
@@ -528,11 +560,12 @@ def _smtlib_underscore(self, stack, tokens, key):
528560
width = int(self.parse_atom(tokens, "expression"))
529561
except ValueError:
530562
raise PysmtSyntaxError("Expected number in '_ bv' expression: "
531-
"'%s'" % op)
563+
"'%s'" % op, tokens.pos_info)
532564
fun = mgr.BV(v, width)
533565

534566
else:
535-
raise PysmtSyntaxError("Unexpected '_' expression '%s'" % op)
567+
raise PysmtSyntaxError("Unexpected '_' expression '%s'" % op,
568+
tokens.pos_info)
536569

537570
stack[-1].append(lambda : fun)
538571

@@ -640,7 +673,8 @@ def _enter_let(self, stack, tokens, key):
640673
self.consume_opening(tokens, "expression")
641674
while current != ")":
642675
if current != "(":
643-
raise PysmtSyntaxError("Expected '(' in let binding")
676+
raise PysmtSyntaxError("Expected '(' in let binding",
677+
tokens.pos_info)
644678
vname = self.parse_atom(tokens, "expression")
645679
expr = self.get_expression(tokens)
646680
newvals[vname] = expr
@@ -669,7 +703,7 @@ def _enter_quantifier(self, stack, tokens, key):
669703
self.consume_opening(tokens, "expression")
670704
while current != ")":
671705
if current != "(":
672-
raise PysmtSyntaxError("Expected '(' in let binding")
706+
raise PysmtSyntaxError("Expected '(' in let binding", tokens.pos_info)
673707
vname = self.parse_atom(tokens, "expression")
674708
typename = self.parse_type(tokens, "expression")
675709

@@ -700,7 +734,8 @@ def _enter_annotation(self, stack, tokens, key):
700734
while tk != ")":
701735
if not tk.startswith(":"):
702736
raise PysmtSyntaxError("Annotations keyword should start with"
703-
" colon! Offending token: '%s'" % tk)
737+
" colon! Offending token: '%s'" % tk,
738+
tokens.pos_info)
704739
keyword = tk[1:]
705740
tk = tokens.consume()
706741
value = None
@@ -752,7 +787,8 @@ def get_expression(self, tokens):
752787
lst = stack.pop()
753788
fun = lst.pop(0)
754789
except IndexError:
755-
raise PysmtSyntaxError("Unexpected ')'")
790+
raise PysmtSyntaxError("Unexpected ')'",
791+
tokens.pos_info)
756792

757793
try:
758794
res = fun(*lst)
@@ -816,10 +852,12 @@ def parse_atoms(self, tokens, command, min_size, max_size=None):
816852
if current == ")":
817853
raise PysmtSyntaxError("Expected at least %d arguments in "
818854
"%s command." %\
819-
(min_size, command))
855+
(min_size, command),
856+
tokens.pos_info)
820857
if current == "(":
821858
raise PysmtSyntaxError("Unexpected token '(' in %s "
822-
"command." % command)
859+
"command." % command,
860+
tokens.pos_info)
823861
res.append(current)
824862

825863
for _ in xrange(min_size, max_size + 1):
@@ -828,11 +866,13 @@ def parse_atoms(self, tokens, command, min_size, max_size=None):
828866
return res
829867
if current == "(":
830868
raise PysmtSyntaxError("Unexpected token '(' in %s "
831-
"command." % command)
869+
"command." % command,
870+
tokens.pos_info)
832871
res.append(current)
833872
raise PysmtSyntaxError("Unexpected token '%s' in %s command. Expected " \
834-
"at most %d arguments." % (current, command,
835-
max_size))
873+
"at most %d arguments." %\
874+
(current, command, max_size),
875+
tokens.pos_info)
836876

837877
def parse_type(self, tokens, command, type_params=None, additional_token=None):
838878
"""Parses a single type name from the tokens"""
@@ -857,15 +897,17 @@ def parse_type(self, tokens, command, type_params=None, additional_token=None):
857897
ts = tokens.consume()
858898
if ts != "BitVec":
859899
raise PysmtSyntaxError("Unexpected token '%s' in %s command." % \
860-
(ts, command))
900+
(ts, command),
901+
tokens.pos_info)
861902

862903
size = 0
863904
dim = tokens.consume()
864905
try:
865906
size = int(dim)
866907
except ValueError:
867908
raise PysmtSyntaxError("Unexpected token '%s' in %s command." % \
868-
(dim, command))
909+
(dim, command),
910+
tokens.pos_info)
869911

870912
self.consume_closing(tokens, command)
871913
res = self.env.type_manager.BVType(size)
@@ -874,7 +916,8 @@ def parse_type(self, tokens, command, type_params=None, additional_token=None):
874916
base_type = self.cache.get(op)
875917
if base_type is None or not isinstance(base_type, _TypeDecl):
876918
raise PysmtSyntaxError("Unexpected token '%s' in %s command." % \
877-
(op, command))
919+
(op, command),
920+
tokens.pos_info)
878921
pparams = []
879922
has_free_params = False
880923
for _ in range(base_type.arity):
@@ -907,7 +950,8 @@ def definition(*args):
907950
res = self.cache.get(var)
908951
else:
909952
raise PysmtSyntaxError("Unexpected token '%s' in %s command." % \
910-
(var, command))
953+
(var, command),
954+
tokens.pos_info)
911955

912956
if isinstance(res, _TypeDecl):
913957
return res()
@@ -920,7 +964,8 @@ def parse_atom(self, tokens, command):
920964
var = tokens.consume()
921965
if var == "(" or var == ")":
922966
raise PysmtSyntaxError("Unexpected token '%s' in %s command." % \
923-
(var, command))
967+
(var, command),
968+
tokens.pos_info)
924969
return var
925970

926971
def parse_params(self, tokens, command):
@@ -962,14 +1007,16 @@ def consume_opening(self, tokens, command):
9621007
p = tokens.consume()
9631008
if p != "(":
9641009
raise PysmtSyntaxError("Unexpected token '%s' in %s command. " \
965-
"Expected '('" % (p, command))
1010+
"Expected '('" %
1011+
(p, command), tokens.pos_info)
9661012

9671013
def consume_closing(self, tokens, command):
9681014
""" Consumes a single ')' """
9691015
p = tokens.consume()
9701016
if p != ")":
9711017
raise PysmtSyntaxError("Unexpected token '%s' in %s command. " \
972-
"Expected ')'" % (p, command))
1018+
"Expected ')'" %
1019+
(p, command, tokens.pos_info))
9731020

9741021
def _function_call_helper(self, v, *args):
9751022
""" Helper function for dealing with function calls """
@@ -988,7 +1035,7 @@ def get_assignment_list(self, script):
9881035
current = tokens.consume()
9891036
while current != ")":
9901037
if current != "(":
991-
raise PysmtSyntaxError("'(' expected")
1038+
raise PysmtSyntaxError("'(' expected", tokens.pos_info)
9921039
vname = self.get_expression(tokens)
9931040
expr = self.get_expression(tokens)
9941041
self.consume_closing(tokens, current)
@@ -1125,7 +1172,8 @@ def _cmd_declare_sort(self, current, tokens):
11251172
try:
11261173
type_ = self.env.type_manager.Type(typename, int(arity))
11271174
except ValueError:
1128-
raise PysmtSyntaxError("Expected an integer as arity of type %s" % typename)
1175+
raise PysmtSyntaxError("Expected an integer as arity of type %s."%\
1176+
typename, tokens.pos_info)
11291177
self.cache.bind(typename, type_)
11301178
return SmtLibCommand(current, [type_])
11311179

pysmt/test/test_regressions.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,19 @@ def test_z3_conversion_ite(self):
449449
solver.add_assertion(f)
450450
self.assertTrue(solver.solve())
451451

452+
def test_parse_exception(self):
453+
from pysmt.exceptions import PysmtSyntaxError
454+
smtlib_input = "(declare-const x x x Int)" +\
455+
"(check-sat)"
456+
parser = SmtLibParser()
457+
buffer_ = cStringIO(smtlib_input)
458+
try:
459+
parser.get_script(buffer_)
460+
self.assertFalse(True)
461+
except PysmtSyntaxError as ex:
462+
self.assertEqual(ex.pos_info[0], 0)
463+
self.assertEqual(ex.pos_info[1], 19)
464+
452465

453466
if __name__ == "__main__":
454467
main()

0 commit comments

Comments
 (0)