diff --git a/CHANGELOG.md b/CHANGELOG.md index 64e02268..6be94fc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ Bug fixes * Syntax such as `foo.bar::baz` no longer panics, but returns a proper parse error. * `x += y` where `x` and `y` are `char` now works correctly. * Expressions such as `!inside` now parses correctly instead of as `!in` followed by `side`. +* Custom syntax starting with symbols now works correctly and no longer raises a parse error. Potentially breaking changes ---------------------------- diff --git a/src/api/custom_syntax.rs b/src/api/custom_syntax.rs index f881ff88..91b89b2c 100644 --- a/src/api/custom_syntax.rs +++ b/src/api/custom_syntax.rs @@ -255,7 +255,8 @@ impl Engine { // Markers not in first position #[cfg(not(feature = "no_float"))] CUSTOM_SYNTAX_MARKER_FLOAT if !segments.is_empty() => s.into(), - // Standard or reserved keyword/symbol not in first position + + // Keyword/symbol not in first position _ if !segments.is_empty() && token.is_some() => { // Make it a custom keyword/symbol if it is disabled or reserved if (self @@ -274,6 +275,7 @@ impl Engine { } s.into() } + // Standard keyword in first position but not disabled _ if segments.is_empty() && token.as_ref().map_or(false, Token::is_standard_keyword) @@ -291,8 +293,11 @@ impl Engine { ) .into_err(Position::NONE)); } - // Identifier in first position - _ if segments.is_empty() && is_valid_identifier(s) => { + + // Identifier or symbol in first position + _ if segments.is_empty() + && (is_valid_identifier(s) || is_reserved_keyword_or_symbol(s)) => + { // Make it a custom keyword/symbol if it is disabled or reserved if self .disabled_symbols @@ -310,6 +315,7 @@ impl Engine { } s.into() } + // Anything else is an error _ => { return Err(LexError::ImproperSymbol( @@ -326,23 +332,20 @@ impl Engine { segments.push(seg); } - // If the syntax has no symbols, just ignore the registration + // If the syntax has nothing, just ignore the registration if segments.is_empty() { return Ok(self); } - // The first keyword is the discriminator + // The first keyword/symbol is the discriminator let key = segments[0].clone(); self.register_custom_syntax_with_state_raw( key, // Construct the parsing function - move |stream, _, _| { - if stream.len() >= segments.len() { - Ok(None) - } else { - Ok(Some(segments[stream.len()].clone())) - } + move |stream, _, _| match stream.len() { + len if len >= segments.len() => Ok(None), + len => Ok(Some(segments[len].clone())), }, scope_may_be_changed, move |context, expressions, _| func(context, expressions), diff --git a/src/parser.rs b/src/parser.rs index f4fd7ad5..a45ce89d 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3792,7 +3792,7 @@ impl Engine { (.., pos) => { return Err(PERR::MissingToken( Token::Pipe.into(), - "to close the parameters list of anonymous function".into(), + "to close the parameters list of anonymous function or closure".into(), ) .into_err(pos)) } diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 4ec35bea..40d49057 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -2066,7 +2066,7 @@ pub const fn is_id_continue(x: char) -> bool { x.is_ascii_alphanumeric() || x == '_' } -/// Is a piece of syntax a reserved keyword or symbol? +/// Is a piece of syntax a reserved keyword or reserved symbol? #[must_use] pub fn is_reserved_keyword_or_symbol(syntax: &str) -> bool { match syntax { diff --git a/tests/custom_syntax.rs b/tests/custom_syntax.rs index 32382acd..ec71e904 100644 --- a/tests/custom_syntax.rs +++ b/tests/custom_syntax.rs @@ -251,6 +251,62 @@ fn test_custom_syntax() -> Result<(), Box> { Ok(()) } +#[test] +fn test_custom_syntax_matrix() -> Result<(), Box> { + let mut engine = Engine::new(); + + engine.disable_symbol("|"); + + engine.register_custom_syntax( + [ + "@", // + "|", "$expr$", "$expr$", "$expr$", "|", // + "|", "$expr$", "$expr$", "$expr$", "|", // + "|", "$expr$", "$expr$", "$expr$", "|", + ], + false, + |context, inputs| { + let mut values = [[0; 3]; 3]; + + for y in 0..3 { + for x in 0..3 { + let offset = y * 3 + x; + + match context.eval_expression_tree(&inputs[offset])?.as_int() { + Ok(v) => values[y][x] = v, + Err(typ) => { + return Err(Box::new(EvalAltResult::ErrorMismatchDataType( + "integer".to_string(), + typ.to_string(), + inputs[offset].position(), + ))) + } + } + } + } + + Ok(Dynamic::from(values)) + }, + )?; + + let r = engine.eval::<[[INT; 3]; 3]>( + " + let a = 42; + let b = 123; + let c = 1; + let d = 99; + + @| a b 0 | + | -b a 0 | + | 0 0 c*d | + ", + )?; + + assert_eq!(r, [[42, 123, 0], [-123, 42, 0], [0, 0, 99]]); + + Ok(()) +} + #[test] fn test_custom_syntax_raw() -> Result<(), Box> { let mut engine = Engine::new();