Commit e8ce08a9 authored by Alexandre Duret-Lutz's avatar Alexandre Duret-Lutz
Browse files

python: better way to extend existing classes

* wrap/python/spot.py: Use a decorator to extend classes.
* wrap/python/tests/formulas.ipynb: Adjust expected help text.
parent 1f0258e9
...@@ -23,6 +23,21 @@ import sys ...@@ -23,6 +23,21 @@ import sys
from functools import lru_cache from functools import lru_cache
def _extend(*classes):
"""
Decorator that extends all the given classes with the contents
of the class currently being defined.
"""
def wrap(this):
for cls in classes:
for (name, val) in this.__dict__.items():
if name not in ('__dict__', '__weakref__') \
and not (name == '__doc__' and val is None):
setattr(cls, name, val)
return classes[0]
return wrap
def setup(**kwargs): def setup(**kwargs):
"""Configure Spot for fancy display. """Configure Spot for fancy display.
...@@ -56,6 +71,7 @@ def setup(**kwargs): ...@@ -56,6 +71,7 @@ def setup(**kwargs):
d = 'rf({})'.format(kwargs.get('font', 'Lato')) + bullets d = 'rf({})'.format(kwargs.get('font', 'Lato')) + bullets
os.environ['SPOT_DOTDEFAULT'] = d os.environ['SPOT_DOTDEFAULT'] = d
# In version 3.0.2, Swig puts strongly typed enum in the main # In version 3.0.2, Swig puts strongly typed enum in the main
# namespace without prefixing them. Latter versions fix this. So we # namespace without prefixing them. Latter versions fix this. So we
# can remove for following hack once 3.0.2 is no longer used in our # can remove for following hack once 3.0.2 is no longer used in our
...@@ -92,201 +108,184 @@ def _ostream_to_svg(ostr): ...@@ -92,201 +108,184 @@ def _ostream_to_svg(ostr):
return _str_to_svg(ostr.str().encode('utf-8')) return _str_to_svg(ostr.str().encode('utf-8'))
def _render_automaton_as_svg(a, opt=None): @_extend(twa, ta)
ostr = ostringstream() class twa:
print_dot(ostr, a, opt) def _repr_svg_(self, opt=None):
return _ostream_to_svg(ostr) """Output the automaton as SVG"""
ostr = ostringstream()
print_dot(ostr, self, opt)
twa._repr_svg_ = _render_automaton_as_svg return _ostream_to_svg(ostr)
ta._repr_svg_ = _render_automaton_as_svg
def show(self, opt=None):
"""Display the automaton as SVG, in the IPython/Jupyter notebook"""
def _render_formula_as_svg(a): # Load the SVG function only if we need it. This way the
# Load the SVG function only if we need it. This way the bindings # bindings can still be used outside of IPython if IPython is
# can still be used outside of IPython if IPython is not # not installed.
# installed. from IPython.display import SVG
from IPython.display import SVG return SVG(self._repr_svg_(opt))
ostr = ostringstream()
print_dot_psl(ostr, a)
return SVG(_ostream_to_svg(ostr)) @_extend(twa)
class twa:
def to_str(a, format='hoa', opt=None):
def _return_automaton_as_svg(a, opt=None): format = format.lower()
# Load the SVG function only if we need it. This way the bindings if format == 'hoa':
# can still be used outside of IPython if IPython is not ostr = ostringstream()
# installed. print_hoa(ostr, a, opt)
from IPython.display import SVG return ostr.str()
return SVG(_render_automaton_as_svg(a, opt)) if format == 'dot':
ostr = ostringstream()
print_dot(ostr, a, opt)
twa.show = _return_automaton_as_svg return ostr.str()
ta.show = _return_automaton_as_svg if format == 'spin':
ostr = ostringstream()
print_never_claim(ostr, a, opt)
def _formula_str_ctor(self, str): return ostr.str()
self.this = parse_formula(str) if format == 'lbtt':
ostr = ostringstream()
print_lbtt(ostr, a, opt)
def _formula_to_str(self, format='spot', parenth=False): return ostr.str()
if format == 'spot' or format == 'f':
return str_psl(self, parenth)
elif format == 'spin' or format == 's':
return str_spin_ltl(self, parenth)
elif format == 'utf8' or format == '8':
return str_utf8_psl(self, parenth)
elif format == 'lbt' or format == 'l':
return str_lbt_ltl(self)
elif format == 'wring' or format == 'w':
return str_wring_ltl(self)
elif format == 'latex' or format == 'x':
return str_latex_psl(self, parenth)
elif format == 'sclatex' or format == 'X':
return str_sclatex_psl(self, parenth)
else:
raise ValueError("unknown string format: " + format) raise ValueError("unknown string format: " + format)
def save(a, filename, format='hoa', opt=None, append=False):
def _formula_format(self, spec): with open(filename, 'a' if append else 'w') as f:
"""Format the formula according to `spec`. s = a.to_str(format, opt)
f.write(s)
Parameters if s[-1] != '\n':
---------- f.write('\n')
spec : str, optional return a
a list of letters that specify how the formula
should be formatted.
@_extend(formula)
Supported specifiers class formula:
-------------------- def __init__(self, str):
"""Parse the given string to create a formula."""
- 'f': use Spot's syntax (default) self.this = parse_formula(str)
- '8': use Spot's syntax in UTF-8 mode
- 's': use Spin's syntax def show_ast(self):
- 'l': use LBT's syntax """Display the syntax tree of the formula."""
- 'w': use Wring's syntax # Load the SVG function only if we need it. This way the bindings
- 'x': use LaTeX output # can still be used outside of IPython if IPython is not
- 'X': use self-contained LaTeX output # installed.
from IPython.display import SVG
Add some of those letters for additional options:
- 'p': use full parentheses
- 'c': escape the formula for CSV output (this will
enclose the formula in double quotes, and escape
any included double quotes)
- 'h': escape the formula for HTML output
- 'd': escape double quotes and backslash,
for use in C-strings (the outermost double
quotes are *not* added)
- 'q': quote and escape for shell output, using single
quotes or double quotes depending on the contents.
- ':spec': pass the remaining specification to the
formating function for strings.
"""
syntax = 'f'
parent = False
escape = None
while spec:
c, spec = spec[0], spec[1:]
if c in ('f', 's', '8', 'l', 'w', 'x', 'X'):
syntax = c
elif c == 'p':
parent = True
elif c in ('c', 'd', 'h', 'q'):
escape = c
elif c == ':':
break
else:
raise ValueError("unknown format specification: " + c + spec)
s = self.to_str(syntax, parent)
if escape == 'c':
o = ostringstream()
escape_rfc4180(o, s)
s = '"' + o.str() + '"'
elif escape == 'd':
s = escape_str(s)
elif escape == 'h':
o = ostringstream()
escape_html(o, s)
s = o.str()
elif escape == 'q':
o = ostringstream()
quote_shell_string(o, s)
s = o.str()
return s.__format__(spec)
def _formula_traverse(self, func):
if func(self):
return
for f in self:
f.traverse(func)
def _formula_map(self, func):
k = self.kind()
if k in (op_ff, op_tt, op_eword, op_ap):
return self
if k in (op_Not, op_X, op_F, op_G, op_Closure,
op_NegClosure, op_NegClosureMarked):
return formula.unop(k, func(self[0]))
if k in (op_Xor, op_Implies, op_Equiv, op_U, op_R, op_W,
op_M, op_EConcat, op_EConcatMarked, op_UConcat):
return formula.binop(k, func(self[0]), func(self[1]))
if k in (op_Or, op_OrRat, op_And, op_AndRat, op_AndNLM,
op_Concat, op_Fusion):
return formula.multop(k, [func(x) for x in self])
if k in (op_Star, op_FStar):
return formula.bunop(k, func(self[0]), self.min(), self.max())
raise ValueError("unknown type of formula")
formula.__init__ = _formula_str_ctor
formula.to_str = _formula_to_str
formula.show_ast = _render_formula_as_svg
formula.traverse = _formula_traverse
formula.__format__ = _formula_format
formula.map = _formula_map
def _twa_to_str(a, format='hoa', opt=None):
format = format.lower()
if format == 'hoa':
ostr = ostringstream() ostr = ostringstream()
print_hoa(ostr, a, opt) print_dot_psl(ostr, self)
return ostr.str() return SVG(_ostream_to_svg(ostr))
if format == 'dot':
ostr = ostringstream() def to_str(self, format='spot', parenth=False):
print_dot(ostr, a, opt) if format == 'spot' or format == 'f':
return ostr.str() return str_psl(self, parenth)
if format == 'spin': elif format == 'spin' or format == 's':
ostr = ostringstream() return str_spin_ltl(self, parenth)
print_never_claim(ostr, a, opt) elif format == 'utf8' or format == '8':
return ostr.str() return str_utf8_psl(self, parenth)
if format == 'lbtt': elif format == 'lbt' or format == 'l':
ostr = ostringstream() return str_lbt_ltl(self)
print_lbtt(ostr, a, opt) elif format == 'wring' or format == 'w':
return ostr.str() return str_wring_ltl(self)
raise ValueError("unknown string format: " + format) elif format == 'latex' or format == 'x':
return str_latex_psl(self, parenth)
elif format == 'sclatex' or format == 'X':
def _twa_save(a, filename, format='hoa', opt=None, append=False): return str_sclatex_psl(self, parenth)
with open(filename, 'a' if append else 'w') as f: else:
s = a.to_str(format, opt) raise ValueError("unknown string format: " + format)
f.write(s)
if s[-1] != '\n': def __format__(self, spec):
f.write('\n') """Format the formula according to `spec`.
return a
Parameters
----------
twa.to_str = _twa_to_str spec : str, optional
twa.save = _twa_save a list of letters that specify how the formula
should be formatted.
Supported specifiers
--------------------
- 'f': use Spot's syntax (default)
- '8': use Spot's syntax in UTF-8 mode
- 's': use Spin's syntax
- 'l': use LBT's syntax
- 'w': use Wring's syntax
- 'x': use LaTeX output
- 'X': use self-contained LaTeX output
Add some of those letters for additional options:
- 'p': use full parentheses
- 'c': escape the formula for CSV output (this will
enclose the formula in double quotes, and escape
any included double quotes)
- 'h': escape the formula for HTML output
- 'd': escape double quotes and backslash,
for use in C-strings (the outermost double
quotes are *not* added)
- 'q': quote and escape for shell output, using single
quotes or double quotes depending on the contents.
- ':spec': pass the remaining specification to the
formating function for strings.
"""
syntax = 'f'
parent = False
escape = None
while spec:
c, spec = spec[0], spec[1:]
if c in ('f', 's', '8', 'l', 'w', 'x', 'X'):
syntax = c
elif c == 'p':
parent = True
elif c in ('c', 'd', 'h', 'q'):
escape = c
elif c == ':':
break
else:
raise ValueError("unknown format specification: " + c + spec)
s = self.to_str(syntax, parent)
if escape == 'c':
o = ostringstream()
escape_rfc4180(o, s)
s = '"' + o.str() + '"'
elif escape == 'd':
s = escape_str(s)
elif escape == 'h':
o = ostringstream()
escape_html(o, s)
s = o.str()
elif escape == 'q':
o = ostringstream()
quote_shell_string(o, s)
s = o.str()
return s.__format__(spec)
def traverse(self, func):
if func(self):
return
for f in self:
f.traverse(func)
def map(self, func):
k = self.kind()
if k in (op_ff, op_tt, op_eword, op_ap):
return self
if k in (op_Not, op_X, op_F, op_G, op_Closure,
op_NegClosure, op_NegClosureMarked):
return formula.unop(k, func(self[0]))
if k in (op_Xor, op_Implies, op_Equiv, op_U, op_R, op_W,
op_M, op_EConcat, op_EConcatMarked, op_UConcat):
return formula.binop(k, func(self[0]), func(self[1]))
if k in (op_Or, op_OrRat, op_And, op_AndRat, op_AndNLM,
op_Concat, op_Fusion):
return formula.multop(k, [func(x) for x in self])
if k in (op_Star, op_FStar):
return formula.bunop(k, func(self[0]), self.min(), self.max())
raise ValueError("unknown type of formula")
def automata(*filenames): def automata(*filenames):
......
...@@ -243,9 +243,9 @@ ...@@ -243,9 +243,9 @@
"output_type": "stream", "output_type": "stream",
"stream": "stdout", "stream": "stdout",
"text": [ "text": [
"Help on function _formula_format in module spot:\n", "Help on function __format__ in module spot:\n",
"\n", "\n",
"_formula_format(self, spec)\n", "__format__(self, spec)\n",
" Format the formula according to `spec`.\n", " Format the formula according to `spec`.\n",
" \n", " \n",
" Parameters\n", " Parameters\n",
...@@ -724,4 +724,4 @@ ...@@ -724,4 +724,4 @@
"metadata": {} "metadata": {}
} }
] ]
} }
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment