Skip to content

Commit

Permalink
feat: ergonomic improvements for dicts without quotes
Browse files Browse the repository at this point in the history
  • Loading branch information
vberlier committed Jul 17, 2022
1 parent c1c7c28 commit 18c747e
Show file tree
Hide file tree
Showing 13 changed files with 110 additions and 70 deletions.
8 changes: 7 additions & 1 deletion bolt/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
"AstFormatString",
"AstTuple",
"AstList",
"AstDictItem",
"AstDict",
"AstDictItem",
"AstDictUnquotedKey",
"AstSlice",
"AstUnpack",
"AstKeyword",
Expand Down Expand Up @@ -125,6 +126,11 @@ class AstList(AstExpression):
items: AstChildren[AstExpression] = required_field()


@dataclass(frozen=True)
class AstDictUnquotedKey(AstValue):
"""Ast dict unquoted key node."""


@dataclass(frozen=True)
class AstDictItem(AstNode):
"""Ast dict item node."""
Expand Down
23 changes: 21 additions & 2 deletions bolt/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
AstDeferredRoot,
AstDict,
AstDictItem,
AstDictUnquotedKey,
AstExpression,
AstExpressionBinary,
AstExpressionUnary,
Expand Down Expand Up @@ -1931,7 +1932,7 @@ def parse_dict_item(stream: TokenStream) -> Any:
if identifier.value in identifiers:
key = AstIdentifier(value=identifier.value)
else:
key = AstValue(value=identifier.value)
key = AstDictUnquotedKey(value=identifier.value)

key = set_location(key, identifier)

Expand Down Expand Up @@ -1982,15 +1983,33 @@ def __call__(self, stream: TokenStream) -> Any:

if curly:
items: List[Any] = []
unquoted = False

with stream.ignore("newline"):
for _ in stream.peek_until(("curly", "}")):
items.append(delegate("bolt:dict_item", stream))
items.append(item := delegate("bolt:dict_item", stream))
unquoted = unquoted or (
isinstance(item, AstDictItem)
and isinstance(item.key, AstDictUnquotedKey)
)

if not stream.get("comma"):
stream.expect(("curly", "}"))
break

if unquoted:
for i, item in enumerate(items):
if isinstance(item, AstDictItem):
if isinstance(item.key, AstIdentifier):
key = AstDictUnquotedKey(value=item.key.value)
key = set_location(key, item.key)
items[i] = replace(item, key=key)
elif not isinstance(item.key, AstDictUnquotedKey):
exc = InvalidSyntax(
"Forbidden dynamic key in dict without quotes."
)
raise set_location(exc, item.key)

node = AstDict(items=AstChildren(items))
return set_location(node, curly, stream.current)

