diff --git a/lib/pinc_compiler/compiler.ml b/lib/pinc_compiler/compiler.ml index bedf817..1e2badb 100644 --- a/lib/pinc_compiler/compiler.ml +++ b/lib/pinc_compiler/compiler.ml @@ -495,17 +495,39 @@ and compile_loop_expression t ~index ~iterator ~reverse:_ ~iterable ~body = let t = emit t @@ Pinc_Bytecode.Instruction.I_Array in t +and compile_let_stmt ~predefined t definition = + let ~is_optional:_, ~is_mutable, Pinc_Types.Ast.Lowercase_Id (name, _), body = + definition + in + let t = compile_expr t body in + let t, symbol = + match List.assoc_opt name predefined with + | None -> define_symbol t name ~is_mutable + | Some symbol -> (t, symbol) + 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 + 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) -> - let t = compile_expr t expr in - let t, symbol = define_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 + | LetGroupStatement let_definitions -> + let t, predefined = + List.fold_left + (fun (t, symbols) definition -> + let ~is_optional:_, ~is_mutable, Pinc_Types.Ast.Lowercase_Id (name, _), _ = + definition + in + let t, symbol = define_symbol t name ~is_mutable in + (t, (name, symbol) :: symbols)) + (t, []) + let_definitions + in + List.fold_left (compile_let_stmt ~predefined) t let_definitions + | LetStatement definition -> compile_let_stmt ~predefined:[] t definition | MutationStatement (Lowercase_Id (name, loc), expr) -> let t, symbol = get_symbol ~loc t name in let t = diff --git a/lib/pinc_format/Formatter.ml b/lib/pinc_format/Formatter.ml index 43d5928..0003605 100644 --- a/lib/pinc_format/Formatter.ml +++ b/lib/pinc_format/Formatter.ml @@ -465,7 +465,7 @@ and format_continue_stmt i = in string "continue" ^^ num -and format_let ~is_optional ~is_mutable id expr = +and format_let_body ~is_optional ~is_mutable id expr = let maybe_mutable = if is_mutable then space ^^ string "mutable" @@ -478,8 +478,7 @@ and format_let ~is_optional ~is_mutable id expr = else empty in - string "let" - ^^ maybe_mutable + maybe_mutable ^^ space ^^ format_lowercase_id id ^^ maybe_optional @@ -488,6 +487,19 @@ and format_let ~is_optional ~is_mutable id expr = ^^ space ^^ format_expression expr +and format_let ~is_optional ~is_mutable id expr = + string "let" ^^ format_let_body ~is_optional ~is_mutable id expr + +and format_let_group let_definitions = + let let_bodies = + separate (space ^^ string "and") + @@ List.map + (fun (~is_optional, ~is_mutable, id, expr) -> + format_let_body ~is_optional ~is_mutable id expr) + let_definitions + in + string "let" ^^ let_bodies + and format_mutation id expr = format_lowercase_id id ^^ space ^^ colon ^^ equals ^^ space ^^ format_expression expr @@ -501,6 +513,7 @@ and format_statement ~last (statement : Parsetree.statement) = | P_ContinueStatement s -> format_continue_stmt s ^^ semi | P_LetStatement (~is_optional, ~is_mutable, id, expr) -> format_let ~is_optional ~is_mutable id expr ^^ semi + | P_LetGroupStatement let_definitions -> format_let_group let_definitions ^^ semi | P_MutationStatement (id, expr) -> format_mutation id expr ^^ semi | P_ExpressionStatement s when last -> format_expression_stmt s | P_ExpressionStatement s -> format_expression_stmt s ^^ semi diff --git a/lib/pinc_interpreter/Interpreter.ml b/lib/pinc_interpreter/Interpreter.ml index e814e70..d0ee287 100644 --- a/lib/pinc_interpreter/Interpreter.ml +++ b/lib/pinc_interpreter/Interpreter.ml @@ -75,6 +75,7 @@ and eval_statement ~state statement = match statement.Ast.statement_desc with | Ast.LetStatement (~is_optional, ~is_mutable, Lowercase_Id ident, expression) -> eval_let ~state ~ident ~is_mutable ~is_optional expression + | Ast.LetGroupStatement let_definitions -> eval_let_group ~state let_definitions | Ast.MutationStatement (Lowercase_Id ident, expression) -> eval_mutation ~state ~ident expression | Ast.BreakStatement _ -> raise_notrace (Loop_Break state) @@ -934,6 +935,13 @@ and eval_let ~state ~ident ~is_mutable ~is_optional expression = |> State.add_value_to_scope ~ident ~value ~is_mutable ~is_optional |> State.add_output ~output:(Helpers.Value.null ~loc:expression.expression_loc ()) +and eval_let_group ~state let_definitions = + List.fold_left + (fun state (~is_optional, ~is_mutable, Ast.Lowercase_Id ident, expression) -> + eval_let ~state ~ident ~is_mutable ~is_optional expression) + state + let_definitions + and eval_mutation ~state ~ident expression = let ident, ident_location = ident in let current_binding = State.get_value_from_scope ~ident state in diff --git a/lib/pinc_parser/DependencyGraph.ml b/lib/pinc_parser/DependencyGraph.ml index 95e35b5..73063f4 100644 --- a/lib/pinc_parser/DependencyGraph.ml +++ b/lib/pinc_parser/DependencyGraph.ml @@ -37,6 +37,11 @@ let rec collect_expr acc (expr : Pinc_Types.Ast.expression) = and collect_stmt acc (stmt : Pinc_Types.Ast.statement) = match stmt.statement_desc with | BreakStatement _ | ContinueStatement _ -> acc + | LetGroupStatement let_definitions -> + List.fold_left + (fun acc (~is_optional:_, ~is_mutable:_, _, e) -> collect_expr acc e) + acc + let_definitions | LetStatement (_, e, ..) | MutationStatement (_, e) | ExpressionStatement e -> collect_expr acc e diff --git a/lib/pinc_parser/Parser.ml b/lib/pinc_parser/Parser.ml index e8aaa43..2cc58e6 100644 --- a/lib/pinc_parser/Parser.ml +++ b/lib/pinc_parser/Parser.ml @@ -24,6 +24,18 @@ let next t = t.token <- token ;; +let current_non_annotation_token t = + let rec loop token = + match token.Token.typ with + | Token.COMMENT _ | Token.BLANKLINE -> + let token = Lexer.scan t.lexer in + Queue.add token t.next; + loop token + | t -> t + in + loop t.token +;; + let peek t = let token = match Queue.peek_opt t.next with @@ -454,24 +466,55 @@ module Rules = struct in Some (Parsetree.P_ContinueStatement num_loops) (* PARSING LET STATEMENT *) - | Token.KEYWORD_LET -> ( - let start_token = t.token in - next t; - let is_mutable = t |> optional Token.KEYWORD_MUTABLE in - let identifier = Helpers.expect_identifier ~typ:`Lower t in - let is_optional = t |> optional Token.QUESTIONMARK in - t |> expect Token.EQUAL; - let end_token = t.token in - let expression = parse_expression t in - match expression with - | Some expression -> - Some - (Parsetree.P_LetStatement - (~is_optional, ~is_mutable, P_Lowercase_Id identifier, expression)) - | None -> - Diagnostics.raise_error - (Location.merge ~s:start_token.location ~e:end_token.location ()) - "Expected expression as right hand side of let declaration") + | Token.KEYWORD_LET -> + let rec parse_let_definitions ~expect_function acc t = + let start_token = t.token in + next t; + let is_mutable = t |> optional Token.KEYWORD_MUTABLE in + let identifier = Helpers.expect_identifier ~typ:`Lower t in + let is_optional = t |> optional Token.QUESTIONMARK in + t |> expect Token.EQUAL; + let end_token = t.token in + let expression = + match parse_expression t with + | Some expression -> expression + | None -> + Diagnostics.raise_error + (Location.merge ~s:start_token.location ~e:end_token.location ()) + "Expected expression as right hand side of let declaration" + in + let let_definition = + (~is_optional, ~is_mutable, Parsetree.P_Lowercase_Id identifier, expression) + in + let is_function = + match expression.expression_desc with + | Parsetree.P_Function _ -> true + | _ when expect_function -> + Diagnostics.raise_error + expression.expression_loc + "All expressions in `let ... and` declarations must be function \ + definitions" + | _ -> false + in + match current_non_annotation_token t with + | Token.KEYWORD_AND when is_function -> + let () = ignore @@ parse_annotations t in + parse_let_definitions ~expect_function:true (let_definition :: acc) t + | Token.KEYWORD_AND -> + Diagnostics.raise_error + expression.expression_loc + "All expressions in `let ... and` declarations must be function \ + definitions" + | _ -> List.rev (let_definition :: acc) + in + let let_definitions = parse_let_definitions ~expect_function:false [] t in + let stmt = + match let_definitions with + | [] -> assert false + | [ definition ] -> Parsetree.P_LetStatement definition + | definitions -> Parsetree.P_LetGroupStatement definitions + in + Some stmt (* PARSING MUTATION STATEMENT *) | Token.IDENT_LOWER identifier when peek t = Token.COLON_EQUAL -> let start_token = t.token in diff --git a/lib/pinc_parser/Parsetree.ml b/lib/pinc_parser/Parsetree.ml index 334d565..59f88ae 100644 --- a/lib/pinc_parser/Parsetree.ml +++ b/lib/pinc_parser/Parsetree.ml @@ -124,6 +124,8 @@ and statement_desc = | P_ContinueStatement of int | P_LetStatement of (is_optional:bool * is_mutable:bool * lowercase_identifier * expression) + | P_LetGroupStatement of + (is_optional:bool * is_mutable:bool * lowercase_identifier * expression) list | P_MutationStatement of lowercase_identifier * expression | P_ExpressionStatement of expression diff --git a/lib/pinc_parser/Token.ml b/lib/pinc_parser/Token.ml index 69d0ad3..18ddf95 100644 --- a/lib/pinc_parser/Token.ml +++ b/lib/pinc_parser/Token.ml @@ -50,6 +50,7 @@ type token_type = | PLUSPLUS | KEYWORD_FN | KEYWORD_LET + | KEYWORD_AND | KEYWORD_MUTABLE | KEYWORD_TRUE | KEYWORD_FALSE @@ -140,6 +141,7 @@ let to_string = function | PERCENT -> "%" | KEYWORD_FN -> "fn" | KEYWORD_LET -> "let" + | KEYWORD_AND -> "and" | KEYWORD_MUTABLE -> "mutable" | KEYWORD_IF -> "if" | KEYWORD_ELSE -> "else" @@ -173,6 +175,7 @@ let to_string = function let is_keyword = function | KEYWORD_FN | KEYWORD_LET + | KEYWORD_AND | KEYWORD_MUTABLE | KEYWORD_TRUE | KEYWORD_FALSE @@ -253,6 +256,7 @@ let is_keyword = function let keyword_of_string = function | "fn" -> Some KEYWORD_FN | "let" -> Some KEYWORD_LET + | "and" -> Some KEYWORD_AND | "mutable" -> Some KEYWORD_MUTABLE | "true" -> Some KEYWORD_TRUE | "false" -> Some KEYWORD_FALSE diff --git a/lib/pinc_parser/Token.mli b/lib/pinc_parser/Token.mli index b3addc1..3eec031 100644 --- a/lib/pinc_parser/Token.mli +++ b/lib/pinc_parser/Token.mli @@ -50,6 +50,7 @@ type token_type = | PLUSPLUS | KEYWORD_FN | KEYWORD_LET + | KEYWORD_AND | KEYWORD_MUTABLE | KEYWORD_TRUE | KEYWORD_FALSE diff --git a/lib/pinc_parser/Transformer.ml b/lib/pinc_parser/Transformer.ml index 6bb67eb..baabd74 100644 --- a/lib/pinc_parser/Transformer.ml +++ b/lib/pinc_parser/Transformer.ml @@ -492,18 +492,29 @@ and transform_expression env (exression : Parsetree.expression) = and transform_break_stmt env s = (env, BreakStatement s) and transform_continue_stmt env s = (env, ContinueStatement s) -and transform_let env ~is_optional ~is_mutable id expr = +and transform_let_payload env payload = + let ~is_optional, ~is_mutable, id, expr = payload in let requirement = if is_optional then `Optional else `Required in + let env, id' = transform_lowercase_id env id in let env = { env with Env.current_identifier = Some (requirement, id) } in - let env, id = transform_lowercase_id env id in let env, expr = transform_expression env expr in let env = { env with Env.current_identifier = None } in - (env, LetStatement (~is_optional, ~is_mutable, id, expr)) + (env, (~is_optional, ~is_mutable, id', expr)) + +and transform_let env ~is_optional ~is_mutable id expr = + let env, payload = transform_let_payload env (~is_optional, ~is_mutable, id, expr) in + (env, LetStatement payload) + +and transform_let_group env let_definitions = + let env, let_definitions = + List.fold_map ~init:env ~f:transform_let_payload let_definitions + in + (env, LetGroupStatement let_definitions) and transform_mutation env id expr = let env, id = transform_lowercase_id env id in @@ -521,6 +532,7 @@ and transform_statement env (statement : Parsetree.statement) = | P_ContinueStatement s -> transform_continue_stmt env s | P_LetStatement (~is_optional, ~is_mutable, id, expr) -> transform_let env ~is_optional ~is_mutable id expr + | P_LetGroupStatement let_definitions -> transform_let_group env let_definitions | P_MutationStatement (id, expr) -> transform_mutation env id expr | P_ExpressionStatement s -> transform_expression_stmt env s in diff --git a/lib/pinc_types/Ast.ml b/lib/pinc_types/Ast.ml index 84298e4..88aab2f 100644 --- a/lib/pinc_types/Ast.ml +++ b/lib/pinc_types/Ast.ml @@ -119,6 +119,8 @@ and statement_desc = | ContinueStatement of int | LetStatement of (is_optional:bool * is_mutable:bool * lowercase_identifier * expression) + | LetGroupStatement of + (is_optional:bool * is_mutable:bool * lowercase_identifier * expression) list | MutationStatement of lowercase_identifier * expression | ExpressionStatement of expression diff --git a/test/function/data.pi b/test/function/data.pi index 9e35281..9756721 100644 --- a/test/function/data.pi +++ b/test/function/data.pi @@ -36,9 +36,9 @@ component Component { } else { is_odd(i - 1) } - }; - - let is_odd = fn (i) -> { + } + + and is_odd = fn (i) -> { if (i == 0) { false } else { diff --git a/test/function/run.t b/test/function/run.t index 01bca54..0380b00 100644 --- a/test/function/run.t +++ b/test/function/run.t @@ -62,9 +62,7 @@ } else { is_odd(i - 1) } - }; - - let is_odd = fn (i) -> { + } and is_odd = fn (i) -> { if (i == 0) { false } else { diff --git a/test/parse_error/let_and_without_function.pi b/test/parse_error/let_and_without_function.pi new file mode 100644 index 0000000..16326c0 --- /dev/null +++ b/test/parse_error/let_and_without_function.pi @@ -0,0 +1,3 @@ +component Component { + let a = 1 and b = 2; +} diff --git a/test/parse_error/run.t b/test/parse_error/run.t index 185c9c8..9270557 100644 --- a/test/parse_error/run.t +++ b/test/parse_error/run.t @@ -74,3 +74,15 @@ Tag keys may only contain ASCII characters (a-Z), numbers (0-9) and underscores (_). [1] + + $ NO_COLOR="1" print ./let_and_without_function.pi Component + + ERROR in file ./let_and_without_function.pi:2:11-12 + + 1 │ component Component { + 2 │ let a = 1 and b = 2; + │ ^ + 3 │ } + + All expressions in `let ... and` declarations must be function definitions + [1] diff --git a/test/vm/functions.pi b/test/vm/functions.pi index 574ce6e..24310b7 100644 --- a/test/vm/functions.pi +++ b/test/vm/functions.pi @@ -108,9 +108,7 @@ component MutuallyRecursiveFunction { } else { is_odd(i - 1) } - }; - - let is_odd = fn (i) -> { + } and is_odd = fn (i) -> { if (i == 0) { false } else { @@ -118,5 +116,5 @@ component MutuallyRecursiveFunction { } }; - is_even(1) + is_even(2) } diff --git a/test/vm/functions.t b/test/vm/functions.t index f48ea0a..2c0fc0c 100644 --- a/test/vm/functions.t +++ b/test/vm/functions.t @@ -27,15 +27,5 @@ $ NO_COLOR="1" print_vm . RecursiveFunction 154 -# TODO: - $ NO_COLOR="1" print_instructions . MutuallyRecursiveFunction - - ERROR in file ./functions.pi:109:7-13 - - 108 │ } else { - 109 │ is_odd(i - 1) - │ ^^^^^^ - 110 │ } - - Unbound identifier `is_odd` - [1] + $ NO_COLOR="1" print_vm . MutuallyRecursiveFunction + true diff --git a/test/vm/functions_instructions.t b/test/vm/functions_instructions.t index ceedbf4..42edf9d 100644 --- a/test/vm/functions_instructions.t +++ b/test/vm/functions_instructions.t @@ -315,15 +315,52 @@ 0031 I_Call 2 0036 I_Pop -# TODO: $ NO_COLOR="1" print_instructions . MutuallyRecursiveFunction + [CONSTANTS] + 0x00000000 (00000000) : 0 + 0x00000001 (00000001) : 1 + 0x00000002 (00000002) : [ + 0000 I_Get_Local 0x00000000 (00000000) + 0005 I_Constant 0x00000000 (00000000) + 0010 I_Equal + 0011 I_Jump_If_False 0x00000006 (00000006) + 0016 I_True + 0017 I_Jump 0x0000000B (00000011) + 0022 I_Get_Global 0x00000001 (00000001) + 0027 I_Get_Local 0x00000000 (00000000) + 0032 I_Constant 0x00000001 (00000001) + 0037 I_Sub + 0038 I_Call 1 + 0043 I_Return + ] + 0x00000003 (00000003) : 0 + 0x00000004 (00000004) : 1 + 0x00000005 (00000005) : [ + 0000 I_Get_Local 0x00000000 (00000000) + 0005 I_Constant 0x00000003 (00000003) + 0010 I_Equal + 0011 I_Jump_If_False 0x00000006 (00000006) + 0016 I_False + 0017 I_Jump 0x0000000B (00000011) + 0022 I_Get_Global 0x00000000 (00000000) + 0027 I_Get_Local 0x00000000 (00000000) + 0032 I_Constant 0x00000004 (00000004) + 0037 I_Sub + 0038 I_Call 1 + 0043 I_Return + ] + 0x00000006 (00000006) : 2 - ERROR in file ./functions.pi:109:7-13 - - 108 │ } else { - 109 │ is_odd(i - 1) - │ ^^^^^^ - 110 │ } - - Unbound identifier `is_odd` - [1] + [INSTRUCTIONS] + 0000 I_Closure 0x00000002 (00000002) (free variables: 0) + 0009 I_Set_Global 0x00000000 (00000000) + 0014 I_Null + 0015 I_Pop + 0016 I_Closure 0x00000005 (00000005) (free variables: 0) + 0025 I_Set_Global 0x00000001 (00000001) + 0030 I_Null + 0031 I_Pop + 0032 I_Get_Global 0x00000000 (00000000) + 0037 I_Constant 0x00000006 (00000006) + 0042 I_Call 1 + 0047 I_Pop