diff --git a/lib/pinc_bytecode/instruction.ml b/lib/pinc_bytecode/instruction.ml index 6ecbbde..263da4e 100644 --- a/lib/pinc_bytecode/instruction.ml +++ b/lib/pinc_bytecode/instruction.ml @@ -1,4 +1,5 @@ type t = + | I_Null | I_Pop | I_Constant of UInt16.t | I_Add @@ -19,6 +20,8 @@ type t = | I_Or | I_Minus | I_Not + | I_Jump of UInt16.t + | I_Jump_If_False of UInt16.t let byte = function | I_Pop -> 0x00 @@ -41,10 +44,15 @@ let byte = function | I_Or -> 0x11 | I_Minus -> 0x12 | I_Not -> 0x13 + | I_Jump _ -> 0x14 + | I_Jump_If_False _ -> 0x15 + | I_Null -> 0x16 ;; let operands_length = function | I_Constant addr -> UInt16.width addr + | I_Jump addr -> UInt16.width addr + | I_Jump_If_False addr -> UInt16.width addr | I_Pop | I_Add | I_Sub @@ -63,7 +71,8 @@ let operands_length = function | I_And | I_Or | I_Minus - | I_Not -> 0 + | I_Not + | I_Null -> 0 ;; let decode bytes offset = @@ -92,6 +101,13 @@ let decode bytes offset = | 0x11 -> (offset, I_Or) | 0x12 -> (offset, I_Minus) | 0x13 -> (offset, I_Not) + | 0x14 -> + let offset, addr = UInt16.read bytes offset in + (offset, I_Jump addr) + | 0x15 -> + let offset, addr = UInt16.read bytes offset in + (offset, I_Jump_If_False addr) + | 0x16 -> (offset, I_Null) | _ -> raise_notrace (Invalid_argument (Printf.sprintf "unknown instruction: 0x%.2X" instruction)) @@ -118,6 +134,9 @@ let pp fmt = function | I_Or -> Format.fprintf fmt "I_Or" | I_Minus -> Format.fprintf fmt "I_Minus" | I_Not -> Format.fprintf fmt "I_Not" + | I_Jump addr -> Format.fprintf fmt "I_Jump %a" UInt16.pp addr + | I_Jump_If_False addr -> Format.fprintf fmt "I_Jump_If_False %a" UInt16.pp addr + | I_Null -> Format.fprintf fmt "I_Null" ;; let to_bytes t = @@ -133,6 +152,8 @@ let to_bytes t = let () = match t with | I_Constant addr -> offset := UInt16.write bytes !offset addr + | I_Jump addr -> offset := UInt16.write bytes !offset addr + | I_Jump_If_False addr -> offset := UInt16.write bytes !offset addr | I_Pop | I_Add | I_Sub @@ -151,7 +172,8 @@ let to_bytes t = | I_And | I_Or | I_Minus - | I_Not -> () + | I_Not + | I_Null -> () in bytes diff --git a/lib/pinc_compiler/compiler.ml b/lib/pinc_compiler/compiler.ml index 56507aa..60d6c66 100644 --- a/lib/pinc_compiler/compiler.ml +++ b/lib/pinc_compiler/compiler.ml @@ -1,8 +1,43 @@ +type emitted_instruction = { + offset : int; + instruction : Pinc_Bytecode.Instruction.t; +} + type t = { instructions : Buffer.t; constants : Pinc_Bytecode.Value.t UInt16.Map.t; + mutable last_instruction : emitted_instruction; + mutable previous_instruction : emitted_instruction; } +let set_last_instruction t offset instruction = + t.previous_instruction <- t.last_instruction; + t.last_instruction <- { offset; instruction } +;; + +let replace_instruction t offset instruction = + let current_instructions = Buffer.to_bytes t.instructions in + let src = Pinc_Bytecode.Instruction.to_bytes instruction in + let srcoff = 0 in + let len = Bytes.length src in + Bytes.blit src srcoff current_instructions offset len; + Buffer.truncate t.instructions 0; + Buffer.add_bytes t.instructions current_instructions; + t +;; + +let remove_last_instruction t = + Buffer.truncate t.instructions t.last_instruction.offset; + t.last_instruction <- t.previous_instruction; + t +;; + +let remove_last_pop t = + match t.last_instruction.instruction with + | I_Pop -> remove_last_instruction t + | _ -> t +;; + let add_constant = let id = let id' = ref (UInt16.make 0) in @@ -16,20 +51,19 @@ let add_constant = (new_id, { t with constants }) ;; -let emit_constant t constant = - let id, t = add_constant t constant in - let constant = - Pinc_Bytecode.Instruction.to_bytes @@ Pinc_Bytecode.Instruction.I_Constant id - in - Buffer.add_bytes t.instructions constant; - t -;; - let emit t opcode = + let offset = Buffer.length t.instructions in Buffer.add_bytes t.instructions @@ Pinc_Bytecode.Instruction.to_bytes @@ opcode; + set_last_instruction t offset opcode; t ;; +let emit_constant t constant = + let id, t = add_constant t constant in + let constant = Pinc_Bytecode.Instruction.I_Constant id in + emit t constant +;; + let rec compile_expr t (expr : Pinc_Types.Ast.expression) = match expr.expression_desc with | Void -> t @@ -53,7 +87,39 @@ let rec compile_expr t (expr : Pinc_Types.Ast.expression) = | ForInExpression _ -> t | TemplateExpression node -> compile_template_node t node | BlockExpression stmts -> List.fold_left compile_stmt t stmts - | ConditionalExpression _ -> t + | ConditionalExpression { condition; consequent; alternate } -> + (* Condition *) + let t = compile_expr t condition in + + (* Consequent *) + (* We create a conditional jump with a temporary address first, because we do not know where we should jump to next. *) + let t = emit t (Pinc_Bytecode.Instruction.I_Jump_If_False (UInt16.make 0xFFFF)) in + let jump_consequent_offset = t.last_instruction.offset in + let t = compile_expr t consequent in + let t = remove_last_pop t in + + (* Alternate *) + let t = emit t (Pinc_Bytecode.Instruction.I_Jump (UInt16.make 0xFFFF)) in + let jump_alternate_offset = t.last_instruction.offset in + let jump_address = UInt16.make (Buffer.length t.instructions) in + let t = + replace_instruction t jump_consequent_offset + @@ Pinc_Bytecode.Instruction.I_Jump_If_False jump_address + in + let t = + match alternate with + | None -> emit t Pinc_Bytecode.Instruction.I_Null + | Some alternate -> + let t = compile_expr t alternate in + let t = remove_last_pop t in + t + in + let jump_address = UInt16.make (Buffer.length t.instructions) in + let t = + replace_instruction t jump_alternate_offset + @@ Pinc_Bytecode.Instruction.I_Jump jump_address + in + t | UnaryExpression (op, e) -> let t = compile_expr t e in let t = @@ -125,7 +191,15 @@ let compile_declaration (decl : Pinc_Types.Ast.declaration) t = ;; let compile (ast : Pinc_Types.Ast.t) = - let t = { instructions = Buffer.create 8; constants = UInt16.Map.empty } in + let empty_instruction = { offset = 0; instruction = I_Null } in + let t = + { + instructions = Buffer.create 8; + constants = UInt16.Map.empty; + previous_instruction = empty_instruction; + last_instruction = empty_instruction; + } + in let t = StringMap.fold (fun _ -> compile_declaration) ast t in Pinc_Bytecode.Bytecode.make ~instructions:(Buffer.to_bytes t.instructions) diff --git a/lib/pinc_core/UInt16.ml b/lib/pinc_core/UInt16.ml index ad00d95..f974690 100644 --- a/lib/pinc_core/UInt16.ml +++ b/lib/pinc_core/UInt16.ml @@ -8,6 +8,7 @@ module T = struct i ;; + let to_int t = t let width _ = 2 let write bytes offset t = @@ -21,7 +22,7 @@ module T = struct ;; let incr = incr - let pp fmt = Format.fprintf fmt "0x%04X" + let pp fmt t = Format.fprintf fmt "0x%04X (%04i)" t t end include T diff --git a/lib/pinc_core/UInt16.mli b/lib/pinc_core/UInt16.mli index 4f66e16..fdaef46 100644 --- a/lib/pinc_core/UInt16.mli +++ b/lib/pinc_core/UInt16.mli @@ -1,6 +1,7 @@ type t val make : int -> t +val to_int : t -> int val width : t -> int val write : Bytes.t -> int -> t -> int val read : Bytes.t -> int -> int * t diff --git a/lib/pinc_vm/vm.ml b/lib/pinc_vm/vm.ml index dafd424..96445a9 100644 --- a/lib/pinc_vm/vm.ml +++ b/lib/pinc_vm/vm.ml @@ -191,6 +191,7 @@ let run t = let instruction_length = Bytes.length t.bytecode.instructions in while !ip < instruction_length do let new_ip, op = Instruction.decode t.bytecode.instructions !ip in + let () = ip := new_ip in let () = match op with | Instruction.I_Pop -> ignore @@ Stack.pop t.stack @@ -216,8 +217,17 @@ let run t = | Instruction.I_Or -> execute_binary_operation t Operators.Binary.OR | Instruction.I_Minus -> execute_unary_operation t Operators.Unary.MINUS | Instruction.I_Not -> execute_unary_operation t Operators.Unary.NOT + | Instruction.I_Jump addr -> ip := UInt16.to_int addr + | Instruction.I_Jump_If_False addr -> + let condition = Stack.pop t.stack in + let () = + if not @@ Value.is_true condition then + ip := UInt16.to_int addr + in + () + | Instruction.I_Null -> Stack.push t.stack Value.Null in - ip := new_ip + () done; t ;; diff --git a/test/vm/conditionals.pi b/test/vm/conditionals.pi new file mode 100644 index 0000000..a6bc360 --- /dev/null +++ b/test/vm/conditionals.pi @@ -0,0 +1,27 @@ +component IfTrue { + if (true) { + true + } +} + +component IfTrueElse { + if (true) { + true + } else { + false + } +} + +component IfFalse { + if (false) { + true + } +} + +component IfFalseElse { + if (false) { + true + } else { + false + } +} diff --git a/test/vm/run.t b/test/vm/run.t index ef607c6..5f3bd1e 100644 --- a/test/vm/run.t +++ b/test/vm/run.t @@ -2,8 +2,8 @@ 12 $ NO_COLOR="1" print_instructions . Add - 0000 I_Constant 0x0001 - 0003 I_Constant 0x0002 + 0000 I_Constant 0x0001 (0001) + 0003 I_Constant 0x0002 (0002) 0006 I_Add 0007 I_Pop @@ -11,8 +11,8 @@ 2 $ NO_COLOR="1" print_instructions . Sub - 0000 I_Constant 0x0001 - 0003 I_Constant 0x0002 + 0000 I_Constant 0x0001 (0001) + 0003 I_Constant 0x0002 (0002) 0006 I_Sub 0007 I_Pop @@ -20,8 +20,8 @@ 1.4 $ NO_COLOR="1" print_instructions . Div - 0000 I_Constant 0x0001 - 0003 I_Constant 0x0002 + 0000 I_Constant 0x0001 (0001) + 0003 I_Constant 0x0002 (0002) 0006 I_Div 0007 I_Pop @@ -29,8 +29,8 @@ 35 $ NO_COLOR="1" print_instructions . Mul - 0000 I_Constant 0x0001 - 0003 I_Constant 0x0002 + 0000 I_Constant 0x0001 (0001) + 0003 I_Constant 0x0002 (0002) 0006 I_Mul 0007 I_Pop @@ -38,8 +38,8 @@ 2 $ NO_COLOR="1" print_instructions . Mod - 0000 I_Constant 0x0001 - 0003 I_Constant 0x0002 + 0000 I_Constant 0x0001 (0001) + 0003 I_Constant 0x0002 (0002) 0006 I_Mod 0007 I_Pop @@ -47,8 +47,8 @@ 16807 $ NO_COLOR="1" print_instructions . Pow - 0000 I_Constant 0x0001 - 0003 I_Constant 0x0002 + 0000 I_Constant 0x0001 (0001) + 0003 I_Constant 0x0002 (0002) 0006 I_Pow 0007 I_Pop @@ -56,7 +56,7 @@ -5 $ NO_COLOR="1" print_instructions . MinusInt - 0000 I_Constant 0x0001 + 0000 I_Constant 0x0001 (0001) 0003 I_Minus 0004 I_Pop @@ -64,7 +64,7 @@ -3.14 $ NO_COLOR="1" print_instructions . MinusFloat - 0000 I_Constant 0x0001 + 0000 I_Constant 0x0001 (0001) 0003 I_Minus 0004 I_Pop @@ -72,20 +72,20 @@ 5 $ NO_COLOR="1" print_instructions . Math - 0000 I_Constant 0x0001 - 0003 I_Constant 0x0002 + 0000 I_Constant 0x0001 (0001) + 0003 I_Constant 0x0002 (0002) 0006 I_Mul - 0007 I_Constant 0x0003 - 0010 I_Constant 0x0004 - 0013 I_Constant 0x0005 - 0016 I_Constant 0x0006 + 0007 I_Constant 0x0003 (0003) + 0010 I_Constant 0x0004 (0004) + 0013 I_Constant 0x0005 (0005) + 0016 I_Constant 0x0006 (0006) 0019 I_Mul 0020 I_Add - 0021 I_Constant 0x0007 + 0021 I_Constant 0x0007 (0007) 0024 I_Pow 0025 I_Div 0026 I_Add - 0027 I_Constant 0x0008 + 0027 I_Constant 0x0008 (0008) 0030 I_Minus 0031 I_Add 0032 I_Pop @@ -126,8 +126,8 @@ false $ NO_COLOR="1" print_instructions . Equal - 0000 I_Constant 0x0001 - 0003 I_Constant 0x0002 + 0000 I_Constant 0x0001 (0001) + 0003 I_Constant 0x0002 (0002) 0006 I_Equal 0007 I_Pop @@ -135,8 +135,8 @@ true $ NO_COLOR="1" print_instructions . NotEqual - 0000 I_Constant 0x0001 - 0003 I_Constant 0x0002 + 0000 I_Constant 0x0001 (0001) + 0003 I_Constant 0x0002 (0002) 0006 I_Not_Equal 0007 I_Pop @@ -144,8 +144,8 @@ false $ NO_COLOR="1" print_instructions . Greater - 0000 I_Constant 0x0001 - 0003 I_Constant 0x0002 + 0000 I_Constant 0x0001 (0001) + 0003 I_Constant 0x0002 (0002) 0006 I_Greater 0007 I_Pop @@ -153,8 +153,8 @@ false $ NO_COLOR="1" print_instructions . GreaterEqual - 0000 I_Constant 0x0001 - 0003 I_Constant 0x0002 + 0000 I_Constant 0x0001 (0001) + 0003 I_Constant 0x0002 (0002) 0006 I_Greater_Equal 0007 I_Pop @@ -162,8 +162,8 @@ true $ NO_COLOR="1" print_instructions . Less - 0000 I_Constant 0x0001 - 0003 I_Constant 0x0002 + 0000 I_Constant 0x0001 (0001) + 0003 I_Constant 0x0002 (0002) 0006 I_Less 0007 I_Pop @@ -171,8 +171,8 @@ true $ NO_COLOR="1" print_instructions . LessEqual - 0000 I_Constant 0x0001 - 0003 I_Constant 0x0002 + 0000 I_Constant 0x0001 (0001) + 0003 I_Constant 0x0002 (0002) 0006 I_Less_Equal 0007 I_Pop @@ -183,3 +183,47 @@ 0000 I_True 0001 I_Not 0002 I_Pop + + $ NO_COLOR="1" print_vm . IfTrue + true + + $ NO_COLOR="1" print_instructions . IfTrue + 0000 I_True + 0001 I_Jump_If_False 0x0008 (0008) + 0004 I_True + 0005 I_Jump 0x0009 (0009) + 0008 I_Null + 0009 I_Pop + + $ NO_COLOR="1" print_vm . IfTrueElse + true + + $ NO_COLOR="1" print_instructions . IfTrueElse + 0000 I_True + 0001 I_Jump_If_False 0x0008 (0008) + 0004 I_True + 0005 I_Jump 0x0009 (0009) + 0008 I_False + 0009 I_Pop + + $ NO_COLOR="1" print_vm . IfFalse + + + $ NO_COLOR="1" print_instructions . IfFalse + 0000 I_False + 0001 I_Jump_If_False 0x0008 (0008) + 0004 I_True + 0005 I_Jump 0x0009 (0009) + 0008 I_Null + 0009 I_Pop + + $ NO_COLOR="1" print_vm . IfFalseElse + false + + $ NO_COLOR="1" print_instructions . IfFalseElse + 0000 I_False + 0001 I_Jump_If_False 0x0008 (0008) + 0004 I_True + 0005 I_Jump 0x0009 (0009) + 0008 I_False + 0009 I_Pop