Skip to content

Commit

Permalink
feat: add context managers
Browse files Browse the repository at this point in the history
  • Loading branch information
vberlier committed Jun 18, 2022
1 parent b02aa7d commit 38e614e
Show file tree
Hide file tree
Showing 32 changed files with 2,303 additions and 95 deletions.
37 changes: 37 additions & 0 deletions bolt/codegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
AstList,
AstLookup,
AstSlice,
AstTarget,
AstTargetAttribute,
AstTargetIdentifier,
AstTargetItem,
Expand Down Expand Up @@ -570,6 +571,42 @@ def continue_statement(
acc.statement("continue")
return []

@rule(AstCommand, identifier="with:subcommand")
def with_statement(
self,
node: AstCommand,
acc: Accumulator,
) -> Generator[AstNode, Optional[List[str]], Optional[List[str]]]:
clauses: List[str] = []
bindings: List[Tuple[str, AstTarget]] = []

subcommand = cast(AstCommand, node.arguments[0])
body: Optional[AstNode] = None

while not body:
value = yield from visit_single(subcommand.arguments[0], required=True)

if isinstance(target := subcommand.arguments[1], AstTarget):
tmp = f"_bolt_with{len(bindings)}"
clauses.append(f"{value} as {tmp}")
bindings.append((tmp, target))
else:
clauses.append(value)

last_arg = subcommand.arguments[-1]
if isinstance(last_arg, AstRoot):
body = last_arg
elif isinstance(last_arg, AstCommand):
subcommand = last_arg

acc.statement(f"with {', '.join(clauses)}:", lineno=node)
with acc.block():
for tmp, target in bindings:
yield from visit_binding(target, "=", tmp, acc)
yield from visit_body(body, acc)

return []

@rule(AstCommand, identifier="import:module")
@rule(AstCommand, identifier="import:module:as:alias")
@rule(AstCommand, identifier="from:module:import:subcommand")
Expand Down
50 changes: 25 additions & 25 deletions bolt/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
"get_stream_branch_scope",
"UndefinedIdentifier",
"create_bolt_root_parser",
"ExecuteIfConditionConstraint",
"BranchScopeManager",
"check_final_expression",
"InterpolationParser",
Expand All @@ -21,6 +20,7 @@
"parse_function_root",
"parse_del_target",
"parse_identifier",
"TrailingCommaParser",
"ImportLocationConstraint",
"ImportStatementHandler",
"parse_python_import",
Expand Down Expand Up @@ -48,7 +48,6 @@
from difflib import get_close_matches
from typing import Any, Dict, List, Optional, Set, Tuple, cast

from beet.core.utils import required_field
from mecha import (
AlternativeParser,
AstChildren,
Expand Down Expand Up @@ -128,16 +127,16 @@ def get_bolt_parsers(
"root": create_bolt_root_parser(parsers["root"]),
"nested_root": create_bolt_root_parser(parsers["nested_root"]),
"command": UndefinedIdentifierErrorHandler(
ImportStatementHandler(
GlobalNonlocalHandler(ExecuteIfConditionConstraint(parsers["command"]))
)
ImportStatementHandler(GlobalNonlocalHandler(parsers["command"]))
),
"command:argument:bolt:if_block": delegate("bolt:if_block"),
"command:argument:bolt:elif_condition": delegate("bolt:elif_condition"),
"command:argument:bolt:elif_block": delegate("bolt:elif_block"),
"command:argument:bolt:else_block": delegate("bolt:else_block"),
"command:argument:bolt:statement": delegate("bolt:statement"),
"command:argument:bolt:assignment_target": delegate("bolt:assignment_target"),
"command:argument:bolt:with_expression": delegate("bolt:with_expression"),
"command:argument:bolt:with_target": delegate("bolt:with_target"),
"command:argument:bolt:import": delegate("bolt:import"),
"command:argument:bolt:import_name": delegate("bolt:import_name"),
"command:argument:bolt:global_name": delegate("bolt:global_name"),
Expand Down Expand Up @@ -178,13 +177,17 @@ def get_bolt_parsers(
"bolt:del_target": parse_del_target,
"bolt:interpolation": PrimaryParser(delegate("bolt:identifier")),
"bolt:identifier": parse_identifier,
"bolt:with_expression": TrailingCommaParser(delegate("bolt:expression")),
"bolt:with_target": TrailingCommaParser(
AssignmentTargetParser(allow_undefined_identifiers=True)
),
"bolt:import": AlternativeParser(
[
ImportLocationConstraint(parsers["resource_location_or_tag"]),
parse_python_import,
]
),
"bolt:import_name": parse_import_name,
"bolt:import_name": TrailingCommaParser(parse_import_name),
"bolt:global_name": parse_name_list,
"bolt:nonlocal_name": parse_name_list,
"bolt:expression": delegate("bolt:disjunction"),
Expand Down Expand Up @@ -432,21 +435,6 @@ def create_bolt_root_parser(parser: Parser):
)


@dataclass
class ExecuteIfConditionConstraint:
"""Constraint that prevents inlining if conditions as execute subcommands."""

parser: Parser

def __call__(self, stream: TokenStream) -> Any:
if isinstance(node := self.parser(stream), AstCommand):
if node.identifier == "execute:if:condition:body":
exc = InvalidSyntax("Can't inline conditions as execute subcommands.")
raise set_location(exc, node)

return node


@dataclass
class BranchScopeManager:
"""Parser that manages accessible identifiers for conditional branches."""
Expand Down Expand Up @@ -626,7 +614,7 @@ def parse_decorator(stream: TokenStream) -> Any:
class DecoratorResolver:
"""Parser for resolving decorators."""

parser: Parser = required_field()
parser: Parser

def __call__(self, stream: TokenStream) -> AstRoot:
node: AstRoot = self.parser(stream)
Expand Down Expand Up @@ -931,6 +919,19 @@ def parse_identifier(stream: TokenStream) -> AstIdentifier:
return set_location(AstIdentifier(value=token.value), token)


@dataclass
class TrailingCommaParser:
"""Parser for discarding trailing comma."""

parser: Parser

def __call__(self, stream: TokenStream) -> Any:
node = self.parser(stream)
with stream.syntax(comma=r","):
stream.get("comma")
return node


@dataclass
class ImportLocationConstraint:
"""Constraint for import location."""
Expand Down Expand Up @@ -996,9 +997,8 @@ def parse_python_import(stream: TokenStream) -> Any:

def parse_import_name(stream: TokenStream) -> AstImportedIdentifier:
"""Parse import name."""
with stream.syntax(name=IDENTIFIER_PATTERN, comma=r","):
with stream.syntax(name=IDENTIFIER_PATTERN):
token = stream.expect("name")
stream.get("comma")
return set_location(AstImportedIdentifier(value=token.value), token)


Expand Down Expand Up @@ -1041,7 +1041,7 @@ def parse_name_list(stream: TokenStream) -> AstIdentifier:
class FunctionRootBacktracker:
"""Parser for backtracking over function root nodes."""

parser: Parser = required_field()
parser: Parser

def __call__(self, stream: TokenStream) -> AstRoot:
should_replace = False
Expand Down
34 changes: 34 additions & 0 deletions bolt/resources/commands.json
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,40 @@
"type": "literal",
"executable": true
},
"with": {
"type": "literal",
"children": {
"expression": {
"type": "argument",
"parser": "bolt:with_expression",
"redirect": ["with"],
"children": {
"as": {
"type": "literal",
"children": {
"target": {
"type": "argument",
"parser": "bolt:with_target",
"redirect": ["with"],
"children": {
"body": {
"type": "argument",
"parser": "mecha:nested_root",
"executable": true
}
}
}
}
},
"body": {
"type": "argument",
"parser": "mecha:nested_root",
"executable": true
}
}
}
}
},
"import": {
"type": "literal",
"children": {
Expand Down
29 changes: 21 additions & 8 deletions examples/bolt_basic/src/data/demo/functions/foo.mcfunction
Original file line number Diff line number Diff line change
Expand Up @@ -381,15 +381,15 @@ x = 12
y = 23
z = 34

tp @s f"~{x} ~{y} ~{z}"
tp @s f"~{x}" y f"~{z}"
setblock f"~{x} ~{y} ~{z}" stone
setblock f"~{x}" y f"~{z}" stone

tp @s x y z
tp @s ~x ~y ~z
tp @s ^x ^y ^z
tp @s -x -y -z
tp @s ~-x ~-y ~-z
tp @s ^-x ^-y ^-z
setblock x y z stone
setblock ~x ~y ~z stone
setblock ^x ^y ^z stone
setblock -x -y -z stone
setblock ~-x ~-y ~-z stone
setblock ^-x ^-y ^-z stone

for i, x in enumerate("abc"):
say f"{i} {x}"
Expand Down Expand Up @@ -638,3 +638,16 @@ def testing_decorator(f):
]) or None
def f(x):
say f"inner {x}"

from contextlib import suppress

with suppress(ZeroDivisionError):
1 / 0

from bolt import Runtime
runtime = ctx.inject(Runtime)

with runtime.scope() as commands:
tellraw @p "hello"

say commands[0]
36 changes: 18 additions & 18 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,16 @@ include = ["bolt/py.typed"]

[tool.poetry.dependencies]
python = "^3.8"
beet = ">=0.67.0"
mecha = ">=0.52.2"
beet = ">=0.68.1"
mecha = ">=0.54.1"

[tool.poetry.dev-dependencies]
pytest = "^7.1.2"
black = "^22.3.0"
isort = "^5.10.1"
python-semantic-release = "^7.28.1"
pytest-insta = "^0.1.11"
lectern = ">=0.21.2"
lectern = ">=0.22.0"

[tool.pytest.ini_options]
addopts = "tests bolt --doctest-modules"
Expand Down
Loading

0 comments on commit 38e614e

Please sign in to comment.