From ea6a4e1f76863826877d09c1fcbd74663d61b611 Mon Sep 17 00:00:00 2001 From: Torben Ewert Date: Sat, 11 Jul 2026 22:07:24 +0200 Subject: [PATCH] feat: implement mutation and mutable bindings --- lib/pinc_compiler/compiler.ml | 37 +++++++++++---- lib/pinc_core/SymbolTable.ml | 13 ++++-- test/vm/bindings.pi | 26 +++++++++++ test/vm/bindings.t | 2 + test/vm/bindings_instructions.t | 81 +++++++++++++++++++++++++++++++++ 5 files changed, 144 insertions(+), 15 deletions(-) diff --git a/lib/pinc_compiler/compiler.ml b/lib/pinc_compiler/compiler.ml index e3ef76d..cef8ff9 100644 --- a/lib/pinc_compiler/compiler.ml +++ b/lib/pinc_compiler/compiler.ml @@ -118,8 +118,10 @@ let emit_constant t constant = emit t constant ;; -let add_symbol t name = - let symbol_table, address = SymbolTable.define_symbol t.symbol_table ~name in +let add_symbol t name ~is_mutable = + let symbol_table, address = + SymbolTable.define_symbol t.symbol_table ~name ~is_mutable + in ({ t with symbol_table }, address) ;; @@ -245,7 +247,8 @@ let rec compile_expr t (expr : Pinc_Types.Ast.expression) = in let t = List.fold_left - (fun t (Pinc_Types.Ast.Lowercase_Id (name, _)) -> fst @@ add_symbol t name) + (fun t (Pinc_Types.Ast.Lowercase_Id (name, _)) -> + fst @@ add_symbol t name ~is_mutable:false) t parameters in @@ -445,8 +448,8 @@ and compile_conditional_expression t ~condition ~consequent ~alternate = and compile_loop_expression t ~index ~iterator ~reverse:_ ~iterable ~body = let (Lowercase_Id (iterator, _)) = iterator in (* TODO: Add scope *) - let t, iterator_symbol = add_symbol t iterator in - let t, length_symbol = add_symbol t ".length" in + let t, iterator_symbol = add_symbol t iterator ~is_mutable:false in + let t, length_symbol = add_symbol t ".length" ~is_mutable:false in (* Index *) let index_identifier = match index with @@ -454,11 +457,11 @@ and compile_loop_expression t ~index ~iterator ~reverse:_ ~iterable ~body = | None -> ".index" in let t = emit_constant t @@ Pinc_Bytecode.Value.Int 0 in - let t, index_symbol = add_symbol t index_identifier in + let t, index_symbol = add_symbol t index_identifier ~is_mutable:false in let t = emit_set_symbol t index_symbol in (* Iterable *) let t = compile_expr t iterable in - let t, iterable_symbol = add_symbol t ".iterable" in + let t, iterable_symbol = add_symbol t ".iterable" ~is_mutable:false in let t = emit_set_symbol t iterable_symbol in let t = emit_get_symbol t iterable_symbol in let t = emit t @@ Pinc_Bytecode.Instruction.I_Length in @@ -496,14 +499,28 @@ and compile_stmt t (stmt : Pinc_Types.Ast.statement) = match stmt.statement_desc with | BreakStatement _ -> raise_notrace TODO | ContinueStatement _ -> raise_notrace TODO - | LetStatement (~is_optional:_, ~is_mutable:_, Lowercase_Id (name, _), expr) -> + | LetStatement (~is_optional:_, ~is_mutable, Lowercase_Id (name, _), expr) -> let t = compile_expr t expr in - let t, symbol = add_symbol t name in + let t, symbol = add_symbol t name ~is_mutable in let t = emit_set_symbol t symbol in let t = emit t @@ Pinc_Bytecode.Instruction.I_Null in let t = emit t Pinc_Bytecode.Instruction.I_Pop in t - | MutationStatement (_, _) -> raise_notrace TODO + | MutationStatement (Lowercase_Id (name, loc), expr) -> + let t, symbol = get_symbol ~loc t name in + let t = + match SymbolTable.Symbol.is_mutable symbol with + | true -> + let t = compile_expr t expr in + emit_set_symbol t symbol + | false -> + Pinc_Diagnostics.raise_error + loc + ("Trying to update a non mutable variable `" ^ name ^ "`.") + in + let t = emit t @@ Pinc_Bytecode.Instruction.I_Null in + let t = emit t Pinc_Bytecode.Instruction.I_Pop in + t | ExpressionStatement e -> let t = compile_expr t e in emit t Pinc_Bytecode.Instruction.I_Pop diff --git a/lib/pinc_core/SymbolTable.ml b/lib/pinc_core/SymbolTable.ml index 4c30c9c..0f7a6c6 100644 --- a/lib/pinc_core/SymbolTable.ml +++ b/lib/pinc_core/SymbolTable.ml @@ -11,12 +11,14 @@ module Symbol = struct name : string; scope : Scope.t; address : int; + is_mutable : bool; } - let make ~name ~scope ~address = { name; scope; address } + let make ~name ~scope ~address ~is_mutable = { name; scope; address; is_mutable } let name t = t.name let scope t = t.scope let address t = t.address + let is_mutable t = t.is_mutable end type t = { @@ -41,13 +43,13 @@ let pop_scope t = | Some t -> t ;; -let define_symbol t ~name = +let define_symbol t ~name ~is_mutable = let scope = match t.outer with | None -> Scope.Global | Some _ -> Scope.Local in - let symbol = Symbol.make ~name ~scope ~address:t.num_bindings in + let symbol = Symbol.make ~name ~scope ~address:t.num_bindings ~is_mutable in let t' = { t with @@ -60,8 +62,9 @@ let define_symbol t ~name = let define_free_symbol t symbol = let name = Symbol.name symbol in + let is_mutable = Symbol.is_mutable symbol in let free_symbol = - Symbol.make ~name ~scope:Free ~address:(List.length t.free_variables) + Symbol.make ~name ~scope:Free ~address:(List.length t.free_variables) ~is_mutable in let t' = { @@ -74,7 +77,7 @@ let define_free_symbol t symbol = ;; let define_function_symbol t ~name = - let symbol = Symbol.make ~name ~scope:Function ~address:0 in + let symbol = Symbol.make ~name ~scope:Function ~address:0 ~is_mutable:false in let t' = { t with store = StringMap.add name symbol t.store } in (t', symbol) ;; diff --git a/test/vm/bindings.pi b/test/vm/bindings.pi index 8f4823c..c7e69f7 100644 --- a/test/vm/bindings.pi +++ b/test/vm/bindings.pi @@ -23,3 +23,29 @@ component LocalBindings { f() } + +component MutableBindings { + let mutable a = "wrong"; + if (true) { + a := "correct"; + }; + + let mutable b = "wrong"; + let get_b = fn () -> b; + b := "correct"; + + let mutable c = "correct"; + let get_c = fn () -> c; + let mutable c = "wrong"; + c := "wrong"; + + [a, get_b(), get_c()] +} + +component WrongMutation { + let a = ""; + if (true) { + a := "error!"; + }; + a +} diff --git a/test/vm/bindings.t b/test/vm/bindings.t index 7d49bfb..5e680cc 100644 --- a/test/vm/bindings.t +++ b/test/vm/bindings.t @@ -7,3 +7,5 @@ $ NO_COLOR="1" print_vm . LocalBindings 21 + $ NO_COLOR="1" print_vm . MutableBindings + correct correct correct diff --git a/test/vm/bindings_instructions.t b/test/vm/bindings_instructions.t index 148537c..de8e764 100644 --- a/test/vm/bindings_instructions.t +++ b/test/vm/bindings_instructions.t @@ -68,3 +68,84 @@ 0016 I_Get_Global 0x00000000 (00000000) 0021 I_Call 0 0026 I_Pop + + $ NO_COLOR="1" print_instructions . MutableBindings + [CONSTANTS] + 0x00000000 (00000000) : "wrong" + 0x00000001 (00000001) : "correct" + 0x00000002 (00000002) : "wrong" + 0x00000003 (00000003) : [ + 0000 I_Get_Global 0x00000001 (00000001) + 0005 I_Return + ] + 0x00000004 (00000004) : "correct" + 0x00000005 (00000005) : "correct" + 0x00000006 (00000006) : [ + 0000 I_Get_Global 0x00000003 (00000003) + 0005 I_Return + ] + 0x00000007 (00000007) : "wrong" + 0x00000008 (00000008) : "wrong" + 0x00000009 (00000009) : 3 + + [INSTRUCTIONS] + 0000 I_Constant 0x00000000 (00000000) + 0005 I_Set_Global 0x00000000 (00000000) + 0010 I_Null + 0011 I_Pop + 0012 I_True + 0013 I_Jump_If_False 0x0000000A (00000010) + 0018 I_Constant 0x00000001 (00000001) + 0023 I_Set_Global 0x00000000 (00000000) + 0028 I_Null + 0029 I_Jump 0x0000000B (00000011) + 0034 I_Null + 0035 I_Pop + 0036 I_Constant 0x00000002 (00000002) + 0041 I_Set_Global 0x00000001 (00000001) + 0046 I_Null + 0047 I_Pop + 0048 I_Closure 0x00000003 (00000003) (free variables: 0) + 0057 I_Set_Global 0x00000002 (00000002) + 0062 I_Null + 0063 I_Pop + 0064 I_Constant 0x00000004 (00000004) + 0069 I_Set_Global 0x00000001 (00000001) + 0074 I_Null + 0075 I_Pop + 0076 I_Constant 0x00000005 (00000005) + 0081 I_Set_Global 0x00000003 (00000003) + 0086 I_Null + 0087 I_Pop + 0088 I_Closure 0x00000006 (00000006) (free variables: 0) + 0097 I_Set_Global 0x00000004 (00000004) + 0102 I_Null + 0103 I_Pop + 0104 I_Constant 0x00000007 (00000007) + 0109 I_Set_Global 0x00000005 (00000005) + 0114 I_Null + 0115 I_Pop + 0116 I_Constant 0x00000008 (00000008) + 0121 I_Set_Global 0x00000005 (00000005) + 0126 I_Null + 0127 I_Pop + 0128 I_Get_Global 0x00000000 (00000000) + 0133 I_Get_Global 0x00000002 (00000002) + 0138 I_Call 0 + 0143 I_Get_Global 0x00000004 (00000004) + 0148 I_Call 0 + 0153 I_Constant 0x00000009 (00000009) + 0158 I_Array + 0159 I_Pop + + $ NO_COLOR="1" print_instructions . WrongMutation + + ERROR in file ./bindings.pi:48:5-6 + + 47 │ if (true) { + 48 │ a := "error!"; + │ ^ + 49 │ }; + + Trying to update a non mutable variable `a`. + [1] -- 2.51.2