Skip to content

Commit

Permalink
Requirement Parsing Fixes (#299)
Browse files Browse the repository at this point in the history
* Added deep boolean operator tests.

* Moved requirement atomic checks to separate transformer.
  • Loading branch information
Eric-Vin authored Aug 14, 2024
1 parent 797efdd commit 50eed8e
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 16 deletions.
30 changes: 14 additions & 16 deletions src/scenic/syntax/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,11 +236,20 @@ def makeSyntaxError(self, msg, node: ast.AST) -> ScenicParseError:
}


class AtomicCheckTransformer(Transformer):
def visit_Call(self, node: ast.Call):
func = node.func
if isinstance(func, ast.Name) and func.id in TEMPORAL_PREFIX_OPS:
self.makeSyntaxError(
f'malformed use of the "{func.id}" temporal operator', node
)
return self.generic_visit(node)


class PropositionTransformer(Transformer):
def __init__(self, filename="<unknown>") -> None:
super().__init__(filename)
self.nextSyntaxId = 0
self.inAtomic = False

def transform(
self, node: ast.AST, nextSyntaxId=0
Expand All @@ -262,12 +271,9 @@ def transform(
return newNode, self.nextSyntaxId

def generic_visit(self, node):
# Don't recurse inside atomics.
old_inAtomic = self.inAtomic
self.inAtomic = True
super_val = super().generic_visit(node)
self.inAtomic = old_inAtomic
return super_val
acv = AtomicCheckTransformer(self.filename)
acv.visit(node)
return node

def _register_requirement_syntax(self, syntax):
"""register requirement syntax for later use
Expand Down Expand Up @@ -346,7 +352,7 @@ def visit_BoolOp(self, node: ast.BoolOp) -> ast.AST:

def visit_UnaryOp(self, node):
# rewrite `not` in requirements into a proposition factory
if not isinstance(node.op, ast.Not) or self.inAtomic:
if not isinstance(node.op, ast.Not):
return self.generic_visit(node)

lineNum = ast.Constant(node.lineno)
Expand All @@ -367,14 +373,6 @@ def visit_UnaryOp(self, node):
)
return ast.copy_location(newNode, node)

def visit_Call(self, node: ast.Call):
func = node.func
if isinstance(func, ast.Name) and func.id in TEMPORAL_PREFIX_OPS:
self.makeSyntaxError(
f'malformed use of the "{func.id}" temporal operator', node
)
return self.generic_visit(node)

def visit_Always(self, node: s.Always):
value = self.visit(node.value)
if not self.is_proposition_factory(value):
Expand Down
30 changes: 30 additions & 0 deletions tests/syntax/test_requirements.py
Original file line number Diff line number Diff line change
Expand Up @@ -508,3 +508,33 @@ def test_deep_not():
require all(not o.x > 0 for o in objs)
"""
)


def test_deep_and():
with pytest.raises(RejectionException):
sampleSceneFrom(
"""
objs = [new Object at 10@10, new Object at 20@20]
require all(o.x > 0 and o.x < 0 for o in objs)
"""
)


def test_deep_or():
with pytest.raises(RejectionException):
sampleSceneFrom(
"""
objs = [new Object at 10@10, new Object at 20@20]
require all(o.x < 0 or o.x < -1 for o in objs)
"""
)


def test_temporal_in_atomic():
with pytest.raises(ScenicSyntaxError):
sampleSceneFrom(
"""
objs = [new Object at 10@10, new Object at 20@20]
require all(eventually(o.x > 0) for o in objs)
"""
)

0 comments on commit 50eed8e

Please sign in to comment.