Skip to content

Commit

Permalink
fix: only allow call expressions on builtins for interpolation
Browse files Browse the repository at this point in the history
  • Loading branch information
vberlier committed Aug 6, 2022
1 parent 465b494 commit 0526a85
Show file tree
Hide file tree
Showing 15 changed files with 525 additions and 1 deletion.
39 changes: 38 additions & 1 deletion bolt/parse.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
__all__ = [
"get_bolt_parsers",
"get_stream_builtins",
"get_stream_identifiers",
"get_stream_pending_identifiers",
"get_stream_identifiers_storage",
Expand Down Expand Up @@ -52,6 +53,7 @@
"KeywordParser",
"LookupParser",
"PrimaryParser",
"BuiltinCallRestriction",
"parse_dict_item",
"LiteralParser",
]
Expand Down Expand Up @@ -256,7 +258,9 @@ def get_bolt_parsers(
"bolt:class_bases": parse_class_bases,
"bolt:class_root": parse_class_root,
"bolt:del_target": parse_del_target,
"bolt:interpolation": PrimaryParser(delegate("bolt:identifier"), truncate=True),
"bolt:interpolation": BuiltinCallRestriction(
PrimaryParser(delegate("bolt:identifier"), truncate=True)
),
"bolt:identifier": parse_identifier,
"bolt:with_expression": TrailingCommaParser(delegate("bolt:expression")),
"bolt:with_target": TrailingCommaParser(
Expand Down Expand Up @@ -439,6 +443,11 @@ def get_bolt_parsers(
}


def get_stream_builtins(stream: TokenStream) -> Set[str]:
"""Return the set of builtin identifiers currently associated with the token stream."""
return stream.data.setdefault("builtins", set())


def get_stream_identifiers(stream: TokenStream) -> Set[str]:
"""Return the set of accessible identifiers currently associated with the token stream."""
return stream.data.setdefault("identifiers", set())
Expand Down Expand Up @@ -498,6 +507,7 @@ def __call__(self, stream: TokenStream) -> Any:

with self.modules.parse_push(current), stream.provide(
resource_location=self.modules.database[current].resource_location,
builtins=self.modules.builtins,
identifiers=set(self.modules.globals)
| self.modules.builtins
| {"__name__"},
Expand Down Expand Up @@ -2068,6 +2078,33 @@ def __call__(self, stream: TokenStream) -> Any:
return node


@dataclass
class BuiltinCallRestriction:
"""Only allow call expressions on builtins."""

parser: Parser

def __call__(self, stream: TokenStream) -> Any:
parent = None
node = self.parser(stream)
original = node

while isinstance(node, (AstAttribute, AstLookup, AstCall)):
parent = node
node = node.value

if (
isinstance(node, AstIdentifier)
and not isinstance(parent, AstCall)
and node.value in get_stream_builtins(stream)
and node.value not in get_stream_identifiers_storage(stream)
):
msg = f'Expected call expression on builtin "{node.value}".'
raise set_location(InvalidSyntax(msg), parent)

return original


def parse_dict_item(stream: TokenStream) -> Any:
"""Parse dict item node."""
identifiers = get_stream_identifiers(stream)
Expand Down
16 changes: 16 additions & 0 deletions tests/resources/bolt_examples.mcfunction
Original file line number Diff line number Diff line change
Expand Up @@ -1019,3 +1019,19 @@ class B:
import math
###
particle minecraft:dust 0.00000 (117/255) (164/255) 0.5 ~ ~ ~ 0 0 0 0 1 force @s
###
say @s[tag=hex.summon]
###
hex = 42
say @s[tag=hex.summon]
###
say @s[tag=hex(42)]
###
time set hex.time
###
hex = 123
time set hex.time
###
time set hex(123)
###
say @s[tag=id]
34 changes: 34 additions & 0 deletions tests/snapshots/bolt__parse_274__0.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
say @s[tag=hex.summon]
---
<class 'mecha.ast.AstRoot'>
location: SourceLocation(pos=0, lineno=1, colno=1)
end_location: SourceLocation(pos=22, lineno=1, colno=23)
commands:
<class 'mecha.ast.AstCommand'>
location: SourceLocation(pos=0, lineno=1, colno=1)
end_location: SourceLocation(pos=22, lineno=1, colno=23)
identifier: 'say:message'
arguments:
<class 'mecha.ast.AstMessage'>
location: SourceLocation(pos=4, lineno=1, colno=5)
end_location: SourceLocation(pos=22, lineno=1, colno=23)
fragments:
<class 'mecha.ast.AstSelector'>
location: SourceLocation(pos=4, lineno=1, colno=5)
end_location: SourceLocation(pos=22, lineno=1, colno=23)
variable: 's'
arguments:
<class 'mecha.ast.AstSelectorArgument'>
location: SourceLocation(pos=7, lineno=1, colno=8)
end_location: SourceLocation(pos=21, lineno=1, colno=22)
inverted: False
key:
<class 'mecha.ast.AstString'>
location: SourceLocation(pos=7, lineno=1, colno=8)
end_location: SourceLocation(pos=10, lineno=1, colno=11)
value: 'tag'
value:
<class 'mecha.ast.AstWord'>
location: SourceLocation(pos=11, lineno=1, colno=12)
end_location: SourceLocation(pos=21, lineno=1, colno=22)
value: 'hex.summon'
4 changes: 4 additions & 0 deletions tests/snapshots/bolt__parse_274__1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Nothing
---
output = None
---
67 changes: 67 additions & 0 deletions tests/snapshots/bolt__parse_275__0.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
hex = 42
say @s[tag=hex.summon]
---
<class 'mecha.ast.AstRoot'>
location: SourceLocation(pos=0, lineno=1, colno=1)
end_location: SourceLocation(pos=31, lineno=2, colno=23)
commands:
<class 'mecha.ast.AstCommand'>
location: SourceLocation(pos=0, lineno=1, colno=1)
end_location: SourceLocation(pos=8, lineno=1, colno=9)
identifier: 'statement'
arguments:
<class 'bolt.ast.AstAssignment'>
location: SourceLocation(pos=0, lineno=1, colno=1)
end_location: SourceLocation(pos=8, lineno=1, colno=9)
operator: '='
target:
<class 'bolt.ast.AstTargetIdentifier'>
location: SourceLocation(pos=0, lineno=1, colno=1)
end_location: SourceLocation(pos=3, lineno=1, colno=4)
value: 'hex'
rebind: False
value:
<class 'bolt.ast.AstValue'>
location: SourceLocation(pos=6, lineno=1, colno=7)
end_location: SourceLocation(pos=8, lineno=1, colno=9)
value: 42
<class 'mecha.ast.AstCommand'>
location: SourceLocation(pos=9, lineno=2, colno=1)
end_location: SourceLocation(pos=31, lineno=2, colno=23)
identifier: 'say:message'
arguments:
<class 'mecha.ast.AstMessage'>
location: SourceLocation(pos=13, lineno=2, colno=5)
end_location: SourceLocation(pos=31, lineno=2, colno=23)
fragments:
<class 'mecha.ast.AstSelector'>
location: SourceLocation(pos=13, lineno=2, colno=5)
end_location: SourceLocation(pos=31, lineno=2, colno=23)
variable: 's'
arguments:
<class 'mecha.ast.AstSelectorArgument'>
location: SourceLocation(pos=16, lineno=2, colno=8)
end_location: SourceLocation(pos=30, lineno=2, colno=22)
inverted: False
key:
<class 'mecha.ast.AstString'>
location: SourceLocation(pos=16, lineno=2, colno=8)
end_location: SourceLocation(pos=19, lineno=2, colno=11)
value: 'tag'
value:
<class 'bolt.ast.AstInterpolation'>
location: SourceLocation(pos=20, lineno=2, colno=12)
end_location: SourceLocation(pos=30, lineno=2, colno=22)
prefix: None
unpack: None
converter: 'word'
value:
<class 'bolt.ast.AstAttribute'>
location: SourceLocation(pos=20, lineno=2, colno=12)
end_location: SourceLocation(pos=30, lineno=2, colno=22)
name: 'summon'
value:
<class 'bolt.ast.AstIdentifier'>
location: SourceLocation(pos=20, lineno=2, colno=12)
end_location: SourceLocation(pos=23, lineno=2, colno=15)
value: 'hex'
61 changes: 61 additions & 0 deletions tests/snapshots/bolt__parse_275__1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
_bolt_lineno = [1, 9], [1, 2]
_bolt_helper_get_attribute = _bolt_runtime.helpers['get_attribute']
_bolt_helper_interpolate_word = _bolt_runtime.helpers['interpolate_word']
_bolt_helper_replace = _bolt_runtime.helpers['replace']
_bolt_helper_children = _bolt_runtime.helpers['children']
with _bolt_runtime.scope() as _bolt_var2:
_bolt_var0 = 42
hex = _bolt_var0
_bolt_var1 = hex
_bolt_var1 = _bolt_helper_get_attribute(_bolt_var1, 'summon')
_bolt_var1 = _bolt_helper_interpolate_word(_bolt_var1, _bolt_refs[0])
_bolt_runtime.commands.append(_bolt_helper_replace(_bolt_refs[4], arguments=_bolt_helper_children([_bolt_helper_replace(_bolt_refs[3], fragments=_bolt_helper_children([_bolt_helper_replace(_bolt_refs[2], arguments=_bolt_helper_children([_bolt_helper_replace(_bolt_refs[1], value=_bolt_var1)]))]))])))
_bolt_var3 = _bolt_helper_replace(_bolt_refs[5], commands=_bolt_helper_children(_bolt_var2))
---
output = _bolt_var3
---
_bolt_refs[0]
<class 'bolt.ast.AstInterpolation'>
location: SourceLocation(pos=20, lineno=2, colno=12)
end_location: SourceLocation(pos=30, lineno=2, colno=22)
prefix: None
unpack: None
converter: 'word'
value:
<class 'bolt.ast.AstAttribute'>
_bolt_refs[1]
<class 'mecha.ast.AstSelectorArgument'>
location: SourceLocation(pos=16, lineno=2, colno=8)
end_location: SourceLocation(pos=30, lineno=2, colno=22)
inverted: False
key:
<class 'mecha.ast.AstString'>
value:
<class 'bolt.ast.AstInterpolation'>
_bolt_refs[2]
<class 'mecha.ast.AstSelector'>
location: SourceLocation(pos=13, lineno=2, colno=5)
end_location: SourceLocation(pos=31, lineno=2, colno=23)
variable: 's'
arguments:
<class 'mecha.ast.AstSelectorArgument'>
_bolt_refs[3]
<class 'mecha.ast.AstMessage'>
location: SourceLocation(pos=13, lineno=2, colno=5)
end_location: SourceLocation(pos=31, lineno=2, colno=23)
fragments:
<class 'mecha.ast.AstSelector'>
_bolt_refs[4]
<class 'mecha.ast.AstCommand'>
location: SourceLocation(pos=9, lineno=2, colno=1)
end_location: SourceLocation(pos=31, lineno=2, colno=23)
identifier: 'say:message'
arguments:
<class 'mecha.ast.AstMessage'>
_bolt_refs[5]
<class 'mecha.ast.AstRoot'>
location: SourceLocation(pos=0, lineno=1, colno=1)
end_location: SourceLocation(pos=31, lineno=2, colno=23)
commands:
<class 'mecha.ast.AstCommand'>
<class 'mecha.ast.AstCommand'>
50 changes: 50 additions & 0 deletions tests/snapshots/bolt__parse_276__0.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
say @s[tag=hex(42)]
---
<class 'mecha.ast.AstRoot'>
location: SourceLocation(pos=0, lineno=1, colno=1)
end_location: SourceLocation(pos=19, lineno=1, colno=20)
commands:
<class 'mecha.ast.AstCommand'>
location: SourceLocation(pos=0, lineno=1, colno=1)
end_location: SourceLocation(pos=19, lineno=1, colno=20)
identifier: 'say:message'
arguments:
<class 'mecha.ast.AstMessage'>
location: SourceLocation(pos=4, lineno=1, colno=5)
end_location: SourceLocation(pos=19, lineno=1, colno=20)
fragments:
<class 'mecha.ast.AstSelector'>
location: SourceLocation(pos=4, lineno=1, colno=5)
end_location: SourceLocation(pos=19, lineno=1, colno=20)
variable: 's'
arguments:
<class 'mecha.ast.AstSelectorArgument'>
location: SourceLocation(pos=7, lineno=1, colno=8)
end_location: SourceLocation(pos=18, lineno=1, colno=19)
inverted: False
key:
<class 'mecha.ast.AstString'>
location: SourceLocation(pos=7, lineno=1, colno=8)
end_location: SourceLocation(pos=10, lineno=1, colno=11)
value: 'tag'
value:
<class 'bolt.ast.AstInterpolation'>
location: SourceLocation(pos=11, lineno=1, colno=12)
end_location: SourceLocation(pos=18, lineno=1, colno=19)
prefix: None
unpack: None
converter: 'word'
value:
<class 'bolt.ast.AstCall'>
location: SourceLocation(pos=11, lineno=1, colno=12)
end_location: SourceLocation(pos=18, lineno=1, colno=19)
value:
<class 'bolt.ast.AstIdentifier'>
location: SourceLocation(pos=11, lineno=1, colno=12)
end_location: SourceLocation(pos=14, lineno=1, colno=15)
value: 'hex'
arguments:
<class 'bolt.ast.AstValue'>
location: SourceLocation(pos=15, lineno=1, colno=16)
end_location: SourceLocation(pos=17, lineno=1, colno=18)
value: 42
58 changes: 58 additions & 0 deletions tests/snapshots/bolt__parse_276__1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
_bolt_lineno = [1], [1]
_bolt_helper_interpolate_word = _bolt_runtime.helpers['interpolate_word']
_bolt_helper_replace = _bolt_runtime.helpers['replace']
_bolt_helper_children = _bolt_runtime.helpers['children']
with _bolt_runtime.scope() as _bolt_var2:
_bolt_var0 = hex
_bolt_var1 = 42
_bolt_var0 = _bolt_var0(_bolt_var1)
_bolt_var0 = _bolt_helper_interpolate_word(_bolt_var0, _bolt_refs[0])
_bolt_runtime.commands.append(_bolt_helper_replace(_bolt_refs[4], arguments=_bolt_helper_children([_bolt_helper_replace(_bolt_refs[3], fragments=_bolt_helper_children([_bolt_helper_replace(_bolt_refs[2], arguments=_bolt_helper_children([_bolt_helper_replace(_bolt_refs[1], value=_bolt_var0)]))]))])))
_bolt_var3 = _bolt_helper_replace(_bolt_refs[5], commands=_bolt_helper_children(_bolt_var2))
---
output = _bolt_var3
---
_bolt_refs[0]
<class 'bolt.ast.AstInterpolation'>
location: SourceLocation(pos=11, lineno=1, colno=12)
end_location: SourceLocation(pos=18, lineno=1, colno=19)
prefix: None
unpack: None
converter: 'word'
value:
<class 'bolt.ast.AstCall'>
_bolt_refs[1]
<class 'mecha.ast.AstSelectorArgument'>
location: SourceLocation(pos=7, lineno=1, colno=8)
end_location: SourceLocation(pos=18, lineno=1, colno=19)
inverted: False
key:
<class 'mecha.ast.AstString'>
value:
<class 'bolt.ast.AstInterpolation'>
_bolt_refs[2]
<class 'mecha.ast.AstSelector'>
location: SourceLocation(pos=4, lineno=1, colno=5)
end_location: SourceLocation(pos=19, lineno=1, colno=20)
variable: 's'
arguments:
<class 'mecha.ast.AstSelectorArgument'>
_bolt_refs[3]
<class 'mecha.ast.AstMessage'>
location: SourceLocation(pos=4, lineno=1, colno=5)
end_location: SourceLocation(pos=19, lineno=1, colno=20)
fragments:
<class 'mecha.ast.AstSelector'>
_bolt_refs[4]
<class 'mecha.ast.AstCommand'>
location: SourceLocation(pos=0, lineno=1, colno=1)
end_location: SourceLocation(pos=19, lineno=1, colno=20)
identifier: 'say:message'
arguments:
<class 'mecha.ast.AstMessage'>
_bolt_refs[5]
<class 'mecha.ast.AstRoot'>
location: SourceLocation(pos=0, lineno=1, colno=1)
end_location: SourceLocation(pos=19, lineno=1, colno=20)
commands:
<class 'mecha.ast.AstCommand'>
5 changes: 5 additions & 0 deletions tests/snapshots/bolt__parse_277__0.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#>ERROR Expected literal 'day', literal 'midnight', literal 'night', literal 'noon' or time but got literal 'hex.time'.
# line 1, column 10
# 1 | time set hex.time
# : ^^^^^^^^
time set hex.time
Loading

0 comments on commit 0526a85

Please sign in to comment.