From 228a1864ce9321c4e5740e949f35eaee1086f95e Mon Sep 17 00:00:00 2001 From: Torben Ewert Date: Fri, 17 Jul 2026 19:02:50 +0200 Subject: [PATCH] feat: replace @@ operator with stdlib functions --- lib/pinc_bytecode/externals.ml | 51 +++++++++++ lib/pinc_compiler/compiler.ml | 1 - lib/pinc_format/Formatter.ml | 1 - lib/pinc_interpreter/Externals.ml | 120 ++++++++++++++++++++++++++ lib/pinc_interpreter/Helpers.ml | 17 ++++ lib/pinc_interpreter/Interpreter.ml | 44 ---------- lib/pinc_parser/Lexer.ml | 9 -- lib/pinc_parser/Parser.ml | 1 - lib/pinc_parser/Token.ml | 3 - lib/pinc_parser/Token.mli | 1 - lib/pinc_parser/stdlib/Base_Html.pi | 3 + lib/pinc_parser/stdlib/Base_Record.pi | 3 + lib/pinc_types/Operators.ml | 4 +- test/array/data.pi | 4 +- test/array/run.t | 31 ++++--- test/slot/SlotProvider.pi | 6 +- test/slot/run.t | 6 +- 17 files changed, 221 insertions(+), 84 deletions(-) create mode 100644 lib/pinc_parser/stdlib/Base_Html.pi create mode 100644 lib/pinc_parser/stdlib/Base_Record.pi diff --git a/lib/pinc_bytecode/externals.ml b/lib/pinc_bytecode/externals.ml index b01ff35..657cb8a 100644 --- a/lib/pinc_bytecode/externals.ml +++ b/lib/pinc_bytecode/externals.ml @@ -40,6 +40,55 @@ module PincArray = struct ;; end +module PincRecord = struct + let merge ~arguments = + let record1, record2 = + match arguments with + | [| Value.Record a; Value.Record b |] -> (a, b) + | [| _; Value.Record _ |] -> + raise_notrace + (Invalid_argument + "The first argument given to the Record.merge function is not of type \ + record") + | [| Value.Array _; _ |] -> + raise_notrace + (Invalid_argument + "The second argument given to the Record.merge function is not of type \ + record") + | _ -> + raise_notrace + (Invalid_argument "The Record.merge function expects two arguments") + in + + Value.Record (StringMap.union (fun _ _ v -> Some v) record1 record2) + ;; +end + +module PincHtml = struct + let with_attribute ~arguments = + let element, key, value = + match arguments with + | [| Value.HtmlTemplateNode el; Value.String key; value |] -> (el, key, value) + | [| _; Value.String _; _ |] -> + raise_notrace + (Invalid_argument + "The first argument given to the Html.with_attribute function is not of \ + type html element") + | [| Value.HtmlTemplateNode _; _; _ |] -> + raise_notrace + (Invalid_argument + "The second argument given to the Html.with_attribute function is not of \ + type string") + | _ -> + raise_notrace + (Invalid_argument "The Record.merge function expects exactly three arguments") + in + + let attributes = StringMap.add key value element.attributes in + Value.HtmlTemplateNode { element with attributes } + ;; +end + module PincString = struct let length ~arguments = let string = @@ -103,6 +152,8 @@ let all = ("pinc_array_append", (2, PincArray.append)); ("pinc_string_length", (1, PincString.length)); ("pinc_string_sub", (3, PincString.sub)); + ("pinc_record_merge", (2, PincRecord.merge)); + ("pinc_html_with_attribute", (3, PincHtml.with_attribute)); |] ;; diff --git a/lib/pinc_compiler/compiler.ml b/lib/pinc_compiler/compiler.ml index 5dbad2f..5a9d6f5 100644 --- a/lib/pinc_compiler/compiler.ml +++ b/lib/pinc_compiler/compiler.ml @@ -500,7 +500,6 @@ and compile_binary_expression t ~left ~op ~right = let t = compile_expr t right in emit t Pinc_Bytecode.Instruction.I_Index | Pinc_Types.Operators.Binary.PIPE -> assert false - | Pinc_Types.Operators.Binary.MERGE -> raise_notrace TODO | Pinc_Types.Operators.Binary.RANGE -> let t = compile_expr t left in let t = compile_expr t right in diff --git a/lib/pinc_format/Formatter.ml b/lib/pinc_format/Formatter.ml index f7d6fdb..0ad1975 100644 --- a/lib/pinc_format/Formatter.ml +++ b/lib/pinc_format/Formatter.ml @@ -248,7 +248,6 @@ and format_binary_expression left op right = | Parsetree.Operators.Binary.BRACKET_ACCESS -> l ^^ brackets r | Parsetree.Operators.Binary.PIPE -> l ^^ break 1 ^^ ifflat empty (twice space) ^^ bar ^^ rangle ^^ space ^^ r - | Parsetree.Operators.Binary.MERGE -> l ^^ space ^^ twice at ^^ space ^^ r | Parsetree.Operators.Binary.RANGE -> l ^^ twice dot ^^ r | Parsetree.Operators.Binary.INCLUSIVE_RANGE -> l ^^ repeat 3 dot ^^ r diff --git a/lib/pinc_interpreter/Externals.ml b/lib/pinc_interpreter/Externals.ml index bf50858..ad210ba 100644 --- a/lib/pinc_interpreter/Externals.ml +++ b/lib/pinc_interpreter/Externals.ml @@ -76,6 +76,124 @@ module PincArray = struct ;; end +module PincRecord = struct + let merge ~loc ~arguments state = + let record1 = + let open Helpers.Expect in + arguments |> required (attribute "record1" record) + in + let record2 = + let open Helpers.Expect in + arguments |> required (attribute "record2" record) + in + + let record1 = + match record1 with + | Error `Required -> + Pinc_Diagnostics.raise_error + loc + (Printf.sprintf + "The Record.merge function expects a record as the first and second \ + agument.") + | Error (`UnexpectedType value_loc) -> + Pinc_Diagnostics.raise_error + value_loc + (Printf.sprintf + "The first argument given to the Record.merge function is not of type \ + record") + | Ok v -> v + in + + let record2 = + match record2 with + | Error `Required -> + Pinc_Diagnostics.raise_error + loc + (Printf.sprintf + "The Record.merge function expects a record as the first and second \ + agument.") + | Error (`UnexpectedType value_loc) -> + Pinc_Diagnostics.raise_error + value_loc + (Printf.sprintf + "The second argument given to the Record.merge function is not of type \ + record") + | Ok v -> v + in + + let result = StringMap.union (fun _ _ v -> Some v) record1 record2 in + + let output = Helpers.Value.record result in + State.add_output state ~output + ;; +end + +module PincHtml = struct + let with_attribute ~loc ~arguments state = + let element = + let open Helpers.Expect in + arguments |> required (attribute "element" Helpers.Expect.template_node) + in + let key = + let open Helpers.Expect in + arguments |> required (attribute "key" string) + in + let value = + let open Helpers.Expect in + arguments |> required (attribute "value" (fun v -> Ok (Some v))) + in + + let element = + match element with + | Error `Required -> + Pinc_Diagnostics.raise_error + loc + (Printf.sprintf + "The Html.with_attribute function expects a html template node as the \ + first argument.") + | Error (`UnexpectedType value_loc) -> + Pinc_Diagnostics.raise_error + value_loc + (Printf.sprintf + "The first argument given to the Html.with_attribute function is not an \ + html template node") + | Ok v -> v + in + + let key = + match key with + | Error `Required -> + Pinc_Diagnostics.raise_error + loc + (Printf.sprintf + "The Html.with_attribute function expects a string as the second argument.") + | Error (`UnexpectedType value_loc) -> + Pinc_Diagnostics.raise_error + value_loc + (Printf.sprintf + "The second argument given to the Html.with_attribute function is not of \ + type string") + | Ok v -> v + in + + let value = + match value with + | Error `Required -> + Pinc_Diagnostics.raise_error + loc + (Printf.sprintf + "The Html.with_attribute function expects a value as the third argument.") + | Ok v -> v + in + + let tag, attributes, children = element in + let attributes = StringMap.add key value attributes in + + let output = Helpers.Value.html_template_node ~tag ~attributes ~children () in + State.add_output state ~output + ;; +end + module PincString = struct let length ~loc ~arguments state = let str = arguments |> Helpers.Expect.(required (attribute "string" string)) in @@ -169,5 +287,7 @@ let all = ("pinc_array_append", PincArray.append); ("pinc_string_length", PincString.length); ("pinc_string_sub", PincString.sub); + ("pinc_record_merge", PincRecord.merge); + ("pinc_html_with_attribute", PincHtml.with_attribute); ] ;; diff --git a/lib/pinc_interpreter/Helpers.ml b/lib/pinc_interpreter/Helpers.ml index 66e7483..5531e60 100644 --- a/lib/pinc_interpreter/Helpers.ml +++ b/lib/pinc_interpreter/Helpers.ml @@ -38,6 +38,15 @@ module Value = struct let record ?(loc = Pinc_Diagnostics.Location.none) m = { value_loc = loc; value_desc = Record m } ;; + + let html_template_node + ?(loc = Pinc_Diagnostics.Location.none) + ~tag + ~attributes + ~children + () = + { value_loc = loc; value_desc = HtmlTemplateNode (tag, attributes, children) } + ;; end module Expect = struct @@ -94,6 +103,14 @@ module Expect = struct | _ -> Result.error (`UnexpectedType v.value_loc) ;; + let template_node v = + match v.value_desc with + | Null -> Result.ok None + | HtmlTemplateNode (tag, attributes, children) -> + Result.ok (Some (tag, attributes, children)) + | _ -> Result.error (`UnexpectedType v.value_loc) + ;; + let list fn v = match v.value_desc with | Null -> Result.ok None diff --git a/lib/pinc_interpreter/Interpreter.ml b/lib/pinc_interpreter/Interpreter.ml index b472286..6594a36 100644 --- a/lib/pinc_interpreter/Interpreter.ml +++ b/lib/pinc_interpreter/Interpreter.ml @@ -208,8 +208,6 @@ and eval_expression ~state expression = eval_binary_dot_access ~state left right | Ast.BinaryExpression (left, Ast.Operators.Binary.BRACKET_ACCESS, right) -> eval_binary_bracket_access ~state left right - | Ast.BinaryExpression (left, Ast.Operators.Binary.MERGE, right) -> - eval_binary_merge ~state left right | Ast.BinaryExpression (left, Ast.Operators.Binary.RANGE, right) -> eval_range ~state ~inclusive:false left right | Ast.BinaryExpression (left, Ast.Operators.Binary.INCLUSIVE_RANGE, right) -> @@ -816,48 +814,6 @@ and eval_binary_bracket_access ~state left right = "Trying to access a property on a non record or array value (%s)." (Value.to_string left_value)) -and eval_binary_merge ~state left_expression right_expression = - let left = left_expression |> eval_expression ~state |> State.get_output in - let right = right_expression |> eval_expression ~state |> State.get_output in - let eval_merge left right = - match (left.value_desc, right.value_desc) with - | Array l, Array r -> - Helpers.Value.array - ~loc:(Location.merge ~s:left.value_loc ~e:right.value_loc ()) - (Array.append l r) - | Record l, Record r -> - Helpers.Value.record - ~loc:(Location.merge ~s:left.value_loc ~e:right.value_loc ()) - (StringMap.union (fun _key _x y -> Some y) l r) - | HtmlTemplateNode (tag, attributes, children), Record right -> - let attributes = StringMap.union (fun _key _x y -> Some y) attributes right in - { left with value_desc = HtmlTemplateNode (tag, attributes, children) } - | HtmlTemplateNode _, _ -> - Diagnostics.raise_error - right_expression.expression_loc - "Trying to merge a non record value onto tag attributes." - | ComponentTemplateNode _, _ -> - Diagnostics.raise_error - right_expression.expression_loc - "Component attributes can't be modified." - | Array _, _ -> - Diagnostics.raise_error - right_expression.expression_loc - "Trying to merge a non array value onto an array." - | _, Array _ -> - Diagnostics.raise_error - right_expression.expression_loc - "Trying to merge an array value onto a non array." - | _ -> - Diagnostics.raise_error - (Location.merge - ~s:left_expression.expression_loc - ~e:right_expression.expression_loc - ()) - "Trying to merge two non array values." - in - state |> State.add_output ~output:(eval_merge left right) - and eval_unary_not ~state expression = let expression_value = eval_expression ~state expression |> State.get_output in match expression_value.value_desc with diff --git a/lib/pinc_parser/Lexer.ml b/lib/pinc_parser/Lexer.ml index e7f3147..390326b 100644 --- a/lib/pinc_parser/Lexer.ml +++ b/lib/pinc_parser/Lexer.ml @@ -1021,15 +1021,6 @@ and scan_normal_token ~start_pos t = | _ -> eat t; Token.DOT) - | `Chr '@' -> ( - match peek t with - | `Chr '@' -> - eat2 t; - Token.ATAT - | _ -> - Diagnostics.raise_error - (Location.make ~s:start_pos ~e:(make_position t) ()) - "The character @ is unknown. You should remove it.") | `Chr '#' -> ( match peek t with | `Chr 'A' .. 'Z' -> diff --git a/lib/pinc_parser/Parser.ml b/lib/pinc_parser/Parser.ml index bfab972..2cfe06e 100644 --- a/lib/pinc_parser/Parser.ml +++ b/lib/pinc_parser/Parser.ml @@ -799,7 +799,6 @@ module Rules = struct | Token.STAR_STAR -> Some Operators.Binary.POW | Token.PERCENT -> Some Operators.Binary.MODULO | Token.DOT -> Some Operators.Binary.DOT_ACCESS - | Token.ATAT -> Some Operators.Binary.MERGE | Token.LEFT_BRACK -> Some Operators.Binary.BRACKET_ACCESS | Token.DOTDOT -> Some Operators.Binary.RANGE | Token.DOTDOTDOT -> Some Operators.Binary.INCLUSIVE_RANGE diff --git a/lib/pinc_parser/Token.ml b/lib/pinc_parser/Token.ml index 50fc311..76aeb36 100644 --- a/lib/pinc_parser/Token.ml +++ b/lib/pinc_parser/Token.ml @@ -27,7 +27,6 @@ type token_type = | DOTDOTDOT | ARROW | PIPE - | ATAT | QUESTIONMARK | EQUAL | COLON_EQUAL @@ -118,7 +117,6 @@ let to_string = function | QUESTIONMARK -> "?" | ARROW -> "->" | PIPE -> "|>" - | ATAT -> "@@" | EQUAL -> "=" | COLON_EQUAL -> ":=" | NOT_EQUAL -> "!=" @@ -206,7 +204,6 @@ let is_keyword = function | DOTDOTDOT | ARROW | PIPE - | ATAT | QUESTIONMARK | EQUAL | COLON_EQUAL diff --git a/lib/pinc_parser/Token.mli b/lib/pinc_parser/Token.mli index 765dcdc..8dbab87 100644 --- a/lib/pinc_parser/Token.mli +++ b/lib/pinc_parser/Token.mli @@ -27,7 +27,6 @@ type token_type = | DOTDOTDOT | ARROW | PIPE - | ATAT | QUESTIONMARK | EQUAL | COLON_EQUAL diff --git a/lib/pinc_parser/stdlib/Base_Html.pi b/lib/pinc_parser/stdlib/Base_Html.pi new file mode 100644 index 0000000..5c6e6ae --- /dev/null +++ b/lib/pinc_parser/stdlib/Base_Html.pi @@ -0,0 +1,3 @@ +library Base_Html { + let with_attribute = fn (element, key, value) -> %%pinc_html_with_attribute%%; +} diff --git a/lib/pinc_parser/stdlib/Base_Record.pi b/lib/pinc_parser/stdlib/Base_Record.pi new file mode 100644 index 0000000..0886875 --- /dev/null +++ b/lib/pinc_parser/stdlib/Base_Record.pi @@ -0,0 +1,3 @@ +library Base_Record { + let merge = fn (record1, record2) -> %%pinc_record_merge%%; +} diff --git a/lib/pinc_types/Operators.ml b/lib/pinc_types/Operators.ml index 0272fd6..d4f5b2b 100644 --- a/lib/pinc_types/Operators.ml +++ b/lib/pinc_types/Operators.ml @@ -24,7 +24,6 @@ module Binary = struct | DOT_ACCESS | BRACKET_ACCESS | PIPE - | MERGE | RANGE | INCLUSIVE_RANGE @@ -38,7 +37,7 @@ module Binary = struct | AND -> 20 | OR -> 10 | RANGE | INCLUSIVE_RANGE -> 5 - | MERGE | BRACKET_ACCESS | PIPE -> 0 + | BRACKET_ACCESS | PIPE -> 0 ;; let get_associativity = function @@ -63,7 +62,6 @@ module Binary = struct | LESS_EQUAL -> "<=" | AND -> "&&" | OR -> "||" - | MERGE -> "@@" | BRACKET_ACCESS -> "[" | RANGE -> ".." | INCLUSIVE_RANGE -> "..." diff --git a/test/array/data.pi b/test/array/data.pi index 9ec76c9..36f0e3a 100644 --- a/test/array/data.pi +++ b/test/array/data.pi @@ -6,8 +6,8 @@ component C { let merged_array = new_array |> Base_Array.append(5...7); let merged_array_2 = new_array |> Base_Array.append(5...7) |> Base_Array.append([8, 9]); - let merged_record = { a: 1, b: 2 } @@ { c: 3 }; - let merged_record_2 = { a: 1, b: 2 } @@ { b: 3 } @@ { y: 8 }; + let merged_record = Base_Record.merge({ a: 1, b: 2 }, { c: 3 }); + let merged_record_2 = { a: 1, b: 2 } |> Base_Record.merge({ b: 3 }) |> Base_Record.merge({ y: 8 }); let nested_array = new_array |> Base_Array.append([merged_array |> Base_Array.append([merged_record_2])]); let index = 8; diff --git a/test/array/run.t b/test/array/run.t index fcc3a08..538b4fe 100644 --- a/test/array/run.t +++ b/test/array/run.t @@ -57,20 +57,29 @@ |> Base_Array.append(5...7) |> Base_Array.append([8, 9]); - let merged_record = { - a: 1, - b: 2, - } @@ { - c: 3, - }; + let merged_record = Base_Record.merge( + { + a: 1, + b: 2, + }, + { + c: 3, + }, + ); let merged_record_2 = { a: 1, b: 2, - } @@ { - b: 3, - } @@ { - y: 8, - }; + } + |> Base_Record.merge( + { + b: 3, + }, + ) + |> Base_Record.merge( + { + y: 8, + }, + ); let nested_array = new_array |> Base_Array.append([merged_array |> Base_Array.append([merged_record_2])]); diff --git a/test/slot/SlotProvider.pi b/test/slot/SlotProvider.pi index 640caef..9324ef6 100644 --- a/test/slot/SlotProvider.pi +++ b/test/slot/SlotProvider.pi @@ -2,9 +2,9 @@ component SlotProvider { let slot_default = #Slot(key: ""); let restricted = #Slot(max: 1, constraints: [SubComponent]); - - let multi = #Slot :: fn els -> for (el in els) { -
{el @@ { class: "item" }}
+ + let multi = #Slot :: fn (els) -> for (el in els) { +
{Base_Html.with_attribute(el, "class", "item")}
}; let slot_nothing = #Slot(key: "nothing", constraints: []); diff --git a/test/slot/run.t b/test/slot/run.t index d40f403..1247984 100644 --- a/test/slot/run.t +++ b/test/slot/run.t @@ -83,11 +83,7 @@ let restricted = #Slot(max: 1, constraints: [SubComponent]); let multi = #Slot :: fn (els) -> for (el in els) { -
{ - el @@ { - class: "item", - } - }
+
{Base_Html.with_attribute(el, "class", "item")}
}; let slot_nothing = #Slot(key: "nothing", constraints: []); -- 2.51.2