diff --git a/CompileError.py b/CompileError.py index 0f34308..ee9fc78 100644 --- a/CompileError.py +++ b/CompileError.py @@ -1,3 +1,38 @@ +from traceback import print_exception +from pathlib import Path +from contextvars import ContextVar + +cur_file_var = ContextVar("cur_file_var",default=None) + +class Pos: + file: Path + line: int + + def __init__(self, line: int, file: Path | None = None): + self.line = line + self.file = file or cur_file_var.get() + class CompileError(Exception): - def __init__(self, text): - super(Exception, self).__init__(text) \ No newline at end of file + where: None | Pos + def __init__(self, text, where = None): + super(Exception, self).__init__(text) + self.where = where + +line_cache = {} + +def register_lines(filename: str, lines: list[str]): + line_cache[filename] = lines + +def pretty_print_error(ex: Exception, file = None): + stack = [] + while (ex.__cause__ is not None): + stack.append(ex) + ex = ex.__cause__ + stack.append(ex) + for e in stack[::-1]: + if isinstance(e, CompileError): + print(e,file=file) + if e.where is not None and e.where.file in line_cache: + print(f"{e.where.line:>4} |",line_cache[e.where.file][e.where.line-1],file=file) + continue + print_exception(e,chain=False, file = file) diff --git a/Scripts/perf.cbscript b/Scripts/perf.cbscript index 310a3af..8fa2279 100644 --- a/Scripts/perf.cbscript +++ b/Scripts/perf.cbscript @@ -1,4 +1,4 @@ -dir "C:\Users\Seth\AppData\Roaming\.minecraft 1.20\saves\Perf" +dir "Perf" desc "Test performance concerns" import common @@ -64,4 +64,4 @@ function macro_call_2() end k = val.val + 1 val.val = k -end \ No newline at end of file +end diff --git a/Scripts/physics_sandbox.cbscript b/Scripts/physics_sandbox.cbscript index fa1ba42..36c7006 100644 --- a/Scripts/physics_sandbox.cbscript +++ b/Scripts/physics_sandbox.cbscript @@ -1,4 +1,4 @@ -dir "C:\Users\Seth\AppData\Roaming\.minecraft 1.20\saves\Physics - One Wall" +dir "Physics - One Wall" desc "Rigid Body Physics Sandbox" scale 1000 diff --git a/Scripts/plinko_demo.cbscript b/Scripts/plinko_demo.cbscript index fe96399..eb065d3 100644 --- a/Scripts/plinko_demo.cbscript +++ b/Scripts/plinko_demo.cbscript @@ -1,4 +1,4 @@ -dir "C:\Users\Seth\AppData\Roaming\.minecraft 1.20\saves\Plinko Demo" +dir "Plinko Demo" desc "Demonstrates plinko" scale 1000 @@ -37,4 +37,4 @@ clock tick end end end -end \ No newline at end of file +end diff --git a/Scripts/stupid_mobile_game.cbscript b/Scripts/stupid_mobile_game.cbscript index 795dbd5..72cf4d6 100644 --- a/Scripts/stupid_mobile_game.cbscript +++ b/Scripts/stupid_mobile_game.cbscript @@ -1,4 +1,4 @@ -dir "C:\Users\Seth\AppData\Roaming\.minecraft 1.20\saves\Stupid Mobile Game Test" +dir "Stupid Mobile Game Test" desc "Physics engine for stupid mobile game." scale 1000 @@ -492,4 +492,4 @@ function load_state() end end end -end \ No newline at end of file +end diff --git a/Scripts/tetherblock.cbscript b/Scripts/tetherblock.cbscript index 31e325a..9222f62 100644 --- a/Scripts/tetherblock.cbscript +++ b/Scripts/tetherblock.cbscript @@ -1,4 +1,4 @@ -dir "C:\Users\Seth\AppData\Roaming\.minecraft 1.20\saves\TetherBlock" +dir "TetherBlock" desc "TetherBlock game" scale 1000 diff --git a/Scripts/transform_demo.cbscript b/Scripts/transform_demo.cbscript index b7951d5..3a041a6 100644 --- a/Scripts/transform_demo.cbscript +++ b/Scripts/transform_demo.cbscript @@ -1,4 +1,4 @@ -dir "C:\Users\Seth\AppData\Roaming\.minecraft 1.20\saves\Transform Demo" +dir "Transform Demo" desc "Demonstrates transform parameters" scale 1000 @@ -123,4 +123,4 @@ clock tick end end -end \ No newline at end of file +end diff --git a/block_types/array_definition_block.py b/block_types/array_definition_block.py index 8cb05f9..0609866 100644 --- a/block_types/array_definition_block.py +++ b/block_types/array_definition_block.py @@ -1,7 +1,7 @@ from .block_base import block_base from .command_block import command_block from variable_types.scoreboard_var import scoreboard_var -from CompileError import CompileError +from CompileError import CompileError, Pos class array_definition_block(block_base): def __init__(self, line, name, from_val, to_val, selector_based): @@ -16,7 +16,7 @@ def compile(self, func): from_val = int(self.from_val.get_value(func)) to_val = int(self.to_val.get_value(func)) except Exception: - raise CompileError(f'Unable to get array range for "{self.name}" at line {self.line}') + raise CompileError(f'Unable to get array range for "{self.name}" at line {self.line}', Pos(self.line)) from None name = self.name diff --git a/block_types/block_base.py b/block_types/block_base.py index 55e78dd..be3d33b 100644 --- a/block_types/block_base.py +++ b/block_types/block_base.py @@ -1,7 +1,9 @@ -from CompileError import CompileError +from CompileError import CompileError, Pos import traceback class block_base(object): + def __init__(self, line): + self.line = line def compile(self, func): raise NotImplementedError('Section does not implement compile()') @@ -12,11 +14,9 @@ def compile_lines(self, func, lines): try: func.compile_blocks(lines) except CompileError as e: - print(e) - raise CompileError(f'Unable to compile {self.block_name} at line {self.line}') - except e: - print(traceback.format_exc()) - raise CompileError(f'Error compiling {self.block_name} at line {self.line}') + raise CompileError(f'Unable to compile {self.block_name} at line {self.line}', Pos(self.line)) from e + except Exception as e: + raise CompileError(f'Error compiling {self.block_name} at line {self.line}', Pos(self.line)) from e @property def block_name(self): diff --git a/block_types/block_definition_block.py b/block_types/block_definition_block.py index a3b6488..03c502d 100644 --- a/block_types/block_definition_block.py +++ b/block_types/block_definition_block.py @@ -1,6 +1,6 @@ from .block_base import block_base from data_types.relcoords import relcoords -from CompileError import CompileError +from CompileError import CompileError, Pos class block_definition_block(block_base): def __init__(self, line, block_id, items, coords): @@ -21,7 +21,10 @@ def copy_to_objective(self, func, path, coords, macro_args, objective): coords = self.coords if path not in self.paths: - raise CompileError(f'No path "{path}" defined for [{self.block_id}].') + raise CompileError( + f'No path "{path}" defined for [{self.block_id}].', + Pos(self.line) + ) self.paths[id].copy_to_objective(func, coords, macro_args, objective) @@ -30,7 +33,10 @@ def copy_from(self, func, path, coords, macro_args, var): coords = self.coords if path not in self.paths: - raise CompileError(f'No path "{path}" defined for [{self.block_id}].') + raise CompileError( + f'No path "{path}" defined for [{self.block_id}].', + Pos(self.line) + ) self.paths[path].copy_from(func, coords, macro_args, var) @@ -39,6 +45,6 @@ def get_command(self, func, path, coords, macro_args): coords = self.coords if path not in self.paths: - raise CompileError(f'No path "{path}" defined for [{self.block_id}].') + raise CompileError(f'No path "{path}" defined for [{self.block_id}].',Pos(self.line)) return self.paths[path].get_command(func, coords, macro_args) diff --git a/block_types/block_id_switch_block.py b/block_types/block_id_switch_block.py index c7ddbc1..05048a1 100644 --- a/block_types/block_id_switch_block.py +++ b/block_types/block_id_switch_block.py @@ -1,5 +1,5 @@ from .block_switch_base import block_switch_base -from CompileError import CompileError +from CompileError import CompileError, Pos class block_id_switch_block(block_switch_base): def __init__(self, line, expr, cases, include_block_states): @@ -15,8 +15,7 @@ def compile_initialization(self, func): var = self.expr.compile(func) self.condition_var = var.get_scoreboard_var(func) except CompileError as e: - print(e) - raise CompileError(f'Unable to compile switch expression at line {self.line}.') + raise CompileError(f'Unable to compile switch expression at line {self.line}.', Pos(self.line)) from e def case_condition(self, block_id): return f'score {self.condition_var.selector} {self.condition_var.objective} matches {block_id}' @@ -36,8 +35,7 @@ def compile_block_case(self, func, id): try: case.compile(block, block_state, id, case_func, falling_block_nbt) except CompileError as e: - print(e) - raise CompileError(f'Unable to compile block switch at line {self.line}') + raise CompileError(f'Unable to compile block switch at line {self.line}', Pos(self.line)) from e func.call_function( case_func, diff --git a/block_types/block_switch_base.py b/block_types/block_switch_base.py index 1f69024..a64acb8 100644 --- a/block_types/block_switch_base.py +++ b/block_types/block_switch_base.py @@ -1,5 +1,5 @@ from .block_base import block_base -from CompileError import CompileError +from CompileError import CompileError, Pos class block_switch_base(block_base): def __init__(self): @@ -7,7 +7,8 @@ def __init__(self): for case in self.cases: if case.is_default: if self.default_case: - raise CompileError(f'Block switch at line {self.line} has multiple default cases.') + raise CompileError( + f'Block switch at line {self.line} has multiple default cases.', Pos(self.line)) else: self.default_case = case @@ -123,4 +124,4 @@ def get_matching_case(self, func, block, state): if not case.is_default and case.block_name in func.block_tags and block.replace('minecraft:','') in func.block_tags[case.block_name]: return case - return self.default_case \ No newline at end of file + return self.default_case diff --git a/block_types/block_switch_block.py b/block_types/block_switch_block.py index caee58c..ba188e5 100644 --- a/block_types/block_switch_block.py +++ b/block_types/block_switch_block.py @@ -1,5 +1,5 @@ from .block_switch_base import block_switch_base -from CompileError import CompileError +from CompileError import CompileError, Pos class block_switch_block(block_switch_base): def __init__(self, line, coords, cases, include_block_states): @@ -98,7 +98,7 @@ def compile_single_case(self, func, block, block_test, block_state, case_func_na case.compile(block.replace('minecraft:', ''), block_state, id, case_func, falling_block_nbt) except CompileError as e: print(e) - raise CompileError(f'Unable to compile block switch case "{block_state}" at line {self.line}') + raise CompileError(f'Unable to compile block switch case "{block_state}" at line {self.line}', Pos(self.line)) from e func.call_function( case_func, @@ -115,4 +115,4 @@ def get_range_condition(self, func, blocks): return f'block {self.coords.get_value(func)} #{func.namespace}:{block_tag_name}' def get_case_ids(self): - return sorted(self.block_list.keys()) \ No newline at end of file + return sorted(self.block_list.keys()) diff --git a/block_types/execute_base.py b/block_types/execute_base.py index 5b76318..b2be668 100644 --- a/block_types/execute_base.py +++ b/block_types/execute_base.py @@ -1,6 +1,6 @@ from .block_base import block_base import traceback -from CompileError import CompileError +from CompileError import CompileError, Pos class execute_base(block_base): # Override to force the creation of a sub function, even for a single command @@ -20,16 +20,14 @@ def perform_execute(self, func): cmd = 'execute ' + func.get_execute_items(self.exec_items, exec_func) if cmd == None: - raise CompileError(f'Unable to compile {self.display_name()} block at line {self.line}') + raise CompileError(f'Unable to compile {self.display_name()} block at line {self.line}', Pos(self.line)) try: exec_func.compile_blocks(self.sub) except CompileError as e: - print(e) - raise CompileError(f'Unable to compile {self.display_name()} block contents at line {self.line}') + raise CompileError(f'Unable to compile {self.display_name()} block contents at line {self.line}', Pos(self.line)) from e except Exception as e: - print(traceback.format_exc()) - raise CompileError(f'Unable to compile {self.display_name()} block contents at line {self.line}') + raise CompileError(f'Unable to compile {self.display_name()} block contents at line {self.line}') from e single = exec_func.single_command() if single == None or self.force_sub_function(): @@ -62,4 +60,4 @@ def perform_execute(self, func): self.compile_else(func) def compile_else(self, func): - None \ No newline at end of file + None diff --git a/block_types/execute_block.py b/block_types/execute_block.py index fe6c766..687b989 100644 --- a/block_types/execute_block.py +++ b/block_types/execute_block.py @@ -1,5 +1,5 @@ from .execute_base import execute_base -from CompileError import CompileError +from CompileError import CompileError, Pos import traceback class execute_block(execute_base): @@ -38,11 +38,9 @@ def compile_else(self, func): try: exec_func.compile_blocks(else_sub) except CompileError as e: - print(e) - raise CompileError(f'Unable to compile else block contents at line {self.display_name()}') + raise CompileError(f'Unable to compile else block contents at line {self.display_name()}', Pos(self.line)) from e except Exception as e: - print(traceback.format_exc()) - raise CompileError(f'Unable to compile else block contents at line {self.display_name()}') + raise CompileError(f'Unable to compile else block contents at line {self.display_name()}',Pos(self.line)) from e if execute_items == None: exec_text = '' @@ -77,4 +75,4 @@ def compile_else(self, func): func.add_command(cmd) - func.free_scratch(self.scratch) \ No newline at end of file + func.free_scratch(self.scratch) diff --git a/block_types/for_index_block.py b/block_types/for_index_block.py index 86ae5c8..50f6036 100644 --- a/block_types/for_index_block.py +++ b/block_types/for_index_block.py @@ -1,6 +1,6 @@ from .block_base import block_base import traceback -from CompileError import CompileError +from CompileError import CompileError, Pos class for_index_block(block_base): def __init__(self, line, var, fr, to, by, sub): @@ -46,11 +46,9 @@ def compile(self, func): try: loop_func.compile_blocks(sub) except CompileError as e: - print(e) - raise CompileError(f'Unable to compile for block contents at line {self.line}') + raise CompileError(f'Unable to compile for block contents at line {self.line}', Pos(self.line)) from e except Exception as e: - print(traceback.print_exc()) - raise CompileError(f'Unable to compile for block contents at line {self.line}') + raise CompileError(f'Unable to compile for block contents at line {self.line}', Pos(self.line)) from e if by == None: # Use a temporary version of the counting var to work with the scoreboard diff --git a/block_types/for_selector_block.py b/block_types/for_selector_block.py index a11c862..16943e3 100644 --- a/block_types/for_selector_block.py +++ b/block_types/for_selector_block.py @@ -1,5 +1,5 @@ from .block_base import block_base -from CompileError import CompileError +from CompileError import CompileError, Pos import traceback class for_selector_block(block_base): @@ -26,11 +26,9 @@ def compile(self, func): try: exec_func.compile_blocks(self.sub) except CompileError as e: - print(e) - raise CompileError(f'Unable to compile for block contents at line {self.line}') - except: - print(traceback.format_exc()) - raise CompileError(f'Unable to compile for block contents at line {self.line}') + raise CompileError(f'Unable to compile for block contents at line {self.line}',Pos(self.line)) from e + except Exception as e: + raise CompileError(f'Unable to compile for block contents at line {self.line}',Pos(self.line)) from e exec_func.add_command(f'scoreboard players set @s {scratch_id} 0') diff --git a/block_types/import_block.py b/block_types/import_block.py index 2f1e88b..4133417 100644 --- a/block_types/import_block.py +++ b/block_types/import_block.py @@ -1,6 +1,6 @@ from .block_base import block_base import traceback -from CompileError import CompileError +from CompileError import CompileError, Pos class import_block(block_base): def __init__(self, line, filename): @@ -11,11 +11,11 @@ def compile(self, func): try: func.import_file(self.filename + '.cblib') except SyntaxError as e: - print(e) - raise CompileError(f'Importing file "{self.filename}" failed at line {self.line}:\n{e}') + raise CompileError(f'Importing file "{self.filename}" failed at line {self.line}:\n{e}',Pos(self.line)) from e except CompileError as e: - print(e) - raise CompileError(f'Importing file "{self.filename}" failed at line {self.line}:\n{e}') + raise CompileError(f'Importing file "{self.filename}" failed at line {self.line}:\n{e}',Pos(self.line)) from e + except FileNotFoundError: + raise CompileError(f'Could not find file "{self.filename}", at line {self.line}',Pos(self.line)) from None except Exception as e: print(traceback.format_exc()) - raise CompileError(f'Importing file "{self.filename}" failed at line {self.line}:\n{e}') + raise CompileError(f'Importing file "{self.filename}" failed at line {self.line}:\n{e}',Pos(self.line)) from e diff --git a/block_types/macro_call_block.py b/block_types/macro_call_block.py index f4edeb5..eeb612e 100644 --- a/block_types/macro_call_block.py +++ b/block_types/macro_call_block.py @@ -1,6 +1,6 @@ from .block_base import block_base from mcfunction import isNumber -from CompileError import CompileError +from CompileError import CompileError, Pos import traceback class macro_call_block(block_base): @@ -10,12 +10,12 @@ def __init__(self, line, macro, args): def compile(self, func): if self.macro not in func.macros: - raise CompileError(f'Line {self.line}: macro "{self.macro}" does not exist') + raise CompileError(f'Line {self.line}: macro "{self.macro}" does not exist', Pos(self.line)) params, sub = func.macros[self.macro] if len(self.args) != len(params): - raise CompileError(f'Tried to call Macro "{macro}" with {len(args)} arguments at line {get_line(line)}, but it requires {len(params)}') + raise CompileError(f'Tried to call Macro "{self.macro}" with {len(self.args)} arguments at line {self.line}, but it requires {len(params)}', Pos(self.line)) new_env = func.clone_environment() @@ -27,10 +27,8 @@ def compile(self, func): try: func.compile_blocks(sub) except CompileError as e: - print(e) - raise CompileError(f'Unable to compile macro contents at line {self.line}') + raise CompileError(f'Unable to compile macro contents at line {self.line}', Pos(self.line)) from e except Exception as e: - print(traceback.format_exc()) - raise CompileError(f'Unable to compile macro contents at line {self.line}') + raise CompileError(f'Unable to compile macro contents at line {self.line}') from e func.pop_environment() diff --git a/block_types/python_for_block.py b/block_types/python_for_block.py index a3abeca..cccf253 100644 --- a/block_types/python_for_block.py +++ b/block_types/python_for_block.py @@ -1,6 +1,6 @@ from .block_base import block_base import math -from CompileError import CompileError +from CompileError import CompileError, Pos class python_for_block(block_base): def __init__(self, line, ids, val, sub): @@ -14,8 +14,8 @@ def compile(self, func): try: iter(set) - except: - raise CompileError(f'"{set}" in "for" block at line {self.line} is not an iterable set.') + except TypeError: + raise CompileError(f'"{set}" in "for" block at line {self.line} is not an iterable set.', Pos(self.line)) from None for v in set: if len(self.ids) == 1: @@ -24,17 +24,14 @@ def compile(self, func): try: v_len = len(v) except Exception as e: - print(e) - raise CompileError(f'Set is not a tuple at line {self.line}') + raise CompileError(f'Set is not a tuple at line {self.line}', Pos(self.line)) from e if v_len != len(self.ids): - raise CompileError(f'Expected {len(self.ids)} tuple items at line {self.line}, got {v_len}.') + raise CompileError(f'Expected {len(self.ids)} tuple items at line {self.line}, got {v_len}.', Pos(self.line)) for idx in range(v_len): func.set_dollarid(self.ids[idx], v[idx]) try: func.compile_blocks(self.sub) except CompileError as e: - print(e) - raise CompileError(f'Unable to compile python for block contents at line {self.line}') - except: - print(traceback.format_exc()) - raise CompileError(f'Unable to compile python for block contents at line {self.line}') + raise CompileError(f'Unable to compile python for block contents at line {self.line}', Pos(self.line)) from e + except Exception as e: + raise CompileError(f'Unable to compile python for block contents at line {self.line}', Pos(self.line)) from e diff --git a/block_types/python_import_block.py b/block_types/python_import_block.py index f284755..2ffb1cc 100644 --- a/block_types/python_import_block.py +++ b/block_types/python_import_block.py @@ -1,6 +1,6 @@ from .block_base import block_base import traceback -from CompileError import CompileError +from CompileError import CompileError, Pos class python_import_block(block_base): def __init__(self, line, filename): @@ -11,11 +11,8 @@ def compile(self, func): try: func.import_python_file(self.filename + '.py') except SyntaxError as e: - print(e) - raise CompileError(f'Importing file "{self.filename}" failed at line {self.line}:\n{e}') + raise CompileError(f'Importing file "{self.filename}" failed at line {self.line}', Pos(self.line)) from e except CompileError as e: - print(e) - raise CompileError(f'Importing file "{self.filename}" failed at line {self.line}:\n{e}') + raise CompileError(f'Importing file "{self.filename}" failed at line {self.line}', Pos(self.line)) from e except Exception as e: - print(traceback.format_exc()) - raise CompileError(f'Importing file "{self.filename}" failed at line {self.line}:\n{e}') + raise CompileError(f'Importing file "{self.filename}" failed at line {self.line}', Pos(self.line)) from e diff --git a/block_types/python_tuple_assignment_block.py b/block_types/python_tuple_assignment_block.py index b326dc1..51c8b90 100644 --- a/block_types/python_tuple_assignment_block.py +++ b/block_types/python_tuple_assignment_block.py @@ -1,6 +1,6 @@ from .block_base import block_base import math -from CompileError import CompileError +from CompileError import CompileError, Pos class python_tuple_assignment_block(block_base): def __init__(self, line, ids, val): @@ -12,11 +12,10 @@ def compile(self, func): try: val_len = len(val) except Exception as e: - print(e) - raise CompileError(f'Expression at line {self.line} is not a tuple.') + raise CompileError(f'Expression at line {self.line} is not a tuple.', Pos(self.line)) from e if val_len != len(self.ids): - raise CompileError(f'Expected {len(self.ids)} values for tuple expression at line {self.line}, got {len(val)}') + raise CompileError(f'Expected {len(self.ids)} values for tuple expression at line {self.line}, got {len(val)}', Pos(self.line)) for idx in range(val_len): func.set_dollarid(self.ids[idx], val[idx]) diff --git a/block_types/switch_block.py b/block_types/switch_block.py index d820448..349ee61 100644 --- a/block_types/switch_block.py +++ b/block_types/switch_block.py @@ -1,6 +1,6 @@ from .block_base import block_base import collections -from CompileError import CompileError +from CompileError import CompileError, Pos class switch_block(block_base): def __init__(self, line, expr, cases_raw): @@ -14,7 +14,7 @@ def compile(self, func): result = self.expr.compile(func, None).get_scoreboard_var(func) if result == None: - raise Exception(f'Unable to compute switch expression at line {self.line}') + raise CompileError(f'Unable to compute switch expression at line {self.line}', Pos(self.line)) cases = [] for case in self.cases_raw: @@ -25,16 +25,15 @@ def compile(self, func): vmin = int(vmin.get_value(func)) vmax = int(vmax.get_value(func)) except Exception as e: - print(e) - raise Exception(f'Unable to get values of range for case at line {line}') + raise Exception(f'Unable to get values of range for case at line {line}', Pos(self.line)) from e cases.append((vmin, vmax, sub, line, None)) elif type == 'python': dollarid, python, sub = content try: vals = python.get_value(func) - except: - raise Exception(f'Could not evaluate case value at line {line}') + except Exception: + raise Exception(f'Could not evaluate case value at line {line}') from None if not isinstance(vals, collections.Iterable): raise Exception(f'Python "{python}" is not iterable at line {line}') @@ -42,12 +41,12 @@ def compile(self, func): for val in vals: try: ival = int(val) - except: + except Exception: print(f'Value "{val}" is not an integer at line {line}') return False cases.append((ival, ival, sub, line, dollarid)) else: - raise CompileError(f'Unknown switch case type "{type}"') + raise CompileError(f'Unknown switch case type "{type}"', Pos(self.line)) cases = sorted(cases, key=lambda case: case[0]) @@ -62,7 +61,7 @@ def compile(self, func): rangestr = f'{vmin}' else: rangestr = f'{vmin}-{vmax}' - raise CompileError(f'"case {rangestr}" overlaps another case at line {line}') + raise CompileError(f'"case {rangestr}" overlaps another case at line {line}', Pos(self.line)) prevmax = vmax diff --git a/block_types/vector_assignment_base.py b/block_types/vector_assignment_base.py index 3621186..7ce94d3 100644 --- a/block_types/vector_assignment_base.py +++ b/block_types/vector_assignment_base.py @@ -1,6 +1,6 @@ from .block_base import block_base from variable_types.scoreboard_var import scoreboard_var -from CompileError import CompileError +from CompileError import CompileError, Pos class vector_assignment_base(block_base): def perform_vector_assignment(self, func): @@ -17,7 +17,7 @@ def perform_vector_assignment(self, func): selector, id = var_content components = [scoreboard_var(selector, f'_{id}_{i}') for i in range(3)] elif var_type == 'VAR_CONST': - raise CompileError(f'Cannot assign to vector constant at line {self.line}.') + raise CompileError(f'Cannot assign to vector constant at line {self.line}.', Pos(self.line)) if op == '=': assignto = [] diff --git a/block_types/with_anonymous_block.py b/block_types/with_anonymous_block.py index 0d960e7..22dfa56 100644 --- a/block_types/with_anonymous_block.py +++ b/block_types/with_anonymous_block.py @@ -1,5 +1,5 @@ import traceback -from CompileError import CompileError +from CompileError import CompileError, Pos from .block_base import block_base class with_anonymous_block(block_base): @@ -17,11 +17,9 @@ def compile(self, func): try: anon_func.compile_blocks(self.sub) except CompileError as e: - print(e) - raise CompileError(f'Unable to compile with block contents at line {self.line}') + raise CompileError(f'Unable to compile with block contents at line {self.line}', Pos(self.line)) from e except Exception as e: - print(traceback.format_exc()) - raise CompileError(f'Unable to compile with block contents at line {self.line}') + raise CompileError(f'Unable to compile with block contents at line {self.line}', Pos(self.line)) from e unique = func.get_unique_id() func_name = f'line{self.line:03}/{"with"}{unique:03}' diff --git a/cbscript.py b/cbscript.py index 609f294..9f65305 100644 --- a/cbscript.py +++ b/cbscript.py @@ -4,7 +4,7 @@ from mcfunction import mcfunction, compile_section from selector_definition import selector_definition from source_file import source_file -from CompileError import CompileError +from CompileError import CompileError, pretty_print_error, cur_file_var import tellraw import traceback import math @@ -12,6 +12,8 @@ import time import os +from config import Config + class cbscript(object): def __init__(self, source_file, parse_func): self.source_file = source_file @@ -75,27 +77,30 @@ def try_to_compile(self): except SyntaxError as e: self.log(str(e) + '\a') except CompileError as e: - self.log(str(e) + '\a') + pretty_print_error(e) except Exception as e: self.log("Compiler encountered unexpected error during compilation:\a") self.log_traceback() - def create_world(self, dir, namespace): - return mcworld.mcworld(dir, namespace) + def create_world(self, world: str, namespace): + return mcworld.mcworld(Config.get_world(world), namespace) def compile_all(self): text = self.source_file.get_text() + token = cur_file_var.set(self.source_file.filename) result = self.parse(text + "\n") if result == None: self.log('Unable to parse script.') + cur_file_var.reset(token) return False type, parsed = result if type != 'program': self.log('Script does not contain a full program.') + cur_file_var.reset(token) return False self.global_context = global_context.global_context(self.namespace) @@ -103,10 +108,11 @@ def compile_all(self): global_environment.set_dollarid('namespace', self.namespace) global_environment.set_dollarid('get_num_blocks', self.global_context.get_num_blocks) global_environment.set_dollarid('get_num_block_states', self.global_context.get_num_block_states) - global_environment.set_dollarid('global_scale', parsed['scale']) + + global_environment.set_dollarid('global_scale', parsed.get('scale',1)) global_func = mcfunction(global_environment) - self.global_context.scale = parsed['scale'] + self.global_context.scale = parsed.get('scale',1) self.global_context.parser = self.parse lines = parsed['lines'] @@ -119,8 +125,9 @@ def compile_all(self): try: global_func.compile_blocks(lines) except Exception as e: - print(e) + pretty_print_error(e) self.dependencies = [source_file(d) for d in self.global_context.dependencies] + cur_file_var.reset(token) return False self.post_processing() @@ -142,6 +149,7 @@ def compile_all(self): self.dependencies = [source_file(d) for d in self.global_context.dependencies] + cur_file_var.reset(token) return True def post_processing(self): diff --git a/cli.py b/cli.py new file mode 100644 index 0000000..39431f5 --- /dev/null +++ b/cli.py @@ -0,0 +1,44 @@ +import argparse +from sys import argv +from os import chdir +from pathlib import Path +import time + +import cbscript +from config import Config +import source_file +import scriptparse + +program = argparse.ArgumentParser( + "cbscript", + "cbscript [options] file" +) +program.add_argument("--instance","-i",type=Path,default=None) +program.add_argument("--optimize","-o",type=int,default=None) +program.add_argument("--watch","-w", action="store_true") +program.add_argument("file",type=Path) + +def main(*args): + options = program.parse_args(args) + Config.load() + if options.instance: + Config.instance_path = options.instance + if options.optimize: + Config.optm_level = options.optimize + source = source_file.source_file(options.file) + chdir(options.file.parent) + script = cbscript.cbscript(source,scriptparse.parse) + if options.watch: + try: + print("Press CTRL-C to stop") + while True: + script.check_for_update() + + time.sleep(1) + except KeyboardInterrupt: + print("Goodbye!") + else: + script.try_to_compile() + +if __name__ == "__main__": + main(*argv[1:]) diff --git a/config.py b/config.py new file mode 100644 index 0000000..3d28b63 --- /dev/null +++ b/config.py @@ -0,0 +1,112 @@ +from typing import Literal + +import yaml +from pathlib import Path +import fnmatch + +config_path = Path(__file__).parent/"config.yml" + +class Config: + optm_level: int = 1 + instance_path: Path | None = None + world_redirects: dict[str,str] = {} + glob_redirects: dict[str,str] = {} + + @classmethod + def load(cls, error: Literal['silence','print','raise'] = 'raise'): + if not config_path.exists(): + return + if error not in ('silence','print','raise'): + # default to raise + error = 'raise' + try: + data = yaml.safe_load(config_path.open()) + except Exception as ex: + if error == 'raise': + raise ex + if error == 'print': + print("Could not parse config file!") + print(type(ex).__name__+':',ex) + return + # silence + return + + cls.optm_level = data.get("optm_level",1) + instance_path = data.get('instance_path') + if instance_path is None: + cls.instance_path = cls.try_guess_instance_path() + else: + cls.instance_path = Path(instance_path) + + cls.world_redirects.clear() + + if "world_redirects" in data and data['world_redirects'] is not None: + try: + cls.parse_redirects(data["world_redirects"]) + except TypeError as ex: + if error == 'raise': + raise ex + if error == 'print': + print("Could not parse world_redirects!") + print(type(ex).__name__+':',ex) + return + # silence + return + + @classmethod + def parse_redirects(cls, redirects): + if not isinstance(redirects, dict): + raise TypeError("redirects can only be a dictionary") + + for key,val in redirects.items(): + if not isinstance(val,str): + raise TypeError(f"redirect '{key}' has invalid type") + if any(c in key for c in '[*?'): + cls.glob_redirects[key] = val + else: + cls.world_redirects[key] = val + + @classmethod + def resolve(cls, path: str, prefix = ""): + if Path(path).is_absolute(): + return Path(path) + if cls.instance_path is None: + return config_path.parent / 'out' + return cls.instance_path / prefix / path + + @classmethod + def get_world(cls, world: str) -> Path | None: + if Path(world).is_absolute(): + out = Path(world) + if not out.exists() and '_' in cls.world_redirects: + return cls.resolve(cls.world_redirects['_'],'saves') + return out + if world in cls.world_redirects: + return cls.resolve(cls.world_redirects[world], 'saves') + for k,v in cls.glob_redirects.items(): + if fnmatch.fnmatch(world,k): + return cls.resolve(v, 'saves') + out = cls.resolve(world,'saves') + if not out.exists() and '_' in cls.world_redirects: + return cls.resolve(cls.world_redirects['_'],'saves') + return out + + @classmethod + def try_guess_instance_path(cls) -> Path | None: + # TODO: use the path of the last used instance by the offical client + # IIRC this can be done with loading some json from the .minecraft folder + return None + +# little script to help check the config +if __name__ == "__main__": + from sys import argv + Config.load() + print("Loaded successfully!") + print("world_redirects:") + for k,v in Config.world_redirects.items(): + print(f" {k}: {v}") + for k,v in Config.glob_redirects.items(): + print(f" {k}: {v}") + + for i in argv[1:]: + print(i,"->",Config.get_world(i)) diff --git a/config.yml b/config.yml new file mode 100644 index 0000000..55227ae --- /dev/null +++ b/config.yml @@ -0,0 +1,7 @@ +instance_path: + +# in the form +# from: to +# +# from can be a glob pattern +world_redirects: diff --git a/mcfunction.py b/mcfunction.py index 6f59455..214e43e 100644 --- a/mcfunction.py +++ b/mcfunction.py @@ -4,7 +4,7 @@ from variable_types.scoreboard_var import scoreboard_var from block_types.push_block import push_block from block_types.pop_block import pop_block -from CompileError import CompileError +from CompileError import CompileError, cur_file_var, Pos import math import traceback import json @@ -772,8 +772,7 @@ def compile_blocks(self, lines): try: block.compile(self) except CompileError as e: - print(e) - raise CompileError(f'Error compiling block at line {block.line}') + raise e except: print(traceback.format_exc()) raise CompileError(f'Error compiling block at line {block.line}') @@ -786,11 +785,13 @@ def import_file(self, filename): self.environment.register_dependency(filename) file = source_file(filename) - + result = self.parser('import ' + file.get_text() + '\n') if result == None: raise CompileError(f'Unable to parse file "{filename}"') + + type, parsed = result if type != 'lib': raise CompileError(f'Unable to import non-lib-file "{filename}"') @@ -798,7 +799,12 @@ def import_file(self, filename): for line in parsed['lines']: line.register(self.global_context) - self.compile_blocks(parsed['lines']) + token = cur_file_var.set(file.filename) + try: + self.compile_blocks(parsed['lines']) + finally: + cur_file_var.reset(token) + def import_python_file(self, filename): self.environment.register_dependency(filename) @@ -900,4 +906,4 @@ def pop_locals(self, locals): @property def predicates(self): - return self.environment.predicates \ No newline at end of file + return self.environment.predicates diff --git a/mcworld.py b/mcworld.py index b96eb27..d46b330 100644 --- a/mcworld.py +++ b/mcworld.py @@ -108,6 +108,7 @@ def write_mcmeta(self, desc): def write_zip(self): self.zip.close() - zip_filename = os.path.join(self.dir, f'datapacks/{self.namespace}.zip') - with open(zip_filename, 'wb') as file: + zip_filename = self.dir / 'datapacks' / f'{self.namespace}.zip' + zip_filename.parent.mkdir(parents=True,exist_ok=True) + with zip_filename.open('wb') as file: file.write(self.zipbytes.getvalue()) diff --git a/run.cmd b/run.cmd index 58f245a..d820785 100644 --- a/run.cmd +++ b/run.cmd @@ -8,5 +8,5 @@ @ECHO CBScript 1.20 @title %~nx1 @cd "%~dp0" -py -3 compile.py %1 -@pause \ No newline at end of file +py -3 cli.py -w %1 +@pause diff --git a/scriptparse.py b/scriptparse.py index 0ad56f3..9c7e012 100644 --- a/scriptparse.py +++ b/scriptparse.py @@ -1854,10 +1854,5 @@ def parse(data,debug=0): scriptlex.lexer.lineno = 1 bparser.error = 0 bparser.data = data - try: - p = bparser.parse(data,debug=debug,tracking=True) - return p - except SyntaxError as e: - print(e) - except Exception as e: - print(traceback.format_exc()) \ No newline at end of file + p = bparser.parse(data,debug=debug,tracking=True) + return p diff --git a/source_file.py b/source_file.py index 141c36b..aa32d58 100644 --- a/source_file.py +++ b/source_file.py @@ -1,13 +1,25 @@ import os import time +from pathlib import Path + +from CompileError import register_lines class source_file(object): - def __init__(self, filename): - self.filename = os.path.abspath(filename) + filename: Path + is_log: bool + + def __init__(self, filename, is_log = False): + if isinstance(filename,str): + filename = Path(filename) + self.filename = filename.absolute() self.modified = self.get_last_modified() - self.last_size = os.path.getsize(filename) + if self.filename.exists(): + self.last_size = os.path.getsize(filename) + self.is_log = is_log def get_last_modified(self): + if not self.filename.exists(): + return 0 return time.ctime(os.path.getmtime(self.filename)) def was_updated(self): @@ -19,10 +31,10 @@ def was_updated(self): return False def get_base_name(self): - return os.path.basename(self.filename) + return self.filename.name def get_directory(self): - return os.path.dirname(self.filename) + return self.filename.parent def get_text(self, only_new_text = False): text = "" @@ -31,8 +43,10 @@ def get_text(self, only_new_text = False): if only_new_text: content_file.seek(self.last_size) text = content_file.read() - - time.sleep(0.1) + if not self.is_log: + register_lines(self.filename,text.splitlines()) + + # time.sleep(0.1) self.last_size = os.path.getsize(self.filename) - return text \ No newline at end of file + return text