diff --git a/book/src/example-numbat_syntax.md b/book/src/example-numbat_syntax.md index 44145178a..6c3331045 100644 --- a/book/src/example-numbat_syntax.md +++ b/book/src/example-numbat_syntax.md @@ -52,6 +52,9 @@ mod(17, 4) # Modulo 3 in -> cm # Unit conversion, can also be → or ➞ 3 in to cm # Unit conversion with the 'to' keyword +0b011 ⊕ 0b110 # bitwise xor operator +0b011 xor 0b110 # bitwise xor with the 'xor' keyword + cos(pi/3 + pi) # Call mathematical functions pi/3 + pi |> cos # Same, 'arg |> f' is equivalent to 'f(arg)' # The '|>' operator has the lowest precedence diff --git a/book/src/list-functions-math.md b/book/src/list-functions-math.md index 20b518c9f..91b999d34 100644 --- a/book/src/list-functions-math.md +++ b/book/src/list-functions-math.md @@ -321,6 +321,38 @@ fn mod(a: T, b: T) -> T +### `wrapped_shl` (Bitwise left shift) + +```nbt +fn wrapped_shl(lhs: Scalar, rhs: Scalar) -> Scalar +``` + +
+Examples + +
wrapped_shl(0b0000_0101, 4) -> bin + + = "0b1010000" [String] +
+ +
+ +### `wrapped_shr` (Bitwise right shift) + +```nbt +fn wrapped_shr(lhs: Scalar, rhs: Scalar) -> Scalar +``` + +
+Examples + +
wrapped_shr(0b0101_0000, 4) -> bin + + = "0b101" [String] +
+ +
+ ## Transcendental functions Defined in: `math::transcendental` diff --git a/book/src/operations.md b/book/src/operations.md index 3c8b89ba9..b21a1d368 100644 --- a/book/src/operations.md +++ b/book/src/operations.md @@ -9,6 +9,7 @@ Numbat operators and other language constructs, ordered by precedence form *high | exponentiation | `x^y`, `x**y` | | multiplication (implicit) | `x y` (*whitespace*) | | unary negation | `-x` | +| bitwise 'not' | `~x` | | division | `x per y` | | division | `x / y`, `x ÷ y` | | multiplication (explicit) | `x * y`, `x · y`, `x × y` | @@ -16,7 +17,12 @@ Numbat operators and other language constructs, ordered by precedence form *high | addition | `x + y` | | comparisons | `x < y`, `x <= y`, `x ≤ y`, … `x == y`, `x != y` | | logical negation | `!x` | +| bitwise shift right | `x >> y` | +| bitwise shift left | `x << y` | +| bitwise 'xor' | `x ⨁ y`, `x xor y` | +| bitwise 'and' | `x & y` | | logical 'and' | `x && y` | +| bitwise 'or' | x | y | | logical 'or' | x || y | | unit conversion | `x -> y`, `x → y`, `x ➞ y`, `x to y` | | conditionals | `if x then y else z` | @@ -28,6 +34,7 @@ Also, note that `per`-division has a higher precedence than `/`-division. This m If in doubt, you can always look at the pretty-printing output (second line in the snippet below) to make sure that your input was parsed correctly: + ``` numbat >>> 1 / meter per second diff --git a/examples/numbat_syntax.nbt b/examples/numbat_syntax.nbt index 73f55a4f8..cdca34ff6 100644 --- a/examples/numbat_syntax.nbt +++ b/examples/numbat_syntax.nbt @@ -47,6 +47,9 @@ mod(17, 4) # Modulo 3 in -> cm # Unit conversion, can also be → or ➞ 3 in to cm # Unit conversion with the 'to' keyword +0b011 ⊕ 0b110 # bitwise xor operator +0b011 xor 0b110 # bitwise xor with the 'xor' keyword + cos(pi/3 + pi) # Call mathematical functions pi/3 + pi |> cos # Same, 'arg |> f' is equivalent to 'f(arg)' # The '|>' operator has the lowest precedence diff --git a/examples/parse_error/unexpected_character.nbt b/examples/parse_error/unexpected_character.nbt deleted file mode 100644 index f7d55c7d9..000000000 --- a/examples/parse_error/unexpected_character.nbt +++ /dev/null @@ -1 +0,0 @@ -2 & 3 diff --git a/examples/runtime_error/nonscalar_bitwise_not.nbt b/examples/runtime_error/nonscalar_bitwise_not.nbt new file mode 100644 index 000000000..e2dfd8095 --- /dev/null +++ b/examples/runtime_error/nonscalar_bitwise_not.nbt @@ -0,0 +1 @@ +~2.2 \ No newline at end of file diff --git a/examples/tests/bitwise.nbt b/examples/tests/bitwise.nbt new file mode 100644 index 000000000..8ce1c00b4 --- /dev/null +++ b/examples/tests/bitwise.nbt @@ -0,0 +1,36 @@ +# bitwise and truth table +assert_eq(1 & 1, 1) +assert_eq(1 & 0, 0) +assert_eq(0 & 1, 0) +assert_eq(0 & 0, 0) + +# bitwise or truth table +assert_eq(1 | 1, 1) +assert_eq(1 | 0, 1) +assert_eq(0 | 1, 1) +assert_eq(0 | 0, 0) + +# bitwise xor truth table +assert_eq(0 ⨁ 0, 0) +assert_eq(1 ⨁ 0, 1) +assert_eq(0 ⨁ 1, 1) +assert_eq(1 ⨁ 1, 0) + +assert_eq(0 xor 0, 0 ⨁ 0) +assert_eq(1 xor 0, 1 ⨁ 0) +assert_eq(0 xor 1, 0 ⨁ 1) +assert_eq(1 xor 1, 1 ⨁ 1) + +# bitshift left checks +assert_eq(0xFF << 2, 0x3FC) +assert_eq(0xFF << 0, 0xFF) + +# bitshift right checks +assert_eq(0xC7A5 >> 8, 0xC7) + +# bit flipping operation +assert_eq(0xC700 ⨁ (0xC0 << 8), 0x700) +assert_eq(0xC700 xor (0xC0 << 8), 0x700) + +# bit clearing operation +assert_eq(0xC700 & ~(1 << 15), 0x4700) \ No newline at end of file diff --git a/numbat/modules/core/functions.nbt b/numbat/modules/core/functions.nbt index 8dfdbde3e..6db0a36b0 100644 --- a/numbat/modules/core/functions.nbt +++ b/numbat/modules/core/functions.nbt @@ -95,3 +95,13 @@ fn fract(x: Scalar) -> Scalar @url("https://doc.rust-lang.org/std/primitive.f64.html#method.rem_euclid") @example("mod(27, 5)") fn mod(a: T, b: T) -> T + +#TODO: Add description and URL +@name("Bitwise left shift") +@example("wrapped_shl(0b0000_0101, 4) -> bin") +fn wrapped_shl(lhs: Scalar, rhs: Scalar) -> Scalar + +#TODO: Add description and URL +@name("Bitwise right shift") +@example("wrapped_shr(0b0101_0000, 4) -> bin") +fn wrapped_shr(lhs: Scalar, rhs: Scalar) -> Scalar diff --git a/numbat/src/ast.rs b/numbat/src/ast.rs index ae0ed2df6..b4af1280f 100644 --- a/numbat/src/ast.rs +++ b/numbat/src/ast.rs @@ -16,6 +16,7 @@ pub enum UnaryOperator { Factorial(NonZeroUsize), Negate, LogicalNeg, + BitwiseNot, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -34,6 +35,11 @@ pub enum BinaryOperator { NotEqual, LogicalAnd, LogicalOr, + BitwiseOr, + BitwiseAnd, + BitwiseXor, + BitShiftLeft, + BitShiftRight, } impl PrettyPrint for BinaryOperator { @@ -55,6 +61,11 @@ impl PrettyPrint for BinaryOperator { NotEqual => "≠", LogicalAnd => "&&", LogicalOr => "||", + BitwiseOr => "|", + BitwiseAnd => "&", + BitwiseXor => "⨁", + BitShiftLeft => "<<", + BitShiftRight => ">>", }); match self { diff --git a/numbat/src/bytecode_interpreter.rs b/numbat/src/bytecode_interpreter.rs index f9bb7ed8a..3c781c884 100644 --- a/numbat/src/bytecode_interpreter.rs +++ b/numbat/src/bytecode_interpreter.rs @@ -109,6 +109,10 @@ impl BytecodeInterpreter { self.compile_expression(lhs); self.vm.add_op(Op::LogicalNeg); } + Expression::UnaryOperator(_span, UnaryOperator::BitwiseNot, lhs, _type) => { + self.compile_expression(lhs); + self.vm.add_op(Op::BitwiseNot); + } Expression::BinaryOperator(_span, operator, lhs, rhs, _type) => { self.compile_expression(lhs); self.compile_expression(rhs); @@ -128,6 +132,11 @@ impl BytecodeInterpreter { BinaryOperator::NotEqual => Op::NotEqual, BinaryOperator::LogicalAnd => Op::LogicalAnd, BinaryOperator::LogicalOr => Op::LogicalOr, + BinaryOperator::BitwiseOr => Op::BitwiseOr, + BinaryOperator::BitwiseAnd => Op::BitwiseAnd, + BinaryOperator::BitwiseXor => Op::BitwiseXor, + BinaryOperator::BitShiftLeft => Op::BitShiftLeft, + BinaryOperator::BitShiftRight => Op::BitShiftRight, }; self.vm.add_op(op); } diff --git a/numbat/src/diagnostic.rs b/numbat/src/diagnostic.rs index ca6750d5d..da247f581 100644 --- a/numbat/src/diagnostic.rs +++ b/numbat/src/diagnostic.rs @@ -125,7 +125,8 @@ impl ErrorDiagnostic for TypeCheckError { d.with_labels(labels).with_notes(vec![inner_error]) } TypeCheckError::NonScalarExponent(span, type_) - | TypeCheckError::NonScalarFactorialArgument(span, type_) => d + | TypeCheckError::NonScalarFactorialArgument(span, type_) + | TypeCheckError::NonScalarBitwiseNotArgument(span, type_) => d .with_labels(vec![span .diagnostic_label(LabelStyle::Primary) .with_message(format!("{type_}"))]) diff --git a/numbat/src/ffi/functions.rs b/numbat/src/ffi/functions.rs index d3da47e11..7cbf48b39 100644 --- a/numbat/src/ffi/functions.rs +++ b/numbat/src/ffi/functions.rs @@ -45,6 +45,8 @@ pub(crate) fn functions() -> &'static HashMap<&'static str, ForeignFunction> { // Math insert_function!("mod", mod_, 2..=2); + insert_function!(wrapped_shl, 2..=2); + insert_function!(wrapped_shr, 2..=2); insert_function!(abs, 1..=1); insert_function!(round, 1..=1); diff --git a/numbat/src/ffi/math.rs b/numbat/src/ffi/math.rs index 68c4449f2..857eb7acd 100644 --- a/numbat/src/ffi/math.rs +++ b/numbat/src/ffi/math.rs @@ -1,3 +1,8 @@ + +use std::ops::{Shl, Shr}; + + + use super::macros::*; use super::Args; use super::Result; @@ -15,6 +20,24 @@ pub fn mod_(mut args: Args) -> Result { return_quantity!(x_value.rem_euclid(y_value), x.unit().clone()) } +pub fn wrapped_shl(mut args: Args) -> Result { + let lhs = scalar_arg!(args); + let rhs = scalar_arg!(args); + + let result = lhs.shl(rhs).to_f64(); //TODO: Implement wrapped left shift + + return_scalar!(result) +} + +pub fn wrapped_shr(mut args: Args) -> Result { + let lhs = scalar_arg!(args); + let rhs = scalar_arg!(args); + + let result = lhs.shr(rhs).to_f64(); //TODO: Implement wrapped right shift + + return_scalar!(result) +} + // A simple math function with signature 'Fn[(Scalar) -> Scalar]' macro_rules! simple_scalar_math_function { ($name:ident, $op:ident) => { diff --git a/numbat/src/interpreter/mod.rs b/numbat/src/interpreter/mod.rs index bf8dc7f0f..96d215686 100644 --- a/numbat/src/interpreter/mod.rs +++ b/numbat/src/interpreter/mod.rs @@ -27,6 +27,10 @@ pub enum RuntimeError { FactorialOfNegativeNumber, #[error("Expected factorial argument to be a finite integer number")] FactorialOfNonInteger, + #[error("Expected bitwise argument(s) to be a finite integer number")] + BitwiseOperationOfNonInteger, + #[error("Expected rhs of bitwise shift to be positive integer number")] + BitwiseShiftOfNegativeInteger, #[error("{0}")] UnitRegistryError(UnitRegistryError), // TODO: can this even be triggered? #[error("{0}")] diff --git a/numbat/src/keywords.rs b/numbat/src/keywords.rs index 62c69a572..068441c4b 100644 --- a/numbat/src/keywords.rs +++ b/numbat/src/keywords.rs @@ -11,6 +11,7 @@ pub const KEYWORDS: &[&str] = &[ "unit ", "use ", "struct ", + "xor ", // 'inline' keywords "long", "short", diff --git a/numbat/src/number.rs b/numbat/src/number.rs index 47286396f..48e01c7b0 100644 --- a/numbat/src/number.rs +++ b/numbat/src/number.rs @@ -136,6 +136,46 @@ impl std::ops::Mul for Number { } } +impl std::ops::BitOr for Number { + type Output = Number; + + fn bitor(self, rhs: Self) -> Self::Output { + Number((self.0 as i64 | rhs.0 as i64) as f64) + } +} + +impl std::ops::BitAnd for Number { + type Output = Number; + + fn bitand(self, rhs: Self) -> Self::Output { + Number((self.0 as i64 & rhs.0 as i64) as f64) + } +} + +impl std::ops::BitXor for Number { + type Output = Number; + + fn bitxor(self, rhs: Self) -> Self::Output { + Number((self.0 as i64 ^ rhs.0 as i64) as f64) + } +} + +impl std::ops::Shl for Number { + type Output = Number; + + fn shl(self, rhs: Self) -> Self::Output { + Number((self.0 as i64).unbounded_shl(rhs.0 as u32) as f64) + } +} + +impl std::ops::Shr for Number { + type Output = Number; + + fn shr(self, rhs: Self) -> Self::Output { + Number((self.0 as i64).unbounded_shr(rhs.0 as u32) as f64) + } +} + impl std::ops::Div for Number { type Output = Number; @@ -152,6 +192,14 @@ impl std::ops::Neg for Number { } } +impl std::ops::Not for Number { + type Output = Number; + + fn not(self) -> Self::Output { + Number(!(self.0 as i64) as f64) + } +} + impl std::iter::Product for Number { fn product>(iter: I) -> Self { iter.fold(Number::from_f64(1.0), |acc, n| acc * n) diff --git a/numbat/src/parser.rs b/numbat/src/parser.rs index b99063096..03d98a6be 100644 --- a/numbat/src/parser.rs +++ b/numbat/src/parser.rs @@ -1139,6 +1139,15 @@ impl<'a> Parser<'a> { tokens, &[TokenKind::LogicalOr], |_| BinaryOperator::LogicalOr, + |parser| parser.bitwise_or(tokens), + ) + } + + fn bitwise_or(&mut self, tokens: &[Token<'a>]) -> Result> { + self.parse_binop( + tokens, + &[TokenKind::BitwiseOr], + |_| BinaryOperator::BitwiseOr, |parser| parser.logical_and(tokens), ) } @@ -1148,6 +1157,42 @@ impl<'a> Parser<'a> { tokens, &[TokenKind::LogicalAnd], |_| BinaryOperator::LogicalAnd, + |parser| parser.bitwise_and(tokens), + ) + } + + fn bitwise_and(&mut self, tokens: &[Token<'a>]) -> Result> { + self.parse_binop( + tokens, + &[TokenKind::BitwiseAnd], + |_| BinaryOperator::BitwiseAnd, + |parser| parser.bitwise_xor(tokens), + ) + } + + fn bitwise_xor(&mut self, tokens: &[Token<'a>]) -> Result> { + self.parse_binop( + tokens, + &[TokenKind::BitwiseXor], + |_| BinaryOperator::BitwiseXor, + |parser| parser.bitshift_left(tokens), + ) + } + + fn bitshift_left(&mut self, tokens: &[Token<'a>]) -> Result> { + self.parse_binop( + tokens, + &[TokenKind::BitShiftLeft], + |_| BinaryOperator::BitShiftLeft, + |parser| parser.bitshift_right(tokens), + ) + } + + fn bitshift_right(&mut self, tokens: &[Token<'a>]) -> Result> { + self.parse_binop( + tokens, + &[TokenKind::BitShiftRight], + |_| BinaryOperator::BitShiftRight, |parser| parser.logical_neg(tokens), ) } @@ -1239,6 +1284,15 @@ impl<'a> Parser<'a> { expr: Box::new(rhs), span_op: span, }) + } else if self.match_exact(tokens, TokenKind::BitwiseNot).is_some() { + let span = self.last(tokens).unwrap().span; + let rhs = self.unary(tokens)?; + + Ok(Expression::UnaryOperator { + op: UnaryOperator::BitwiseNot, + expr: Box::new(rhs), + span_op: span, + }) } else if self.match_exact(tokens, TokenKind::Plus).is_some() { // A unary `+` is equivalent to nothing. We can get rid of the // symbol without inserting any nodes in the AST. @@ -3189,6 +3243,8 @@ mod tests { "###); } + // TODO: Add test for bitwise operations + #[test] fn logical_operation() { // basic diff --git a/numbat/src/quantity.rs b/numbat/src/quantity.rs index 9664b3712..c26504868 100644 --- a/numbat/src/quantity.rs +++ b/numbat/src/quantity.rs @@ -306,6 +306,46 @@ impl std::ops::Mul for Quantity { } } +impl std::ops::BitOr for Quantity { + type Output = Quantity; + + fn bitor(self, rhs: Self) -> Self::Output { + Quantity::new(self.value | rhs.value, self.unit.clone()) + } +} + +impl std::ops::BitAnd for Quantity { + type Output = Quantity; + + fn bitand(self, rhs: Self) -> Self::Output { + Quantity::new(self.value & rhs.value, self.unit.clone()) + } +} + +impl std::ops::BitXor for Quantity { + type Output = Quantity; + + fn bitxor(self, rhs: Self) -> Self::Output { + Quantity::new(self.value ^ rhs.value, self.unit.clone()) + } +} + +impl std::ops::Shl for Quantity { + type Output = Quantity; + + fn shl(self, rhs: Self) -> Self::Output { + Quantity::new(self.value << rhs.value, self.unit.clone()) + } +} + +impl std::ops::Shr for Quantity { + type Output = Quantity; + + fn shr(self, rhs: Self) -> Self::Output { + Quantity::new(self.value >> rhs.value, self.unit.clone()) + } +} + impl std::ops::Div for Quantity { type Output = Quantity; @@ -314,6 +354,14 @@ impl std::ops::Div for Quantity { } } +impl std::ops::Not for Quantity { + type Output = Quantity; + + fn not(self) -> Self::Output { + Quantity::new(!self.value, self.unit) + } +} + impl std::ops::Neg for Quantity { type Output = Quantity; diff --git a/numbat/src/tokenizer.rs b/numbat/src/tokenizer.rs index b3fcf0269..7d36daad3 100644 --- a/numbat/src/tokenizer.rs +++ b/numbat/src/tokenizer.rs @@ -99,6 +99,12 @@ pub enum TokenKind { GreaterOrEqual, LogicalAnd, LogicalOr, + BitwiseOr, + BitwiseAnd, + BitwiseNot, + BitwiseXor, + BitShiftLeft, + BitShiftRight, Period, QuestionMark, @@ -495,6 +501,7 @@ impl Tokenizer { m.insert("false", TokenKind::False); m.insert("NaN", TokenKind::NaN); m.insert("inf", TokenKind::Inf); + m.insert("xor", TokenKind::BitwiseXor); // procedures m.insert(ProcedureKind::Print.name(), TokenKind::ProcedurePrint); @@ -551,9 +558,11 @@ impl Tokenizer { } '≤' => TokenKind::LessOrEqual, '<' if self.match_char(input, '=') => TokenKind::LessOrEqual, + '<' if self.match_char(input, '<') => TokenKind::BitShiftLeft, '<' => TokenKind::LessThan, '≥' => TokenKind::GreaterOrEqual, '>' if self.match_char(input, '=') => TokenKind::GreaterOrEqual, + '>' if self.match_char(input, '>') => TokenKind::BitShiftRight, '>' => TokenKind::GreaterThan, '?' => TokenKind::QuestionMark, '0' if self @@ -643,6 +652,10 @@ impl Tokenizer { '|' if self.match_char(input, '|') => TokenKind::LogicalOr, '|' if self.match_char(input, '>') => TokenKind::PostfixApply, '*' if self.match_char(input, '*') => TokenKind::Power, + '|' => TokenKind::BitwiseOr, + '&' => TokenKind::BitwiseAnd, + '⨁' | '⊕' => TokenKind::BitwiseXor, + '~' => TokenKind::BitwiseNot, '+' => TokenKind::Plus, '*' | '·' | '⋅' | '×' => TokenKind::Multiply, '/' => TokenKind::Divide, @@ -997,9 +1010,29 @@ fn test_tokenize_basic() { [("...", Ellipsis, ByteIndex(0)), ("", Eof, ByteIndex(3))] ); - insta::assert_snapshot!( - tokenize_reduced_pretty("~").unwrap_err(), - @"Error at index 0: `Unexpected character: '~'`"); + assert_eq!( + tokenize_reduced("1<<2\n42").unwrap(), + [ + ("1", Number, ByteIndex(0)), + ("<<", BitShiftLeft, ByteIndex(1)), + ("2", Number, ByteIndex(3)), + ("\n", Newline, ByteIndex(4)), + ("42", Number, ByteIndex(5)), + ("", Eof, ByteIndex(7)) + ] + ); + + assert_eq!( + tokenize_reduced("1|2&42").unwrap(), + [ + ("1", Number, ByteIndex(0)), + ("|", BitwiseOr, ByteIndex(1)), + ("2", Number, ByteIndex(2)), + ("&", BitwiseAnd, ByteIndex(3)), + ("42", Number, ByteIndex(4)), + ("", Eof, ByteIndex(6)) + ] + ); } #[test] @@ -1331,16 +1364,6 @@ fn test_logical_operators() { "", Eof, 13 "### ); - - insta::assert_snapshot!( - tokenize_reduced_pretty("true | false").unwrap_err(), - @"Error at index 5: `Unexpected character: '|'`" - ); - - insta::assert_snapshot!( - tokenize_reduced_pretty("true & false").unwrap_err(), - @"Error at index 5: `Unexpected character: '&'`" - ); } #[test] diff --git a/numbat/src/typechecker/const_evaluation.rs b/numbat/src/typechecker/const_evaluation.rs index b3cd4aa69..636dde5f7 100644 --- a/numbat/src/typechecker/const_evaluation.rs +++ b/numbat/src/typechecker/const_evaluation.rs @@ -25,6 +25,7 @@ pub fn evaluate_const_expr(expr: &typed_ast::Expression) -> Result { "factorial" } typed_ast::Expression::UnaryOperator(_, ast::UnaryOperator::LogicalNeg, _, _) => "logical", + typed_ast::Expression::UnaryOperator(_, ast::UnaryOperator::BitwiseNot, _, _) => "bitwise", e @ typed_ast::Expression::BinaryOperator(_span_op, op, lhs_expr, rhs_expr, _) => { let lhs = evaluate_const_expr(lhs_expr)?; @@ -83,6 +84,15 @@ pub fn evaluate_const_expr(expr: &typed_ast::Expression) -> Result { "logical", ))) } + typed_ast::BinaryOperator::BitwiseOr + | typed_ast::BinaryOperator::BitwiseAnd + | typed_ast::BinaryOperator::BitwiseXor => Err(Box::new( + TypeCheckError::UnsupportedConstEvalExpression(e.full_span(), "bitwise"), + )), + typed_ast::BinaryOperator::BitShiftLeft + | typed_ast::BinaryOperator::BitShiftRight => Err(Box::new( + TypeCheckError::UnsupportedConstEvalExpression(e.full_span(), "bitshift"), + )), }; } typed_ast::Expression::Identifier(..) => "variable", diff --git a/numbat/src/typechecker/error.rs b/numbat/src/typechecker/error.rs index dd416db3c..b5578cf71 100644 --- a/numbat/src/typechecker/error.rs +++ b/numbat/src/typechecker/error.rs @@ -24,6 +24,9 @@ pub enum TypeCheckError { #[error("Argument of factorial needs to be dimensionless (got {1}).")] NonScalarFactorialArgument(Span, Type), + #[error("Argument of bitwise not needs to be dimensionless (got {1}).")] + NonScalarBitwiseNotArgument(Span, Type), + #[error("Unsupported expression in const-evaluation of exponent: {1}.")] UnsupportedConstEvalExpression(Span, &'static str), diff --git a/numbat/src/typechecker/mod.rs b/numbat/src/typechecker/mod.rs index 0217642d4..14983d302 100644 --- a/numbat/src/typechecker/mod.rs +++ b/numbat/src/typechecker/mod.rs @@ -368,6 +368,17 @@ impl TypeChecker { ))); } } + ast::UnaryOperator::BitwiseNot => { + if self + .add_equal_constraint(&type_, &Type::scalar()) + .is_trivially_violated() + { + return Err(Box::new(TypeCheckError::NonScalarBitwiseNotArgument( + expr.full_span(), + type_, + ))); + } + } ast::UnaryOperator::Negate => { self.enforce_dtype(&type_, expr.full_span())?; } @@ -523,6 +534,19 @@ impl TypeChecker { } typed_ast::BinaryOperator::LogicalAnd => "and".into(), typed_ast::BinaryOperator::LogicalOr => "or".into(), + typed_ast::BinaryOperator::BitwiseOr => "bitwise or".into(), + typed_ast::BinaryOperator::BitwiseAnd => { + "bitwise and".into() + } + typed_ast::BinaryOperator::BitwiseXor => { + "bitwise xor".into() + } + typed_ast::BinaryOperator::BitShiftLeft => { + "bit shift left".into() + } + typed_ast::BinaryOperator::BitShiftRight => { + "bit shift right".into() + } }, span_expected: lhs.full_span(), expected_name: " left hand side", @@ -550,6 +574,19 @@ impl TypeChecker { let type_ = match op { typed_ast::BinaryOperator::Add => get_type_and_assert_equal_dtypes()?, typed_ast::BinaryOperator::Sub => get_type_and_assert_equal_dtypes()?, + typed_ast::BinaryOperator::BitwiseOr => get_type_and_assert_equal_dtypes()?, + typed_ast::BinaryOperator::BitwiseAnd => { + get_type_and_assert_equal_dtypes()? + } + typed_ast::BinaryOperator::BitwiseXor => { + get_type_and_assert_equal_dtypes()? + } + typed_ast::BinaryOperator::BitShiftLeft => { + get_type_and_assert_equal_dtypes()? + } + typed_ast::BinaryOperator::BitShiftRight => { + get_type_and_assert_equal_dtypes()? + } typed_ast::BinaryOperator::Mul | typed_ast::BinaryOperator::Div => { let type_lhs = lhs_checked.get_type(); let type_rhs = rhs_checked.get_type(); diff --git a/numbat/src/typed_ast.rs b/numbat/src/typed_ast.rs index 16decf70a..0c15b8839 100644 --- a/numbat/src/typed_ast.rs +++ b/numbat/src/typed_ast.rs @@ -1300,6 +1300,9 @@ impl PrettyPrint for Expression<'_> { UnaryOperator(_, self::UnaryOperator::LogicalNeg, expr, _type) => { m::operator("!") + with_parens(expr) } + UnaryOperator(_, self::UnaryOperator::BitwiseNot, expr, _type) => { + m::operator("~") + with_parens(expr) + } BinaryOperator(_, op, lhs, rhs, _type) => pretty_print_binop(op, lhs, rhs), BinaryOperatorForDate(_, op, lhs, rhs, _type) => pretty_print_binop(op, lhs, rhs), FunctionCall(_, _, name, args, _type) => { diff --git a/numbat/src/vm.rs b/numbat/src/vm.rs index dd3ec68cd..2f7690ecf 100644 --- a/numbat/src/vm.rs +++ b/numbat/src/vm.rs @@ -77,6 +77,12 @@ pub enum Op { NotEqual, LogicalAnd, LogicalOr, + BitwiseOr, + BitwiseAnd, + BitwiseXor, + BitwiseNot, + BitShiftLeft, + BitShiftRight, LogicalNeg, /// Similar to Add, but has DateTime on the LHS and a quantity on the RHS @@ -138,6 +144,7 @@ impl Op { | Op::AccessStructField | Op::BuildList => 1, Op::Negate + | Op::BitwiseNot | Op::Factorial | Op::Add | Op::AddToDateTime @@ -156,6 +163,11 @@ impl Op { | Op::NotEqual | Op::LogicalAnd | Op::LogicalOr + | Op::BitwiseOr + | Op::BitwiseAnd + | Op::BitwiseXor + | Op::BitShiftRight + | Op::BitShiftLeft | Op::LogicalNeg | Op::Return | Op::GetLastResult => 0, @@ -189,6 +201,12 @@ impl Op { Op::NotEqual => "NotEqual", Op::LogicalAnd => "LogicalAnd", Op::LogicalOr => "LogicalOr", + Op::BitwiseOr => "BitwiseOr", + Op::BitwiseNot => "BitwiseNot", + Op::BitwiseAnd => "BitwiseAnd", + Op::BitwiseXor => "BitwiseXor", + Op::BitShiftLeft => "BitShiftLeft", + Op::BitShiftRight => "BitShiftRight", Op::LogicalNeg => "LogicalNeg", Op::JumpIfFalse => "JumpIfFalse", Op::Jump => "Jump", @@ -799,10 +817,137 @@ impl Vm { }; self.push_bool(result); } + Op::BitwiseOr => { + let rhs = self.pop_quantity(); + let lhs = self.pop_quantity(); + + let check_rhs = rhs + .as_scalar() + .expect("Expected bitwise operands to be scalar") + .to_f64(); + + let check_lhs = lhs + .as_scalar() + .expect("Expected bitwise operands to be scalar") + .to_f64(); + + if check_rhs.fract() != 0. || check_lhs.fract() != 0. { + return Err(Box::new(RuntimeError::BitwiseOperationOfNonInteger)); + } + + let result = Ok(lhs | rhs); + + self.push_quantity(result.map_err(RuntimeError::QuantityError)?); + } + Op::BitwiseAnd => { + let rhs = self.pop_quantity(); + let lhs = self.pop_quantity(); + + let check_rhs = rhs + .as_scalar() + .expect("Expected bitwise operands to be scalar") + .to_f64(); + + let check_lhs = lhs + .as_scalar() + .expect("Expected bitwise operands to be scalar") + .to_f64(); + + if check_rhs.fract() != 0. || check_lhs.fract() != 0. { + return Err(Box::new(RuntimeError::BitwiseOperationOfNonInteger)); + } + + let result = Ok(lhs & rhs); + + self.push_quantity(result.map_err(RuntimeError::QuantityError)?); + } + Op::BitwiseXor => { + let rhs = self.pop_quantity(); + let lhs = self.pop_quantity(); + + let check_rhs = rhs + .as_scalar() + .expect("Expected bitwise not operand to be scalar") + .to_f64(); + + let check_lhs = lhs + .as_scalar() + .expect("Expected bitwise not operand to be scalar") + .to_f64(); + + if check_rhs.fract() != 0. || check_lhs.fract() != 0. { + return Err(Box::new(RuntimeError::BitwiseOperationOfNonInteger)); + } + + let result = Ok(lhs ^ rhs); + + self.push_quantity(result.map_err(RuntimeError::QuantityError)?); + } + Op::BitShiftLeft => { + let rhs = self.pop_quantity(); + let lhs = self.pop_quantity(); + + let check_rhs = rhs + .as_scalar() + .expect("Expected bitwise operands to be scalar") + .to_f64(); + + let check_lhs = lhs + .as_scalar() + .expect("Expected bitwise operands to be scalar") + .to_f64(); + + if check_rhs.fract() != 0. || check_lhs.fract() != 0. { + return Err(Box::new(RuntimeError::BitwiseOperationOfNonInteger)); + } else if check_rhs < 0. { + return Err(Box::new(RuntimeError::BitwiseShiftOfNegativeInteger)); + } + + let result = Ok(lhs << rhs); + + self.push_quantity(result.map_err(RuntimeError::QuantityError)?); + } + Op::BitShiftRight => { + let rhs = self.pop_quantity(); + let lhs = self.pop_quantity(); + + let check_rhs = rhs + .as_scalar() + .expect("Expected bitwise operands to be scalar") + .to_f64(); + + let check_lhs = lhs + .as_scalar() + .expect("Expected bitwise operands to be scalar") + .to_f64(); + + if check_rhs.fract() != 0. || check_lhs.fract() != 0. { + return Err(Box::new(RuntimeError::BitwiseOperationOfNonInteger)); + } else if check_rhs < 0. { + return Err(Box::new(RuntimeError::BitwiseShiftOfNegativeInteger)); + } + + let result = Ok(lhs >> rhs); + + self.push_quantity(result.map_err(RuntimeError::QuantityError)?); + } Op::LogicalNeg => { let rhs = self.pop_bool(); self.push_bool(!rhs); } + Op::BitwiseNot => { + let rhs = self.pop_quantity(); + + let check_rhs = rhs + .as_scalar() + .expect("Expected bitwise not operand to be scalar") + .to_f64(); + + if check_rhs.fract() != 0. { + return Err(Box::new(RuntimeError::BitwiseOperationOfNonInteger)); + } + self.push_quantity(!rhs); + } Op::Negate => { let rhs = self.pop_quantity(); self.push_quantity(-rhs); diff --git a/vscode-extension/syntaxes/numbat.tmLanguage.json b/vscode-extension/syntaxes/numbat.tmLanguage.json index 6eacb21e1..6ec29a68c 100644 --- a/vscode-extension/syntaxes/numbat.tmLanguage.json +++ b/vscode-extension/syntaxes/numbat.tmLanguage.json @@ -32,7 +32,7 @@ "patterns": [ { "name": "keyword.control.numbat", - "match": "\\b(per|to|let|fn|where|and|dimension|unit|use|struct|long|short|both|none|if|then|else|true|false|print|assert|assert_eq|type)\\b" + "match": "\\b(per|to|let|fn|where|and|dimension|unit|use|struct|long|short|both|none|if|then|else|true|false|print|assert|assert_eq|type|xor)\\b" } ] },