From 87929800c6dfd3204751afcc4edd13592819507b Mon Sep 17 00:00:00 2001 From: Antoine Sauzeau Date: Tue, 9 Sep 2025 22:21:25 +0200 Subject: [PATCH 1/2] feat(operators): Add AND, OR, and NOT bitwise operators Signed-off-by: Antoine Sauzeau --- lib/fuzz/inputs.txt | 5 +- .../arithmetic/bitwise_and/integer.vrl | 3 + .../arithmetic/bitwise_or/integer.vrl | 3 + .../tests/expressions/unary/bitwise_not.vrl | 3 + src/compiler/compiler.rs | 19 ++- src/compiler/expression.rs | 2 + src/compiler/expression/bitwise_not.rs | 160 ++++++++++++++++++ src/compiler/expression/op.rs | 31 +++- src/compiler/expression/unary.rs | 18 +- src/compiler/unused_expression_checker.rs | 3 + src/compiler/value/arithmetic.rs | 39 +++++ src/compiler/value/error.rs | 16 +- src/parser/ast.rs | 54 +++++- src/parser/lex.rs | 65 ++++--- src/parser/parser.lalrpop | 27 ++- 15 files changed, 405 insertions(+), 43 deletions(-) create mode 100644 lib/tests/tests/expressions/arithmetic/bitwise_and/integer.vrl create mode 100644 lib/tests/tests/expressions/arithmetic/bitwise_or/integer.vrl create mode 100644 lib/tests/tests/expressions/unary/bitwise_not.vrl create mode 100644 src/compiler/expression/bitwise_not.rs diff --git a/lib/fuzz/inputs.txt b/lib/fuzz/inputs.txt index dd3b198b07..8961672e80 100644 --- a/lib/fuzz/inputs.txt +++ b/lib/fuzz/inputs.txt @@ -43,4 +43,7 @@ decode_zlib!(decode_base64!("eJwNy4ENwCAIBMCNXIlQ/KqplUSgCdvXAS41qPMHshCB2R1zJlW decode_zstd!(decode_base64!("KLUv/QBY/QEAYsQOFKClbQBedqXsb96EWDax/f/F/z+gNU4ZTInaUeAj82KqPFjUzKqhcfDqAIsLvAsnY1bI/N2mHzDixRQA")) encode_base16("please encode me") encode_base64("please encode me") -encode_base64(encode_gzip("please encode me")) \ No newline at end of file +encode_base64(encode_gzip("please encode me")) +15 & 20 +15 ^ 20 +~20 diff --git a/lib/tests/tests/expressions/arithmetic/bitwise_and/integer.vrl b/lib/tests/tests/expressions/arithmetic/bitwise_and/integer.vrl new file mode 100644 index 0000000000..e51c47badf --- /dev/null +++ b/lib/tests/tests/expressions/arithmetic/bitwise_and/integer.vrl @@ -0,0 +1,3 @@ +# result: 5 + +21 & 45 diff --git a/lib/tests/tests/expressions/arithmetic/bitwise_or/integer.vrl b/lib/tests/tests/expressions/arithmetic/bitwise_or/integer.vrl new file mode 100644 index 0000000000..0316453bcc --- /dev/null +++ b/lib/tests/tests/expressions/arithmetic/bitwise_or/integer.vrl @@ -0,0 +1,3 @@ +# result: 61 + +21 ^ 45 diff --git a/lib/tests/tests/expressions/unary/bitwise_not.vrl b/lib/tests/tests/expressions/unary/bitwise_not.vrl new file mode 100644 index 0000000000..4183037292 --- /dev/null +++ b/lib/tests/tests/expressions/unary/bitwise_not.vrl @@ -0,0 +1,3 @@ +# result: [-76, 40, -26] + +[~75, ~~40, ~~~25] diff --git a/src/compiler/compiler.rs b/src/compiler/compiler.rs index 4fd1b42127..db1b56b3cf 100644 --- a/src/compiler/compiler.rs +++ b/src/compiler/compiler.rs @@ -3,7 +3,7 @@ use crate::compiler::expression::function_call::FunctionCallError; use crate::compiler::{ CompileConfig, Function, Program, TypeDef, expression::{ - Abort, Array, Assignment, Block, Container, Expr, Expression, FunctionArgument, + Abort, Array, Assignment, BitwiseNot, Block, Container, Expr, Expression, FunctionArgument, FunctionCall, Group, IfStatement, Literal, Noop, Not, Object, Op, Predicate, Query, Return, Target, Unary, Variable, assignment, function_call, literal, predicate, query, }, @@ -801,10 +801,11 @@ impl<'a> Compiler<'a> { } fn compile_unary(&mut self, node: Node, state: &mut TypeState) -> Option { - use ast::Unary::Not; + use ast::Unary::{BitwiseNot, Not}; let variant = match node.into_inner() { Not(node) => self.compile_not(node, state)?.into(), + BitwiseNot(node) => self.compile_bitwise_not(node, state)?.into(), }; Some(Unary::new(variant)) @@ -820,6 +821,20 @@ impl<'a> Compiler<'a> { .ok() } + fn compile_bitwise_not( + &mut self, + node: Node, + state: &mut TypeState, + ) -> Option { + let (not, expr) = node.into_inner().take(); + + let node = Node::new(expr.span(), self.compile_expr(*expr, state)?); + + BitwiseNot::new(node, not.span(), state) + .map_err(|err| self.diagnostics.push(Box::new(err))) + .ok() + } + fn compile_abort(&mut self, node: Node, state: &mut TypeState) -> Option { self.abortable = true; let (span, abort) = node.take(); diff --git a/src/compiler/expression.rs b/src/compiler/expression.rs index 928898a66d..1c4d621e04 100644 --- a/src/compiler/expression.rs +++ b/src/compiler/expression.rs @@ -5,6 +5,7 @@ use dyn_clone::{DynClone, clone_trait_object}; pub use abort::Abort; pub use array::Array; pub use assignment::Assignment; +pub use bitwise_not::BitwiseNot; pub use block::Block; pub use container::{Container, Variant}; #[allow(clippy::module_name_repetitions)] @@ -34,6 +35,7 @@ use super::{Context, TypeDef}; mod abort; mod array; +mod bitwise_not; mod block; mod function_argument; mod group; diff --git a/src/compiler/expression/bitwise_not.rs b/src/compiler/expression/bitwise_not.rs new file mode 100644 index 0000000000..c775e86935 --- /dev/null +++ b/src/compiler/expression/bitwise_not.rs @@ -0,0 +1,160 @@ +use std::fmt; + +use crate::compiler::state::{TypeInfo, TypeState}; +use crate::compiler::{ + Context, Expression, Span, + expression::{Expr, Resolved}, + parser::Node, + value::{Kind, VrlValueArithmetic}, +}; +use crate::diagnostic::{DiagnosticMessage, Label, Note, Urls}; + +#[derive(Debug, Clone, PartialEq)] +pub struct BitwiseNot { + inner: Box, +} + +pub(crate) type Result = std::result::Result; + +impl BitwiseNot { + /// Creates a new `BitwiseNot` expression. + /// + /// # Errors + /// Returns an `Error` if the provided expression's type is not integer or bytes. + /// + /// # Arguments + /// * `node` - The node representing the expression. + /// * `not_span` - The span of the `bitwise not` operator. + /// * `state` - The current type state. + /// + /// # Returns + /// A `Result` containing the new `BitwiseNot` expression or an error. + /// + /// # Errors + /// - `NonInteger`: If operand is not of type integer. + pub fn new(node: Node, not_span: Span, state: &TypeState) -> Result { + let (expr_span, expr) = node.take(); + let type_def = expr.type_info(state).result; + + if !type_def.is_integer() && !type_def.is_bytes() { + return Err(Error { + variant: ErrorVariant::NonInteger(type_def.into()), + not_span, + expr_span, + }); + } + + Ok(Self { + inner: Box::new(expr), + }) + } +} + +impl Expression for BitwiseNot { + fn resolve(&self, ctx: &mut Context) -> Resolved { + Ok(self.inner.resolve(ctx)?.try_bitwise_not()?) + } + + fn type_info(&self, state: &TypeState) -> TypeInfo { + let mut state = state.clone(); + let mut inner_def = self.inner.apply_type_info(&mut state); + if inner_def.is_integer() { + inner_def = inner_def.infallible().with_kind(Kind::integer()); + } else { + inner_def = inner_def.fallible().with_kind(Kind::integer()); + } + TypeInfo::new(state, inner_def) + } +} + +impl fmt::Display for BitwiseNot { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "~{}", self.inner) + } +} + +// ----------------------------------------------------------------------------- + +#[derive(Debug)] +pub struct Error { + pub(crate) variant: ErrorVariant, + + not_span: Span, + expr_span: Span, +} + +#[derive(thiserror::Error, Debug)] +pub(crate) enum ErrorVariant { + #[error("non-integer bitwise negation")] + NonInteger(Kind), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:#}", self.variant) + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + Some(&self.variant) + } +} + +impl DiagnosticMessage for Error { + fn code(&self) -> usize { + use ErrorVariant::NonInteger; + + match &self.variant { + NonInteger(..) => 670, + } + } + + fn labels(&self) -> Vec