Expand Down
9 changes: 7 additions & 2 deletions tests/resources/bolt_examples.mcfunction
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ my_predicate = {
scores: {
score1: {
"min": {
"type": "minecraft:score",
type: "minecraft:score",
target: "this",
score: "score2",
scale: 1
Expand All @@ -354,7 +354,7 @@ my_predicate = {
}
###
def f():
return {f(): f(), other: [{}, {}, "wat"]}
return {f(): f(), "other": [{}, {}, "wat"]}
###
at @s if "foo" == "bar":
say yolo
Expand Down Expand Up @@ -890,3 +890,8 @@ raise
raise ValueError()
###
raise ValueError() from None
###
foo = {
a: 1,
"b" + "c": 2
}
2 changes: 1 addition & 1 deletion tests/snapshots/bolt__parse_162__0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ d.foo += 1
location: SourceLocation(pos=5, lineno=1, colno=6)
end_location: SourceLocation(pos=11, lineno=1, colno=12)
key:
<class 'bolt.ast.AstValue'>
<class 'bolt.ast.AstDictUnquotedKey'>
location: SourceLocation(pos=5, lineno=1, colno=6)
end_location: SourceLocation(pos=8, lineno=1, colno=9)
value: 'foo'
Expand Down
4 changes: 2 additions & 2 deletions tests/snapshots/bolt__parse_168__0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ item replace entity @s my_weapon.offhand from entity @s my_weapon.mainhand
location: SourceLocation(pos=13, lineno=1, colno=14)
end_location: SourceLocation(pos=38, lineno=1, colno=39)
key:
<class 'bolt.ast.AstValue'>
<class 'bolt.ast.AstDictUnquotedKey'>
location: SourceLocation(pos=13, lineno=1, colno=14)
end_location: SourceLocation(pos=20, lineno=1, colno=21)
value: 'offhand'
Expand All @@ -42,7 +42,7 @@ item replace entity @s my_weapon.offhand from entity @s my_weapon.mainhand
location: SourceLocation(pos=40, lineno=1, colno=41)
end_location: SourceLocation(pos=67, lineno=1, colno=68)
key:
<class 'bolt.ast.AstValue'>
<class 'bolt.ast.AstDictUnquotedKey'>
location: SourceLocation(pos=40, lineno=1, colno=41)
end_location: SourceLocation(pos=48, lineno=1, colno=49)
value: 'mainhand'
Expand Down
2 changes: 1 addition & 1 deletion tests/snapshots/bolt__parse_205__0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ give @s bow{**bow_data}
location: SourceLocation(pos=12, lineno=1, colno=13)
end_location: SourceLocation(pos=30, lineno=1, colno=31)
key:
<class 'bolt.ast.AstValue'>
<class 'bolt.ast.AstDictUnquotedKey'>
location: SourceLocation(pos=12, lineno=1, colno=13)
end_location: SourceLocation(pos=27, lineno=1, colno=28)
value: 'CustomModelData'
Expand Down
2 changes: 1 addition & 1 deletion tests/snapshots/bolt__parse_206__0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ give @s stone{CanPlaceOn: [*stone_can_place_on], **custom_model_data}
location: SourceLocation(pos=21, lineno=1, colno=22)
end_location: SourceLocation(pos=39, lineno=1, colno=40)
key:
<class 'bolt.ast.AstValue'>
<class 'bolt.ast.AstDictUnquotedKey'>
location: SourceLocation(pos=21, lineno=1, colno=22)
end_location: SourceLocation(pos=36, lineno=1, colno=37)
value: 'CustomModelData'
Expand Down
10 changes: 10 additions & 0 deletions tests/snapshots/bolt__parse_240__0.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
foo = {
a: 1,
#>ERROR Forbidden dynamic key in dict without quotes.
# line 3, column 5
# 2 | a: 1,
# 3 | "b" + "c": 2
# : ^^^^^^^^^
# 4 | }
"b" + "c": 2
}
2 changes: 1 addition & 1 deletion tests/snapshots/bolt__parse_91__0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
location: SourceLocation(pos=6, lineno=2, colno=5)
end_location: SourceLocation(pos=16, lineno=2, colno=15)
key:
<class 'bolt.ast.AstValue'>
<class 'bolt.ast.AstDictUnquotedKey'>
location: SourceLocation(pos=6, lineno=2, colno=5)
end_location: SourceLocation(pos=9, lineno=2, colno=8)
value: 'foo'
Expand Down
82 changes: 41 additions & 41 deletions tests/snapshots/bolt__parse_96__0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ my_predicate = {
scores: {
score1: {
"min": {
"type": "minecraft:score",
type: "minecraft:score",
target: "this",
score: "score2",
scale: 1
Expand All @@ -15,16 +15,16 @@ my_predicate = {
---
<class 'mecha.ast.AstRoot'>
location: SourceLocation(pos=0, lineno=1, colno=1)
end_location: SourceLocation(pos=237, lineno=15, colno=1)
end_location: SourceLocation(pos=235, lineno=15, colno=1)
commands:
<class 'mecha.ast.AstCommand'>
location: SourceLocation(pos=0, lineno=1, colno=1)
end_location: SourceLocation(pos=236, lineno=14, colno=2)
end_location: SourceLocation(pos=234, lineno=14, colno=2)
identifier: 'statement'
arguments:
<class 'bolt.ast.AstAssignment'>
location: SourceLocation(pos=0, lineno=1, colno=1)
end_location: SourceLocation(pos=236, lineno=14, colno=2)
end_location: SourceLocation(pos=234, lineno=14, colno=2)
operator: '='
target:
<class 'bolt.ast.AstTargetIdentifier'>
Expand All @@ -35,13 +35,13 @@ my_predicate = {
value:
<class 'bolt.ast.AstDict'>
location: SourceLocation(pos=15, lineno=1, colno=16)
end_location: SourceLocation(pos=236, lineno=14, colno=2)
end_location: SourceLocation(pos=234, lineno=14, colno=2)
items:
<class 'bolt.ast.AstDictItem'>
location: SourceLocation(pos=19, lineno=2, colno=3)
end_location: SourceLocation(pos=55, lineno=2, colno=39)
key:
<class 'bolt.ast.AstValue'>
<class 'bolt.ast.AstDictUnquotedKey'>
location: SourceLocation(pos=19, lineno=2, colno=3)
end_location: SourceLocation(pos=28, lineno=2, colno=12)
value: 'condition'
Expand All @@ -54,7 +54,7 @@ my_predicate = {
location: SourceLocation(pos=59, lineno=3, colno=3)
end_location: SourceLocation(pos=73, lineno=3, colno=17)
key:
<class 'bolt.ast.AstValue'>
<class 'bolt.ast.AstDictUnquotedKey'>
location: SourceLocation(pos=59, lineno=3, colno=3)
end_location: SourceLocation(pos=65, lineno=3, colno=9)
value: 'entity'
Expand All @@ -65,33 +65,33 @@ my_predicate = {
value: 'this'
<class 'bolt.ast.AstDictItem'>
location: SourceLocation(pos=77, lineno=4, colno=3)
end_location: SourceLocation(pos=234, lineno=13, colno=4)
end_location: SourceLocation(pos=232, lineno=13, colno=4)
key:
<class 'bolt.ast.AstValue'>
<class 'bolt.ast.AstDictUnquotedKey'>
location: SourceLocation(pos=77, lineno=4, colno=3)
end_location: SourceLocation(pos=83, lineno=4, colno=9)
value: 'scores'
value:
<class 'bolt.ast.AstDict'>
location: SourceLocation(pos=85, lineno=4, colno=11)
end_location: SourceLocation(pos=234, lineno=13, colno=4)
end_location: SourceLocation(pos=232, lineno=13, colno=4)
items:
<class 'bolt.ast.AstDictItem'>
location: SourceLocation(pos=91, lineno=5, colno=5)
end_location: SourceLocation(pos=230, lineno=12, colno=6)
end_location: SourceLocation(pos=228, lineno=12, colno=6)
key:
<class 'bolt.ast.AstValue'>
<class 'bolt.ast.AstDictUnquotedKey'>
location: SourceLocation(pos=91, lineno=5, colno=5)
end_location: SourceLocation(pos=97, lineno=5, colno=11)
value: 'score1'
value:
<class 'bolt.ast.AstDict'>
location: SourceLocation(pos=99, lineno=5, colno=13)
end_location: SourceLocation(pos=230, lineno=12, colno=6)
end_location: SourceLocation(pos=228, lineno=12, colno=6)
items:
<class 'bolt.ast.AstDictItem'>
location: SourceLocation(pos=107, lineno=6, colno=7)
end_location: SourceLocation(pos=224, lineno=11, colno=8)
end_location: SourceLocation(pos=222, lineno=11, colno=8)
key:
<class 'bolt.ast.AstValue'>
location: SourceLocation(pos=107, lineno=6, colno=7)
Expand All @@ -100,57 +100,57 @@ my_predicate = {
value:
<class 'bolt.ast.AstDict'>
location: SourceLocation(pos=114, lineno=6, colno=14)
end_location: SourceLocation(pos=224, lineno=11, colno=8)
end_location: SourceLocation(pos=222, lineno=11, colno=8)
items:
<class 'bolt.ast.AstDictItem'>
location: SourceLocation(pos=124, lineno=7, colno=9)
end_location: SourceLocation(pos=149, lineno=7, colno=34)
end_location: SourceLocation(pos=147, lineno=7, colno=32)
key:
<class 'bolt.ast.AstValue'>
<class 'bolt.ast.AstDictUnquotedKey'>
location: SourceLocation(pos=124, lineno=7, colno=9)
end_location: SourceLocation(pos=130, lineno=7, colno=15)
end_location: SourceLocation(pos=128, lineno=7, colno=13)
value: 'type'
value:
<class 'bolt.ast.AstValue'>
location: SourceLocation(pos=132, lineno=7, colno=17)
end_location: SourceLocation(pos=149, lineno=7, colno=34)
location: SourceLocation(pos=130, lineno=7, colno=15)
end_location: SourceLocation(pos=147, lineno=7, colno=32)
value: 'minecraft:score'
<class 'bolt.ast.AstDictItem'>
location: SourceLocation(pos=159, lineno=8, colno=9)
end_location: SourceLocation(pos=173, lineno=8, colno=23)
location: SourceLocation(pos=157, lineno=8, colno=9)
end_location: SourceLocation(pos=171, lineno=8, colno=23)
key:
<class 'bolt.ast.AstValue'>
location: SourceLocation(pos=159, lineno=8, colno=9)
end_location: SourceLocation(pos=165, lineno=8, colno=15)
<class 'bolt.ast.AstDictUnquotedKey'>
location: SourceLocation(pos=157, lineno=8, colno=9)
end_location: SourceLocation(pos=163, lineno=8, colno=15)
value: 'target'
value:
<class 'bolt.ast.AstValue'>
location: SourceLocation(pos=167, lineno=8, colno=17)
end_location: SourceLocation(pos=173, lineno=8, colno=23)
location: SourceLocation(pos=165, lineno=8, colno=17)
end_location: SourceLocation(pos=171, lineno=8, colno=23)
value: 'this'
<class 'bolt.ast.AstDictItem'>
location: SourceLocation(pos=183, lineno=9, colno=9)
end_location: SourceLocation(pos=198, lineno=9, colno=24)
location: SourceLocation(pos=181, lineno=9, colno=9)
end_location: SourceLocation(pos=196, lineno=9, colno=24)
key:
<class 'bolt.ast.AstValue'>
location: SourceLocation(pos=183, lineno=9, colno=9)
end_location: SourceLocation(pos=188, lineno=9, colno=14)
<class 'bolt.ast.AstDictUnquotedKey'>
location: SourceLocation(pos=181, lineno=9, colno=9)
end_location: SourceLocation(pos=186, lineno=9, colno=14)
value: 'score'
value:
<class 'bolt.ast.AstValue'>
location: SourceLocation(pos=190, lineno=9, colno=16)
end_location: SourceLocation(pos=198, lineno=9, colno=24)
location: SourceLocation(pos=188, lineno=9, colno=16)
end_location: SourceLocation(pos=196, lineno=9, colno=24)
value: 'score2'
<class 'bolt.ast.AstDictItem'>
location: SourceLocation(pos=208, lineno=10, colno=9)
end_location: SourceLocation(pos=216, lineno=10, colno=17)
location: SourceLocation(pos=206, lineno=10, colno=9)
end_location: SourceLocation(pos=214, lineno=10, colno=17)
key:
<class 'bolt.ast.AstValue'>
location: SourceLocation(pos=208, lineno=10, colno=9)
end_location: SourceLocation(pos=213, lineno=10, colno=14)
<class 'bolt.ast.AstDictUnquotedKey'>
location: SourceLocation(pos=206, lineno=10, colno=9)
end_location: SourceLocation(pos=211, lineno=10, colno=14)
value: 'scale'
value:
<class 'bolt.ast.AstValue'>
location: SourceLocation(pos=215, lineno=10, colno=16)
end_location: SourceLocation(pos=216, lineno=10, colno=17)
location: SourceLocation(pos=213, lineno=10, colno=16)
end_location: SourceLocation(pos=214, lineno=10, colno=17)
value: 1
2 changes: 1 addition & 1 deletion tests/snapshots/bolt__parse_96__1.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,6 @@ output = _bolt_var20
_bolt_refs[0]
<class 'mecha.ast.AstRoot'>
location: SourceLocation(pos=0, lineno=1, colno=1)
end_location: SourceLocation(pos=237, lineno=15, colno=1)
end_location: SourceLocation(pos=235, lineno=15, colno=1)
commands:
<class 'mecha.ast.AstCommand'>
Loading

0 comments on commit 18c747e

Please sign in to comment.