From b2a22cc0d37e246926a183661656bd4f8adedd3c Mon Sep 17 00:00:00 2001 From: OusmBlueNinja <89956790+OusmBlueNinja@users.noreply.github.com> Date: Wed, 16 Apr 2025 17:27:55 -0500 Subject: [PATCH] mython lua runtime --- pylua/main.py | 243 +++++++++++++++++++++++++++++++++++++++++++++++ pylua/out.c | 26 +++++ pylua/runtime.py | 138 +++++++++++++++++++++++++++ 3 files changed, 407 insertions(+) create mode 100644 pylua/main.py create mode 100644 pylua/out.c create mode 100644 pylua/runtime.py diff --git a/pylua/main.py b/pylua/main.py new file mode 100644 index 0000000..b926e12 --- /dev/null +++ b/pylua/main.py @@ -0,0 +1,243 @@ +import re + +# --- Tokenizer --- +TOKEN_REGEX = [ + ('KEYWORD', r'\b(and|break|do|else|elseif|end|false|for|function|goto|if|in|local|nil|not|or|repeat|return|then|true|until|while)\b'), + ('NAME', r'[A-Za-z_][A-Za-z0-9_]*'), + ('NUMBER', r'\d+(\.\d+)?'), + ('STRING', r'"([^"\\]|\\.)*"|\'([^\'\\]|\\.)*\''), + ('SYMBOL', r'==|~=|<=|>=|\.{2}|[+\-*/%^#=<>;:,.\[\](){}]'), + ('COMMENT', r'--\[=*?\[.*?\]\=*?\]|--.*'), + ('SKIP', r'[ \t\r\n]+'), + ('MISMATCH', r'.'), +] + +TOKEN_RE = re.compile('|'.join(f'(?P<{name}>{pattern})' for name, pattern in TOKEN_REGEX), re.DOTALL) + +def tokenize(code): + for match in TOKEN_RE.finditer(code): + kind = match.lastgroup + value = match.group() + + if kind in ('SKIP', 'COMMENT'): + continue + elif kind == 'MISMATCH': + raise SyntaxError(f'Unexpected token: {value}') + else: + yield kind, value + + +# --- Parser --- +class LuaParser: + def __init__(self, tokens): + self.tokens = list(tokens) + self.pos = 0 + + def peek(self): + return self.tokens[self.pos] if self.pos < len(self.tokens) else (None, None) + + def eat(self, kind=None, value=None): + token = self.peek() + if kind and token[0] != kind: + raise SyntaxError(f'Expected {kind}, got {token[0]}') + if value and token[1] != value: + raise SyntaxError(f'Expected {value}, got {token[1]}') + self.pos += 1 + return token + + def parse(self): + return self.parse_block() + + def parse_block(self): + block = [] + while self.pos < len(self.tokens): + kind, value = self.peek() + if value in ('end', 'elseif', 'else', 'until'): + break + stmt = self.parse_statement() + block.append(stmt) + return ('block', block) + + def parse_statement(self): + kind, value = self.peek() + if value == 'local': + return self.parse_local() + elif value == 'function': + return self.parse_function() + elif value == 'if': + return self.parse_if() + elif value == 'while': + return self.parse_while() + elif value == 'return': + return self.parse_return() + elif kind == 'NAME': + return self.parse_assignment_or_call() + else: + raise SyntaxError(f'Unexpected statement: {value}') + + def parse_local(self): + self.eat('KEYWORD', 'local') + name = self.eat('NAME')[1] + if self.peek()[1] == '=': + self.eat('SYMBOL', '=') + expr = self.parse_expression() + return ('local', name, expr) + return ('local', name, None) + + def parse_function(self): + self.eat('KEYWORD', 'function') + name = self.eat('NAME')[1] + self.eat('SYMBOL', '(') + args = [] + while self.peek()[1] != ')': + if args: + self.eat('SYMBOL', ',') + args.append(self.eat('NAME')[1]) + self.eat('SYMBOL', ')') + body = self.parse_block() + self.eat('KEYWORD', 'end') + return ('function', name, args, body) + + def parse_if(self): + self.eat('KEYWORD', 'if') + cond = self.parse_expression() + self.eat('KEYWORD', 'then') + then_block = self.parse_block() + elseif_blocks = [] + while self.peek()[1] == 'elseif': + self.eat('KEYWORD', 'elseif') + elseif_cond = self.parse_expression() + self.eat('KEYWORD', 'then') + elseif_block = self.parse_block() + elseif_blocks.append((elseif_cond, elseif_block)) + else_block = None + if self.peek()[1] == 'else': + self.eat('KEYWORD', 'else') + else_block = self.parse_block() + self.eat('KEYWORD', 'end') + return ('if', cond, then_block, elseif_blocks, else_block) + + def parse_while(self): + self.eat('KEYWORD', 'while') + cond = self.parse_expression() + self.eat('KEYWORD', 'do') + body = self.parse_block() + self.eat('KEYWORD', 'end') + return ('while', cond, body) + + def parse_return(self): + self.eat('KEYWORD', 'return') + expr = self.parse_expression() + return ('return', expr) + + def parse_assignment_or_call(self): + name = self.eat('NAME')[1] + if self.peek()[1] == '=': + self.eat('SYMBOL', '=') + expr = self.parse_expression() + return ('assign', name, expr) + elif self.peek()[1] == '(': + self.eat('SYMBOL', '(') + args = [] + while self.peek()[1] != ')': + if args: + self.eat('SYMBOL', ',') + args.append(self.parse_expression()) + self.eat('SYMBOL', ')') + return ('call', name, args) + else: + raise SyntaxError(f'Unexpected token after identifier: {self.peek()}') + + def parse_expression(self, precedence=0): + expr = self.parse_primary() + while True: + kind, op = self.peek() + if kind != 'SYMBOL' and kind != 'KEYWORD': + break + prec = self.get_precedence(op) + if prec < precedence: + break + self.eat() + right = self.parse_expression(prec + 1) + expr = ('binop', op, expr, right) + return expr + + def parse_primary(self): + kind, value = self.peek() + if kind == 'NUMBER': + return ('number', float(self.eat()[1])) + elif kind == 'STRING': + return ('string', self.eat()[1]) + elif kind == 'NAME': + return ('name', self.eat()[1]) + elif value == '(': + self.eat('SYMBOL', '(') + expr = self.parse_expression() + self.eat('SYMBOL', ')') + return expr + elif value == '{': + return self.parse_table() + elif value == 'nil': + self.eat('KEYWORD', 'nil') + return ('nil',) + elif value == 'true': + self.eat('KEYWORD', 'true') + return ('bool', True) + elif value == 'false': + self.eat('KEYWORD', 'false') + return ('bool', False) + else: + raise SyntaxError(f'Unexpected token in expression: {value}') + + def parse_table(self): + self.eat('SYMBOL', '{') + fields = [] + while self.peek()[1] != '}': + if fields: + self.eat('SYMBOL', ',') + fields.append(self.parse_expression()) + self.eat('SYMBOL', '}') + return ('table', fields) + + def get_precedence(self, op): + return { + 'or': 1, + 'and': 2, + '<': 3, '>': 3, '<=': 3, '>=': 3, '==': 3, '~=': 3, + '..': 4, + '+': 5, '-': 5, + '*': 6, '/': 6, '%': 6, + }.get(op, -1) + + + + + + +# --- Test --- +if __name__ == '__main__': + code = """ + local x = 1 + local y = x + 2 * 3 + function hello(a, b) + if a > b then + return a + elseif b > a then + return b + else + return 0 + end + end + while x < 10 do + x = x + 1 + print(x) + end + """ + + tokens = tokenize(code) + parser = LuaParser(tokens) + ast = parser.parse() + + from pprint import pprint + pprint(ast) + diff --git a/pylua/out.c b/pylua/out.c new file mode 100644 index 0000000..b19230c --- /dev/null +++ b/pylua/out.c @@ -0,0 +1,26 @@ +float x = 1.0; +float y = (x + (2.0 * 3.0)); + + +float hello(float a, float b) +{ + if ((a > b)) + { + return a; + } + else if ((b > a)) + { + return b; + } + else + { + return 0.0; + } +} + + +while ((x < 10.0)) +{ + x = (x + 1.0); +} +printf("%f", x); \ No newline at end of file diff --git a/pylua/runtime.py b/pylua/runtime.py new file mode 100644 index 0000000..84405a3 --- /dev/null +++ b/pylua/runtime.py @@ -0,0 +1,138 @@ +class LuaState: + def __init__(self, ast): + self.env = {} + self.functions = {} + self.last_error = 0 + print("[LUA] Evaluating") + self._eval_block(ast) + print("[LUA] Done") + + + def _eval_expr(self, expr, local_env): + kind = expr[0] + try: + if kind == 'number': + return expr[1] + elif kind == 'name': + return local_env.get(expr[1], self.env.get(expr[1])) + elif kind == 'binop': + op = expr[1] + a = self._eval_expr(expr[2], local_env) + b = self._eval_expr(expr[3], local_env) + return { + '+': a + b, + '-': a - b, + '*': a * b, + '/': a / b, + '>': a > b, + '<': a < b, + '>=': a >= b, + '<=': a <= b, + '==': a == b, + '!=': a != b + }[op] + elif kind == 'call': + func_name = expr[1] + args = [self._eval_expr(arg, local_env) for arg in expr[2]] + if func_name == 'print': + print(*args) + else: + return self.call(func_name, *args) + except Exception as e: + self.last_error = 1 + return str(e) + + def _eval_stmt(self, stmt, local_env): + kind = stmt[0] + if kind == 'local': + local_env[stmt[1]] = self._eval_expr(stmt[2], local_env) + elif kind == 'assign': + val = self._eval_expr(stmt[2], local_env) + if stmt[1] in local_env: + local_env[stmt[1]] = val + else: + self.env[stmt[1]] = val + elif kind == 'function': + name, args, body = stmt[1], stmt[2], stmt[3] + self.functions[name] = (args, body) + elif kind == 'if': + cond, true_blk, elifs, else_blk = stmt[1], stmt[2], stmt[3], stmt[4] + if self._eval_expr(cond, local_env): + return self._eval_block(true_blk, local_env) + for elif_cond, elif_blk in elifs: + if self._eval_expr(elif_cond, local_env): + return self._eval_block(elif_blk, local_env) + return self._eval_block(else_blk, local_env) + elif kind == 'while': + while self._eval_expr(stmt[1], local_env): + result = self._eval_block(stmt[2], local_env) + if result is not None: + return result + elif kind == 'return': + return self._eval_expr(stmt[1], local_env) + elif kind == 'call': + self._eval_expr(stmt, local_env) + + def _eval_block(self, block, env=None): + local_env = {} if env is None else dict(env) + + for stmt in block[1]: + result = self._eval_stmt(stmt, local_env) + if result is not None: + return result + + def call(self, name, *args): + self.last_error = 0 + if name not in self.functions: + self.last_error = 2 + return self.last_error, f"function '{name}' not defined" + try: + params, body = self.functions[name] + call_env = {param: arg for param, arg in zip(params, args)} + result = self._eval_block(body, call_env) + return self.last_error, result + except Exception as e: + self.last_error = 3 + return self.last_error, str(e) + + + + + +ast = ('block', + [('local', 'x', ('number', 1.0)), + ('local', + 'y', + ('binop', + '+', + ('name', 'x'), + ('binop', '*', ('number', 2.0), ('number', 3.0)))), + ('function', + 'hello', + ['a', 'b'], + ('block', + [('if', + ('binop', '>', ('name', 'a'), ('name', 'b')), + ('block', [('return', ('name', 'a'))]), + [(('binop', '>', ('name', 'b'), ('name', 'a')), + ('block', [('return', ('name', 'b'))]))], + ('block', [('return', ('number', 0.0))]))])), + ('while', + ('binop', '<', ('name', 'x'), ('number', 10.0)), + ('block', + [('assign', 'x', ('binop', '+', ('name', 'x'), ('number', 1.0))), + ('call', 'print', [('name', 'x')])]))]) + + +print("State") +state = LuaState(ast) +print("call") + +err, out = state.call("hello", 10, 20) +print("hello(10, 20):", err, out) + +err, out = state.call("hello", 5, 1) +print("hello(5, 1):", err, out) + +err, out = state.call("not_real") +print("not_real():", err, out) \ No newline at end of file