diff --git a/CHANGELOG.md b/CHANGELOG.md index c9c092867..cb949f71b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Display an error for integer overflow at compile-time: PR [#200](https://github.com/tact-lang/tact/pull/200) - Non-modifying `StringBuilder`'s `concat` method for chained string concatenations: PR [#217](https://github.com/tact-lang/tact/pull/217) - `toString` extension function for `Address` type: PR [#224](https://github.com/tact-lang/tact/pull/224) +- Bitwise XOR operation (`^`): PR [#238](https://github.com/tact-lang/tact/pull/238) - `isEmpty` method for `Map` type: PR [#266](https://github.com/tact-lang/tact/pull/266) ### Changed diff --git a/src/generator/writers/writeExpression.ts b/src/generator/writers/writeExpression.ts index 747516fa6..55499590b 100644 --- a/src/generator/writers/writeExpression.ts +++ b/src/generator/writers/writeExpression.ts @@ -421,6 +421,8 @@ export function writeExpression(f: ASTExpression, ctx: WriterContext): string { op = "&"; } else if (f.op === "|") { op = "|"; + } else if (f.op === "^") { + op = "^"; } else { throwError("Unknown binary operator: " + f.op, f.ref); } diff --git a/src/grammar/ast.ts b/src/grammar/ast.ts index 89c3a3c1b..2f7fde8ce 100644 --- a/src/grammar/ast.ts +++ b/src/grammar/ast.ts @@ -144,7 +144,8 @@ export type ASTOpBinary = { | "<<" | ">>" | "&" - | "|"; + | "|" + | "^"; left: ASTExpression; right: ASTExpression; ref: ASTRef; diff --git a/src/grammar/grammar.ohm b/src/grammar/grammar.ohm index 272fda7f2..a15799bde 100644 --- a/src/grammar/grammar.ohm +++ b/src/grammar/grammar.ohm @@ -136,7 +136,10 @@ Tact { ExpressionAnd = ExpressionAnd "&&" ExpressionBinaryOr --and | ExpressionBinaryOr - ExpressionBinaryOr = ExpressionBinaryOr "|" ExpressionBinaryAnd --bin_or + ExpressionBinaryOr = ExpressionBinaryOr "|" ExpressionBinaryXor --bin_or + | ExpressionBinaryXor + + ExpressionBinaryXor = ExpressionBinaryXor "^" ExpressionBinaryAnd --bin_xor | ExpressionBinaryAnd ExpressionBinaryAnd = ExpressionBinaryAnd "&" ExpressionEquality --bin_and diff --git a/src/grammar/grammar.ts b/src/grammar/grammar.ts index 2f74f7509..c3a18b2c4 100644 --- a/src/grammar/grammar.ts +++ b/src/grammar/grammar.ts @@ -1081,6 +1081,15 @@ semantics.addOperation("resolve_expression", { ref: createRef(this), }); }, + ExpressionBinaryXor_bin_xor(arg0, _arg1, arg2) { + return createNode({ + kind: "op_binary", + op: "^", + left: arg0.resolve_expression(), + right: arg2.resolve_expression(), + ref: createRef(this), + }); + }, // Unary ExpressionUnary_add(_arg0, arg1) { diff --git a/src/test/feature-constants.spec.ts b/src/test/feature-constants.spec.ts index b1f36a084..d7e6ac41e 100644 --- a/src/test/feature-constants.spec.ts +++ b/src/test/feature-constants.spec.ts @@ -30,6 +30,9 @@ describe("feature-constants", () => { expect((await contract.getSomething10()).toRawString()).toEqual( "0:4a81708d2cf7b15a1b362fbf64880451d698461f52f05f145b36c08517d76873", ); + expect(await contract.getSomething11()).toEqual(88n); + expect(await contract.getSomething12()).toEqual(-90n); + expect(await contract.getSomething13()).toEqual(88n); expect(await contract.getGlobalConst()).toEqual(100n); }); }); diff --git a/src/test/feature-math.spec.ts b/src/test/feature-math.spec.ts index 77fb9cc31..5f93c76df 100644 --- a/src/test/feature-math.spec.ts +++ b/src/test/feature-math.spec.ts @@ -42,6 +42,15 @@ describe("feature-math", () => { expect(await contract.getSub(1n, -2n)).toBe(3n); expect(await contract.getMul(2n, 2n)).toBe(4n); expect(await contract.getDiv(2n, 2n)).toBe(1n); + expect(await contract.getMod(2n, 2n)).toBe(0n); + expect(await contract.getShr(4n, 1n)).toBe(2n); + expect(await contract.getShl(2n, 1n)).toBe(4n); + expect(await contract.getAnd(2n, 3n)).toBe(2n); + expect(await contract.getOr(2n, 3n)).toBe(3n); + expect(await contract.getXor(2n, 3n)).toBe(1n); + expect(await contract.getXor(2n, -3n)).toBe(-1n); + expect(await contract.getXor(-2n, 3n)).toBe(-3n); + expect(await contract.getXor(-2n, -3n)).toBe(3n); // Augmented Assign expect(await contract.getAddAug(1n, 2n)).toBe(3n); @@ -397,5 +406,11 @@ describe("feature-math", () => { expect(await contract.getPrecendence4()).toBe(12n); expect(await contract.getPrecendence5()).toBe(5n); expect(await contract.getPrecendence6()).toBe(0n); + expect(await contract.getPrecendence7()).toBe(7n); + expect(await contract.getPrecendence8()).toBe(3n); + expect(await contract.getPrecendence9()).toBe(7n); + expect(await contract.getPrecendence10()).toBe(3n); + expect(await contract.getPrecendence11()).toBe(3n); + expect(await contract.getPrecendence12()).toBe(5n); }); }); diff --git a/src/test/features/constants.tact b/src/test/features/constants.tact index 7042cdba1..ad00feedb 100644 --- a/src/test/features/constants.tact +++ b/src/test/features/constants.tact @@ -11,6 +11,9 @@ contract ConstantTester { const something8: Int = (2 + 4) & 4; const something9: Address = address("UQBKgXCNLPexWhs2L79kiARR1phGH1LwXxRbNsCFF9doczSI"); const something10: Address = newAddress(0, 0x4a81708d2cf7b15a1b362fbf64880451d698461f52f05f145b36c08517d76873); + const something11: Int = 123 ^ 35; + const something12: Int = -123 ^ 35; + const something13: Int = -123 ^ -35; init() { @@ -60,6 +63,18 @@ contract ConstantTester { return self.something10; } + get fun something11(): Int { + return self.something11; + } + + get fun something12(): Int { + return self.something12; + } + + get fun something13(): Int { + return self.something13; + } + get fun globalConst(): Int { return someGlobalConst; } diff --git a/src/test/features/math.tact b/src/test/features/math.tact index 8c828add4..248c33e08 100644 --- a/src/test/features/math.tact +++ b/src/test/features/math.tact @@ -41,6 +41,10 @@ contract MathTester with Deployable { return a | b; } + get fun xor(a: Int, b: Int): Int { + return a ^ b; + } + // // Augmented assignment // @@ -331,4 +335,28 @@ contract MathTester with Deployable { get fun precendence6(): Int { return (5 | 6) & 8; } + + get fun precendence7(): Int { + return 5 ^ 6 | 7; + } + + get fun precendence8(): Int { + return 5 ^ 6 & 7; + } + + get fun precendence9(): Int { + return (5 ^ 6) | 7; + } + + get fun precendence10(): Int { + return 5 ^ 6 | 7 & 8; + } + + get fun precendence11(): Int { + return (5 ^ 6) | (7 & 8); + } + + get fun precendence12(): Int { + return 5 ^ (6 | 7) & 8; + } } \ No newline at end of file diff --git a/src/types/resolveConstantValue.ts b/src/types/resolveConstantValue.ts index 7dc4df07b..a4b5f940e 100644 --- a/src/types/resolveConstantValue.ts +++ b/src/types/resolveConstantValue.ts @@ -29,6 +29,8 @@ function reduceIntImpl(ast: ASTExpression): bigint { return l & r; } else if (ast.op === "|") { return l | r; + } else if (ast.op === "^") { + return l ^ r; } } else if (ast.kind === "op_unary") { if (ast.op === "-") { diff --git a/src/types/resolveExpression.ts b/src/types/resolveExpression.ts index d57bcbd30..4bf502371 100644 --- a/src/types/resolveExpression.ts +++ b/src/types/resolveExpression.ts @@ -187,7 +187,8 @@ function resolveBinaryOp( exp.op === ">>" || exp.op === "<<" || exp.op === "&" || - exp.op === "|" + exp.op === "|" || + exp.op === "^" ) { if (le.kind !== "ref" || le.optional || le.name !== "Int") { throwError(