Skip to content

Commit

Permalink
Better Handling of Type Casting Functions (#283)
Browse files Browse the repository at this point in the history
* Type casting functions to classes to support type checking.

* Revert "Type casting functions to classes to support type checking."

This reverts commit 7c24395.

* Compiler based approach to Scenic friendly primitive type conversion.

* Re-added tests and type signatures to cast functions.

* Enhanced type checking tests.

* Added documentation about int/float/str overrides.

* Banned assignment to builtin type keywords.

* Updated docs.

* Added explicit compiler import.
  • Loading branch information
Eric-Vin authored Jul 10, 2024
1 parent 2d26e8d commit a34460a
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 17 deletions.
5 changes: 5 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
sphinx._buildingScenicDocs = True

from scenic.core.simulators import SimulatorInterfaceWarning
import scenic.syntax.compiler
from scenic.syntax.translator import CompileOptions
import scenic.syntax.veneer as veneer

Expand Down Expand Up @@ -185,6 +186,10 @@ def maketable(items, columns=5, gap=4):
with open("_build/keywords_soft.txt", "w") as outFile:
for row in maketable(ScenicParser.SOFT_KEYWORDS):
outFile.write(row + "\n")
with open("_build/builtin_names.txt", "w") as outFile:
for row in maketable(scenic.syntax.compiler.builtinNames):
outFile.write(row + "\n")


# -- Monkeypatch ModuleAnalyzer to handle Scenic modules ---------------------

Expand Down
7 changes: 7 additions & 0 deletions docs/reference/general.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,10 @@ To avoid confusion, we recommend not using ``distance``, ``angle``, ``offset``,

.. literalinclude:: /_build/keywords_soft.txt
:language: text

.. rubric:: Builtin Names

The following names are built into Scenic and can be used but not overwritten .

.. literalinclude:: /_build/builtin_names.txt
:language: text
18 changes: 16 additions & 2 deletions src/scenic/syntax/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def compileScenicAST(

trackedNames = {"ego", "workspace"}
globalParametersName = "globalParameters"
builtinNames = {globalParametersName}
builtinNames = {globalParametersName, "str", "int", "float"}


# shorthands for convenience
Expand Down Expand Up @@ -540,7 +540,11 @@ def visit_Name(self, node: ast.Name) -> Any:
if node.id in builtinNames:
if not isinstance(node.ctx, ast.Load):
raise self.makeSyntaxError(f'unexpected keyword "{node.id}"', node)
node = ast.copy_location(ast.Call(ast.Name(node.id, loadCtx), [], []), node)
# Convert global parameters name to a call
if node.id == globalParametersName:
node = ast.copy_location(
ast.Call(ast.Name(node.id, loadCtx), [], []), node
)
elif node.id in trackedNames:
if not isinstance(node.ctx, ast.Load):
raise self.makeSyntaxError(
Expand Down Expand Up @@ -1078,6 +1082,16 @@ def visit_Call(self, node: ast.Call) -> Any:
newArgs.append(self.visit(arg))
newKeywords = [self.visit(kwarg) for kwarg in node.keywords]
newFunc = self.visit(node.func)

# Convert primitive type conversions to their Scenic equivalents
if isinstance(newFunc, ast.Name):
if newFunc.id == "str":
newFunc.id = "_toStrScenic"
elif newFunc.id == "float":
newFunc.id = "_toFloatScenic"
elif newFunc.id == "int":
newFunc.id = "_toIntScenic"

if wrappedStar:
newNode = ast.Call(
ast.Name("callWithStarArgs", ast.Load()),
Expand Down
33 changes: 18 additions & 15 deletions src/scenic/syntax/veneer.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@
"hypot",
"max",
"min",
"_toStrScenic",
"_toFloatScenic",
"_toIntScenic",
"filter",
"str",
"float",
"int",
"round",
"len",
"range",
Expand Down Expand Up @@ -2068,32 +2068,35 @@ def helper(context):
)


### Primitive functions overriding Python builtins

# N.B. applying functools.wraps to preserve the metadata of the original
# functions seems to break pickling/unpickling


@distributionFunction
def filter(function, iterable):
return list(builtins.filter(function, iterable))
### Primitive internal functions, utilized after compiler conversion


@distributionFunction
def str(*args, **kwargs):
def _toStrScenic(*args, **kwargs) -> str:
return builtins.str(*args, **kwargs)


@distributionFunction
def float(*args, **kwargs):
def _toFloatScenic(*args, **kwargs) -> float:
return builtins.float(*args, **kwargs)


@distributionFunction
def int(*args, **kwargs):
def _toIntScenic(*args, **kwargs) -> int:
return builtins.int(*args, **kwargs)


### Primitive functions overriding Python builtins

# N.B. applying functools.wraps to preserve the metadata of the original
# functions seems to break pickling/unpickling


@distributionFunction
def filter(function, iterable):
return list(builtins.filter(function, iterable))


@distributionFunction
def round(*args, **kwargs):
return builtins.round(*args, **kwargs)
Expand Down
13 changes: 13 additions & 0 deletions tests/syntax/test_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,19 @@ def test_bad_extension(tmpdir):

### Parse errors


## Reserved names
def test_reserved_type_names():
with pytest.raises(ScenicSyntaxError):
compileScenic("float = 3")

with pytest.raises(ScenicSyntaxError):
compileScenic("int = 3")

with pytest.raises(ScenicSyntaxError):
compileScenic("str = 3")


## Constructor definitions


Expand Down
64 changes: 64 additions & 0 deletions tests/syntax/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,67 @@ def test_list_as_vector_3():
param p = distance to [-2, -2, 0, 6]
"""
)


# Builtin Type Conversion Tests
def test_isinstance_str():
p = sampleParamPFrom(
"""
from scenic.core.type_support import isA
param p = str(1)
assert isinstance(globalParameters.p, str)
assert isA(globalParameters.p, str)
"""
)
assert isinstance(p, str)

p = sampleParamPFrom(
"""
from scenic.core.type_support import isA
param p = str(Range(0,2))
assert isA(globalParameters.p, str)
"""
)
assert isinstance(p, str)


def test_isinstance_float():
p = sampleParamPFrom(
"""
from scenic.core.type_support import isA
param p = float(1)
assert isinstance(globalParameters.p, float)
assert isA(globalParameters.p, float)
"""
)
assert isinstance(p, float)

p = sampleParamPFrom(
"""
from scenic.core.type_support import isA
param p = float(Range(0,2))
assert isA(globalParameters.p, float)
"""
)
assert isinstance(p, float)


def test_isinstance_int():
p = sampleParamPFrom(
"""
from scenic.core.type_support import isA
param p = int(1.5)
assert isinstance(globalParameters.p, int)
assert isA(globalParameters.p, int)
"""
)
assert isinstance(p, int)

p = sampleParamPFrom(
"""
from scenic.core.type_support import isA
param p = int(Range(0,2))
assert isA(globalParameters.p, int)
"""
)
assert isinstance(p, int)

0 comments on commit a34460a

Please sign in to comment.