Skip to content

Commit 9254b5d

Browse files
authored
Merge pull request #1 from sbrodehl/master
Multiple run() invocations.
2 parents 6a25af6 + d0457ab commit 9254b5d

File tree

3 files changed

+90
-29
lines changed

3 files changed

+90
-29
lines changed

src/bison/__init__.py

Lines changed: 66 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,12 @@
2323
import traceback
2424

2525
from bison_ import ParserEngine
26+
from os import makedirs
27+
2628
from .node import BisonNode
2729
from .convert import bisonToPython
2830

31+
2932
class BisonSyntaxError(Exception):
3033
def __init__(self, msg, args=[]):
3134
super(BisonSyntaxError, self).__init__(msg)
@@ -34,6 +37,7 @@ def __init__(self, msg, args=[]):
3437
self.first_line, self.first_col, self.last_line, self.last_col, \
3538
self.message, self.token_value = args
3639

40+
3741
class TimeoutError(Exception):
3842
pass
3943

@@ -94,11 +98,14 @@ class BisonParser(object):
9498
# Default to sys.stdin.
9599
file = None
96100

101+
# Create a marker for input parsing.
102+
marker = 0
103+
97104
# Last parsed target, top of parse tree.
98105
last = None
99106

100107
# Enable this to keep all temporary engine build files.
101-
keepfiles = 0
108+
keepfiles = 1
102109

