diff --git a/lang/bytecode.c b/lang/bytecode.c index 4b49eee..197ddbb 100644 --- a/lang/bytecode.c +++ b/lang/bytecode.c @@ -25,6 +25,7 @@ OP_BOOLEAN = 0x0B, OP_JUMP_FALSE = 0x0C, OP_JUMP_ALWAYS = 0x0D, + OP_TAIL_CALL = 0x0E, }; void bytecode_run(VmState state, Object obj, const unsigned char *bytecode) { @@ -63,6 +64,28 @@ object_drop(&obj); break; } + // OP_TAIL_CALL queues a call and returns + // First argument is a 1-byte argument count + // Second argument is the call string + case OP_TAIL_CALL: { + int arg_count = *pos_code++; + const char *dispatch = (const char *)pos_code; + pos_code += strlen(dispatch) + 1; // Skip NULL too + Object obj = vm_stack_pop(state); + int args_start = vm_stack_depth(state) - arg_count; + for (int i = 0; i < arg_count; ++i) { + int stack_pos = args_start + i; + Object arg = vm_stack_get(state, stack_pos); + vm_stack_set(state, i, arg); + } + int remaining = vm_stack_depth(state) - arg_count; + if (remaining) { + vm_stack_drop(state, remaining); + } + vm_tailcall(state, obj, dispatch); + object_drop(&obj); + return; + } // OP_NONE pushes object_none on to the stack case OP_NONE: { vm_stack_push(state, object_none()); diff --git a/lang/compile.py b/lang/compile.py index d45a4c9..d51d8f3 100755 --- a/lang/compile.py +++ b/lang/compile.py @@ -50,6 +50,13 @@ def __repr__(self): return '\n\t\t\t\tASTCall(subject=%s, verb="%s", args=%s)' % (self.subject, self.verb, self.args) +class ASTJump(): + def __init__(self, call): + self.call = call + + def __repr__(self): + return '\n\t\t\tASTJump(call=%s)' % (self.call) + class ASTSet(): def __init__(self, name, command): self.name = name @@ -183,6 +190,16 @@ return None return ASTReturn(value) +def parse_jump(line): + if line[0] != "Jump" or len(line) < 3: + print("not a jump? line: %s" % (' '.join(line))) + return None + jump_call = parse_call(line, 2) + if not jump_call: + print("not a jump call? line: %s" % (' '.join(line))) + return None + return ASTJump(jump_call) + def parse_command(line): instr = line[0] command = None @@ -190,6 +207,8 @@ command = parse_set(line) elif instr == "Return": command = parse_return(line) + elif instr == "Jump": + command = parse_jump(line) if not command: print("not a command? line: %s" % (' '.join(line))) return None @@ -342,6 +361,14 @@ def __repr__(self): return 'IRCall(name="%s", args=%i)' % (self.name, self.args) +class IRTailCall(): + def __init__(self, name, args): + self.name = name + self.args = args + + def __repr__(self): + return 'IRTailCall(name="%s", args=%i)' % (self.name, self.args) + class IRReturn(): def __repr__(self): return 'IRReturn()' @@ -401,7 +428,7 @@ print("Unknown value ast node: %s" % (node)) return None -def generate_ir_call(ast): +def generate_ir_call(ast, is_jump): final_ir = [] subject_ir = generate_ir_value(ast.subject) if not subject_ir: @@ -418,16 +445,23 @@ args_ir = args_ir + arg_ir args_count = len(ast.args) alloc_ir = [IRAllocate(1)] - call_ir = [IRCall(ast.verb, args_count)] + if is_jump: + call_ir = [IRTailCall(ast.verb, args_count)] + else: + call_ir = [IRCall(ast.verb, args_count)] final_ir = final_ir + alloc_ir final_ir = final_ir + args_ir final_ir = final_ir + subject_ir final_ir = final_ir + call_ir return final_ir +def generate_ir_jump(ast): + # juggle args to arg count then cleanup stack + return generate_ir_call(ast.call, True) + def generate_ir_set(ast, create): command = ast.command - sub_ir = generate_ir_call(command) + sub_ir = generate_ir_call(command, False) if not sub_ir: print("Unknown set ast node: %s" % (node)) return None @@ -450,6 +484,8 @@ sub_ir = generate_ir_set(node, create) elif isinstance(node, ASTReturn): sub_ir = generate_ir_return(node) + elif isinstance(node, ASTJump): + sub_ir = generate_ir_jump(node) if not sub_ir: print("Unknown command ast node: %s" % (node)) return None @@ -468,7 +504,7 @@ clause_num += 1 ir.append(IRLabel(label_clause)) if clause.test: - ir += generate_ir_call(clause.test) + ir += generate_ir_call(clause.test, False) ir.append(IRJumpFalse(label_next)) ir += generate_ir_command(clause.success, False) ir.append(IRJumpAlways(label_end)) @@ -484,6 +520,8 @@ sub_ir = generate_ir_conditional(node, id) elif isinstance(node, ASTCommand): sub_ir = generate_ir_command(node, True) + elif isinstance(node, ASTJump): + sub_ir = generate_ir_jump(node) if not sub_ir: print("Unknown statement ast node: %s" % (node)) return None @@ -641,6 +679,11 @@ bytes += (node.args + 1).to_bytes(1) bytes += node.name.encode('utf-8') bytes += b"\x00" # NULL terminator + elif isinstance(node, IRTailCall): + bytes += b"\x0E" # OP_TAIL_CALL + bytes += (node.args + 1).to_bytes(1) + bytes += node.name.encode('utf-8') + bytes += b"\x00" # NULL terminator elif isinstance(node, IRReturn): bytes += b"\x03" # OP_RET elif isinstance(node, IRDepthCheck): diff --git a/lang/modules/main.txt b/lang/modules/main.txt index bc5801b..96eedd6 100644 --- a/lang/modules/main.txt +++ b/lang/modules/main.txt @@ -24,13 +24,11 @@ If NumFrom Equals 0 Then Return True Set Next To NumFrom Subtract 1 - Set CountedDown To Self CountDown Next - Return CountedDown + Jump Self CountDown Next EndFunction Function LoopTest - Set Looped To Self CountDown 1 - Return Looped + Jump Self CountDown 10000 EndFunction Function GetHalfish Args Num diff --git a/lang/vm.c b/lang/vm.c index ab98e90..bb4af40 100644 --- a/lang/vm.c +++ b/lang/vm.c @@ -12,6 +12,8 @@ int stack_base; int stack_next; int stack_size; + Object *tail_obj; + const char *tail_name; }; VmState vm_create(void) { @@ -19,6 +21,8 @@ state->stack_size = 16; state->stack_base = 0; state->stack_next = 0; + state->tail_obj = object_none(); + state->tail_name = NULL; Object *stack = malloc(sizeof(Object) * state->stack_size); state->stack = stack; return (VmState)state; @@ -87,6 +91,21 @@ struct vm_state *priv = (struct vm_state *)state; int old_base = priv->stack_base; priv->stack_base = priv->stack_next - arg_count; - dispatch_call(priv, obj, name); + object_hold(obj); + do { + dispatch_call(priv, obj, name); + object_drop(&obj); + obj = priv->tail_obj; + name = priv->tail_name; + priv->tail_obj = object_none(); + priv->tail_name = NULL; + } while (obj != object_none()); priv->stack_base = old_base; } + +void vm_tailcall(VmState state, Object obj, const char *name) { + struct vm_state *priv = (struct vm_state *)state; + object_hold(obj); + priv->tail_obj = obj; + priv->tail_name = name; +} diff --git a/lang/vm.h b/lang/vm.h index 052c7d4..9dd38fe 100644 --- a/lang/vm.h +++ b/lang/vm.h @@ -39,4 +39,7 @@ // Creates a stack frame at arg_count offset from the top then calls an object void vm_call(VmState state, Object obj, const char *name, int arg_count); +// Queues a call to be scheduled on next return to vm_call +void vm_tailcall(VmState state, Object obj, const char *name); + #endif