diff --git a/src/jmc/compile/command/builtin_function/execute_excluded.py b/src/jmc/compile/command/builtin_function/execute_excluded.py index c51b392f..2f4449f9 100644 --- a/src/jmc/compile/command/builtin_function/execute_excluded.py +++ b/src/jmc/compile/command/builtin_function/execute_excluded.py @@ -22,10 +22,11 @@ def _hardcode_parse(calc_pos: int, string: str, token: Token, elif char == ")": count -= 1 - if char not in {"0", "1", "2", "3", "4", "5", "6", "7", - "8", "9", "+", "-", "*", "/", " ", "\t", "\n", "(", ")"}: + if char not in {"0", "1", "2", "3", "4", "5", "6", "7", + "8", "9", "+", "-", "*", "/", "\\", "%", + " ", "\t", "\n", "(", ")"}: raise JMCSyntaxException( - f"Invalid charater({char}) in Hardcode.calc", token, tokenizer, display_col_length=False) + f"Invalid character({char}) in Hardcode.calc", token, tokenizer, display_col_length=False) expression += char if count == 0: @@ -35,7 +36,7 @@ def _hardcode_parse(calc_pos: int, string: str, token: Token, raise JMCSyntaxException( "Invalid syntax in Hardcode.calc", token, tokenizer, display_col_length=False) - return string[:calc_pos] + eval_expr(expression) + string[index + 13:] + return string[:calc_pos] + eval_expr(expression.replace("\\", "//")) + string[index + 13:] def _hardcode_process(string: str, index_string: str, diff --git a/src/jmc/compile/command/builtin_function/load_only.py b/src/jmc/compile/command/builtin_function/load_only.py index 598ed57b..ec3296e4 100644 --- a/src/jmc/compile/command/builtin_function/load_only.py +++ b/src/jmc/compile/command/builtin_function/load_only.py @@ -1,10 +1,11 @@ """Module containing JMCFunction subclasses for custom JMC function that can only be used on load function""" +import json from ..jmc_function_mixin import EventMixin, ItemMixin from ...tokenizer import Token, TokenType from ...exception import JMCSyntaxException, JMCMissingValueError, JMCValueError from ...datapack_data import GUI, SIMPLE_JSON_BODY, GUIMode, Item from ...datapack import DataPack -from ..utils import ArgType, PlayerType, ScoreboardPlayer, FormattedText, convention_jmc_to_mc +from ..utils import ArgType, NumberType, PlayerType, ScoreboardPlayer, FormattedText, convention_jmc_to_mc from ..jmc_function import JMCFunction, FuncType, func_property from .._flow_control import parse_switch @@ -269,7 +270,8 @@ def call(self) -> str: ) class ItemCreateSign(JMCFunction): _VARIANTS = {"oak", "spruce", "birch", "jungle", - "acacia", "dark_oak", "crimson", "warped"} + "acacia", "dark_oak", "crimson", "warped", + "mangrove", "bamboo", "cherry"} def call(self) -> str: variant = self.args["variant"] @@ -1120,6 +1122,657 @@ def inner(arg: str) -> SIMPLE_JSON_BODY: "clickEvent", inner, self.check_bool("local")) return "" + +@func_property( + func_type=FuncType.LOAD_ONLY, + call_string="TextProp.suggestCommand", + arg_type={ + "propertyName": ArgType.STRING, + "function": ArgType.ARROW_FUNC, + "local": ArgType.KEYWORD + }, + name="text_prop_suggest_command", + defaults={ + "local": "false" + }, + ignore={ + "function", + } +) +class TextPropSuggestCommand(JMCFunction): + def call(self) -> str: + command = self.datapack.parse_function_token( + self.raw_args["function"].token, + self.tokenizer) + if not command: + raise JMCValueError( + "Unexpected empty arrow function", + self.raw_args["function"].token, + self.tokenizer) + if len(command) > 1: + raise JMCValueError( + f"'{self.call_string}' only allows 1 command (got {len(command)})", + self.raw_args["function"].token, + self.tokenizer) + if command[0].startswith("say"): + raise JMCValueError( + f"'{self.call_string}' doesn't allow 'say' command", + self.raw_args["function"].token, + self.tokenizer, suggestion="This is due to minecraft's limitation") + self.add_formatted_text_prop( + "clickEvent", { + "action": "suggest_command", "value": "/" + command[0]}, self.check_bool("local")) + return "" + + +@func_property( + func_type=FuncType.LOAD_ONLY, + call_string="TextProps.suggestCommand", + arg_type={ + "propertyName": ArgType.STRING, + "indexString": ArgType.STRING, + "function": ArgType.ARROW_FUNC, + "local": ArgType.KEYWORD + }, + name="text_props_suggest_command", + defaults={ + "local": "false" + }, + ignore={ + "function", + } +) +class TextPropsSuggestCommand(JMCFunction): + def call(self) -> str: + command = self.datapack.parse_function_token( + self.raw_args["function"].token, + self.tokenizer) + if not command: + raise JMCValueError( + "Unexpected empty arrow function", + self.raw_args["function"].token, + self.tokenizer) + if len(command) > 1: + raise JMCValueError( + f"'{self.call_string}' only allows 1 command (got {len(command)})", + self.raw_args["function"].token, + self.tokenizer) + if command[0].startswith("say"): + raise JMCValueError( + f"'{self.call_string}' doesn't allow 'say' command", + self.raw_args["function"].token, + self.tokenizer, suggestion="This is due to minecraft's limitation") + + @lru_cache() + def inner(arg: str) -> SIMPLE_JSON_BODY: + return { + "action": "suggest_command", "value": "/" + command[0].replace(self.args["indexString"], arg)} + self.add_formatted_text_prop( + "clickEvent", inner, self.check_bool("local")) + return "" + + +@func_property( + func_type=FuncType.LOAD_ONLY, + call_string="TextProp.clickURL", + arg_type={ + "propertyName": ArgType.STRING, + "url": ArgType.STRING, + "local": ArgType.KEYWORD + }, + name="text_prop_click_url", + defaults={ + "local": "false" + } +) +class TextPropClickURL(JMCFunction): + def call(self) -> str: + url = self.raw_args["url"] + if not url: + raise JMCValueError( + "Unexpected empty URL", + self.raw_args["url"].token, + self.tokenizer) + + self.add_formatted_text_prop( + "clickEvent", { + "action": "open_url", "value": self.args["url"]}, self.check_bool("local")) + return "" + + +@func_property( + func_type=FuncType.LOAD_ONLY, + call_string="TextProps.clickURL", + arg_type={ + "propertyName": ArgType.STRING, + "indexString": ArgType.STRING, + "url": ArgType.STRING, + "local": ArgType.KEYWORD + }, + name="text_props_click_url", + defaults={ + "local": "false" + } +) +class TextPropsClickURL(JMCFunction): + def call(self) -> str: + url = self.raw_args["url"] + if not url: + raise JMCValueError( + "Unexpected empty URL", + self.raw_args["url"].token, + self.tokenizer) + + @lru_cache() + def inner(arg: str) -> SIMPLE_JSON_BODY: + return { + "action": "open_url", "value": self.args["url"].replace(self.args["indexString"], arg)} + self.add_formatted_text_prop( + "clickEvent", inner, self.check_bool("local")) + return "" + + +@func_property( + func_type=FuncType.LOAD_ONLY, + call_string="TextProp.clickPage", + arg_type={ + "propertyName": ArgType.STRING, + "page": ArgType.INTEGER, + "local": ArgType.KEYWORD + }, + name="text_prop_click_page", + defaults={ + "local": "false" + }, + number_type={ + "page": NumberType.POSITIVE + } +) +class TextPropClickPage(JMCFunction): + def call(self) -> str: + page = self.raw_args["page"] + if not page: + raise JMCValueError( + "Unexpected empty page number", + self.raw_args["page"].token, + self.tokenizer) + + self.add_formatted_text_prop( + "clickEvent", { + "action": "change_page", "value": self.args["page"]}, self.check_bool("local")) + return "" + + +@func_property( + func_type=FuncType.LOAD_ONLY, + call_string="TextProps.clickPage", + arg_type={ + "propertyName": ArgType.STRING, + "local": ArgType.KEYWORD + }, + name="text_props_click_page", + defaults={ + "local": "false" + } +) +class TextPropsClickPage(JMCFunction): + def call(self) -> str: + + @lru_cache() + def inner(arg: str) -> SIMPLE_JSON_BODY: + return { + "action": "change_page", "value": arg} + self.add_formatted_text_prop( + "clickEvent", inner, self.check_bool("local")) + return "" + + +@func_property( + func_type=FuncType.LOAD_ONLY, + call_string="TextProp.clipboard", + arg_type={ + "propertyName": ArgType.STRING, + "text": ArgType.STRING, + "local": ArgType.KEYWORD + }, + name="text_prop_clipboard", + defaults={ + "local": "false" + } +) +class TextPropClipboard(JMCFunction): + def call(self) -> str: + text = self.raw_args["text"] + if not text: + raise JMCValueError( + "Unexpected empty clipboard content", + self.raw_args["text"].token, + self.tokenizer) + + self.add_formatted_text_prop( + "clickEvent", { + "action": "copy_to_clipboard", "value": self.args["text"]}, self.check_bool("local")) + return "" + + +@func_property( + func_type=FuncType.LOAD_ONLY, + call_string="TextProps.clipboard", + arg_type={ + "propertyName": ArgType.STRING, + "indexString": ArgType.STRING, + "text": ArgType.STRING, + "local": ArgType.KEYWORD + }, + name="text_props_clipboard", + defaults={ + "local": "false" + } +) +class TextPropsClipboard(JMCFunction): + def call(self) -> str: + text = self.raw_args["text"] + if not text: + raise JMCValueError( + "Unexpected emptyclipboard content", + self.raw_args["text"].token, + self.tokenizer) + + @lru_cache() + def inner(arg: str) -> SIMPLE_JSON_BODY: + return { + "action": "copy_to_clipboard", "value": self.args["text"].replace(self.args["indexString"], arg)} + self.add_formatted_text_prop( + "clickEvent", inner, self.check_bool("local")) + return "" + + +@func_property( + func_type=FuncType.LOAD_ONLY, + call_string="TextProp.hoverText", + arg_type={ + "propertyName": ArgType.STRING, + "text": ArgType.STRING, + "local": ArgType.KEYWORD + }, + name="text_prop_hover_text", + defaults={ + "local": "false" + } +) +class TextPropHoverText(JMCFunction): + def call(self) -> str: + text = self.raw_args["text"] + if not text: + raise JMCValueError( + "Unexpected empty FormattedText", + self.raw_args["text"].token, + self.tokenizer) + + self.add_formatted_text_prop( + "hoverEvent", { + "action": "show_text", "contents": json.loads(self.format_text("text"))}, self.check_bool("local")) + return "" + + +@func_property( + func_type=FuncType.LOAD_ONLY, + call_string="TextProps.hoverText", + arg_type={ + "propertyName": ArgType.STRING, + "indexString": ArgType.STRING, + "text": ArgType.STRING, + "local": ArgType.KEYWORD + }, + name="text_props_hover_text", + defaults={ + "local": "false" + } +) +class TextPropsHoverText(JMCFunction): + def call(self) -> str: + text = self.raw_args["text"] + if not text: + raise JMCValueError( + "Unexpected empty FormattedText", + self.raw_args["text"].token, + self.tokenizer) + + @lru_cache() + def inner(arg: str) -> SIMPLE_JSON_BODY: + return { + "action": "show_text", "contents": json.loads(self.format_text("text").replace(self.args["indexString"], arg))} + self.add_formatted_text_prop( + "hoverEvent", inner, self.check_bool("local")) + return "" + + +@func_property( + func_type=FuncType.LOAD_ONLY, + call_string="TextProp.hoverItem", + arg_type={ + "propertyName": ArgType.STRING, + "item": ArgType.JSON, + "local": ArgType.KEYWORD + }, + name="text_prop_hover_item", + defaults={ + "local": "false" + } +) +class TextPropHoverItem(JMCFunction): + def call(self) -> str: + item = self.raw_args["item"] + if not item: + raise JMCValueError( + "Missing item in TextProp.hoverItem", + self.raw_args["item"].token, + self.tokenizer) + + self.add_formatted_text_prop( + "hoverEvent", { + "action": "show_item", "contents": json.loads(self.args["item"])}, self.check_bool("local")) + return "" + + +@func_property( + func_type=FuncType.LOAD_ONLY, + call_string="TextProps.hoverItem", + arg_type={ + "propertyName": ArgType.STRING, + "indexString": ArgType.STRING, + "item": ArgType.JSON, + "local": ArgType.KEYWORD + }, + name="text_props_hover_item", + defaults={ + "local": "false" + } +) +class TextPropsHoverItem(JMCFunction): + def call(self) -> str: + item = self.raw_args["item"] + if not item: + raise JMCValueError( + "Missing item in TextProps.hoverItem", + self.raw_args["item"].token, + self.tokenizer) + + @lru_cache() + def inner(arg: str) -> SIMPLE_JSON_BODY: + return { + "action": "show_item", "contents": json.loads(self.args["item"].replace(self.args["indexString"], arg))} + self.add_formatted_text_prop( + "hoverEvent", inner, self.check_bool("local")) + return "" + + +@func_property( + func_type=FuncType.LOAD_ONLY, + call_string="TextProp.hoverEntity", + arg_type={ + "propertyName": ArgType.STRING, + "entity": ArgType.JSON, + "local": ArgType.KEYWORD + }, + name="text_prop_hover_entity", + defaults={ + "local": "false" + } +) +class TextPropHoverEntity(JMCFunction): + def call(self) -> str: + entity = self.raw_args["entity"] + if not entity: + raise JMCValueError( + "Missing entity in TextProp.hoverEntity", + self.raw_args["entity"].token, + self.tokenizer) + + self.add_formatted_text_prop( + "hoverEvent", { + "action": "show_entity", "contents": json.loads(self.args["entity"])}, self.check_bool("local")) + return "" + + +@func_property( + func_type=FuncType.LOAD_ONLY, + call_string="TextProps.hoverEntity", + arg_type={ + "propertyName": ArgType.STRING, + "indexString": ArgType.STRING, + "entity": ArgType.JSON, + "local": ArgType.KEYWORD + }, + name="text_props_hover_entity", + defaults={ + "local": "false" + } +) +class TextPropsHoverEntity(JMCFunction): + def call(self) -> str: + entity = self.raw_args["entity"] + if not entity: + raise JMCValueError( + "Missing entity in TextProps.hoverEntity", + self.raw_args["entity"].token, + self.tokenizer) + + @lru_cache() + def inner(arg: str) -> SIMPLE_JSON_BODY: + return { + "action": "show_entity", "contents": json.loads(self.args["entity"].replace(self.args["indexString"], arg))} + self.add_formatted_text_prop( + "hoverEvent", inner, self.check_bool("local")) + return "" + + +@func_property( + func_type=FuncType.LOAD_ONLY, + call_string="TextProp.font", + arg_type={ + "propertyName": ArgType.STRING, + "font": ArgType.STRING, + "local": ArgType.KEYWORD + }, + name="text_prop_font", + defaults={ + "local": "false" + } +) +class TextPropFont(JMCFunction): + def call(self) -> str: + font = self.raw_args["font"] + if not font: + raise JMCValueError( + "Missing font in TextProp.font", + self.raw_args["font"].token, + self.tokenizer) + + self.add_formatted_text_prop( + "font", self.args["font"], self.check_bool("local")) + return "" + + +@func_property( + func_type=FuncType.LOAD_ONLY, + call_string="TextProps.font", + arg_type={ + "propertyName": ArgType.STRING, + "indexString": ArgType.STRING, + "font": ArgType.STRING, + "local": ArgType.KEYWORD + }, + name="text_props_font", + defaults={ + "local": "false" + } +) +class TextPropsFont(JMCFunction): + def call(self) -> str: + font = self.raw_args["font"] + if not font: + raise JMCValueError( + "Missing font in TextProps.font", + self.raw_args["font"].token, + self.tokenizer) + + @lru_cache() + def inner(arg: str) -> SIMPLE_JSON_BODY: + return self.args["font"].replace(self.args["indexString"], arg) + self.add_formatted_text_prop( + "font", inner, self.check_bool("local")) + return "" + + +@func_property( + func_type=FuncType.LOAD_ONLY, + call_string="TextProp.keybind", + arg_type={ + "propertyName": ArgType.STRING, + "keybind": ArgType.STRING, + "local": ArgType.KEYWORD + }, + name="text_prop_keybind", + defaults={ + "local": "false" + } +) +class TextPropKeybind(JMCFunction): + def call(self) -> str: + keybind = self.raw_args["keybind"] + if not keybind: + raise JMCValueError( + "Missing keybind in TextProp.keybind", + self.raw_args["keybind"].token, + self.tokenizer) + + self.add_formatted_text_prop( + "keybind", self.args["keybind"], self.check_bool("local")) + return "" + + +@func_property( + func_type=FuncType.LOAD_ONLY, + call_string="TextProps.keybind", + arg_type={ + "propertyName": ArgType.STRING, + "indexString": ArgType.STRING, + "keybind": ArgType.STRING, + "local": ArgType.KEYWORD + }, + name="text_props_keybind", + defaults={ + "local": "false" + } +) +class TextPropsKeybind(JMCFunction): + def call(self) -> str: + keybind = self.raw_args["keybind"] + if not keybind: + raise JMCValueError( + "Missing keybind in TextProps.keybind", + self.raw_args["keybind"].token, + self.tokenizer) + + @lru_cache() + def inner(arg: str) -> SIMPLE_JSON_BODY: + return self.args["keybind"].replace(self.args["indexString"], arg) + self.add_formatted_text_prop( + "keybind", inner, self.check_bool("local")) + return "" + + +@func_property( + func_type=FuncType.LOAD_ONLY, + call_string="TextProp.nbt", + arg_type={ + "propertyName": ArgType.STRING, + "type": ArgType.KEYWORD, + "source": ArgType.STRING, + "path": ArgType.KEYWORD, + "interpret": ArgType.KEYWORD, + "local": ArgType.KEYWORD + }, + name="text_prop_nbt", + defaults={ + "interpret": "false", + "local": "false" + } +) +class TextPropNBT(JMCFunction): + def call(self) -> str: + _type = self.raw_args["type"] + if not _type: + raise JMCValueError( + "Missing NBT type in TextProp.nbt (should be `block`, `entity`, or `storage`)", + self.raw_args["type"].token, + self.tokenizer) + source = self.raw_args["source"] + if not source: + raise JMCValueError( + "Missing NBT source in TextProp.nbt", + self.raw_args["source"].token, + self.tokenizer) + path = self.raw_args["path"] + if not path: + raise JMCValueError( + "Missing NBT path in TextProp.nbt", + self.raw_args["path"].token, + self.tokenizer) + + self.add_formatted_text_prop( + "__private_nbt_expand__", {self.args["type"]: self.args["source"], "nbt": self.args["path"], "interpret": self.args["interpret"]}, self.check_bool("local")) + return "" + + +@func_property( + func_type=FuncType.LOAD_ONLY, + call_string="TextProps.nbt", + arg_type={ + "propertyName": ArgType.STRING, + "indexString": ArgType.STRING, + "type": ArgType.KEYWORD, + "source": ArgType.STRING, + "path": ArgType.KEYWORD, + "interpret": ArgType.KEYWORD, + "local": ArgType.KEYWORD + }, + name="text_props_nbt", + defaults={ + "interpret": "false", + "local": "false" + } +) +class TextPropsNBT(JMCFunction): + def call(self) -> str: + _type = self.raw_args["type"] + if not _type: + raise JMCValueError( + "Missing NBT type in TextProp.nbt (should be `block`, `entity`, or `storage`)", + self.raw_args["type"].token, + self.tokenizer) + source = self.raw_args["source"] + if not source: + raise JMCValueError( + "Missing NBT source in TextProp.nbt", + self.raw_args["source"].token, + self.tokenizer) + path = self.raw_args["path"] + if not path: + raise JMCValueError( + "Missing NBT path in TextProp.nbt", + self.raw_args["path"].token, + self.tokenizer) + + @lru_cache() + def inner(arg: str) -> SIMPLE_JSON_BODY: + return {self.args["type"]: self.args["source"], "nbt": self.args["path"].replace(self.args["indexString"], arg), "interpret": self.args["interpret"]} + self.add_formatted_text_prop( + "__private_nbt_expand__", inner, self.check_bool("local")) + return "" + + # @ func_property( # func_type=FuncType.load_only, # call_string='Debug.track', diff --git a/src/jmc/compile/command/condition.py b/src/jmc/compile/command/condition.py index ff9a010a..c990bc9d 100644 --- a/src/jmc/compile/command/condition.py +++ b/src/jmc/compile/command/condition.py @@ -185,7 +185,7 @@ def custom_condition( if tokens[0].string not in {"biome", "block", "blocks", "data", "dimension", "entity", "loaded", "predicate", "score"}: raise JMCValueError( - f"Unrecoginized condition '{tokens[0].string}'", + f"Unrecognized condition '{tokens[0].string}'", tokens[0], tokenizer, suggestion="Consider using 'block' or 'blocks' or 'data' or 'entity' or 'predicate' or 'score'.") diff --git a/src/jmc/compile/command/utils.py b/src/jmc/compile/command/utils.py index 2f8e05ba..ec968042 100644 --- a/src/jmc/compile/command/utils.py +++ b/src/jmc/compile/command/utils.py @@ -371,8 +371,8 @@ def eval_expr(expr: str) -> str: OPERATORS: dict[type, Callable[..., Any]] = {ast.Add: op.add, ast.Sub: op.sub, ast.Mult: op.mul, - ast.Div: op.truediv, ast.Pow: op.pow, - ast.USub: op.neg} + ast.Div: op.truediv, ast.FloorDiv: op.floordiv, ast.Mod: op.mod, + ast.Pow: op.pow, ast.USub: op.neg} def __eval(node): @@ -511,6 +511,7 @@ def __parse_bracket(self) -> None: "aqua", "dark_aqua", "blue", + "dark_blue", "light_purple", "dark_purple", "white", @@ -596,13 +597,24 @@ def __parse_bracket(self) -> None: raise JMCValueError( f"Unknown property '{prop_}'", self.token, self.tokenizer) key, json_body, is_local = self.datapack.data.formatted_text_prop[prop_] - if not callable(json_body): + if isinstance(json_body, dict) and 'nbt' in json_body.keys(): + self.current_json[key] = {} + self.current_json[key]["nbt"] = json_body["nbt"](arg) # type: ignore + self.current_json[key]["interpret"] = json_body["interpret"](arg) # type: ignore + if "entity" in json_body.keys(): + self.current_json[key]["entity"] = json_body["entity"] # type: ignore + if "block" in json_body.keys(): + self.current_json[key]["block"] = json_body["block"] # type: ignore + if "storage" in json_body.keys(): + self.current_json[key]["storage"] = json_body["storage"] # type: ignore + elif not callable(json_body): raise JMCValueError( f"Custom property '{prop_}' expected no argument", self.token, self.tokenizer, suggestion="Remove '()'") - if not arg: + elif not arg: raise JMCValueError( f"Expected value inside parenthesis of property '{prop_}' (got nothing)", self.token, self.tokenizer) - self.current_json[key] = json_body(arg) + else: + self.current_json[key] = json_body(arg) if is_local: del self.datapack.data.formatted_text_prop[prop_] continue @@ -622,7 +634,7 @@ def __parse_bracket(self) -> None: if "color" not in self.current_json and self.current_color: self.current_json["color"] = self.current_color - if "score" in self.current_json or "selector" in self.current_json: + if "score" in self.current_json or "selector" in self.current_json or "keybind" in self.current_json or "__private_nbt_expand__" in self.current_json: if not self.is_allow_score_selector: if "score" in self.current_json: raise JMCValueError( @@ -631,14 +643,25 @@ def __parse_bracket(self) -> None: "selector is not allowed in this context in formatted text", self.token, self.tokenizer) del self.current_json["text"] + if "__private_nbt_expand__" in self.current_json: + self.current_json["nbt"] = self.current_json["__private_nbt_expand__"]["nbt"] # type: ignore + self.current_json["interpret"] = self.current_json["__private_nbt_expand__"]["interpret"] # type: ignore + if "storage" in self.current_json["__private_nbt_expand__"]: # type: ignore + self.current_json["storage"] = self.current_json["__private_nbt_expand__"]["storage"] # type: ignore + if "block" in self.current_json["__private_nbt_expand__"]: # type: ignore + self.current_json["block"] = self.current_json["__private_nbt_expand__"]["block"] # type: ignore + if "entity" in self.current_json["__private_nbt_expand__"]: # type: ignore + self.current_json["entity"] = self.current_json["__private_nbt_expand__"]["entity"] # type: ignore + del self.current_json["__private_nbt_expand__"] + tmp_json: SIMPLE_JSON_TYPE = {"text": ""} for prop_, value_ in self.current_json.items(): - if prop_ in {"bold", "italic", "underlined", + if prop_ in {"bold", "italic", "underlined", "strikethrough", "obfuscated", "color"}: tmp_json[prop_] = value_ self.result.append(self.current_json) self.current_json = tmp_json - + def __parse_code(self, char: str) -> None: """ Parse color code @@ -660,6 +683,7 @@ def __parse_code(self, char: str) -> None: "aqua", "dark_aqua", "blue", + "dark_blue", "light_purple", "dark_purple", "white", diff --git a/src/jmc/compile/command/var_operation.py b/src/jmc/compile/command/var_operation.py index 79c62688..26d71a05 100644 --- a/src/jmc/compile/command/var_operation.py +++ b/src/jmc/compile/command/var_operation.py @@ -189,7 +189,10 @@ def variable_operation( if isinstance(scoreboard_player.value, int): raise ValueError("scoreboard_player.value is int") - return f"scoreboard players operation {left_token.string} {objective_name} {operator} {scoreboard_player.value[1]} {scoreboard_player.value[0]}" + if operator == "??=": + return f"execute unless score {left_token.string} {objective_name} = {left_token.string} {objective_name} run scoreboard players operation {left_token.string} {objective_name} = {scoreboard_player.value[1]} {scoreboard_player.value[0]}" + else: + return f"scoreboard players operation {left_token.string} {objective_name} {operator} {scoreboard_player.value[1]} {scoreboard_player.value[0]}" raise JMCSyntaxException( f"Unrecognized operator ({operator})", tokens[1], tokenizer) diff --git a/src/jmc/compile/compiling.py b/src/jmc/compile/compiling.py index 6f29a0a5..1e75b04a 100644 --- a/src/jmc/compile/compiling.py +++ b/src/jmc/compile/compiling.py @@ -322,10 +322,10 @@ def build(datapack: DataPack, config: "Configuration", is_delete: bool, cert_con dump(tick_json, file, indent=4) for func_path, func in datapack.functions.items(): - if header.is_override_minecraft and func_path.startswith("minecraft/"): - # len("minecraft/") = 10 - path = output_folder / "data" / "minecraft" / "functions" / \ - (func_path[10:] + ".mcfunction") + namespace = func_path.split("/")[0] + if namespace in header.namespace_overrides: + path = output_folder / "data" / namespace / "functions" / \ + (func_path[len(namespace)+1:] + ".mcfunction") else: path = namespace_folder / "functions" / (func_path + ".mcfunction") content = post_process(func.content) @@ -338,10 +338,10 @@ def build(datapack: DataPack, config: "Configuration", is_delete: bool, cert_con file.write(content) for json_path, json in datapack.jsons.items(): - if header.is_override_minecraft and json_path.startswith("minecraft/"): - # len("minecraft/") = 10 + namespace = json_path.split("/")[0] + if namespace in header.namespace_overrides: path = output_folder / "data" / \ - "minecraft" / (json_path[10:] + ".json") + namespace / (json_path[len(namespace)+1:] + ".json") else: path = namespace_folder / (json_path + ".json") if json: diff --git a/src/jmc/compile/header.py b/src/jmc/compile/header.py index 3f9284f4..1fcdb38b 100644 --- a/src/jmc/compile/header.py +++ b/src/jmc/compile/header.py @@ -48,7 +48,7 @@ class Header(SingleTon): """Dictionary of string to replace and what to replace it with""" is_enable_macro: bool """Whether to enable macro at the time of creating a token""" - is_override_minecraft: bool + namespace_overrides: set[str] """Whether to allow jmc to take control over minecraft namespace""" commands: set[str] """List of extra command(first arguments) to allow""" @@ -76,7 +76,7 @@ def __clear(obj: "Header"): obj.macros = {} obj.credits = [] obj.is_enable_macro = True - obj.is_override_minecraft = False + obj.namespace_overrides = set() obj.commands = set() obj.statics = set() obj.dels = set() diff --git a/src/jmc/compile/header_parse.py b/src/jmc/compile/header_parse.py index b0dba687..9155b08a 100644 --- a/src/jmc/compile/header_parse.py +++ b/src/jmc/compile/header_parse.py @@ -175,12 +175,12 @@ def __parse_header(header_str: str, file_name: str, # #define KEYWORD(arg1, arg2) header.macros[key] = __create_macro_factory( arg_tokens[2:], arg_tokens[1], key, tokenizer, HeaderSyntaxException( - "Invalid marcro argument syntax", file_name, line, line_str)) + "Invalid macro argument syntax", file_name, line, line_str)) else: # #define KEYWORD TOKEN header.macros[key] = __create_macro_factory( arg_tokens[1:], None, key, tokenizer, HeaderSyntaxException( - "Invalid marcro argument syntax", file_name, line, line_str)) + "Invalid macro argument syntax", file_name, line, line_str)) # #bind elif directive_token.string == "bind": @@ -256,12 +256,13 @@ def __parse_header(header_str: str, file_name: str, else: header.credits.append("") - # #override_minecraft - elif directive_token.string == "override_minecraft": - if arg_tokens: + # #override + + elif directive_token.string == "override": + if not arg_tokens or len(arg_tokens) != 1: raise HeaderSyntaxException( - f"Expected 0 arguments after '#override_minecraft' (got {len(arg_tokens)})", file_name, line, line_str) - header.is_override_minecraft = True + f"Expected 1 arguments after '#namespace' (got {len(arg_tokens)})", file_name, line, line_str) + header.namespace_overrides.add(arg_tokens[0].string) # #command elif directive_token.string == "command": diff --git a/src/jmc/compile/lexer.py b/src/jmc/compile/lexer.py index 965bbc67..a024425f 100644 --- a/src/jmc/compile/lexer.py +++ b/src/jmc/compile/lexer.py @@ -223,7 +223,8 @@ def parse_decorated_function(self, tokenizer: Tokenizer, raise JMCSyntaxException( f"Unrecognized decorator '{decorator_name}'", command[0], - tokenizer) + tokenizer, + suggestion="Did you mean to write 'import' instead?" if decorator_name == "import" else None) jmc_decorator = DECORATORS[decorator_name] if len(command) < 5: raise JMCSyntaxException( @@ -358,9 +359,9 @@ def parse_new(self, tokenizer: Tokenizer, json_name = prefix + convention_jmc_to_mc( command[2], tokenizer, is_make_lower=False, substr=(1, -1)) - if Header().is_override_minecraft and json_name.startswith("minecraft/"): - # len('minecraft/') = 10 - json_path = "minecraft/" + json_type + "/" + json_name[10:] + namespace = json_name.split("/")[0] + if namespace in Header().namespace_overrides: + json_path = namespace + "/" + json_type + "/" + json_name[len(namespace)+1:] else: json_path = json_type + "/" + json_name