Skip to content

Instantly share code, notes, and snippets.

@MyNameIsTrez
Created September 29, 2025 18:09
Show Gist options
  • Select an option

  • Save MyNameIsTrez/bad3abe2e2414082c94727bc72cfa63e to your computer and use it in GitHub Desktop.

Select an option

Save MyNameIsTrez/bad3abe2e2414082c94727bc72cfa63e to your computer and use it in GitHub Desktop.
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