From a98ca713dff279a47cf322f9fd0fa7c633736bd4 Mon Sep 17 00:00:00 2001 From: Amade Nemes Date: Fri, 14 Jun 2024 12:36:04 +0200 Subject: [PATCH] Implement reification/reflection of Comment AST. Minor bugfixes. --- src/renopro/__main__.py | 5 +- src/renopro/asp/ast.lp | 1 + src/renopro/asp/ast_fact2id.lp | 3 + src/renopro/asp/defined.lp | 1 + src/renopro/enum_fields.py | 14 +- src/renopro/predicates.py | 39 +++-- src/renopro/rast.py | 24 ++- src/renopro/utils/__init__.py | 1 + src/renopro/utils/codegen.py | 39 +++-- .../reify_reflect/reflected/comment_block.lp | 2 + .../reify_reflect/reflected/comment_line.lp | 1 + .../reify_reflect/reified/comment_block.lp | 3 + .../asp/reify_reflect/reified/comment_line.lp | 3 + .../meta-telingo/inputs/telingo-input.lp | 1 - .../meta-telingo/outputs/telingo-output.lp | 15 ++ .../outputs/telingo-parsed-term.lp | 19 +++ tests/test_main.py | 1 + tests/test_preds.py | 1 + tests/test_reify_reflect.py | 20 ++- tests/test_transform.py | 144 +++++++++++------- tmp.lp | 38 ----- 21 files changed, 240 insertions(+), 135 deletions(-) create mode 100644 tests/asp/reify_reflect/reflected/comment_block.lp create mode 100644 tests/asp/reify_reflect/reflected/comment_line.lp create mode 100644 tests/asp/reify_reflect/reified/comment_block.lp create mode 100644 tests/asp/reify_reflect/reified/comment_line.lp delete mode 100644 tmp.lp diff --git a/src/renopro/__main__.py b/src/renopro/__main__.py index ece200f..665a202 100644 --- a/src/renopro/__main__.py +++ b/src/renopro/__main__.py @@ -1,6 +1,7 @@ """ The main entry point for the application. """ + from renopro.rast import ReifiedAST from .utils.logger import setup_logger @@ -35,9 +36,7 @@ def main(): elif args.input_format == "reflected": rast.reify_files(args.infiles) for meta_encoding in args.meta_encodings: - rast.transform( - meta_files=meta_encoding, clingo_options=args.clingo_options - ) + rast.transform(meta_files=meta_encoding, clingo_options=args.clingo_options) if args.output_format == "reified": print(rast.reified_string) elif args.output_format == "reflected": diff --git a/src/renopro/asp/ast.lp b/src/renopro/asp/ast.lp index 9752b6d..13ee568 100644 --- a/src/renopro/asp/ast.lp +++ b/src/renopro/asp/ast.lp @@ -57,5 +57,6 @@ ast(fact(theory_term_definitions(X0,X1,X2,X3))) :- theory_term_definitions(X0,X1 ast(fact(theory_guard_definition(X0,X1,X2))) :- theory_guard_definition(X0,X1,X2). ast(fact(theory_atom_definitions(X0,X1,X2,X3,X4,X5,X6))) :- theory_atom_definitions(X0,X1,X2,X3,X4,X5,X6). ast(fact(theory_definition(X0,X1,X2,X3))) :- theory_definition(X0,X1,X2,X3). +ast(fact(comment(X0,X1,X2))) :- comment(X0,X1,X2). ast(fact(location(X0,X1,X2))) :- location(X0,X1,X2). ast(fact(child(X0,X1))) :- child(X0,X1). diff --git a/src/renopro/asp/ast_fact2id.lp b/src/renopro/asp/ast_fact2id.lp index b8e69d6..f663041 100644 --- a/src/renopro/asp/ast_fact2id.lp +++ b/src/renopro/asp/ast_fact2id.lp @@ -171,4 +171,7 @@ ast_fact2id(theory_atom_definitions(X0,X1,X2,X3,X4,X5,X6),theory_atom_definition ast_fact2id(theory_definition(X0,X1,X2,X3),theory_definition(X0)) :- ast(fact(theory_definition(X0,X1,X2,X3));add(theory_definition(X0,X1,X2,X3));delete(theory_definition(X0,X1,X2,X3))). +ast_fact2id(comment(X0,X1,X2),comment(X0)) + :- ast(fact(comment(X0,X1,X2));add(comment(X0,X1,X2));delete(comment(X0,X1,X2))). + ast_fact2id(location(Id,Begin,End),Id) :- ast(fact(location(Id,Begin,End));add(location(Id,Begin,End));delete(location(Id,Begin,End))). diff --git a/src/renopro/asp/defined.lp b/src/renopro/asp/defined.lp index 9afd574..4d89816 100644 --- a/src/renopro/asp/defined.lp +++ b/src/renopro/asp/defined.lp @@ -57,5 +57,6 @@ #defined theory_guard_definition/3. #defined theory_atom_definitions/7. #defined theory_definition/4. +#defined comment/3. #defined location/3. #defined child/2. diff --git a/src/renopro/enum_fields.py b/src/renopro/enum_fields.py index be39081..441d12b 100644 --- a/src/renopro/enum_fields.py +++ b/src/renopro/enum_fields.py @@ -2,8 +2,9 @@ import enum import inspect from types import new_class -from typing import Type, TypeVar, Any +from typing import Any, Type, TypeVar +from clingo.ast import CommentType from clorm import BaseField, ConstantField, StringField @@ -218,3 +219,14 @@ class TheoryAtomType(str, enum.Enum): TheoryAtomTypeField = define_enum_field( parent_field=ConstantField, enum_class=TheoryAtomType, name="TheoryAtomTypeField" ) + + +class CommentType(str, enum.Enum): + "String enum of clingo's comment types." + Line = "line" + Block = "block" + + +CommentTypeField = define_enum_field( + parent_field=StringField, enum_class=CommentType, name="CommentTypeField" +) diff --git a/src/renopro/predicates.py b/src/renopro/predicates.py index a157853..dac8e2f 100644 --- a/src/renopro/predicates.py +++ b/src/renopro/predicates.py @@ -4,8 +4,9 @@ import re from itertools import count from types import new_class -from typing import TYPE_CHECKING, Any, Protocol, Sequence, Type, Union, cast +from typing import TYPE_CHECKING, Any, Protocol, Sequence, Type, Union +import clingo from clingo import Symbol from clorm import ( BaseField, @@ -22,6 +23,7 @@ from renopro.enum_fields import ( AggregateFunctionField, BinaryOperatorField, + CommentTypeField, ComparisonOperatorField, SignField, TheoryAtomTypeField, @@ -84,7 +86,7 @@ def body(ns: dict[str, Any]) -> None: # by default we use integer identifiers, but allow arbitrary symbols as well # for flexibility when these are user generated IntegerOrRawField = combine_fields([IntegerField, RawField], name="IntegerOrRawField") -IntegerOrRawField = IntegerOrRawField(default=lambda: next(id_count)) # type: ignore +IntegerOrRawField = IntegerOrRawField(default_factory=lambda: clingo.Number(next(id_count))) # type: ignore # Metaclass shenanigans to dynamically create unary versions of AST predicates, # which are used to identify child AST facts @@ -127,8 +129,7 @@ class IdentifierPredicate(Predicate): class IdentifierPredicateConstructor(Protocol): - def __call__(self, id: Any = ..., /) -> IdentifierPredicate: - ... + def __call__(self, id: Any = ..., /) -> IdentifierPredicate: ... class AstPredicate(Predicate, metaclass=_AstPredicateMeta): @@ -263,7 +264,6 @@ class ExternalFunction(AstPredicate): class Pool(AstPredicate): - """Predicate representing a pool of terms. id: Identifier of the pool. @@ -454,7 +454,9 @@ class BooleanConstant(AstPredicate): # have a pool argument. -FunctionOrPoolField = combine_fields([Function.unary.Field, Pool.unary.Field], name="FunctionOrPoolField") +FunctionOrPoolField = combine_fields( + [Function.unary.Field, Pool.unary.Field], name="FunctionOrPoolField" +) class SymbolicAtom(AstPredicate): @@ -1071,6 +1073,18 @@ class TheoryDefinition(AstPredicate): atoms = TheoryAtomDefinitions.unary.Field +class Comment(AstPredicate): + """Predicate representing a comment statement. + + id: The identifier of the comment statement. + value: The string value the comment comprises of. + comment_type: The type of the comment, "block" or "line".""" + + id = IntegerOrRawField + value = StringField + comment_type = CommentTypeField + + StatementField.fields.extend( [ External.unary.Field, @@ -1079,6 +1093,7 @@ class TheoryDefinition(AstPredicate): ProjectAtom.unary.Field, ProjectSignature.unary.Field, TheoryDefinition.unary.Field, + Comment.unary.Field, ] ) @@ -1140,11 +1155,14 @@ class TheoryDefinition(AstPredicate): TheoryTermDefinitions, TheoryGuardDefinition, TheoryAtomDefinitions, - TheoryDefinition + TheoryDefinition, + Comment, ) -AstIdentifierTermField = combine_fields([pred.unary.Field for pred in AstPreds], name="AstIdentifierTermField") +AstIdentifierTermField = combine_fields( + [pred.unary.Field for pred in AstPreds], name="AstIdentifierTermField" +) class Position(ComplexTerm): @@ -1171,6 +1189,7 @@ class Child(Predicate): parent = AstIdentifierTermField child = AstIdentifierTermField + AstPreds = AstPreds + (Location, Child) AstPred = Union[ @@ -1230,9 +1249,10 @@ class Child(Predicate): TheoryTermDefinitions, TheoryGuardDefinition, TheoryAtomDefinitions, + Comment, TheoryDefinition, Location, - Child + Child, ] @@ -1250,6 +1270,7 @@ class Child(Predicate): ProjectAtom, ProjectSignature, TheoryDefinition, + Comment, ) FlattenedTuples = ( diff --git a/src/renopro/rast.py b/src/renopro/rast.py index 7f471e3..75d60d4 100644 --- a/src/renopro/rast.py +++ b/src/renopro/rast.py @@ -13,15 +13,15 @@ Callable, Dict, Iterator, + List, Literal, Optional, Sequence, Tuple, Type, Union, - overload, cast, - List, + overload, ) from clingo import Control, ast, symbol @@ -167,7 +167,10 @@ class ReifiedAST: def __init__(self) -> None: self._reified = FactBase() self._program_ast: List[AST] = [] - self._current_statement: Tuple[Optional[preds.IdentifierPredicate], int] = (None, 0) + self._current_statement: Tuple[Optional[preds.IdentifierPredicate], int] = ( + None, + 0, + ) self._tuple_pos: Iterator[int] = count() self._init_overrides() self._parent_id_term: Optional[preds.IdentifierPredicate] = None @@ -771,6 +774,13 @@ def reflect_fact(self, fact: preds.AstPred) -> AST: # nocoverage elif clorm_enum := getattr(field, "enum", None): ast_enum = getattr(ast, clorm_enum.__name__) ast_enum_member = convert_enum(field_val, ast_enum) + # temporary fix due to bug in clingo ast CommentType + # https://github.com/potassco/clingo/issues/506 + if ast_enum == ast.CommentType: + if ast_enum_member == ast.CommentType.Block: + ast_enum_member = 1 + elif ast_enum_member == ast.CommentType.Line: + ast_enum_member = 0 kwargs_dict.update({key: ast_enum_member}) elif child_type in [str, int]: kwargs_dict.update({key: field_val}) @@ -883,9 +893,11 @@ def transform( ) level = log_lvl_str2int[log_lvl_symb.string] log_strings = [ - location_symb2str(s) - if s.match("location", 3) - else str(s).strip('"') + ( + location_symb2str(s) + if s.match("location", 3) + else str(s).strip('"') + ) for s in symb.arguments[2:] ] msg_str = msg_format_str.format(*log_strings) diff --git a/src/renopro/utils/__init__.py b/src/renopro/utils/__init__.py index b0d5729..0599843 100644 --- a/src/renopro/utils/__init__.py +++ b/src/renopro/utils/__init__.py @@ -1,6 +1,7 @@ """ Utilities. """ + from typing import NoReturn diff --git a/src/renopro/utils/codegen.py b/src/renopro/utils/codegen.py index 11cb023..a2cb99a 100644 --- a/src/renopro/utils/codegen.py +++ b/src/renopro/utils/codegen.py @@ -1,10 +1,20 @@ # nocoverage from pathlib import Path +from typing import Any import renopro.predicates as preds -def generate_replace(): +def is_child_field(field: Any) -> bool: + "Check if field identifies a child predicate." + # Only complex or combined fields which are not IntegerOrRawField + # identify child predicates + return field.complex is not None or ( + hasattr(field, "fields") and not field is preds.IntegerOrRawField + ) + + +def generate_replace() -> None: "Generate replacement rules from predicates." program = ( "% replace(A,B) replaces a child predicate identifier A\n" @@ -24,7 +34,7 @@ def generate_replace(): # we only care about replacing and combined # fields - these are the terms that identify a child # predicate - if field.complex is not None or hasattr(field, "fields"): + if is_child_field(field): replacements.append(idx) for idx in replacements: old_args = ",".join( @@ -39,10 +49,10 @@ def generate_replace(): f"ast(_replace_id(A,B)), ast(fact({name}({old_args}));add({name}({old_args}))).\n\n" ) program += "ast(add(child(X0,B));delete(child(X0,A)))\n :- ast(_replace_id(A,B)), ast(fact(child(X0,A));add(child(X0,A)))." - Path("src", "renopro", "asp", "replace_id.lp").write_text(program) + Path("src", "renopro", "asp", "replace_id.lp").write_text(program, encoding="utf-8") -def generate_add_child(): +def generate_add_child() -> None: """Generate rules to create child relations for all facts added via ast add, and rules to replace identifiers via ast replace.""" @@ -57,9 +67,7 @@ def generate_add_child(): if key == "id": continue field = getattr(predicate, key).meta.field - # we only care about complex adn combined fields - these - # are the terms that identify a child predicate - if field.complex is not None or hasattr(field, "fields"): + if is_child_field(field): child_arg_indices.append(idx) for idx in child_arg_indices: add_child_args = ",".join( @@ -69,10 +77,12 @@ def generate_add_child(): f"ast(add(child({name}(X0),Child)))\n :- " f"ast(add({name}({add_child_args}))).\n\n" ) - Path("src", "renopro", "asp", "add-children.lp").write_text(add_child_program) + Path("src", "renopro", "asp", "add-children.lp").write_text( + add_child_program, encoding="utf-8" + ) -def generate_ast_fact2id(): +def generate_ast_fact2id() -> None: "Generate rules mapping AST facts to their identifiers" program = "% map ast facts to their identifiers.\n\n" for predicate in preds.AstPreds: @@ -88,10 +98,11 @@ def generate_ast_fact2id(): fact = f"{name}({args})" identifier = f"{name}(X0)" program += f"ast_fact2id({fact},{identifier})\n :- ast(fact({fact});add({fact});delete({fact})).\n\n" - Path("src", "renopro", "asp", "ast_fact2id.lp").write_text(program) + path = Path("src", "renopro", "asp", "ast_fact2id.lp") + path.write_text(program, encoding="utf-8") -def generate_ast(): +def generate_ast() -> None: "Generate rules to tag AST facts." program = "% Rules to tag AST facts.\n\n" for predicate in preds.AstPreds: @@ -101,10 +112,10 @@ def generate_ast(): fact = f"{name}({args})" rule = f"ast(fact({fact})) :- {fact}.\n" program += rule - Path("src", "renopro", "asp", "ast.lp").write_text(program) + Path("src", "renopro", "asp", "ast.lp").write_text(program, encoding="utf-8") -def generate_defined(): +def generate_defined() -> None: "Generate defined statements for AST facts." program = "% Defined statements for AST facts.\n\n" for predicate in preds.AstPreds: @@ -112,7 +123,7 @@ def generate_defined(): arity = predicate.meta.arity statement = f"#defined {name}/{arity}.\n" program += statement - Path("src", "renopro", "asp", "defined.lp").write_text(program) + Path("src", "renopro", "asp", "defined.lp").write_text(program, encoding="utf-8") if __name__ == "__main__": diff --git a/tests/asp/reify_reflect/reflected/comment_block.lp b/tests/asp/reify_reflect/reflected/comment_block.lp new file mode 100644 index 0000000..aa11335 --- /dev/null +++ b/tests/asp/reify_reflect/reflected/comment_block.lp @@ -0,0 +1,2 @@ +%* This is a +block comment *% diff --git a/tests/asp/reify_reflect/reflected/comment_line.lp b/tests/asp/reify_reflect/reflected/comment_line.lp new file mode 100644 index 0000000..5ca07cf --- /dev/null +++ b/tests/asp/reify_reflect/reflected/comment_line.lp @@ -0,0 +1 @@ +% This is a line comment diff --git a/tests/asp/reify_reflect/reified/comment_block.lp b/tests/asp/reify_reflect/reified/comment_block.lp new file mode 100644 index 0000000..fdc1f24 --- /dev/null +++ b/tests/asp/reify_reflect/reified/comment_block.lp @@ -0,0 +1,3 @@ +program(0,"base",constants(1),statements(2)). +comment(3,"%* This is a\nblock comment *%","block"). +statements(2,0,comment(3)). diff --git a/tests/asp/reify_reflect/reified/comment_line.lp b/tests/asp/reify_reflect/reified/comment_line.lp new file mode 100644 index 0000000..a57caa6 --- /dev/null +++ b/tests/asp/reify_reflect/reified/comment_line.lp @@ -0,0 +1,3 @@ +program(0,"base",constants(1),statements(2)). +comment(3,"% This is a line comment","line"). +statements(2,0,comment(3)). diff --git a/tests/asp/transform/meta-telingo/inputs/telingo-input.lp b/tests/asp/transform/meta-telingo/inputs/telingo-input.lp index 6ce6bbe..0c5cf82 100644 --- a/tests/asp/transform/meta-telingo/inputs/telingo-input.lp +++ b/tests/asp/transform/meta-telingo/inputs/telingo-input.lp @@ -1,4 +1,3 @@ -% &tel{ >? fail(X) } :- &tel { shoot(X) & < unloaded(X) & > > (unloaded(X) >? < shoot(X)) } :- &tel{ initial & firearm(X) }. diff --git a/tests/asp/transform/meta-telingo/outputs/telingo-output.lp b/tests/asp/transform/meta-telingo/outputs/telingo-output.lp index 716ac6c..c9d6d92 100644 --- a/tests/asp/transform/meta-telingo/outputs/telingo-output.lp +++ b/tests/asp/transform/meta-telingo/outputs/telingo-output.lp @@ -1,3 +1,18 @@ +% previous +% weak previous +% eventually- +% always- +% initially +% next +% weak next +% eventually+ +% always+ +% release +% until +% trigger +% since +% and +% or #theory tel { formula { < : 5, unary; diff --git a/tests/asp/transform/theory-parsing/outputs/telingo-parsed-term.lp b/tests/asp/transform/theory-parsing/outputs/telingo-parsed-term.lp index 8b76f04..0e32cc1 100644 --- a/tests/asp/transform/theory-parsing/outputs/telingo-parsed-term.lp +++ b/tests/asp/transform/theory-parsing/outputs/telingo-parsed-term.lp @@ -1,3 +1,18 @@ +% previous +% weak previous +% eventually- +% always- +% initially +% next +% weak next +% eventually+ +% always+ +% release +% until +% trigger +% since +% and +% or #theory tel { formula { < : 5, unary; @@ -20,3 +35,7 @@ }. #program base. &tel { >?(fail(X)) } :- &tel { &(shoot(X),<(&( : 5, unary; - >: : 5, unary; - >? : 5, unary; - >* : 5, unary; - >* : 4, binary, left; - >? : 4, binary, left; - <* : 4, binary, left; -