Created
September 29, 2025 18:09
-
-
Save MyNameIsTrez/bad3abe2e2414082c94727bc72cfa63e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class BreakException(Exception): | |
| pass | |
| class ContinueException(Exception): | |
| pass | |
| class ReturnException(Exception): | |
| def __init__(self, value): | |
| self.value = value | |
| def run_grug_function(lines, globals_, locals_, game_funcs, functions, base_indent=0): | |
| i = 0 | |
| while i < len(lines): | |
| line = lines[i].rstrip() | |
| if not line or line.strip().startswith("#"): | |
| i += 1 | |
| continue | |
| indent = len(line) - len(line.lstrip()) | |
| if indent < base_indent: | |
| return | |
| line = line.strip() | |
| if line.startswith("break"): | |
| raise BreakException() | |
| elif line.startswith("continue"): | |
| raise ContinueException() | |
| elif line.startswith("return"): | |
| ret_expr = line[6:].strip() | |
| value = eval_expr(ret_expr, globals_, locals_, game_funcs, functions) if ret_expr else None | |
| raise ReturnException(value) | |
| elif line.startswith("if ") and line.endswith("{"): | |
| cond = line[3:-1].strip() | |
| block_lines, jump = get_block(lines, i) | |
| if eval_expr(cond, globals_, locals_, game_funcs, functions): | |
| run_grug_function(block_lines, globals_, locals_, game_funcs, functions, base_indent + 4) | |
| i += jump | |
| continue | |
| elif line.startswith("while ") and line.endswith("{"): | |
| cond = line[6:-1].strip() | |
| block_lines, jump = get_block(lines, i) | |
| while True: | |
| try: | |
| if not eval_expr(cond, globals_, locals_, game_funcs, functions): | |
| break | |
| try: | |
| run_grug_function(block_lines, globals_, locals_, game_funcs, functions, base_indent + 4) | |
| except ContinueException: | |
| continue | |
| except BreakException: | |
| break | |
| i += jump | |
| continue | |
| elif "(" in line and line.endswith("{") and (line.startswith("on_") or line.startswith("helper_")): | |
| block_lines, jump = get_block(lines, i) | |
| i += jump | |
| continue | |
| elif "=" in line: | |
| dest, expr = line.split("=", 1) | |
| dest = dest.strip() | |
| value = eval_expr(expr.strip(), globals_, locals_, game_funcs, functions) | |
| if dest in locals_: | |
| locals_[dest] = value | |
| else: | |
| globals_[dest] = value | |
| elif "(" in line: | |
| eval_expr(line, globals_, locals_, game_funcs, functions) | |
| i += 1 | |
| def get_block(lines, start_index): | |
| start_indent = len(lines[start_index]) - len(lines[start_index].lstrip()) | |
| block_lines = [] | |
| i = start_index + 1 | |
| while i < len(lines): | |
| line = lines[i] | |
| indent = len(line) - len(line.lstrip()) | |
| if indent <= start_indent and line.strip() == "}": | |
| return block_lines, i - start_index + 1 | |
| elif indent > start_indent: | |
| block_lines.append(line) | |
| i += 1 | |
| raise Exception("Unmatched '{' in code") | |
| # Nested call argument splitter | |
| def split_args(arg_str): | |
| args = [] | |
| current = "" | |
| depth = 0 | |
| for c in arg_str: | |
| if c == "," and depth == 0: | |
| args.append(current) | |
| current = "" | |
| else: | |
| current += c | |
| if c == "(": | |
| depth += 1 | |
| elif c == ")": | |
| depth -= 1 | |
| if current.strip(): | |
| args.append(current) | |
| return args | |
| def eval_expr(expr, globals_, locals_, game_funcs, functions): | |
| expr = expr.strip() | |
| if expr.startswith('"') and expr.endswith('"'): | |
| return expr[1:-1] | |
| if expr.isdigit() or (expr.startswith("-") and expr[1:].isdigit()): | |
| return int(expr) | |
| # Function call | |
| if "(" in expr and expr.endswith(")"): | |
| func_name, arg_str = expr.split("(", 1) | |
| func_name = func_name.strip() | |
| arg_str = arg_str[:-1].strip() | |
| args = split_args(arg_str) | |
| evaled_args = [eval_expr(a.strip(), globals_, locals_, game_funcs, functions) for a in args] | |
| if func_name in game_funcs: | |
| return game_funcs[func_name](*evaled_args) | |
| elif func_name in functions["helper"]: | |
| arg_names = functions["helper_args"].get(func_name, []) | |
| new_locals = dict(zip(arg_names, evaled_args)) | |
| try: | |
| run_grug_function(functions["helper"][func_name], globals_, new_locals, game_funcs, functions) | |
| except ReturnException as e: | |
| return e.value | |
| return None | |
| elif func_name in functions["on"]: | |
| arg_names = functions["on_args"].get(func_name, []) | |
| new_locals = dict(zip(arg_names, evaled_args)) | |
| run_grug_function(functions["on"][func_name], globals_, new_locals, game_funcs, functions) | |
| return None | |
| else: | |
| raise Exception(f"Unknown function: {func_name}") | |
| return resolve(expr, globals_, locals_) | |
| def resolve(name, globals_, locals_): | |
| if name in locals_: | |
| return locals_[name] | |
| if name in globals_: | |
| return globals_[name] | |
| if name == "me": | |
| return globals_["me"] | |
| return name | |
| def parse_grug_program(source): | |
| functions = {"on": {}, "helper": {}, "init": {}} | |
| functions["helper_args"] = {} | |
| functions["on_args"] = {} | |
| init_globals_body = [] | |
| lines = [line.rstrip() for line in source.splitlines()] | |
| i = 0 | |
| while i < len(lines): | |
| line = lines[i].rstrip() | |
| if not line.strip(): | |
| i += 1 | |
| continue | |
| if (line.startswith("on_") or line.startswith("helper_")) and line.endswith("{"): | |
| header = line[:-1].strip() | |
| if "(" in header: | |
| name, arglist = header.split("(", 1) | |
| arglist = arglist.rstrip(")") | |
| arg_names = [a.strip() for a in arglist.split(",") if a.strip()] | |
| else: | |
| name = header | |
| arg_names = [] | |
| block_lines, jump = get_block(lines, i) | |
| if name.startswith("on_"): | |
| functions["on"][name] = block_lines | |
| functions["on_args"][name] = arg_names | |
| else: | |
| functions["helper"][name] = block_lines | |
| functions["helper_args"][name] = arg_names | |
| i += jump | |
| elif "=" in line: | |
| init_globals_body.append(line) | |
| i += 1 | |
| else: | |
| i += 1 | |
| if init_globals_body: | |
| functions["init"]["init_globals"] = init_globals_body | |
| return functions | |
| def init_globals(globals_, entity_id, functions, game_funcs): | |
| globals_["me"] = entity_id | |
| if "init_globals" in functions["init"]: | |
| run_grug_function(functions["init"]["init_globals"], globals_, globals_, game_funcs, functions) | |
| def call_function(name, globals_, functions, game_funcs, *args): | |
| if name not in functions["on"]: | |
| raise Exception(f"Game cannot call function {name}") | |
| arg_names = functions["on_args"].get(name, []) | |
| locals_ = dict(zip(arg_names, args)) | |
| return run_grug_function(functions["on"][name], globals_, locals_, game_funcs, functions) | |
| grug_program = """ | |
| # Globals | |
| name = get_my_name(me) | |
| depth = 0 | |
| on_spawn() { | |
| print_string(name) | |
| counter = 0 | |
| while is_less(counter, 5) { | |
| counter = add_one(counter) | |
| if is_equal(counter, 3) { | |
| continue | |
| } | |
| print_number(counter) | |
| if is_equal(counter, 4) { | |
| break | |
| } | |
| } | |
| helper_test() | |
| } | |
| on_fib() { | |
| n1 = helper_fib(6) | |
| print_string("result:") | |
| print_number(n1) | |
| } | |
| helper_test() { | |
| print_string("helper_test()") | |
| if is_less(depth, 2) { | |
| depth = add_one(depth) | |
| helper_test() | |
| } | |
| } | |
| helper_fib(n) { | |
| if or(is_equal(n, 0), is_equal(n, 1)) { | |
| return n | |
| } | |
| return add(helper_fib(add(n, -1)), helper_fib(add(n, -2))) | |
| } | |
| """ | |
| game_funcs = { | |
| "get_my_name": lambda id: f"name_of_{id}", | |
| "print_string": lambda s: print(s), | |
| "print_number": lambda n: print(n), | |
| "add": lambda a, b: a + b, | |
| "add_one": lambda x: x + 1, | |
| "is_less": lambda a, b: a < b, | |
| "is_equal": lambda a, b: a == b, | |
| "or": lambda a, b: a or b, | |
| } | |
| if __name__ == "__main__": | |
| globals_ = {} | |
| functions = parse_grug_program(grug_program) | |
| init_globals(globals_, 42, functions, game_funcs) | |
| print("---- Running on_spawn ----") | |
| call_function("on_spawn", globals_, functions, game_funcs) | |
| print("---- Running on_fib ----") | |
| call_function("on_fib", globals_, functions, game_funcs) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment