def add_var(self, name: str, value: Resource) -> Resource: """ Adds a variable to this context and assigns it the correct variable context. Note that there must be a valid variable context! Fails if the variable name does already exist. Args: name: the name of the variable value: the resource Returns: None Raises: ValueError: If the resource does already exist """ if name in self.namespace: raise ValueError(f"Variable {name} does already exist!") # should an error be thrown when no history is found? variable_context = self.variable_context.get(name, None) if variable_context is None: Logger.debug( f"[Context] No context data available for variable '{name}' ({value})" ) value.is_variable = True self.namespace[name] = self.Variable(value, variable_context) return value
def toJson(self, markup: str, *args: Resource) -> List[dict]: """ Converts a markup string to a minecraft json format string. Args: markup: the markup string *args: resources to insert into the placeholders Returns: A json string """ self.state = dict(args=args, used_placeholders=set()) try: Logger.debug(f"[MarkupParser] parsing '{markup}'") tree = get_json_markup_grammar().parse(markup) debug_log_text(tree.pretty(), "Parse tree: ") data = self.visit(tree) except UnexpectedToken as e: raise McScriptInvalidMarkupError(f"\nFailed to parse Markup string:\n" f"{e.get_context(markup, span=len(markup))}" f"Unexpected token: {e.token.type}('{e.token}')\n" f"Expected one of {e.expected}", self.compileState) all_args = set(range(len(args))) for used_arg in self.state["used_placeholders"]: all_args.remove(used_arg) if all_args: raise McScriptArgumentError(f"Not all arguments were used!\nunused indices: " f"{', '.join(str(i) for i in all_args)}", self.compileState) # remove duplicate text elements return self.compact_data(data)
def debug_log_text(text: str, message): """Debug logs a multi-line text and annotates it with line numbers.""" text = text.strip().split("\n") padding = len(str(len(text))) debug_code = "\n\t".join(f"[{str(index + 1).zfill(padding)}] {i}" for index, i in enumerate(text)) Logger.debug(f"{message}\n\t{debug_code}")
def __init__(self, msg: str): Logger.error(f"Exception occurred: {self.__class__.__name__}") debug_exception = ["\n".join(f"\t* {i}" for i in msg.split("\n"))] super().__init__(msg) debug_exception += self.get_stack() debug_exception = "\n".join(debug_exception) Logger.debug(f"Exception message:\n{debug_exception}")
def __post_init__(self): if len(self.name) > 16: raise ValueError("Cannot create a scoreboard with a length > 16") if self.index >= len(VALID_OBJECTIVE_CHARACTERS)**3: raise ValueError( f"Maximum id exceeded: {self.index} expected at most {len(VALID_OBJECTIVE_CHARACTERS) ** 3 - 1}" ) Logger.debug( f"[Scoreboard] created {self.name} with id {self.get_name()}")
def runDataGenerator(version: str, fpath: str) -> str: """ Downloads the versions, runs the data generator and returns the full path to the generated data""" path, file = split(download_minecraft_server(version, fpath)) Logger.info("[Assets] generating minecraft data...") # test if java is installed process = run(["java", "-version"], cwd=path) Logger.info("Process java version result: {}".format(process.returncode)) completedProcess = run( ["java", "-cp", file, "net.minecraft.data.Main", "-all"], cwd=path) completedProcess.check_returncode() return join(path, "generated")
def run_code(rcon: MCRcon, test_world: MCWorld, code: str) -> Tuple[Optional[int], str]: config = Config() config.output_dir = Path(test_world.getDatapackPath()) / "mcscript" config.input_string = TEST_TEMPLATE.format(code) datapack = compileMcScript(config) Logger.info(datapack.getMainDirectory().getPath("functions").files) generate_datapack(config, datapack) rcon.command("reload") result = rcon.command(f"scoreboard players get result mcscript_test") match = RESULT_PATTERN.match(result) if match is not None: value = int(match.group(1)) else: value = None return value, result
def downloadVersionManifest() -> Dict: """ Downloads the minecraft version manifest and returns it parsed as a dictionary Format: { "latest": { "release": str "snapshot": str }, "versions": [ { "id": str, "type": str, "url": str, "time": str, "releaseTime": str } ] } Returns: The version manifest """ Logger.info("[Assets] Fetching version manifest...") request = _get(VERSION_MANIFEST_URL) manifest = json.load(request) Logger.info("[Assets] Successfully fetched version manifest") Logger.debug(manifest) return manifest
def makeData(version: str) -> str: """ writes all important minecraft data to a file and returns the path Note that this wil crash if the version is below 1.14. """ version = version or get_latest_version() # first test if the file already exists directory = getVersionDir(version) file = join(directory, "data.json") if exists(file): with open(file) as f: try: data = json.load(f) if data.get("version", 0) != assets.__version__: Logger.warn( f"File '{file}' uses old version format {data.get('version', 0)} " f"(Required {assets.__version__})") else: Logger.debug(f"Already got data for version {version}") return file except json.JSONDecodeError: pass contents = getDataJson(version) contents["version"] = assets.__version__ with open(file, "w+") as f: json.dump(contents, f) Logger.info(f"Create data file {file}") return file
def compileMcScript(config: Config, callback: Callable = None) -> Datapack: """ compiles a mcscript string and returns the generated datapack. Args: callback: a callback function that accepts the current state, the progress and the temporary object config: the config. Should contain the input file and the target path Returns: A datapack """ steps = ( (_parseCode, "Parsing"), (lambda tree: Analyzer().analyze(tree), "Analyzing context"), (lambda tree: get_compiler().compile(tree[0], tree[1], text, config), "Compiling"), (lambda ir_master: get_default_backend()(config, ir_master).generate(), "Running ir backend") ) text = config.input_string if Logger.isEnabledFor(DEBUG): debug_log_text(text, "[Compile] parsing the following code: ") global_start_time = perf_counter() arg = text for index, step in enumerate(steps): if callback is not None: callback(step[1], index / len(steps), arg) start_time = perf_counter() try: arg = step[0](arg) except Exception as e: if not isinstance(e, McScriptError): Logger.critical(f"Internal compiler error occurred: {repr(e)}") raise e Logger.info(f"{step[1]} finished in {perf_counter() - start_time:.4f} seconds") if isinstance(arg, Tree): _debug_log_tree(arg) if callback is not None: callback("Done", 1, arg) Logger.info(f"Run all steps in {perf_counter() - global_start_time:.4f} seconds") # noinspection PyTypeChecker return arg
def __init__(self, path: str = None): if Config.currentConfig: Config.currentConfig = self Logger.warning("[Config] currentConfig already exists!") Config.currentConfig = self self.path = path self.config = configparser.ConfigParser() self._input_string: Optional[str] = None self._world: Optional[MCWorld] = None self._output_dir: Optional[str] = None self._data_manager: DataManager = DataManager() self.config["main"] = { "release": "False", "minecraft_version": "", "name": "mcscript" } self.config["scores"] = { "format_string": ".exp_{block}_{index}" } # maximum scoreboard name has 16 chars so `name` must contain 12 chars at most self.config["scoreboards"] = { "main": self["main"]["name"] } self.config["storage"] = { "name": "main", "stack": "state.stack", "temp": "state.temp" } if path: if not exists(path): Logger.info("[Config] Write default config") with open(path, "w+") as f: self.config.write(f) self.config.read(path) Logger.info("[Config] loaded from file") if not self.checkData(): raise ValueError("Invalid values for config detected!")
def download_minecraft_server(version_id: str, target: str) -> str: """ Downloads the minecraft server for version `version_id` and returns the full path. Args: version_id: the id for the version target: the target directory Returns: The full file path to `server.jar` """ url = getVersionUrl(version_id) Logger.info(f"[Assets] Downloading minecraft server json at {url}") version = json.load(_get(url)) Logger.info("[Assets] Downloaded version.json") Logger.debug(version) server_url = version["downloads"]["server"]["url"] Logger.info(f"[Assets] Starting to download server {version['id']}...") with _get(server_url) as server: download_size = server.length with click.progressbar(length=download_size, label="Downloading server jar") as bar: fpath = join(target, "server.jar") with open(fpath, "wb+") as f: while chunk := server.read(16384): f.write(chunk) bar.update(len(chunk)) Logger.info(f"[Assets] Downloaded {download_size / 1000000:.2f} mb") Logger.info(f"[Assets] Created server.jar at {fpath}")
def _debug_log_tree(tree: Tree): if Logger.isEnabledFor(DEBUG): debug_log_text(tree.pretty(), "[Compile] Intermediate Tree:")
def import_sub_modules(): for name in iterBuiltins(): Logger.debug(f"[Resource] auto-importing {name}") import_module("." + name, __name__)
identifier, *_ = accessor.children self._handle_variable(str(identifier), tree) def operation_ip(self, tree: Tree): accessor, operator, expression = tree.children self.visit(expression) identifier, *_ = accessor.children if var := getVar(self.stack, identifier): var.writes.append(VariableAccess(tree, self.stack[-1].definition)) else: # otherwise the user tries to access an undefined variable Logger.error( f"[Analyzer] invalid variable access: '{identifier}' is not defined" ) def index_setter(self, tree: Tree): accessor, index, expression = tree.children self.visit(expression) identifier, *not_implemented = accessor.children if not_implemented: return if var := getVar(self.stack, identifier): var.writes.append(VariableAccess(tree, self.stack[-1].definition)) else: Logger.error( f"[Analyzer] invalid variable array setter: '{identifier}' is not defined"
current_x += x_step j += 1.0 } current_y += y_step i += 1.0 } } execute("kill @e[tag=$marker_name]") } } """ if __name__ == '__main__': mcDir = join(getcwd(), "server") world = getWorld("McScript", mcDir) config = Config("config.ini") config.world = world code = code_temp # code = getScript("mandelbrot") config.input_string = code datapack = compileMcScript( config, lambda a, b, c: Logger.info(f"[compile] {a}: {round(b * 100, 2)}%")) generate_datapack(config, datapack) rcon.send("reload")