diff --git a/CHANGELOG.md b/CHANGELOG.md index f198b4f191..ead3f3eec5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ - Experimental: Support nested/inline record types - records defined inside of other records, without needing explicit separate type definitions. https://github.com/rescript-lang/rescript/pull/7241 - Add unified exponentiation (`**`) operator for numeric types using ES7 `**`. https://github.com/rescript-lang/rescript-compiler/pull/7153 - Rename `raise` to `throw` to align with JavaScript vocabulary. `raise` has been deprecated. https://github.com/rescript-lang/rescript/pull/7346 +- Add unified bitwise (`^`) operator. https://github.com/rescript-lang/rescript/pull/7216 #### :boom: Breaking Change diff --git a/compiler/ml/unified_ops.ml b/compiler/ml/unified_ops.ml index 79d8d3b24f..997606135e 100644 --- a/compiler/ml/unified_ops.ml +++ b/compiler/ml/unified_ops.ml @@ -174,6 +174,19 @@ let entries = string = None; }; }; + { + path = builtin "^"; + name = "%bitxor"; + form = Binary; + specialization = + { + int = Pxorint; + bool = None; + float = None; + bigint = Some Pxorbigint; + string = None; + }; + }; |] let index_by_path = diff --git a/compiler/syntax/src/res_diagnostics.ml b/compiler/syntax/src/res_diagnostics.ml index 7df65840bb..7fd3b4df04 100644 --- a/compiler/syntax/src/res_diagnostics.ml +++ b/compiler/syntax/src/res_diagnostics.ml @@ -65,14 +65,8 @@ let explain t = | UnclosedTemplate -> "Did you forget to close this template expression with a backtick?" | UnclosedComment -> "This comment seems to be missing a closing `*/`" - | UnknownUchar uchar -> ( - match uchar with - | '^' -> - "Not sure what to do with this character.\n" - ^ " If you're trying to dereference a mutable value, use \ - `myValue.contents` instead.\n" - ^ " To concatenate strings, use `\"a\" ++ \"b\"` instead." - | _ -> "Not sure what to do with this character.") + | UnknownUchar uchar -> + "Not sure what to do with this character: \"" ^ Char.escaped uchar ^ "\"." | Expected {context; token = t} -> let hint = match context with diff --git a/compiler/syntax/src/res_parens.ml b/compiler/syntax/src/res_parens.ml index b44f95bbf7..8034e7cf59 100644 --- a/compiler/syntax/src/res_parens.ml +++ b/compiler/syntax/src/res_parens.ml @@ -162,8 +162,7 @@ let rhs_binary_expr_operand parent_operator rhs = }; args = [(_, _left); (_, _right)]; } - when ParsetreeViewer.is_binary_operator operator - && not (operator_loc.loc_ghost && operator = "++") -> + when ParsetreeViewer.not_ghost_operator operator operator_loc -> let prec_parent = ParsetreeViewer.operator_precedence parent_operator in let prec_child = ParsetreeViewer.operator_precedence operator in prec_parent == prec_child @@ -180,8 +179,7 @@ let flatten_operand_rhs parent_operator rhs = }; args = [(_, _left); (_, _right)]; } - when ParsetreeViewer.is_binary_operator operator - && not (operator_loc.loc_ghost && operator = "++") -> + when ParsetreeViewer.not_ghost_operator operator operator_loc -> let prec_parent = ParsetreeViewer.operator_precedence parent_operator in let prec_child = ParsetreeViewer.operator_precedence operator in prec_parent >= prec_child || rhs.pexp_attributes <> [] diff --git a/compiler/syntax/src/res_parsetree_viewer.ml b/compiler/syntax/src/res_parsetree_viewer.ml index 5b3f7ac5f9..3544e9e3fc 100644 --- a/compiler/syntax/src/res_parsetree_viewer.ml +++ b/compiler/syntax/src/res_parsetree_viewer.ml @@ -272,11 +272,12 @@ let operator_precedence operator = | ":=" -> 1 | "||" -> 2 | "&&" -> 3 - | "==" | "===" | "<" | ">" | "!=" | "<>" | "!==" | "<=" | ">=" | "|>" -> 4 - | "+" | "+." | "-" | "-." | "++" -> 5 - | "*" | "*." | "/" | "/." | "%" -> 6 - | "**" -> 7 - | "#" | "##" | "->" -> 8 + | "^" -> 4 + | "==" | "===" | "<" | ">" | "!=" | "<>" | "!==" | "<=" | ">=" | "|>" -> 5 + | "+" | "+." | "-" | "-." | "++" -> 6 + | "*" | "*." | "/" | "/." | "%" -> 7 + | "**" -> 8 + | "#" | "##" | "->" -> 9 | _ -> 0 let is_unary_operator operator = @@ -295,15 +296,17 @@ let is_unary_expression expr = true | _ -> false -(* TODO: tweak this to check for ghost ^ as template literal *) let is_binary_operator operator = match operator with | ":=" | "||" | "&&" | "==" | "===" | "<" | ">" | "!=" | "!==" | "<=" | ">=" | "|>" | "+" | "+." | "-" | "-." | "++" | "*" | "*." | "/" | "/." | "**" - | "->" | "<>" | "%" -> + | "->" | "<>" | "%" | "^" -> true | _ -> false +let not_ghost_operator operator (loc : Location.t) = + is_binary_operator operator && not (loc.loc_ghost && operator = "++") + let is_binary_expression expr = match expr.pexp_desc with | Pexp_apply @@ -315,9 +318,7 @@ let is_binary_expression expr = }; args = [(Nolabel, _operand1); (Nolabel, _operand2)]; } - when is_binary_operator operator - && not (operator_loc.loc_ghost && operator = "++") - (* template literal *) -> + when not_ghost_operator operator operator_loc -> true | _ -> false diff --git a/compiler/syntax/src/res_parsetree_viewer.mli b/compiler/syntax/src/res_parsetree_viewer.mli index efb42df276..a027c78911 100644 --- a/compiler/syntax/src/res_parsetree_viewer.mli +++ b/compiler/syntax/src/res_parsetree_viewer.mli @@ -65,6 +65,7 @@ val is_huggable_rhs : Parsetree.expression -> bool val operator_precedence : string -> int +val not_ghost_operator : string -> Location.t -> bool val is_unary_expression : Parsetree.expression -> bool val is_binary_operator : string -> bool val is_binary_expression : Parsetree.expression -> bool diff --git a/compiler/syntax/src/res_scanner.ml b/compiler/syntax/src/res_scanner.ml index 8912529fe9..c581a52abc 100644 --- a/compiler/syntax/src/res_scanner.ml +++ b/compiler/syntax/src/res_scanner.ml @@ -834,6 +834,9 @@ let rec scan scanner = | _ -> next scanner; Token.Band) + | '^' -> + next scanner; + Token.Caret | ':' -> ( match peek scanner with | '=' -> diff --git a/compiler/syntax/src/res_token.ml b/compiler/syntax/src/res_token.ml index b55d7c0ec7..29d2b503d4 100644 --- a/compiler/syntax/src/res_token.ml +++ b/compiler/syntax/src/res_token.ml @@ -78,6 +78,7 @@ type t = | Land | Lor | Band (* Bitwise and: & *) + | Caret | BangEqual | BangEqualEqual | LessEqual @@ -102,14 +103,15 @@ let precedence = function | HashEqual | ColonEqual -> 1 | Lor -> 2 | Land -> 3 + | Caret -> 4 | Equal | EqualEqual | EqualEqualEqual | LessThan | GreaterThan | BangEqual | BangEqualEqual | LessEqual | GreaterEqual | BarGreater -> - 4 - | Plus | PlusDot | Minus | MinusDot | PlusPlus -> 5 - | Asterisk | AsteriskDot | Forwardslash | ForwardslashDot | Percent -> 6 - | Exponentiation -> 7 - | MinusGreater -> 8 - | Dot -> 9 + 5 + | Plus | PlusDot | Minus | MinusDot | PlusPlus -> 6 + | Asterisk | AsteriskDot | Forwardslash | ForwardslashDot | Percent -> 7 + | Exponentiation -> 8 + | MinusGreater -> 9 + | Dot -> 10 | _ -> 0 let to_string = function @@ -189,6 +191,7 @@ let to_string = function | Of -> "of" | Lor -> "||" | Band -> "&" + | Caret -> "^" | Land -> "&&" | BangEqual -> "!=" | BangEqualEqual -> "!==" diff --git a/runtime/Pervasives.res b/runtime/Pervasives.res index c029346f79..a06f86d6fc 100644 --- a/runtime/Pervasives.res +++ b/runtime/Pervasives.res @@ -63,6 +63,7 @@ external \"/": ('a, 'a) => 'a = "%div" external \"%": ('a, 'a) => 'a = "%mod" external mod: ('a, 'a) => 'a = "%mod" external \"**": ('a, 'a) => 'a = "%pow" +external \"^": ('a, 'a) => 'a = "%bitxor" /* Comparisons */ /* Note: Later comparisons will be converted to unified operations too */ diff --git a/runtime/Pervasives_mini.res b/runtime/Pervasives_mini.res index 24074801a5..d888d08d29 100644 --- a/runtime/Pervasives_mini.res +++ b/runtime/Pervasives_mini.res @@ -31,6 +31,7 @@ external \"/": (int, int) => int = "%divint" external \"%": (int, int) => int = "%modint" external mod: (int, int) => int = "%modint" external \"**": (int, int) => int = "%powint" +external \"^": (int, int) => int = "%bitxorint" /* Comparisons */ /* Note: Later comparisons will be converted to unified operations too */ diff --git a/tests/syntax_tests/data/parsing/errors/pattern/expected/templateLiteral.res.txt b/tests/syntax_tests/data/parsing/errors/pattern/expected/templateLiteral.res.txt index f6d9d66ccb..380027d9ba 100644 --- a/tests/syntax_tests/data/parsing/errors/pattern/expected/templateLiteral.res.txt +++ b/tests/syntax_tests/data/parsing/errors/pattern/expected/templateLiteral.res.txt @@ -20,7 +20,7 @@ 6 │ | _ => () 7 │ } - Not sure what to do with this character. + Not sure what to do with this character: "$". Syntax error! diff --git a/tests/syntax_tests/data/parsing/errors/scanner/expected/badCharacter.res.txt b/tests/syntax_tests/data/parsing/errors/scanner/expected/badCharacter.res.txt index bc363f33c9..02d25879dc 100644 --- a/tests/syntax_tests/data/parsing/errors/scanner/expected/badCharacter.res.txt +++ b/tests/syntax_tests/data/parsing/errors/scanner/expected/badCharacter.res.txt @@ -4,7 +4,7 @@ 1 │ let $ = 1 - Not sure what to do with this character. + Not sure what to do with this character: "$". Syntax error! diff --git a/tests/syntax_tests/data/parsing/errors/scanner/expected/oldDerefOp.res.txt b/tests/syntax_tests/data/parsing/errors/scanner/expected/oldDerefOp.res.txt index 74b1b5bbb4..2a2dcb7c4e 100644 --- a/tests/syntax_tests/data/parsing/errors/scanner/expected/oldDerefOp.res.txt +++ b/tests/syntax_tests/data/parsing/errors/scanner/expected/oldDerefOp.res.txt @@ -1,26 +1,23 @@ Syntax error! - syntax_tests/data/parsing/errors/scanner/oldDerefOp.res:1:50 + syntax_tests/data/parsing/errors/scanner/oldDerefOp.res:1:51 1 │ let newVelocity = velocity +. a *. secondPerFrame^; 2 │ let newX = x +. newVelocity *. secondPerFrame^; 3 │ - Not sure what to do with this character. - If you're trying to dereference a mutable value, use `myValue.contents` instead. - To concatenate strings, use `"a" ++ "b"` instead. + Did you forget to write an expression here? Syntax error! - syntax_tests/data/parsing/errors/scanner/oldDerefOp.res:2:46 + syntax_tests/data/parsing/errors/scanner/oldDerefOp.res:2:47 1 │ let newVelocity = velocity +. a *. secondPerFrame^; 2 │ let newX = x +. newVelocity *. secondPerFrame^; 3 │ - Not sure what to do with this character. - If you're trying to dereference a mutable value, use `myValue.contents` instead. - To concatenate strings, use `"a" ++ "b"` instead. + Did you forget to write an expression here? -let newVelocity = velocity +. (a *. secondPerFrame) -let newX = x +. (newVelocity *. secondPerFrame) \ No newline at end of file +let newVelocity = + (velocity +. (a *. secondPerFrame)) ^ ([%rescript.exprhole ]) +let newX = (x +. (newVelocity *. secondPerFrame)) ^ ([%rescript.exprhole ]) \ No newline at end of file diff --git a/tests/syntax_tests/data/parsing/grammar/expressions/binary.res b/tests/syntax_tests/data/parsing/grammar/expressions/binary.res index 6c89739553..224eb2da39 100644 --- a/tests/syntax_tests/data/parsing/grammar/expressions/binary.res +++ b/tests/syntax_tests/data/parsing/grammar/expressions/binary.res @@ -28,6 +28,7 @@ let x = z |> @attr while condition { () } let x = a + -1 + -2 let x = a + @attr -1 + @attr -2 let x = a % a == 0 +let x = a ^ a == 0 // should be interpreted as binary expression not prefix op let x = a -b diff --git a/tests/syntax_tests/data/parsing/grammar/expressions/expected/binary.res.txt b/tests/syntax_tests/data/parsing/grammar/expressions/expected/binary.res.txt index dd3bd7262d..14d06adaa4 100644 --- a/tests/syntax_tests/data/parsing/grammar/expressions/expected/binary.res.txt +++ b/tests/syntax_tests/data/parsing/grammar/expressions/expected/binary.res.txt @@ -14,6 +14,7 @@ let x = ((while condition do () done)[@attr ]) z let x = (a + (-1)) + (-2) let x = (a + (((-1))[@attr ])) + (((-2))[@attr ]) let x = (a % a) == 0 +let x = a ^ (a == 0) let x = a - b let x = a -. b ;;Constructor (a, b) diff --git a/tests/syntax_tests/data/printer/expr/binary.res b/tests/syntax_tests/data/printer/expr/binary.res index 6e47a546b3..6f43115a00 100644 --- a/tests/syntax_tests/data/printer/expr/binary.res +++ b/tests/syntax_tests/data/printer/expr/binary.res @@ -54,6 +54,7 @@ let () = (x: int) |> (print_int: int => unit) x + y / z x / y + z x % y * z +x ^ y + z 100 * x / total 2 / 3 * 10 / 2 + 2 let rotateX = ((range / rect.height) * refY - range / 2) * getXMultiplication(rect.width) diff --git a/tests/syntax_tests/data/printer/expr/expected/binary.res.txt b/tests/syntax_tests/data/printer/expr/expected/binary.res.txt index e57e20fedb..8717665e87 100644 --- a/tests/syntax_tests/data/printer/expr/expected/binary.res.txt +++ b/tests/syntax_tests/data/printer/expr/expected/binary.res.txt @@ -85,6 +85,7 @@ let () = (print_int: int => unit)((x: int)) x + y / z x / y + z x % y * z +x ^ y + z 100 * x / total 2 / 3 * 10 / 2 + 2 let rotateX = (range / rect.height * refY - range / 2) * getXMultiplication(rect.width) diff --git a/tests/tests/src/belt_int_test.mjs b/tests/tests/src/belt_int_test.mjs index edcada2a0a..1c659da1e9 100644 --- a/tests/tests/src/belt_int_test.mjs +++ b/tests/tests/src/belt_int_test.mjs @@ -36,6 +36,7 @@ Mocha.describe("Belt_int_test", () => { Test_utils.eq("File \"belt_int_test.res\", line 41, characters 7-14", 6, 6); Test_utils.eq("File \"belt_int_test.res\", line 42, characters 7-14", 0, 0); Test_utils.eq("File \"belt_int_test.res\", line 43, characters 7-14", 0, 0); + Test_utils.eq("File \"belt_int_test.res\", line 44, characters 7-14", 1, 1); }); }); diff --git a/tests/tests/src/belt_int_test.res b/tests/tests/src/belt_int_test.res index a6c418b7ca..0c77875493 100644 --- a/tests/tests/src/belt_int_test.res +++ b/tests/tests/src/belt_int_test.res @@ -41,5 +41,6 @@ describe(__MODULE__, () => { eq(__LOC__, 2 * 3, 6) eq(__LOC__, 2 / 3, 0) eq(__LOC__, 2 % 2, 0) + eq(__LOC__, 2 ^ 3, 1) }) }) diff --git a/tests/tests/src/unified_ops_test.mjs b/tests/tests/src/unified_ops_test.mjs index 5ca55fc808..9d1b858078 100644 --- a/tests/tests/src/unified_ops_test.mjs +++ b/tests/tests/src/unified_ops_test.mjs @@ -67,6 +67,14 @@ let pow3 = 2n ** 2n; let pow_overflow = 0; +function bxor_int(a, b) { + return a ^ b; +} + +function bxor_bigint(a, b) { + return a ^ b; +} + let int = 3; export { @@ -91,5 +99,7 @@ export { pow2, pow3, pow_overflow, + bxor_int, + bxor_bigint, } /* No side effect */ diff --git a/tests/tests/src/unified_ops_test.res b/tests/tests/src/unified_ops_test.res index 26137c1c77..d98d453837 100644 --- a/tests/tests/src/unified_ops_test.res +++ b/tests/tests/src/unified_ops_test.res @@ -26,3 +26,6 @@ let pow2 = 2. ** 2. let pow3 = 2n ** 2n let pow_overflow = 2147483647 ** 2 + +let bxor_int = (a, b) => a ^ b +let bxor_bigint = (a: bigint, b) => a ^ b