Skip to content

Commit

Permalink
O(1) switch (#38)
Browse files Browse the repository at this point in the history
* Implement O(1) switch

* Improve code quality of O(1) switch

* Implement O(1) for Hardcode.switch

Also includes Trigger.setup and RightClick.setup functions as those use `parse_switch` internally

* Add `default` case

* Update exception.py

* Update versions dictionary

(Skipped numbers were only used for a few snapshots and never a full release)

* Make linter error go away

* Fix operator precedence issue

* Make requested changes
  • Loading branch information
Nico314159 authored Sep 16, 2023
1 parent 07ffd76 commit 99c20f7
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 17 deletions.
57 changes: 43 additions & 14 deletions src/jmc/compile/command/_flow_control.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Module for parsing flow controls (if-e;se, while, etc.), called from command/flow_control.py"""

from typing import Literal
from .condition import parse_condition
from .utils import ScoreboardPlayer, find_scoreboard_player_type, PlayerType
from ..tokenizer import Token, Tokenizer, TokenType
Expand Down Expand Up @@ -197,20 +198,41 @@ def __parse_switch_binary(min_: int, max_: int, count: str, datapack: DataPack,


def parse_switch(scoreboard_player: ScoreboardPlayer,
func_contents: list[list[str]], datapack: DataPack, name: str = SWITCH_CASE_NAME, start_at: int = 1) -> str:
func_contents: list[list[str]], datapack: DataPack, name: str = SWITCH_CASE_NAME, start_at: int = 1, case_numbers: list[int | Literal["default"]] | None = None) -> str:
"""
Create a binary tree for JMC switch-case
:param scoreboard_player: Minecraft scoreboard objective to check the integer
:param func_contents: List of function content(List of commands(string)) given by user
:param datapack: Datapack object
:param name: Private function's group name, defaults to SWITCH_CASE_NAME
:param case_numbers: List of case numbers provided (only matters with post-1.20.2 switch)
:return: Minecraft function call to initiate switch case
"""
count = datapack.get_count(name)
__parse_switch_binary(start_at, len(func_contents) + start_at - 1, count,
datapack, func_contents, scoreboard_player, name, start_at)
return f"function {datapack.namespace}:{DataPack.private_name}/{name}/{count}"
if case_numbers is None:
case_numbers = [*range(1, len(func_contents) + 1)]
func_count = datapack.get_count(name)
if datapack.version >= 16:
has_default = "default" in case_numbers
for (case_body, case_label) in zip(func_contents, case_numbers):
if has_default and case_label != "default":
case_body.append(f"scoreboard players set __found_case__ {datapack.var_name} 1")
datapack.add_raw_private_function(
name, case_body, f"{str(func_count)}/{case_label}")
datapack.add_raw_private_function(
name, [
f"$function {datapack.namespace}:{DataPack.private_name}/{name}/{func_count}/$(switch_key)"
], f"{str(func_count)}/select")
assert not isinstance(scoreboard_player, int)
return (
(f"scoreboard players set __found_case__ {datapack.var_name} 0\n" if has_default else "") +
f"execute store result storage {datapack.namespace}:{datapack.storage_name} switch_key int 1 run scoreboard players get {scoreboard_player.value[1]} {scoreboard_player.value[0]}" +
f"\nfunction {datapack.namespace}:{DataPack.private_name}/{name}/{func_count}/select with storage {datapack.namespace}:{datapack.storage_name}" +
(f"\nexecute unless score __found_case__ {datapack.var_name} matches 1 run function {datapack.namespace}:{DataPack.private_name}/{name}/{func_count}/default" if has_default else "")
)
__parse_switch_binary(start_at, len(func_contents) + start_at - 1, func_count,
datapack, func_contents, scoreboard_player, name, start_at)
return f"function {datapack.namespace}:{DataPack.private_name}/{name}/{func_count}"


def switch(command: list[Token], datapack: DataPack,
Expand All @@ -237,7 +259,8 @@ def switch(command: list[Token], datapack: DataPack,
list_of_tokens = tokenizer.parse(
command[2].string[1:-1], command[2].line, command[2].col + 1, expect_semicolon=True)

case_count: int | None = None
case_numbers: list[int | Literal["default"]] = []
expected_case: int | None = None
case_start: int | None = None
cases_content: list[list[list[Token]]] = []
current_case_content: list[list[Token]] = []
Expand All @@ -262,13 +285,12 @@ def switch(command: list[Token], datapack: DataPack,
"Expected case number", tokens[1], tokenizer)

count = int(count_str)
if case_count is None:
case_count = count
if expected_case is None:
expected_case = count
if case_start is None:
case_start = case_count
if count != case_count:
raise JMCSyntaxException(
f"Expected case {case_count} got case {count}", tokens[1], tokenizer)
case_start = expected_case
if count != expected_case:
datapack.version.require(16, tokens[1], tokenizer, suggestion=f"Expected case number {expected_case}")
if len(tokens) < 3:
raise JMCSyntaxException(
"Expected colon (:)", tokens[1], tokenizer, col_length=True)
Expand All @@ -277,7 +299,14 @@ def switch(command: list[Token], datapack: DataPack,
"Expected colon (:)", tokens[2], tokenizer)

tokens = tokens[3:]
case_count += 1
expected_case += 1
case_numbers.append(count)
if tokens[0].string == "default" and tokens[0].token_type == TokenType.KEYWORD:
datapack.version.require(16, tokens[1], tokenizer)
cases_content.append(current_case_content)
current_case_content = []
case_numbers.append("default")
tokens = tokens[2:]
# End If case
if tokens[0].string == "break" and tokens[0].token_type == TokenType.KEYWORD and len(
tokens) == 1:
Expand Down Expand Up @@ -310,7 +339,7 @@ def switch(command: list[Token], datapack: DataPack,
raise ValueError("case_start is None")

return parse_switch(scoreboard_player, func_contents,
datapack, start_at=case_start)
datapack, start_at=case_start, case_numbers=case_numbers)


FOR_NAME = "for_loop"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ def call(self) -> str:
raise error

return parse_switch(scoreboard_player, func_contents,
self.datapack, self.name, start_at)
self.datapack, self.name, start_at, [*range(start_at, count + 1)])


@func_property(
Expand Down
3 changes: 2 additions & 1 deletion src/jmc/compile/exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,5 +250,6 @@ def __init__(self, message: str, token: "Token", tokenizer: "Tokenizer", *, col_
JMCSyntaxWarning,
JMCValueError,
MinecraftSyntaxWarning,
JMCBuildError
JMCBuildError,
MinecraftVersionTooLow
)
3 changes: 3 additions & 0 deletions src/jmc/terminal/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ def __str__(self) -> str:


PACK_VERSION = {
MinecraftVersion(1, 20, 2): "18",
MinecraftVersion(1, 20): "15",
MinecraftVersion(1, 19, 4): "12",
MinecraftVersion(1, 19): "10",
MinecraftVersion(1, 18, 2): "9",
MinecraftVersion(1, 18): "8",
Expand Down
2 changes: 1 addition & 1 deletion src/jmc/terminal_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ def __config_edit() -> None:
pprint(f"""Edit configurations (Bypass error checking)
Type `cancel` to cancel
{NEW_LINE.join([f"- {key}" for key in config_json])}""", Colors.PURPLE)
key = get_input("Configuration: ")
key = get_input("Configuration: ").strip()
if key not in config_json:
if key.lower() == "cancel":
return
Expand Down

0 comments on commit 99c20f7

Please sign in to comment.