Skip to content

Commit

Permalink
Handle #show 3. and #show "text". in ASP codes.
Browse files Browse the repository at this point in the history
This required a change in grammar, parsing, and answer set treatment.
Testing proves that it is at least working in most obvious cases.
  • Loading branch information
Aluriak committed Oct 30, 2019
1 parent 3137577 commit f92248e
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 12 deletions.
9 changes: 5 additions & 4 deletions README.mkd
Original file line number Diff line number Diff line change
Expand Up @@ -291,10 +291,11 @@ the official module can be used by clyngor if it is available.
- 0.4.0 (todo)
- see [further ideas](#Further-ideas)
- 0.3.24
- when using clingo module, the models contains only the output atoms, not everything
- access to the answer set number with `.with_answer_number`
- parsing and string reproduction of nested atoms such as `a((a("g(2,3)",(2)),))` is now correctly handled and tested
- fix the `models.command` output when clingo module is used
- `#show 3.` and `#show "hello !".` are [now handled](eb2002dc906d9fc32efd50738fc2c67100e1cd2)
- when using clingo module, the models contains only the output atoms, [not everything](31375774c437403e8a05f5fe8d0346caba0f43e4) (thank you Arnaud)
- [access to](cc60217975de123a5ef0d083fb10971e0d89c03e) the answer set number with `.with_answer_number`
- [parsing and string reproduction](c0c090c34a7028ba34c49815f0197c67c76e7bfb) of nested atoms such as `a((a("g(2,3)",(2)),))` is now correctly handled and tested
- [fix](1840c36e3f57c926a565fef7352cd1b083194e58) the `models.command` output when clingo module is used
- 0.3.20
- fix #7
- improve testing cover, fix warning in recent versions of pytest
Expand Down
8 changes: 5 additions & 3 deletions clyngor/answers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
def naive_parsing_of_answer_set(answer_set:str, *, discard_quotes:bool=False, parse_int:bool=True, parse_args:bool=True) -> [(str, tuple)]:
"""Yield (pred, args), naively parsed from given answer set encoded as clingo output string"""
# print('NAIVE_PARSING_OF_ANSWER_SET:', answer_set, f'\t discard_quotes={discard_quotes}, parse_int={parse_int}, parse_args={parse_args}')
REG_ANSWER_SET = re.compile(r'([a-z][a-zA-Z0-9_]*)(\([^)]+\))?')
REG_ANSWER_SET = re.compile(r'([a-z][a-zA-Z0-9_]*|[0-9]+|"[^"]*")(\([^)]+\))?')

for match in REG_ANSWER_SET.finditer(answer_set):
pred, args = match.groups()
Expand All @@ -27,6 +27,7 @@ def naive_parsing_of_answer_set(answer_set:str, *, discard_quotes:bool=False, pa
) if parse_args else args
elif args: # args should not be parsed
args = args
pred = int(pred) if pred.isnumeric() else pred # handle
yield pred, args or ()
# print('\t>', pred, args)

Expand Down Expand Up @@ -234,7 +235,7 @@ def _parse_answer(self, answer_set:str) -> iter:
elif isinstance(answer_set, str): # the good ol' split
# print('THE GOOD OLD SPLIT:', f"discard_quotes={self._discard_quotes} collapse_atoms={self._collapse_atoms}")
yield from self.__finish_parsing(naive_parsing_of_answer_set(answer_set, discard_quotes=self._discard_quotes and not self._collapse_atoms, parse_int=self._parse_int, parse_args=True or self._collapse_args or self._first_arg_only))
elif isinstance(answer_set, (set, tuple)) and all(isinstance(atom, tuple) for atom in answer_set): # already parsed
elif isinstance(answer_set, (set, tuple)) and all(isinstance(atom, (str, int, tuple)) for atom in answer_set): # already parsed
# print('FROM SET OR TUPLE')
yield from self.__finish_parsing(answer_set)
else: # unknown format
Expand Down Expand Up @@ -316,11 +317,12 @@ def __init__(self, solver, statistics:callable=(lambda: {})):
self._statistics = lambda s=solver: s.statistics
assert callable(self._statistics)


def __compute_answers(self):
kwargs = {'yield_': True, 'async': True} # compat with 3.7
with self._solver.solve(**kwargs) as models:
for model in models:
answer_set = set((a.name, utils.clingo_value_to_python(a.arguments))
answer_set = set(utils.clingo_symbol_as_python_value(a)
for a in model.symbols(shown=True))
yield answer_set, model.cost, model.optimality_proven, model.number

Expand Down
9 changes: 5 additions & 4 deletions clyngor/parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ def visit_args(self, node, children):
def visit_single_value(self, node, children):
return self.visit_subterm(node, children)
def visit_one_uplet(self, node, children):
return self.visit_term(node, ('', children))
return self.visit_atom(node, ('', children))
def visit_n_uplet(self, node, children):
return self.visit_term(node, ('', children))
return self.visit_atom(node, ('', children))

def visit_text(self, node, children):
text = tuple(children)[0] if children else ''
Expand All @@ -54,7 +54,7 @@ def visit_subterm(self, node, children):
else:
return (predicate, tuple(args[0])) if args else predicate

def visit_term(self, node, children):
def visit_atom(self, node, children):
predicate, *args = children

if self.first_arg_only and args:
Expand All @@ -81,7 +81,8 @@ def single_value(): return '(', subterm, ')'
def one_uplet(): return '(', subterm, ',', ')'
def n_uplet(): return '(', subterm, ap.OneOrMore(',', subterm), ')'
# NB: litteral outputed by #show are not handled.
def term(): return ident, ap.Optional("(", args, ")")
def atom(): return ident, ap.Optional("(", args, ")")
def term(): return [litteral, atom]
def terms(): return ap.ZeroOrMore(term)
return terms

Expand Down
24 changes: 24 additions & 0 deletions clyngor/test/test_parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,17 @@ def test_string():
assert args == ('","',)


def test_single_litteral():
"""Show that string with comma in it is handled correctly"""
parsed = Parser().parse_clasp_output(OUTCLASP_SINGLE_LITERAL.splitlines())
type, answer_number = next(parsed)
assert type == 'answer_number'
type, model = next(parsed)
assert next(parsed, None) is None, "there is only one model"
assert type == 'answer', "the model is an answer"
assert len(model) == 2, "only 2 atom in it"
assert model == {3, '"hello !"'}

def test_complex_atoms():
parsed = Parser().parse_clasp_output(OUTCLASP_COMPLEX_ATOMS.splitlines())
type, answer_number = next(parsed)
Expand Down Expand Up @@ -263,6 +274,19 @@ def test_multithread_with_progression():
CPU Time : 0.000s
"""

OUTCLASP_SINGLE_LITERAL = """clasp version 3.2.0
Reading from test.lp
Solving...
Answer: 1
3 "hello !"
SATISFIABLE
Models : 1
Calls : 1
Time : 0.001s (Solving: 0.00s 1st Model: 0.00s Unsat: 0.00s)
CPU Time : 0.001s
"""

OUTCLASP_STRING = """clasp version 3.2.0
Reading from test.lp
Solving...
Expand Down
11 changes: 11 additions & 0 deletions clyngor/test/test_solving.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,15 @@ def test_constants(asp_code_with_constants):
assert next(iter(answer)) == (2,)


def test_restricted_and_literal_outputs():
asp_code = 'a. link(a). #show link/1. #show 3. #show "hello !".'
answers = tuple(solve([], inline=asp_code).by_predicate)
assert len(answers) == 1
answer = answers[0]
print(answers)
assert len(answer) == 3
assert set(answer) == {'link', 3, '"hello !"'}
assert answer == {'link': {('a',)}, 3: {()}, '"hello !"': {()}}


# TODO: test solving.command
18 changes: 17 additions & 1 deletion clyngor/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ def is_quoted(arg:str) -> bool:

def clingo_value_to_python(value:object) -> int or str or tuple:
"""Convert a clingo.Symbol object to the python equivalent"""
if isinstance(value, (int, str)):
if str(type(value)) == 'clingo.Symbol':
return clingo_symbol_as_python_value(value)
elif isinstance(value, (int, str)):
return value
elif isinstance(value, (tuple, list)):
return tuple(map(clingo_value_to_python, value))
Expand Down Expand Up @@ -112,6 +114,20 @@ def clingo_value_to_python(value:object) -> int or str or tuple:
"".format(value, type(value)))


def clingo_symbol_as_python_value(term) -> object:
"Convert a clingo.Symbol object to the python equivalent"
if str(term.type) == 'Function':
assert term.name is not None
return (term.name, clingo_value_to_python(term.arguments))
elif str(term.type) == 'String':
assert term.name is None
return ('"' + term.string + '"', ())
elif str(term.type) == 'Number':
assert term.name is None
return (term.number, ())
raise TypeError("Can't handle clingo.Symbol like {} of type {}."
"".format(value, type(value)))

def python_value_to_asp(val:str or int or list or tuple, *, args_of_predicate:bool=False) -> str or tuple:
"""Convert given python value in an ASP format"""
if isinstance(val, (str, int)):
Expand Down

0 comments on commit f92248e

Please sign in to comment.