From 55fbaa030244b1ff66e233389043631c26e34ae3 Mon Sep 17 00:00:00 2001 From: Elie ROUDNINSKI Date: Thu, 3 Sep 2026 16:45:01 +0200 Subject: [PATCH] Add typed per-function parser settings Associate each function definition with a settings type and store values in a type-erased collection keyed by the concrete function type. Keep parser settings available to function hooks, document shared settings semantics, and cover lookup, cloning, replacement, and duplicate registrations. --- Cargo.lock | 7 + Cargo.toml | 1 + engine/Cargo.toml | 1 + engine/src/ast/field_expr.rs | 18 +- engine/src/ast/function_expr.rs | 25 ++- engine/src/ast/index_expr.rs | 6 +- engine/src/ast/logical_expr.rs | 16 +- engine/src/ast/mod.rs | 24 +-- engine/src/ast/parse.rs | 320 +++++++++++++++++++++++++++--- engine/src/functions/concat.rs | 2 + engine/src/functions/mod.rs | 82 +++++++- engine/src/functions/settings.rs | 92 +++++++++ engine/src/lib.rs | 7 +- engine/src/rhs_types/regex/mod.rs | 37 ++-- engine/src/rhs_types/wildcard.rs | 28 ++- engine/src/scheme.rs | 8 +- 16 files changed, 573 insertions(+), 101 deletions(-) create mode 100644 engine/src/functions/settings.rs diff --git a/Cargo.lock b/Cargo.lock index 2eb04d28..54dec2e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -338,6 +338,12 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "dyn-eq" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2d035d21af5cde1a6f5c7b444a5bf963520a9f142e5d06931178433d7d5388" + [[package]] name = "either" version = "1.17.0" @@ -1223,6 +1229,7 @@ dependencies = [ "cidr", "criterion", "dyn-clone", + "dyn-eq", "erased-serde", "fnv", "get-size2", diff --git a/Cargo.toml b/Cargo.toml index 1f13d21d..dd881055 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ cfg-if = "1.0.4" cidr = { version = "0.2.3", features = ["serde"] } criterion = "0.8.2" dyn-clone = "1.0.20" +dyn-eq = "0.1.3" erased-serde = "0.4.10" fnv = "1.0.7" get-size2 = "0.11.0" diff --git a/engine/Cargo.toml b/engine/Cargo.toml index 616e86c2..c20280e5 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -24,6 +24,7 @@ backtrace.workspace = true cfg-if.workspace = true cidr.workspace = true dyn-clone.workspace = true +dyn-eq.workspace = true erased-serde.workspace = true fnv.workspace = true get-size2 = { workspace = true, optional = true } diff --git a/engine/src/ast/field_expr.rs b/engine/src/ast/field_expr.rs index e908752f..f350a16e 100644 --- a/engine/src/ast/field_expr.rs +++ b/engine/src/ast/field_expr.rs @@ -1,6 +1,8 @@ use super::Expr; use super::function_expr::FunctionCallExpr; +#[cfg(test)] use super::parse::FilterParser; +use super::parse::ParserContext; use super::visitor::{Visitor, VisitorMut}; use crate::ast::index_expr::{Compare, IndexExpr}; use crate::compiler::Compiler; @@ -249,9 +251,9 @@ impl IdentifierExpr { } } -impl<'i, 's> LexWith<'i, &FilterParser<'s>> for IdentifierExpr { - fn lex_with(input: &'i str, parser: &FilterParser<'s>) -> LexResult<'i, Self> { - let (item, input) = Identifier::lex_with(input, parser.scheme)?; +impl<'i> LexWith<'i, &ParserContext<'_>> for IdentifierExpr { + fn lex_with(input: &'i str, parser: &ParserContext<'_>) -> LexResult<'i, Self> { + let (item, input) = Identifier::lex_with(input, parser.scheme())?; match item { Identifier::Field(field) => Ok((IdentifierExpr::Field(field.to_owned()), input)), Identifier::Function(function) => { @@ -296,8 +298,8 @@ impl GetType for ComparisonExpr { } } -impl<'i> LexWith<'i, &FilterParser<'_>> for ComparisonExpr { - fn lex_with(input: &'i str, parser: &FilterParser<'_>) -> LexResult<'i, Self> { +impl<'i> LexWith<'i, &ParserContext<'_>> for ComparisonExpr { + fn lex_with(input: &'i str, parser: &ParserContext<'_>) -> LexResult<'i, Self> { let (lhs, input) = IndexExpr::lex_with(input, parser)?; Self::lex_with_lhs(input, parser, lhs) @@ -307,7 +309,7 @@ impl<'i> LexWith<'i, &FilterParser<'_>> for ComparisonExpr { impl ComparisonExpr { pub(crate) fn lex_with_lhs<'i>( input: &'i str, - parser: &FilterParser<'_>, + parser: &ParserContext<'_>, lhs: IndexExpr, ) -> LexResult<'i, Self> { let lhs_type = lhs.get_type(); @@ -341,7 +343,7 @@ impl ComparisonExpr { | (Type::Int, ComparisonOp::In) => { if expect(input, "$").is_ok() { let (name, input) = ListName::lex(input)?; - let list = parser.scheme.get_list(&lhs_type).ok_or(( + let list = parser.scheme().get_list(&lhs_type).ok_or(( LexErrorKind::UnsupportedOp { lhs_type }, span(initial_input, input), ))?; @@ -856,6 +858,8 @@ mod tests { } impl FunctionDefinition for FilterFunction { + type Settings = (); + fn check_param( &self, _: &ParserSettings, diff --git a/engine/src/ast/function_expr.rs b/engine/src/ast/function_expr.rs index f6706efc..6442b723 100644 --- a/engine/src/ast/function_expr.rs +++ b/engine/src/ast/function_expr.rs @@ -1,5 +1,5 @@ use super::ValueExpr; -use super::parse::FilterParser; +use super::parse::ParserContext; use super::visitor::{Visitor, VisitorMut}; use crate::FunctionRef; use crate::ast::field_expr::{ComparisonExpr, ComparisonOp, ComparisonOpExpr, IdentifierExpr}; @@ -8,8 +8,8 @@ use crate::ast::logical_expr::{LogicalExpr, QuantifierOp, UnaryOp}; use crate::compiler::Compiler; use crate::filter::{CompiledExpr, CompiledValueExpr, CompiledValueResult}; use crate::functions::{ - CompiledFunction, ExactSizeChain, FunctionDefinition, FunctionDefinitionContext, FunctionParam, - FunctionParamError, + CompiledFunction, ErasedFunctionDefinition, ExactSizeChain, FunctionDefinitionContext, + FunctionParam, FunctionParamError, }; use crate::lex::{Lex, LexError, LexErrorKind, LexResult, LexWith, expect, skip_space, span}; use crate::lhs_types::Array; @@ -115,8 +115,8 @@ impl FunctionCallArgExpr { } } -impl<'i, 's> LexWith<'i, &FilterParser<'s>> for FunctionCallArgExpr { - fn lex_with(input: &'i str, parser: &FilterParser<'s>) -> LexResult<'i, Self> { +impl<'i> LexWith<'i, &ParserContext<'_>> for FunctionCallArgExpr { + fn lex_with(input: &'i str, parser: &ParserContext<'_>) -> LexResult<'i, Self> { let _initial_input = input; macro_rules! c_is_field { @@ -409,7 +409,7 @@ impl FunctionCallExpr { pub(crate) fn lex_with_function<'i>( input: &'i str, - parser: &FilterParser<'_>, + parser: &ParserContext<'_>, function: FunctionRef<'_>, ) -> LexResult<'i, Self> { let definition = function.as_definition(); @@ -426,7 +426,7 @@ impl FunctionCallExpr { let mut index = 0; - let mut ctx = definition.context(); + let mut ctx = definition.context(parser.settings()); while let Some(c) = input.chars().next() { if c == ')' { @@ -539,7 +539,7 @@ impl FunctionCallExpr { } } -fn invalid_args_count<'i>(function: &dyn FunctionDefinition, input: &'i str) -> LexError<'i> { +fn invalid_args_count<'i>(function: &dyn ErasedFunctionDefinition, input: &'i str) -> LexError<'i> { let (mandatory, optional) = function.arg_count(); ( LexErrorKind::InvalidArgumentsCount { @@ -560,9 +560,9 @@ impl GetType for FunctionCallExpr { } } -impl<'i> LexWith<'i, &FilterParser<'_>> for FunctionCallExpr { - fn lex_with(input: &'i str, parser: &FilterParser<'_>) -> LexResult<'i, Self> { - let (function, rest) = FunctionRef::lex_with(input, parser.scheme)?; +impl<'i> LexWith<'i, &ParserContext<'_>> for FunctionCallExpr { + fn lex_with(input: &'i str, parser: &ParserContext<'_>) -> LexResult<'i, Self> { + let (function, rest) = FunctionRef::lex_with(input, parser.scheme())?; let nested_parser = parser.with_increased_nesting(skip_space(rest))?; Self::lex_with_function(rest, &nested_parser, function) @@ -1144,9 +1144,8 @@ mod tests { } ); - let expr = FunctionCallArgExpr::lex_with( + let expr = FilterParser::new(&SCHEME).lex_as::( "lower(lower(lower(lower(lower(lower(lower(lower(lower(lower(lower(lower(lower(lower(lower(lower(lower(lower(lower(lower(lower(lower(lower(lower(lower(lower(lower(lower(lower(lower(lower(lower(http.host)))))))))))))))))))))))))))))))) contains \"c\"", - &FilterParser::new(&SCHEME), ); assert!(expr.is_ok()); diff --git a/engine/src/ast/index_expr.rs b/engine/src/ast/index_expr.rs index f1f8443c..e01f81c0 100644 --- a/engine/src/ast/index_expr.rs +++ b/engine/src/ast/index_expr.rs @@ -1,6 +1,6 @@ use super::ValueExpr; use super::field_expr::IdentifierExpr; -use super::parse::FilterParser; +use super::parse::ParserContext; use super::visitor::{Visitor, VisitorMut}; use crate::compiler::Compiler; use crate::execution_context::ExecutionContext; @@ -314,8 +314,8 @@ impl IndexExpr { } } -impl<'i, 's> LexWith<'i, &FilterParser<'s>> for IndexExpr { - fn lex_with(mut input: &'i str, parser: &FilterParser<'s>) -> LexResult<'i, Self> { +impl<'i> LexWith<'i, &ParserContext<'_>> for IndexExpr { + fn lex_with(mut input: &'i str, parser: &ParserContext<'_>) -> LexResult<'i, Self> { let (identifier, rest) = IdentifierExpr::lex_with(input, parser)?; let mut current_type = identifier.get_type(); diff --git a/engine/src/ast/logical_expr.rs b/engine/src/ast/logical_expr.rs index 7c535174..7c6f4fba 100644 --- a/engine/src/ast/logical_expr.rs +++ b/engine/src/ast/logical_expr.rs @@ -2,7 +2,9 @@ use super::Expr; use super::field_expr::ComparisonExpr; use super::function_expr::FunctionCallArgExpr; use super::index_expr::IndexExpr; +#[cfg(test)] use super::parse::FilterParser; +use super::parse::ParserContext; use super::visitor::{Visitor, VisitorMut}; use crate::compiler::Compiler; use crate::filter::{CompiledExpr, CompiledOneExpr, CompiledVecExpr}; @@ -119,8 +121,8 @@ impl GetType for QuantifierArgExpr { } } -impl<'i, 's> LexWith<'i, &FilterParser<'s>> for QuantifierArgExpr { - fn lex_with(input: &'i str, parser: &FilterParser<'s>) -> LexResult<'i, Self> { +impl<'i> LexWith<'i, &ParserContext<'_>> for QuantifierArgExpr { + fn lex_with(input: &'i str, parser: &ParserContext<'_>) -> LexResult<'i, Self> { let (arg, rest) = FunctionCallArgExpr::lex_with(input, parser)?; let arg = match arg { FunctionCallArgExpr::IndexExpr(index_expr) => Self::IndexExpr(index_expr), @@ -208,7 +210,7 @@ impl LogicalExpr { pub(crate) fn lex_quantifier<'i>( input: &'i str, - parser: &FilterParser<'_>, + parser: &ParserContext<'_>, ) -> Option)>> { let (op, rest) = QuantifierOp::lex_call(input)?; let nested_parser = match parser.with_increased_nesting(skip_space(rest)) { @@ -226,7 +228,7 @@ impl LogicalExpr { })()) } - fn lex_simple_expr<'i>(input: &'i str, parser: &FilterParser<'_>) -> LexResult<'i, Self> { + fn lex_simple_expr<'i>(input: &'i str, parser: &ParserContext<'_>) -> LexResult<'i, Self> { Ok(if let Ok(rest) = expect(input, "(") { let nested_parser = parser.with_increased_nesting(input)?; let input = skip_space(rest); @@ -259,7 +261,7 @@ impl LogicalExpr { fn lex_more_with_precedence<'i>( self, - parser: &FilterParser<'_>, + parser: &ParserContext<'_>, min_prec: Option, mut lookahead: (Option, &'i str), ) -> LexResult<'i, Self> { @@ -323,8 +325,8 @@ impl LogicalExpr { } } -impl<'i, 's> LexWith<'i, &FilterParser<'s>> for LogicalExpr { - fn lex_with(input: &'i str, parser: &FilterParser<'s>) -> LexResult<'i, Self> { +impl<'i> LexWith<'i, &ParserContext<'_>> for LogicalExpr { + fn lex_with(input: &'i str, parser: &ParserContext<'_>) -> LexResult<'i, Self> { let (lhs, input) = Self::lex_simple_expr(input, parser)?; let lookahead = Self::lex_combining_op(input); lhs.lex_more_with_precedence(parser, None, lookahead) diff --git a/engine/src/ast/mod.rs b/engine/src/ast/mod.rs index 84236d11..dfeaa0d1 100644 --- a/engine/src/ast/mod.rs +++ b/engine/src/ast/mod.rs @@ -7,7 +7,7 @@ pub mod visitor; use self::index_expr::IndexExpr; use self::logical_expr::{LogicalExpr, QuantifierArgExpr, QuantifierOp}; -use self::parse::FilterParser; +use self::parse::ParserContext; use self::visitor::{UsesListVisitor, UsesVisitor, Visitor, VisitorMut}; use crate::compiler::{Compiler, DefaultCompiler}; use crate::filter::{CompiledExpr, CompiledValueExpr, Filter, FilterValue}; @@ -18,9 +18,7 @@ use serde::Serialize; use std::fmt::{self, Debug}; /// Trait used to represent node that evaluates to a [`bool`] (or a [`Vec`]). -pub trait Expr: - Sized + Eq + Debug + for<'i, 'p, 's> LexWith<'i, &'p FilterParser<'s>> + Serialize -{ +pub trait Expr: Sized + Eq + Debug + Serialize { /// Recursively visit all nodes in the AST using a [`Visitor`]. fn walk<'a, V: Visitor<'a>>(&'a self, visitor: &mut V); /// Recursively visit all nodes in the AST using a [`VisitorMut`]. @@ -35,9 +33,7 @@ pub trait Expr: } /// Trait used to represent node that evaluates to an [`crate::LhsValue`]. -pub trait ValueExpr: - Sized + Eq + Debug + for<'i, 'p, 's> LexWith<'i, &'p FilterParser<'s>> + Serialize -{ +pub trait ValueExpr: Sized + Eq + Debug + Serialize { /// Recursively visit all nodes in the AST using a [`Visitor`]. fn walk<'a, V: Visitor<'a>>(&'a self, visitor: &mut V); /// Recursively visit all nodes in the AST using a [`VisitorMut`]. @@ -71,8 +67,8 @@ impl Debug for FilterAst { } } -impl<'i, 's> LexWith<'i, &FilterParser<'s>> for FilterAst { - fn lex_with(input: &'i str, parser: &FilterParser<'s>) -> LexResult<'i, Self> { +impl<'i> LexWith<'i, &ParserContext<'_>> for FilterAst { + fn lex_with(input: &'i str, parser: &ParserContext<'_>) -> LexResult<'i, Self> { let (op, input) = LogicalExpr::lex_with(input, parser)?; // LogicalExpr::lex_with can return an AST where the root is an // LogicalExpr::Combining of type [`Array(Bool)`]. @@ -88,7 +84,7 @@ impl<'i, 's> LexWith<'i, &FilterParser<'s>> for FilterAst { match ty { Type::Bool => Ok(( FilterAst { - scheme: parser.scheme.clone(), + scheme: parser.scheme().clone(), op, }, input, @@ -192,8 +188,8 @@ impl Debug for FilterValueExpr { } } -impl<'i, 's> LexWith<'i, &FilterParser<'s>> for FilterValueExpr { - fn lex_with(input: &'i str, parser: &FilterParser<'s>) -> LexResult<'i, Self> { +impl<'i> LexWith<'i, &ParserContext<'_>> for FilterValueExpr { + fn lex_with(input: &'i str, parser: &ParserContext<'_>) -> LexResult<'i, Self> { match IndexExpr::lex_with(input, parser) { Ok((expr, rest)) => Ok((FilterValueExpr::Index(expr), rest)), Err(index_err) => match LogicalExpr::lex_quantifier(input, parser) { @@ -265,8 +261,8 @@ impl Debug for FilterValueAst { } } -impl<'i, 's> LexWith<'i, &FilterParser<'s>> for FilterValueAst { - fn lex_with(input: &'i str, parser: &FilterParser<'s>) -> LexResult<'i, Self> { +impl<'i> LexWith<'i, &ParserContext<'_>> for FilterValueAst { + fn lex_with(input: &'i str, parser: &ParserContext<'_>) -> LexResult<'i, Self> { let (op, rest) = FilterValueExpr::lex_with(input.trim(), parser)?; if let FilterValueExpr::Index(expr) = &op && expr.map_each_count() > 0 diff --git a/engine/src/ast/parse.rs b/engine/src/ast/parse.rs index a9b79ffc..7ee72682 100644 --- a/engine/src/ast/parse.rs +++ b/engine/src/ast/parse.rs @@ -1,4 +1,5 @@ use super::{FilterAst, FilterValueAst}; +use crate::functions::{FunctionDefinition, FunctionSettings}; use crate::lex::{LexErrorKind, LexResult, LexWith, complete}; use crate::scheme::Scheme; use std::cmp::{max, min}; @@ -109,6 +110,9 @@ pub struct ParserSettings { /// Maximum nesting depth allowed while parsing. /// Default: 128 pub max_nesting_depth: u16, + /// Settings for custom functions registered by embedders. + /// Default: empty + pub function_settings: FunctionSettings, } impl Default for ParserSettings { @@ -121,16 +125,28 @@ impl Default for ParserSettings { regex_dfa_size_limit: 2 * (1 << 20), wildcard_star_limit: usize::MAX, max_nesting_depth: 128, + function_settings: FunctionSettings::default(), } } } +impl ParserSettings { + /// Sets settings shared by every registration of function definition type `F`. + /// + /// This replaces any previously configured value for `F`. + pub fn set_function_settings( + &mut self, + settings: F::Settings, + ) { + self.function_settings.set::(settings); + } +} + /// A structure used to drive parsing of an expression into a [`FilterAst`]. #[derive(Clone, Debug, PartialEq, Eq)] pub struct FilterParser<'s> { pub(crate) scheme: &'s Scheme, pub(crate) settings: ParserSettings, - current_nesting_depth: u16, } impl<'s> FilterParser<'s> { @@ -140,18 +156,13 @@ impl<'s> FilterParser<'s> { Self { scheme, settings: ParserSettings::default(), - current_nesting_depth: 0, } } /// Creates a new parser with the specified settings. #[inline] pub fn with_settings(scheme: &'s Scheme, settings: ParserSettings) -> Self { - Self { - scheme, - settings, - current_nesting_depth: 0, - } + Self { scheme, settings } } /// Returns the [`Scheme`](struct@Scheme) for which this parser has been constructor for. @@ -160,31 +171,18 @@ impl<'s> FilterParser<'s> { self.scheme } + /// Creates a parsing context borrowing this parser. #[inline] - pub(crate) fn lex_as<'i, L: for<'p> LexWith<'i, &'p FilterParser<'s>>>( - &self, - input: &'i str, - ) -> LexResult<'i, L> { - L::lex_with(input, self) + pub fn context(&self) -> ParserContext<'_> { + ParserContext::new(self) } #[inline] - pub(crate) fn with_increased_nesting<'i>( - &self, - span: &'i str, - ) -> Result { - if self.current_nesting_depth >= self.settings.max_nesting_depth { - Err(( - LexErrorKind::NestingLimitExceeded { - limit: self.settings.max_nesting_depth, - }, - span, - )) - } else { - let mut nested = self.clone(); - nested.current_nesting_depth += 1; - Ok(nested) - } + pub(crate) fn lex_as<'i, L>(&self, input: &'i str) -> LexResult<'i, L> + where + L: for<'p, 'c> LexWith<'i, &'p ParserContext<'c>>, + { + self.context().lex_as(input) } /// Parses a filter expression into an AST form. @@ -250,4 +248,270 @@ impl<'s> FilterParser<'s> { pub fn max_nesting_depth(&self) -> u16 { self.settings.max_nesting_depth } + + /// Sets settings shared by every registration of function definition type `F`. + /// + /// This replaces any previously configured value for `F`. + #[inline] + pub fn set_function_settings( + &mut self, + settings: F::Settings, + ) { + self.settings.set_function_settings::(settings); + } +} + +/// Read-only parser configuration and per-parse state used by lexer implementations. +/// +/// Create a context with [`FilterParser::context`]. Nesting state is managed internally while +/// parsing an expression. +#[derive(Clone, Copy)] +pub struct ParserContext<'a> { + parser: &'a FilterParser<'a>, + current_nesting_depth: u16, +} + +impl<'a> ParserContext<'a> { + fn new(parser: &'a FilterParser<'a>) -> Self { + Self { + parser, + current_nesting_depth: 0, + } + } + + pub(crate) fn lex_as<'i, L>(&self, input: &'i str) -> LexResult<'i, L> + where + L: for<'p> LexWith<'i, &'p Self>, + { + L::lex_with(input, self) + } + + pub(crate) fn with_increased_nesting<'i>( + &self, + span: &'i str, + ) -> Result { + if self.current_nesting_depth >= self.parser.settings.max_nesting_depth { + Err(( + LexErrorKind::NestingLimitExceeded { + limit: self.parser.settings.max_nesting_depth, + }, + span, + )) + } else { + let mut nested = *self; + nested.current_nesting_depth += 1; + Ok(nested) + } + } + + /// Returns the parser settings used by this context. + #[inline] + pub fn settings(&self) -> &ParserSettings { + self.parser.settings() + } + + /// Returns the scheme used by this context. + #[inline] + pub fn scheme(&self) -> &Scheme { + self.parser.scheme() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + CompiledFunction, FunctionCallExpr, FunctionDefinition, FunctionDefinitionContext, + FunctionParam, FunctionParamError, SchemeBuilder, Type, + }; + use std::sync::atomic::{AtomicUsize, Ordering}; + + static FUNCTION_SETTINGS_CLONES: AtomicUsize = AtomicUsize::new(0); + + #[derive(Debug, Eq, PartialEq)] + struct CloneCountingFunctionSettings; + + impl Clone for CloneCountingFunctionSettings { + fn clone(&self) -> Self { + FUNCTION_SETTINGS_CLONES.fetch_add(1, Ordering::Relaxed); + Self + } + } + + #[derive(Clone, Debug, Eq, PartialEq)] + struct TestFunctionSettings { + limit: usize, + } + + #[derive(Debug)] + struct SettingsAwareFunction; + + impl FunctionDefinition for SettingsAwareFunction { + type Settings = TestFunctionSettings; + + fn context(&self, settings: &ParserSettings) -> Option { + let settings = settings.function_settings.get::()?; + Some(FunctionDefinitionContext::new(settings.limit)) + } + + fn check_param( + &self, + _: &ParserSettings, + _: &mut dyn ExactSizeIterator>, + _: &FunctionParam<'_>, + _: Option<&mut FunctionDefinitionContext>, + ) -> Result<(), FunctionParamError> { + unreachable!("settings_aware takes no arguments") + } + + fn return_type( + &self, + _: &mut dyn ExactSizeIterator>, + _: Option<&FunctionDefinitionContext>, + ) -> Type { + Type::Bool + } + + fn arg_count(&self) -> (usize, Option) { + (0, Some(0)) + } + + fn compile( + &self, + _: &mut dyn ExactSizeIterator>, + _: Option, + ) -> CompiledFunction { + Box::new(|_| None) + } + } + + #[derive(Debug)] + struct CloneCountingFunction; + + impl FunctionDefinition for CloneCountingFunction { + type Settings = CloneCountingFunctionSettings; + + fn check_param( + &self, + _: &ParserSettings, + _: &mut dyn ExactSizeIterator>, + _: &FunctionParam<'_>, + _: Option<&mut FunctionDefinitionContext>, + ) -> Result<(), FunctionParamError> { + unreachable!("clone_counting takes no arguments") + } + + fn return_type( + &self, + _: &mut dyn ExactSizeIterator>, + _: Option<&FunctionDefinitionContext>, + ) -> Type { + Type::Bool + } + + fn arg_count(&self) -> (usize, Option) { + (0, Some(0)) + } + + fn compile( + &self, + _: &mut dyn ExactSizeIterator>, + _: Option, + ) -> CompiledFunction { + Box::new(|_| None) + } + } + + #[test] + fn nested_parsing_does_not_clone_function_settings() { + let mut builder = SchemeBuilder::new(); + builder.add_field("flag", Type::Bool).unwrap(); + let scheme = builder.build(); + let mut parser = FilterParser::new(&scheme); + parser.set_function_settings::(CloneCountingFunctionSettings); + FUNCTION_SETTINGS_CLONES.store(0, Ordering::Relaxed); + + parser.parse("(((flag)))").unwrap(); + + assert_eq!(FUNCTION_SETTINGS_CLONES.load(Ordering::Relaxed), 0); + } + + #[test] + fn function_context_receives_parser_settings() { + let mut builder = SchemeBuilder::new(); + builder + .add_function("settings_aware", SettingsAwareFunction) + .unwrap(); + let scheme = builder.build(); + + let parser = FilterParser::new(&scheme); + let (function, _) = parser + .lex_as::("settings_aware()") + .unwrap(); + assert!(function.context().is_none()); + + let mut parser = FilterParser::new(&scheme); + parser.set_function_settings::(TestFunctionSettings { limit: 42 }); + let (function, _) = parser + .lex_as::("settings_aware()") + .unwrap(); + assert_eq!( + function.context().unwrap().downcast_ref::(), + Some(&42) + ); + } + + #[test] + fn function_settings_store_values_by_function_type() { + let mut settings = FunctionSettings::default(); + assert!(settings.get::().is_none()); + assert!(settings.get::().is_none()); + + settings.set::(TestFunctionSettings { limit: 42 }); + settings.set::(CloneCountingFunctionSettings); + assert_eq!( + settings.get::(), + Some(&TestFunctionSettings { limit: 42 }) + ); + assert_eq!( + settings.get::(), + Some(&CloneCountingFunctionSettings) + ); + + let original = settings.clone(); + assert_eq!(settings, original); + + settings.set::(TestFunctionSettings { limit: 7 }); + assert_ne!(settings, original); + assert_eq!( + settings.get::(), + Some(&TestFunctionSettings { limit: 7 }) + ); + assert_eq!( + settings.get::(), + Some(&CloneCountingFunctionSettings) + ); + } + + #[test] + fn registrations_of_same_function_type_share_settings() { + let mut builder = SchemeBuilder::new(); + builder + .add_function("settings_aware_one", SettingsAwareFunction) + .unwrap(); + builder + .add_function("settings_aware_two", SettingsAwareFunction) + .unwrap(); + let scheme = builder.build(); + let mut parser = FilterParser::new(&scheme); + parser.set_function_settings::(TestFunctionSettings { limit: 42 }); + + for name in ["settings_aware_one()", "settings_aware_two()"] { + let (function, _) = parser.lex_as::(name).unwrap(); + assert_eq!( + function.context().unwrap().downcast_ref::(), + Some(&42) + ); + } + } } diff --git a/engine/src/functions/concat.rs b/engine/src/functions/concat.rs index 681b099e..1ed2a50c 100644 --- a/engine/src/functions/concat.rs +++ b/engine/src/functions/concat.rs @@ -80,6 +80,8 @@ pub(crate) const EXPECTED_TYPES: [ExpectedType; 2] = [ExpectedType::Array, ExpectedType::Type(Type::Bytes)]; impl FunctionDefinition for ConcatFunction { + type Settings = (); + fn check_param( &self, _: &ParserSettings, diff --git a/engine/src/functions/mod.rs b/engine/src/functions/mod.rs index e236a0f9..101e1a8f 100644 --- a/engine/src/functions/mod.rs +++ b/engine/src/functions/mod.rs @@ -1,6 +1,8 @@ pub(crate) mod concat; +mod settings; pub use self::concat::ConcatFunction; +pub use self::settings::{FunctionSettings, FunctionSettingsValue}; use crate::ParserSettings; use crate::filter::CompiledValueResult; use crate::types::{ @@ -378,10 +380,17 @@ pub type CompiledFunction = /// Trait to implement function pub trait FunctionDefinition: Debug + Send + Sync { - /// Custom context to store information during parsing - fn context(&self) -> Option { + /// Settings shared by all registrations of this function-definition type while parsing. + /// + /// Functions that do not use settings should specify `()`. + type Settings: FunctionSettingsValue; + + /// Custom context to store information during parsing. + fn context(&self, settings: &ParserSettings) -> Option { + let _ = settings; None } + /// Given a slice of already checked parameters, checks that next_param is /// correct. Return the expected the parameter definition. fn check_param( @@ -410,6 +419,73 @@ pub trait FunctionDefinition: Debug + Send + Sync { ) -> CompiledFunction; } +pub(crate) trait ErasedFunctionDefinition: Debug + Send + Sync { + fn context(&self, settings: &ParserSettings) -> Option; + + fn check_param( + &self, + settings: &ParserSettings, + params: &mut dyn ExactSizeIterator>, + next_param: &FunctionParam<'_>, + ctx: Option<&mut FunctionDefinitionContext>, + ) -> Result<(), FunctionParamError>; + + fn return_type( + &self, + params: &mut dyn ExactSizeIterator>, + ctx: Option<&FunctionDefinitionContext>, + ) -> Type; + + fn arg_count(&self) -> (usize, Option); + + fn compile( + &self, + params: &mut dyn ExactSizeIterator>, + ctx: Option, + ) -> CompiledFunction; +} + +impl ErasedFunctionDefinition for F { + #[inline] + fn context(&self, settings: &ParserSettings) -> Option { + FunctionDefinition::context(self, settings) + } + + #[inline] + fn check_param( + &self, + settings: &ParserSettings, + params: &mut dyn ExactSizeIterator>, + next_param: &FunctionParam<'_>, + ctx: Option<&mut FunctionDefinitionContext>, + ) -> Result<(), FunctionParamError> { + FunctionDefinition::check_param(self, settings, params, next_param, ctx) + } + + #[inline] + fn return_type( + &self, + params: &mut dyn ExactSizeIterator>, + ctx: Option<&FunctionDefinitionContext>, + ) -> Type { + FunctionDefinition::return_type(self, params, ctx) + } + + #[inline] + fn arg_count(&self) -> (usize, Option) { + FunctionDefinition::arg_count(self) + } + + #[inline] + fn compile( + &self, + params: &mut dyn ExactSizeIterator>, + ctx: Option, + ) -> CompiledFunction { + FunctionDefinition::compile(self, params, ctx) + } +} + // Simple function APIs type FunctionPtr = for<'i, 'a> fn(FunctionArgs<'i, 'a>) -> Option>; @@ -494,6 +570,8 @@ pub struct SimpleFunctionDefinition { } impl FunctionDefinition for SimpleFunctionDefinition { + type Settings = (); + fn check_param( &self, _settings: &ParserSettings, diff --git a/engine/src/functions/settings.rs b/engine/src/functions/settings.rs new file mode 100644 index 00000000..fb816eef --- /dev/null +++ b/engine/src/functions/settings.rs @@ -0,0 +1,92 @@ +use super::FunctionDefinition; +use dyn_clone::DynClone; +use dyn_eq::DynEq; +use std::any::{Any, TypeId}; +use std::collections::HashMap; +use std::fmt::Debug; + +/// A value that can be stored as settings for a function definition. +pub trait FunctionSettingsValue: Any + Debug + DynClone + DynEq + Send + Sync {} + +impl FunctionSettingsValue for T where T: Any + Clone + Debug + Eq + Send + Sync {} + +dyn_clone::clone_trait_object!(FunctionSettingsValue); +dyn_eq::eq_trait_object!(FunctionSettingsValue); + +/// Function-specific settings used while parsing expressions. +/// +/// Settings are associated with the concrete function-definition type. If the same definition +/// type is registered in a scheme under multiple names, every registration uses the same settings. +/// Setting another value for the same definition type replaces the previous value. +/// +/// # Example +/// +/// ``` +/// # use wirefilter::{CompiledFunction, FunctionDefinition, FunctionDefinitionContext}; +/// # use wirefilter::{FunctionParam, FunctionParamError, FunctionSettings, ParserSettings, Type}; +/// +/// #[derive(Clone, Debug, Eq, PartialEq)] +/// struct ConcatSettings { +/// max_len: usize, +/// } +/// +/// #[derive(Debug)] +/// struct ConcatFunction; +/// +/// impl FunctionDefinition for ConcatFunction { +/// type Settings = ConcatSettings; +/// +/// # fn check_param( +/// # &self, +/// # _: &ParserSettings, +/// # _: &mut dyn ExactSizeIterator>, +/// # _: &FunctionParam<'_>, +/// # _: Option<&mut FunctionDefinitionContext>, +/// # ) -> Result<(), FunctionParamError> { unreachable!() } +/// # fn return_type( +/// # &self, +/// # _: &mut dyn ExactSizeIterator>, +/// # _: Option<&FunctionDefinitionContext>, +/// # ) -> Type { Type::Bytes } +/// # fn arg_count(&self) -> (usize, Option) { (0, Some(0)) } +/// # fn compile( +/// # &self, +/// # _: &mut dyn ExactSizeIterator>, +/// # _: Option, +/// # ) -> CompiledFunction { Box::new(|_| None) } +/// } +/// +/// let mut function_settings = FunctionSettings::default(); +/// function_settings.set::(ConcatSettings { max_len: 4096 }); +/// +/// assert_eq!( +/// function_settings.get::(), +/// Some(&ConcatSettings { max_len: 4096 }), +/// ); +/// +/// let parser_settings = ParserSettings { +/// function_settings, +/// ..ParserSettings::default() +/// }; +/// ``` +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct FunctionSettings { + values: HashMap>, +} + +impl FunctionSettings { + /// Sets the settings associated with every registration of function definition type `F`. + /// + /// This replaces any previously configured value for `F`. + pub fn set(&mut self, value: F::Settings) { + self.values.insert(TypeId::of::(), Box::new(value)); + } + + /// Returns the settings associated with function definition type `F`, if configured. + pub fn get(&self) -> Option<&F::Settings> { + self.values.get(&TypeId::of::()).and_then(|value| { + let value: &(dyn Any + Send + Sync) = value.as_ref(); + value.downcast_ref() + }) + } +} diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 3f1b5c60..ef073a13 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -87,7 +87,7 @@ pub use self::ast::index_expr::{Compare, IndexExpr}; pub use self::ast::logical_expr::{ LogicalExpr, LogicalOp, ParenthesizedExpr, QuantifierArgExpr, QuantifierOp, UnaryOp, }; -pub use self::ast::parse::{FilterParser, ParseError, ParserSettings}; +pub use self::ast::parse::{FilterParser, ParseError, ParserContext, ParserSettings}; pub use self::ast::visitor::{Visitor, VisitorMut}; pub use self::ast::{Expr, FilterAst, FilterValueAst, FilterValueExpr, ValueExpr}; pub use self::compiler::{Compiler, DefaultCompiler}; @@ -100,8 +100,9 @@ pub use self::filter::{ pub use self::functions::{ CompiledFunction, ConcatFunction, FunctionArgInvalidConstantError, FunctionArgKind, FunctionArgKindMismatchError, FunctionArgs, FunctionDefinition, FunctionDefinitionContext, - FunctionParam, FunctionParamError, SimpleFunctionArgKind, SimpleFunctionDefinition, - SimpleFunctionImpl, SimpleFunctionOptParam, SimpleFunctionParam, + FunctionParam, FunctionParamError, FunctionSettings, FunctionSettingsValue, + SimpleFunctionArgKind, SimpleFunctionDefinition, SimpleFunctionImpl, SimpleFunctionOptParam, + SimpleFunctionParam, }; pub use self::lex::LexErrorKind; pub use self::lhs_types::{Array, Bytes, Map, MapIter, TypedArray, TypedMap}; diff --git a/engine/src/rhs_types/regex/mod.rs b/engine/src/rhs_types/regex/mod.rs index 3ed5b096..6fbdb81e 100644 --- a/engine/src/rhs_types/regex/mod.rs +++ b/engine/src/rhs_types/regex/mod.rs @@ -1,6 +1,7 @@ +use crate::ast::parse::ParserContext; use crate::lex::{LexErrorKind, LexResult, LexWith, span}; use crate::rhs_types::bytes::lex_raw_string_as_str; -use crate::{Compare, ExecutionContext, FilterParser, LhsValue}; +use crate::{Compare, ExecutionContext, FilterParser, LhsValue, ParserSettings}; use cfg_if::cfg_if; use serde::{Serialize, Serializer}; use std::fmt::{self, Debug, Display, Formatter}; @@ -59,16 +60,16 @@ impl Debug for Regex { fn lex_regex_from_raw_string<'i>( input: &'i str, - parser: &FilterParser<'_>, + settings: &ParserSettings, ) -> LexResult<'i, Regex> { let ((lexed, hashes), input) = lex_raw_string_as_str(input)?; - match Regex::new(lexed, RegexFormat::Raw(hashes), parser.settings()) { + match Regex::new(lexed, RegexFormat::Raw(hashes), settings) { Ok(regex) => Ok((regex, input)), Err(err) => Err((LexErrorKind::ParseRegex(err), input)), } } -fn lex_regex_from_literal<'i>(input: &'i str, parser: &FilterParser<'_>) -> LexResult<'i, Regex> { +fn lex_regex_from_literal<'i>(input: &'i str, settings: &ParserSettings) -> LexResult<'i, Regex> { let mut regex_buf = String::new(); let mut in_char_class = false; let (regex_str, input) = { @@ -104,7 +105,7 @@ fn lex_regex_from_literal<'i>(input: &'i str, parser: &FilterParser<'_>) -> LexR }; } }; - match Regex::new(®ex_buf, RegexFormat::Literal, parser.settings()) { + match Regex::new(®ex_buf, RegexFormat::Literal, settings) { Ok(regex) => Ok((regex, input)), Err(err) => Err((LexErrorKind::ParseRegex(err), regex_str)), } @@ -112,15 +113,25 @@ fn lex_regex_from_literal<'i>(input: &'i str, parser: &FilterParser<'_>) -> LexR impl<'i, 's> LexWith<'i, &FilterParser<'s>> for Regex { fn lex_with(input: &'i str, parser: &FilterParser<'s>) -> LexResult<'i, Self> { - if let Some(c) = input.as_bytes().first() { - match c { - b'"' => lex_regex_from_literal(&input[1..], parser), - b'r' => lex_regex_from_raw_string(&input[1..], parser), - _ => Err((LexErrorKind::ExpectedName("\" or r"), input)), - } - } else { - Err((LexErrorKind::EOF, input)) + lex_regex(input, parser.settings()) + } +} + +impl<'i> LexWith<'i, &ParserContext<'_>> for Regex { + fn lex_with(input: &'i str, parser: &ParserContext<'_>) -> LexResult<'i, Self> { + lex_regex(input, parser.settings()) + } +} + +fn lex_regex<'i>(input: &'i str, settings: &ParserSettings) -> LexResult<'i, Regex> { + if let Some(c) = input.as_bytes().first() { + match c { + b'"' => lex_regex_from_literal(&input[1..], settings), + b'r' => lex_regex_from_raw_string(&input[1..], settings), + _ => Err((LexErrorKind::ExpectedName("\" or r"), input)), } + } else { + Err((LexErrorKind::EOF, input)) } } diff --git a/engine/src/rhs_types/wildcard.rs b/engine/src/rhs_types/wildcard.rs index d0db6827..5a8e8107 100644 --- a/engine/src/rhs_types/wildcard.rs +++ b/engine/src/rhs_types/wildcard.rs @@ -1,6 +1,7 @@ +use crate::ast::parse::ParserContext; use crate::lex::{LexResult, LexWith}; use crate::rhs_types::bytes::{BytesExpr, lex_quoted_or_raw_string}; -use crate::{FilterParser, LexErrorKind}; +use crate::{FilterParser, LexErrorKind, ParserSettings}; use serde::{Serialize, Serializer}; use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; @@ -123,15 +124,28 @@ impl Serialize for Wildcard { impl<'i, 's, const STRICT: bool> LexWith<'i, &FilterParser<'s>> for Wildcard { fn lex_with(input: &'i str, parser: &FilterParser<'s>) -> LexResult<'i, Wildcard> { - lex_quoted_or_raw_string(input).and_then(|(pattern, rest)| { - match Wildcard::new(pattern, parser.settings.wildcard_star_limit) { - Ok(wildcard) => Ok((wildcard, rest)), - Err(err) => Err((LexErrorKind::ParseWildcard(err), input)), - } - }) + lex_wildcard(input, parser.settings()) + } +} + +impl<'i, const STRICT: bool> LexWith<'i, &ParserContext<'_>> for Wildcard { + fn lex_with(input: &'i str, parser: &ParserContext<'_>) -> LexResult<'i, Self> { + lex_wildcard(input, parser.settings()) } } +fn lex_wildcard<'i, const STRICT: bool>( + input: &'i str, + settings: &ParserSettings, +) -> LexResult<'i, Wildcard> { + lex_quoted_or_raw_string(input).and_then(|(pattern, rest)| { + match Wildcard::new(pattern, settings.wildcard_star_limit) { + Ok(wildcard) => Ok((wildcard, rest)), + Err(err) => Err((LexErrorKind::ParseWildcard(err), input)), + } + }) +} + #[cfg(test)] mod test { use super::*; diff --git a/engine/src/scheme.rs b/engine/src/scheme.rs index 303fc29f..ddc61eae 100644 --- a/engine/src/scheme.rs +++ b/engine/src/scheme.rs @@ -1,6 +1,6 @@ use crate::ast::parse::{FilterParser, ParseError, ParserSettings}; use crate::ast::{FilterAst, FilterValueAst}; -use crate::functions::FunctionDefinition; +use crate::functions::{ErasedFunctionDefinition, FunctionDefinition}; use crate::lex::{Lex, LexErrorKind, LexResult, LexWith, expect, span, take_while}; use crate::list_matcher::ListDefinition; use crate::types::{GetType, RhsValue, Type}; @@ -318,7 +318,7 @@ impl<'s> FunctionRef<'s> { } #[inline] - pub(crate) fn as_definition(&self) -> &'s dyn FunctionDefinition { + pub(crate) fn as_definition(&self) -> &'s dyn ErasedFunctionDefinition { &*self.scheme.inner.functions[self.index].1 } @@ -394,7 +394,7 @@ impl Function { } #[inline] - pub(crate) fn as_definition(&self) -> &dyn FunctionDefinition { + pub(crate) fn as_definition(&self) -> &dyn ErasedFunctionDefinition { &*self.scheme.inner.functions[self.index].1 } @@ -627,7 +627,7 @@ struct FieldDefinition { #[derive(Default, Debug)] pub struct SchemeBuilder { fields: Vec, - functions: Vec<(IdentifierName, Box)>, + functions: Vec<(IdentifierName, Box)>, items: HashMap, list_types: HashMap,