103110
# Prefix of the shared object / dll file. Defaults to 'modulename-engine'.
104111
# If the module is executed directly, "__main__" will be used (since that
@@ -128,6 +135,9 @@ def __init__(self, **kw):
128135
- defaultNodeClass - the class to use for creating parse nodes, default
129136
is self.defaultNodeClass (in this base class, BisonNode)
130137
"""
138+
self.buildDirectory = './pybison-' + type(self).__name__ + '/'
139+
makedirs(self.buildDirectory, exist_ok=True)
140+
131141
# setup
132142
read = kw.get('read', None)
133143
if read:
@@ -195,7 +205,7 @@ def _handle(self, targetname, option, names, values):
195205
# % (targetname, repr(self.last)))
196206
else:
197207
if self.verbose:
198-
print ('no handler for %s, using default' % targetname)
208+
print('no handler for %s, using default' % targetname)
199209

200210
cls = self.default_node_class
201211
self.last = cls(target=targetname, option=option, names=names,
@@ -208,6 +218,7 @@ def handle_timeout(self, signum, frame):
208218
raise TimeoutError('Computation exceeded timeout limit.')
209219

210220
def reset(self):
221+
self.marker = 0
211222
self.engine.reset()
212223

213224
def run(self, **kw):
@@ -223,11 +234,13 @@ def run(self, **kw):
223234
print('Parser.run: calling engine')
224235

225236
# grab keywords
237+
i_opened_a_file = False
226238
fileobj = kw.get('file', self.file)
227239
if isinstance(fileobj, str):
228240
filename = fileobj
229241
try:
230242
fileobj = open(fileobj, 'rb')
243+
i_opened_a_file = True
231244
except:
232245
raise Exception('Cannot open input file "%s"' % fileobj)
233246
else:
@@ -248,15 +261,15 @@ def run(self, **kw):
248261
if read:
249262
self.read = read
250263

251-
if self.verbose and self.file.closed:
252-
print('Parser.run(): self.file', self.file, 'is closed')
264+
if self.verbose and self.marker:
265+
print('Parser.run(): self.marker (', self.marker, ') is set')
253266

254267
error_count = 0
268+
self.last = None
255269

256270
# TODO: add option to fail on first error.
257-
while not self.file.closed:
271+
while not self.marker:
258272
# do the parsing job, spew if error
259-
self.last = None
260273
self.engine.reset()
261274

262275
try:
@@ -275,7 +288,7 @@ def run(self, **kw):
275288
if hasattr(self, 'hook_run'):
276289
self.last = self.hook_run(filename, self.last)
277290

278-
if self.verbose and not self.file.closed:
291+
if self.verbose and not self.marker:
279292
print('last:', self.last)
280293

281294
if self.verbose:
@@ -284,10 +297,15 @@ def run(self, **kw):
284297
# restore old values
285298
self.file = oldfile
286299
self.read = oldread
300+
self.marker = 0
287301

288302
if self.verbose:
289303
print('------------------ result=', self.last)
290304

305+
# close the file if we opened one
306+
if i_opened_a_file and fileobj:
307+
fileobj.close()
308+
291309
# TODO: return last result (see while loop):
292310
# return self.last[:-1]
293311
return self.last
@@ -305,13 +323,13 @@ def read(self, nbytes):
305323
if self.verbose:
306324
print('Parser.read: want %s bytes' % nbytes)
307325

308-
bytes = self.file.readline(nbytes)
326+
_bytes = self.file.readline(nbytes)
309327

310328
if self.verbose:
311-
print('Parser.read: got %s bytes' % len(bytes))
312-
print(bytes)
329+
print('Parser.read: got %s bytes' % len(_bytes))
330+
print(_bytes)
313331

314-
return bytes
332+
return _bytes
315333

316334
def report_last_error(self, filename, error):
317335
"""
@@ -328,22 +346,22 @@ def report_last_error(self, filename, error):
328346
329347
"""
330348

331-
#if filename != None:
332-
# msg = '%s:%d: "%s" near "%s"' \
333-
# % ((filename,) + error)
349+
# if filename != None:
350+
# msg = '%s:%d: "%s" near "%s"' \
351+
# % ((filename,) + error)
334352

335-
# if not self.interactive:
336-
# raise BisonSyntaxError(msg)
353+
# if not self.interactive:
354+
# raise BisonSyntaxError(msg)
337355

338-
# print >>sys.stderr, msg
339-
#elif hasattr(error, '__getitem__') and isinstance(error[0], int):
340-
# msg = 'Line %d: "%s" near "%s"' % error
356+
# print >>sys.stderr, msg
357+
# elif hasattr(error, '__getitem__') and isinstance(error[0], int):
358+
# msg = 'Line %d: "%s" near "%s"' % error
341359

342-
# if not self.interactive:
343-
# raise BisonSyntaxError(msg)
360+
# if not self.interactive:
361+
# raise BisonSyntaxError(msg)
344362

345-
# print >>sys.stderr, msg
346-
#else:
363+
# print >>sys.stderr, msg
364+
# else:
347365
if not self.interactive:
348366
raise
349367

@@ -352,8 +370,30 @@ def report_last_error(self, filename, error):
352370

353371
print('ERROR:', error)
354372

355-
def report_syntax_error(self, msg, yytext, first_line, first_col,
356-
last_line, last_col):
373+
def report_syntax_error(self, msg, yytext, first_line, first_col, last_line, last_col):
374+
375+
def color_white(txt):
376+
return "\033[39m" + txt
377+
378+
def color_red(txt):
379+
return "\033[31m" + txt
380+
381+
def color_blue(txt):
382+
return "\033[34m" + txt
383+
384+
def make_bold(txt):
385+
return "\033[1m" + txt
386+
387+
def reset_style(txt):
388+
return "\033[0m" + txt
389+
357390
yytext = yytext.replace('\n', '\\n')
358391
args = (msg, yytext, first_line, first_col, last_line, last_col)
359-
raise BisonSyntaxError('\033[1m\033[31mError:\033[0m %s \n\t \033[34m└ near \033[1m"%s"\033[0m\033[34m (see \033[39m\033[1mline %d, pos %d to line %d, pos %d\033[0m\033[34m).\033[0m' % args, args)
392+
err_msg = ''.join([
393+
color_red(make_bold("Error: ")), reset_style("%s"), "\n", "\t ",
394+
color_blue("└ near "), make_bold('"%s"'),
395+
reset_style(color_blue(" (see ")),
396+
color_white(make_bold("line %d, pos %d to line %d, pos %d")),
397+
reset_style(color_blue(").")), reset_style('')
398+
])
399+
raise BisonSyntaxError(err_msg % args, list(args))

src/c/bison_callback.c

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ static PyObject *py_attr_hook_read_before_name;
4545
static PyObject *py_attr_handle_name;
4646
static PyObject *py_attr_read_name;
4747
static PyObject *py_attr_file_name;
48+
static PyObject *py_attr_input_marker;
4849
static PyObject *py_attr_close_name;
4950

5051
// Construct attribute names (only the first time)
@@ -154,6 +155,7 @@ void py_input(PyObject *parser, char *buf, int *result, int max_size)
154155
INIT_ATTR(py_attr_hook_read_before_name, "hook_read_before", return);
155156
INIT_ATTR(py_attr_read_name, "read", return);
156157
INIT_ATTR(py_attr_file_name, "file", return);
158+
INIT_ATTR(py_attr_input_marker, "marker", return);
157159
INIT_ATTR(py_attr_close_name, "close", return);
158160

159161
// Check if the "hook_READ_BEFORE" callback exists
@@ -232,16 +234,35 @@ void py_input(PyObject *parser, char *buf, int *result, int max_size)
232234
// associated C stream (which is not necessary here, otherwise use
233235
// "os.close(0)").
234236
if (!*result && PyObject_HasAttr(parser, py_attr_file_name)) {
237+
// don't mark the file as closed
238+
// set a marker that there is no more input
239+
PyObject *marker_handle = PyObject_GetAttr(parser, py_attr_input_marker);
240+
if (unlikely(!marker_handle)) return;
241+
// create a int object containing a '1'
242+
PyObject* po_long1 = PyLong_FromLong(1);
243+
// execute attribute setting
244+
int success = PyObject_SetAttr(parser, py_attr_input_marker, po_long1);
245+
if (success != 0)
246+
return;
247+
Py_DECREF(marker_handle);
248+
Py_DECREF(po_long1);
249+
return;
250+
251+
// get the 'file' attribute from the parser
235252
PyObject *file_handle = PyObject_GetAttr(parser, py_attr_file_name);
253+
// check if BisonParser['file'] was set
236254
if (unlikely(!file_handle)) return;
237-
255+
// get the handle for file.close operation
238256
handle = PyObject_GetAttr(file_handle, py_attr_close_name);
257+
// delete file attribute pointer
239258
Py_DECREF(file_handle);
259+
// check of BisonParser['file'] has handle 'close'
240260
if (unlikely(!handle)) return;
241261

262+
// get argument to pass to 'close' operation
242263
arglist = PyTuple_New(0);
243264
if (unlikely(!arglist)) { Py_DECREF(handle); return; }
244-
265+
// actually execute BisonParser['file'].close(0)
245266
res = PyObject_CallObject(handle, arglist);
246267

247268
Py_XDECREF(res);

src/pyrex/bison_.pyx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,6 @@ cdef class ParserEngine:
202202

203203
def generate_exception_handler(self):
204204
s = ''
205-
206205
s += ' {\n'
207206
s += ' PyObject* obj = PyErr_Occurred();\n'
208207
s += ' if (obj) {\n'
@@ -466,6 +465,7 @@ cdef class ParserEngine:
466465
f.write('\n'.join(tmp) + '\n')
467466
f.close()
468467

468+
# TODO: WTF is this?
469469
# create and set up a compiler object
470470
if sys.platform == 'win32':
471471
env = distutils.ccompiler.new_compiler(verbose=parser.verbose)

0 commit comments

Comments
 (0)