Skip to content

Commit

Permalink
Implement optional reification of ast locations via toggleable option.
Browse files Browse the repository at this point in the history
  • Loading branch information
namcsi committed Sep 24, 2023
1 parent 9c83ded commit b594464
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 11 deletions.
1 change: 1 addition & 0 deletions src/renopro/asp/ast.lp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
% tag ast facts.
ast(location(Id,position(F1,B1,E1),position(F2,B2,E2))) :- location(Id,position(F1,B1,E1),position(F2,B2,E2)).
ast(string(Id,Val)) :- string(Id,Val).
ast(number(Id,Val)) :- number(Id,Val).
ast(variable(Id,Name)) :- variable(Id,Name).
Expand Down
22 changes: 19 additions & 3 deletions src/renopro/predicates.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,9 +314,6 @@ def id_body(ns):
kwds={"name": underscore_lower_cls_name},
exec_body=id_body,
)
# The unary and non_unary attributes are created by the metaclass,
# and are only defined in AstPredicate and UnaryAstPredicate
# to supply type hints
cls = super().__new__(
mcs, cls_name, bases, namespace, name=underscore_lower_cls_name, **kwargs
)
Expand All @@ -329,6 +326,23 @@ class AstPredicate(Predicate, metaclass=_AstPredicateMeta):
"""A predicate representing an AST node."""


class Position(ComplexTerm):
"""Complex field representing a position in a text file."""

filename = StringField
line = IntegerField
column = IntegerField


class Location(Predicate):
"""Predicate linking an AST identifier to the range in a text
file from where it was reified."""

id = IdentifierField
begin = Position.Field
end = Position.Field


class String(AstPredicate):
"""Predicate representing a string term.
Expand Down Expand Up @@ -1264,6 +1278,7 @@ class TheoryDefinition(AstPredicate):


AstPred = Union[
Location,
String,
Number,
Variable,
Expand Down Expand Up @@ -1324,6 +1339,7 @@ class TheoryDefinition(AstPredicate):
]

AstPreds = [
Location,
String,
Number,
Variable,
Expand Down
31 changes: 25 additions & 6 deletions src/renopro/rast.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,13 +131,13 @@ class ReifiedAST:
representation of ASP programs.
"""

def __init__(self, parse_theory_atoms: bool = False):
def __init__(self, reify_location: bool = False):
self._reified = FactBase()
self._program_ast: Sequence[AST] = []
self._current_statement: Tuple[int, int] = (0, 0)
self._tuple_pos: count = count()
self._init_overrides()
self.parse_theory_atoms = parse_theory_atoms
self.reify_location = reify_location

def add_reified_facts(self, reified_facts: Iterator[preds.AstPred]) -> None:
"""Add iterator of reified AST facts to internal factbase."""
Expand Down Expand Up @@ -234,7 +234,9 @@ def _init_overrides(self):
"node": {
# we don't include this node in our reified representation
# as it doesn't add much from a knowledge representation standpoint.
ASTType.SymbolicTerm: lambda node: self.reify_node(node.symbol),
ASTType.SymbolicTerm: lambda node: self.reify_node(
node.symbol, location=node.location
),
ASTType.Id: self._reify_id,
ASTType.Function: self._reify_function,
},
Expand Down Expand Up @@ -385,6 +387,15 @@ def _get_type_constructor_from_node(
return symbol_type, symbol_constructor
raise TypeError(f"Node must be of type AST or Symbol, got: {type(node)}")

def _reify_location(self, id_term, location: ast.Location):
begin = preds.Position(
location.begin.filename, location.begin.line, location.begin.column
)
end = preds.Position(
location.end.filename, location.end.line, location.end.column
)
self._reified.add(preds.Location(id_term.id, begin, end))

def _reify_attr(self, annotation: Type, attr: NodeAttr, field: BaseField):
"""Reify an AST node's attribute attr based on the type hint
for the respective argument in the AST node's constructor.
Expand All @@ -404,21 +415,26 @@ def _reify_attr(self, annotation: Type, attr: NodeAttr, field: BaseField):
return attr
raise RuntimeError("Code should be unreachable.") # nocoverage

def reify_node(self, node: Union[AST, Symbol], predicate=None, id_term=None):
def reify_node(
self, node: Union[AST, Symbol], predicate=None, id_term=None, location=None
):
"""Reify the input ast node by adding it's clorm fact
representation to the internal fact base, and recursively
reify child nodes.
"""
node_type, node_constructor = self._get_type_constructor_from_node(node)

if node_override_func := self._reify_overrides["node"].get(node_type):
return node_override_func(node)

annotations = node_constructor.__annotations__
predicate = getattr(preds, node_type.name) if predicate is None else predicate
id_term = predicate.unary() if id_term is None else id_term
kwargs_dict = {"id": id_term.id}
if self.reify_location:
if location := (
getattr(node, "location", None) if location is None else location
):
self._reify_location(id_term, location)

for key in predicate.meta.keys():
# the id field has no corresponding attribute in nodes to be reified
Expand All @@ -443,6 +459,7 @@ def reify_node(self, node: Union[AST, Symbol], predicate=None, id_term=None):
field = getattr(predicate, key).meta.field
reified_attr = self._reify_attr(annotation, attr, field)
kwargs_dict.update({key: reified_attr})

reified_fact = predicate(**kwargs_dict)
self._reified.add(reified_fact)
if predicate is preds.Program:
Expand Down Expand Up @@ -505,6 +522,8 @@ def _reify_function(self, node):
arguments=self._reify_ast_sequence(node.arguments, preds.Terms),
)
)
if self.reify_location:
self._reify_location(func1, node.location)
return func1

def _reify_id(self, node):
Expand Down
1 change: 1 addition & 0 deletions tests/asp/reify_reflect/reflected/location.lp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
a(1,b,"c",D).
24 changes: 24 additions & 0 deletions tests/asp/reify_reflect/reified/location.lp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
program(0,"base",constants(1),statements(2)).
location(0,position("<string>",1,1),position("<string>",1,1)).
statements(2,0,rule(3)).
rule(3,literal(4),body_literals(13)).
location(3,position("<string>",1,1),position("<string>",1,14)).
literal(4,"pos",symbolic_atom(5)).
location(4,position("<string>",1,1),position("<string>",1,13)).
symbolic_atom(5,function(6)).
function(6,a,terms(7)).
location(6,position("<string>",1,1),position("<string>",1,13)).
terms(7,0,number(8)).
number(8,1).
location(8,position("<string>",1,3),position("<string>",1,4)).
terms(7,1,function(9)).
function(9,b,terms(10)).
location(9,position("<string>",1,5),position("<string>",1,6)).
terms(7,2,string(11)).
string(11,"c").
location(11,position("<string>",1,7),position("<string>",1,10)).
terms(7,3,variable(12)).
variable(12,"D").
location(12,position("<string>",1,11),position("<string>",1,12)).


10 changes: 8 additions & 2 deletions tests/test_reify_reflect.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,15 @@ class TestReifyReflect(TestReifiedAST):

base_str = "#program base.\n"

def assertReifyEqual(self, file_name: str): # pylint: disable=invalid-name
def assertReifyEqual(
self, file_name: str, reify_location: bool = False
): # pylint: disable=invalid-name
"""Assert that reification of file_name under reflected_files
results in file_name under reified_files."""
reified_file = reified_files / file_name
reflected_file = reflected_files / file_name
reflected_str = reflected_file.read_text().strip()
rast1 = ReifiedAST()
rast1 = ReifiedAST(reify_location=reify_location)
rast1.reify_string(reflected_str)
reified_facts = rast1.reified_facts
rast2 = ReifiedAST()
Expand Down Expand Up @@ -146,6 +148,10 @@ class TestReifyReflectNormalPrograms(TestReifyReflect):
"""Test cases for reification and reflection of disjunctive logic
program AST nodes."""

def test_rast_location(self):
"Test reification of AST node locations"
self.assertReifyEqual("location.lp", reify_location=True)

def test_rast_prop_fact(self):
"Test reification and reflection of a propositional fact."
self.assertReifyReflectEqual("prop_fact.lp")
Expand Down

0 comments on commit b594464

Please sign in to comment.