All symbols to start a custom syntax.

This commit is contained in:
Stephen Chung 2023-02-20 13:28:17 +08:00
parent 426055d4d3
commit 00f2b07d38
5 changed files with 73 additions and 13 deletions

View File

@ -12,6 +12,7 @@ Bug fixes
* Syntax such as `foo.bar::baz` no longer panics, but returns a proper parse error. * 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. * `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`. * 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 Potentially breaking changes
---------------------------- ----------------------------

View File

@ -255,7 +255,8 @@ impl Engine {
// Markers not in first position // Markers not in first position
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
CUSTOM_SYNTAX_MARKER_FLOAT if !segments.is_empty() => s.into(), 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() => { _ if !segments.is_empty() && token.is_some() => {
// Make it a custom keyword/symbol if it is disabled or reserved // Make it a custom keyword/symbol if it is disabled or reserved
if (self if (self
@ -274,6 +275,7 @@ impl Engine {
} }
s.into() s.into()
} }
// Standard keyword in first position but not disabled // Standard keyword in first position but not disabled
_ if segments.is_empty() _ if segments.is_empty()
&& token.as_ref().map_or(false, Token::is_standard_keyword) && token.as_ref().map_or(false, Token::is_standard_keyword)
@ -291,8 +293,11 @@ impl Engine {
) )
.into_err(Position::NONE)); .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 // Make it a custom keyword/symbol if it is disabled or reserved
if self if self
.disabled_symbols .disabled_symbols
@ -310,6 +315,7 @@ impl Engine {
} }
s.into() s.into()
} }
// Anything else is an error // Anything else is an error
_ => { _ => {
return Err(LexError::ImproperSymbol( return Err(LexError::ImproperSymbol(
@ -326,23 +332,20 @@ impl Engine {
segments.push(seg); 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() { if segments.is_empty() {
return Ok(self); return Ok(self);
} }
// The first keyword is the discriminator // The first keyword/symbol is the discriminator
let key = segments[0].clone(); let key = segments[0].clone();
self.register_custom_syntax_with_state_raw( self.register_custom_syntax_with_state_raw(
key, key,
// Construct the parsing function // Construct the parsing function
move |stream, _, _| { move |stream, _, _| match stream.len() {
if stream.len() >= segments.len() { len if len >= segments.len() => Ok(None),
Ok(None) len => Ok(Some(segments[len].clone())),
} else {
Ok(Some(segments[stream.len()].clone()))
}
}, },
scope_may_be_changed, scope_may_be_changed,
move |context, expressions, _| func(context, expressions), move |context, expressions, _| func(context, expressions),

View File

@ -3792,7 +3792,7 @@ impl Engine {
(.., pos) => { (.., pos) => {
return Err(PERR::MissingToken( return Err(PERR::MissingToken(
Token::Pipe.into(), 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)) .into_err(pos))
} }

View File

@ -2066,7 +2066,7 @@ pub const fn is_id_continue(x: char) -> bool {
x.is_ascii_alphanumeric() || x == '_' 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] #[must_use]
pub fn is_reserved_keyword_or_symbol(syntax: &str) -> bool { pub fn is_reserved_keyword_or_symbol(syntax: &str) -> bool {
match syntax { match syntax {

View File

@ -251,6 +251,62 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
Ok(()) Ok(())
} }
#[test]
fn test_custom_syntax_matrix() -> Result<(), Box<EvalAltResult>> {
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] #[test]
fn test_custom_syntax_raw() -> Result<(), Box<EvalAltResult>> { fn test_custom_syntax_raw() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();