diff --git a/doc/src/engine/custom-syntax.md b/doc/src/engine/custom-syntax.md
index 84463935..0cb3bf5b 100644
--- a/doc/src/engine/custom-syntax.md
+++ b/doc/src/engine/custom-syntax.md
@@ -54,16 +54,14 @@ These symbol types can be used:
* `$ident$` - any [variable] name.
-### The First Symbol Must be a Keyword
+### The First Symbol Must be an Identifier
There is no specific limit on the combination and sequencing of each symbol type,
except the _first_ symbol which must be a custom keyword that follows the naming rules
of [variables].
-The first symbol also cannot be a reserved [keyword], unless that keyword
-has been [disabled][disable keywords and operators].
-
-In other words, any valid identifier that is not an active [keyword] will work fine.
+The first symbol also cannot be a normal or reserved [keyword].
+In other words, any valid identifier that is not a [keyword] will work fine.
### The First Symbol Must be Unique
@@ -127,6 +125,8 @@ where:
* `inputs: &[Expression]` - a list of input expression trees.
+* Return value - result of evaluating the custom syntax expression.
+
### Access Arguments
The most important argument is `inputs` where the matched identifiers (`$ident$`), expressions/statements (`$expr$`)
@@ -215,9 +215,9 @@ fn implementation_func(
Ok(().into())
}
-// Register the custom syntax (sample): do |x| -> { x += 1 } while x < 0;
+// Register the custom syntax (sample): exec |x| -> { x += 1 } while x < 0;
engine.register_custom_syntax(
- &[ "do", "|", "$ident$", "|", "->", "$block$", "while", "$expr$" ], // the custom syntax
+ &[ "exec", "|", "$ident$", "|", "->", "$block$", "while", "$expr$" ], // the custom syntax
1, // the number of new variables declared within this custom syntax
implementation_func
)?;
@@ -252,3 +252,70 @@ Make sure there are _lots_ of examples for users to follow.
Step Six - Profit!
------------------
+
+
+Really Advanced - Low Level Custom Syntax API
+--------------------------------------------
+
+Sometimes it is desirable to have multiple custom syntax starting with the
+same symbol. This is especially common for _command-style_ syntax where the
+second symbol calls a particular command:
+
+```rust
+// The following simulates a command-style syntax, all starting with 'perform'.
+perform action 42; // Perform a system action with a parameter
+perform update system; // Update the system
+perform check all; // Check all system settings
+perform cleanup; // Clean up the system
+perform add something; // Add something to the system
+perform remove something; // Delete something from the system
+```
+
+For even more flexibility, there is a _low level_ API for custom syntax that
+allows the registration of an entire mini-parser.
+
+Use `Engine::register_custom_syntax_raw` to register a custom syntax _parser_
+together with the implementation function:
+
+```rust
+// Register the custom syntax (sample): exec |x| -> { x += 1 } while x < 0;
+engine.register_custom_syntax_raw(
+ "exec",
+ |stream| match stream.len() {
+ 1 => Ok(Some("|".into())),
+ 2 => Ok(Some("$ident$".into())),
+ 3 => Ok(Some("|".into())),
+ 4 => Ok(Some("->".into())),
+ 5 => Ok(Some("$block$".into())),
+ 6 => Ok(Some("while".into())),
+ 7 => Ok(Some("$expr$".into())),
+ 8 => Ok(None)
+ _ => unreachable!(),
+ }
+ 1, // the number of new variables declared within this custom syntax
+ implementation_func // implementation function as above
+)?;
+```
+
+### Function Signature
+
+The custom syntax parser has the following signature:
+
+> `Fn(stream: &[&String]) -> Result, ParseError>`
+
+where:
+
+* `stream: &[&String]` - a slice of symbols that have been parsed so far, perhaps containing the following:
+ * `$expr$` - an expression
+ * `$block$` - a statement block
+
+### Return Value
+
+The return value is `Result , ParseError>` where:
+
+* `Ok(None)` - parsing complete and there are no more symbols to match.
+
+* `Ok(Some(symbol))` - next symbol to match.
+
+* `Err(ParseError)` - error is reflected back to the [`Engine`].
+ Normally this is `ParseErrorType::ImproperSymbol` to indicate that there is a syntax error, but it can be any error.
diff --git a/doc/src/engine/var.md b/doc/src/engine/var.md
index 270f122e..85267783 100644
--- a/doc/src/engine/var.md
+++ b/doc/src/engine/var.md
@@ -16,7 +16,7 @@ To do so, provide a closure to the [`Engine`] via the `Engine::on_var` method:
let mut engine = Engine::new();
// Register a variable resolver.
-engine.on_var(|name, index, scope, context| {
+engine.on_var(|name, index, context| {
match name {
"MYSTIC_NUMBER" => Ok(Some((42 as INT).into())),
// Override a variable - make it not found even if it exists!
@@ -24,7 +24,7 @@ engine.on_var(|name, index, scope, context| {
EvalAltResult::ErrorVariableNotFound(name.to_string(), Position::none())
)),
// Silently maps 'chameleon' into 'innocent'.
- "chameleon" => scope.get_value("innocent").map(Some).ok_or_else(|| Box::new(
+ "chameleon" => context.scope.get_value("innocent").map(Some).ok_or_else(|| Box::new(
EvalAltResult::ErrorVariableNotFound(name.to_string(), Position::none())
)),
// Return Ok(None) to continue with the normal variable resolution process.
diff --git a/doc/src/rust/register-raw.md b/doc/src/rust/register-raw.md
index 8e848734..694e6612 100644
--- a/doc/src/rust/register-raw.md
+++ b/doc/src/rust/register-raw.md
@@ -76,6 +76,8 @@ where:
* `args: &mut [&mut Dynamic]` - a slice containing `&mut` references to [`Dynamic`] values.
The slice is guaranteed to contain enough arguments _of the correct types_.
+* Return value - result of the function call.
+
Remember, in Rhai, all arguments _except_ the _first_ one are always passed by _value_ (i.e. cloned).
Therefore, it is unnecessary to ever mutate any argument except the first one, as all mutations
will be on the cloned copy.
diff --git a/src/api.rs b/src/api.rs
index cfb1199a..bfa0abfa 100644
--- a/src/api.rs
+++ b/src/api.rs
@@ -164,14 +164,8 @@ impl Engine {
#[cfg(not(feature = "no_object"))]
#[inline(always)]
pub fn register_type_with_name(&mut self, name: &str) -> &mut Self {
- if self.type_names.is_none() {
- self.type_names = Some(Default::default());
- }
// Add the pretty-print type name into the map
- self.type_names
- .as_mut()
- .unwrap()
- .insert(type_name::().into(), name.into());
+ self.type_names.insert(type_name::().into(), name.into());
self
}
diff --git a/src/engine.rs b/src/engine.rs
index e7de12bd..d1107fa3 100644
--- a/src/engine.rs
+++ b/src/engine.rs
@@ -512,14 +512,14 @@ pub struct Engine {
pub(crate) module_resolver: Option>,
/// A hashmap mapping type names to pretty-print names.
- pub(crate) type_names: Option>,
+ pub(crate) type_names: HashMap,
/// A hashset containing symbols to disable.
- pub(crate) disabled_symbols: Option>,
+ pub(crate) disabled_symbols: HashSet,
/// A hashset containing custom keywords and precedence to recognize.
- pub(crate) custom_keywords: Option>,
+ pub(crate) custom_keywords: HashMap>,
/// Custom syntax.
- pub(crate) custom_syntax: Option>,
+ pub(crate) custom_syntax: HashMap,
/// Callback closure for resolving variable access.
pub(crate) resolve_var: Option,
@@ -658,10 +658,10 @@ impl Engine {
#[cfg(any(feature = "no_std", target_arch = "wasm32",))]
module_resolver: None,
- type_names: None,
- disabled_symbols: None,
- custom_keywords: None,
- custom_syntax: None,
+ type_names: Default::default(),
+ disabled_symbols: Default::default(),
+ custom_keywords: Default::default(),
+ custom_syntax: Default::default(),
// variable resolver
resolve_var: None,
@@ -715,10 +715,10 @@ impl Engine {
#[cfg(not(feature = "no_module"))]
module_resolver: None,
- type_names: None,
- disabled_symbols: None,
- custom_keywords: None,
- custom_syntax: None,
+ type_names: Default::default(),
+ disabled_symbols: Default::default(),
+ custom_keywords: Default::default(),
+ custom_syntax: Default::default(),
resolve_var: None,
@@ -2274,8 +2274,8 @@ impl Engine {
#[inline(always)]
pub(crate) fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str {
self.type_names
- .as_ref()
- .and_then(|t| t.get(name).map(String::as_str))
+ .get(name)
+ .map(String::as_str)
.unwrap_or_else(|| map_std_type_name(name))
}
diff --git a/src/fn_call.rs b/src/fn_call.rs
index fda09ff2..246f3060 100644
--- a/src/fn_call.rs
+++ b/src/fn_call.rs
@@ -1055,7 +1055,7 @@ impl Engine {
} else {
// If the first argument is a variable, and there is no curried arguments, convert to method-call style
// in order to leverage potential &mut first argument and avoid cloning the value
- if args_expr[0].get_variable_access(false).is_some() && curry.is_empty() {
+ if curry.is_empty() && args_expr[0].get_variable_access(false).is_some() {
// func(x, ...) -> x.func(...)
arg_values = args_expr
.iter()
diff --git a/src/parser.rs b/src/parser.rs
index 50eb0454..c13a1390 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -2549,9 +2549,17 @@ fn parse_binary_op(
let mut root = lhs;
loop {
- let (current_op, _) = input.peek().unwrap();
- let custom = state.engine.custom_keywords.as_ref();
- let precedence = current_op.precedence(custom);
+ let (current_op, current_pos) = input.peek().unwrap();
+ let precedence = if let Token::Custom(c) = current_op {
+ // Custom operators
+ if let Some(Some(p)) = state.engine.custom_keywords.get(c) {
+ *p
+ } else {
+ return Err(PERR::Reserved(c.clone()).into_err(*current_pos));
+ }
+ } else {
+ current_op.precedence()
+ };
let bind_right = current_op.is_bind_right();
// Bind left to the parent lhs expression if precedence is higher
@@ -2574,7 +2582,17 @@ fn parse_binary_op(
let rhs = parse_unary(input, state, lib, settings)?;
- let next_precedence = input.peek().unwrap().0.precedence(custom);
+ let (next_op, next_pos) = input.peek().unwrap();
+ let next_precedence = if let Token::Custom(c) = next_op {
+ // Custom operators
+ if let Some(Some(p)) = state.engine.custom_keywords.get(c) {
+ *p
+ } else {
+ return Err(PERR::Reserved(c.clone()).into_err(*next_pos));
+ }
+ } else {
+ next_op.precedence()
+ };
// Bind to right if the next operator has higher precedence
// If same precedence, then check if the operator binds right
@@ -2646,14 +2664,7 @@ fn parse_binary_op(
make_dot_expr(current_lhs, rhs, pos)?
}
- Token::Custom(s)
- if state
- .engine
- .custom_keywords
- .as_ref()
- .map(|c| c.contains_key(&s))
- .unwrap_or(false) =>
- {
+ Token::Custom(s) if state.engine.custom_keywords.contains_key(&s) => {
// Accept non-native functions for custom operators
let op = (op.0, false, op.2, op.3);
Expr::FnCall(Box::new((op, None, hash, args, None)))
@@ -2665,12 +2676,12 @@ fn parse_binary_op(
}
/// Parse a custom syntax.
-fn parse_custom(
+fn parse_custom_syntax(
input: &mut TokenStream,
state: &mut ParseState,
lib: &mut FunctionsLib,
mut settings: ParseSettings,
- key: &str,
+ key: String,
syntax: &CustomSyntax,
pos: Position,
) -> Result {
@@ -2691,13 +2702,26 @@ fn parse_custom(
_ => (),
}
- for segment in syntax.segments.iter() {
+ let mut segments: StaticVec<_> = Default::default();
+ segments.push(key);
+
+ loop {
settings.pos = input.peek().unwrap().1;
+
+ let token = if let Some(seg) = (syntax.parse)(&segments.iter().collect::>())
+ .map_err(|err| err.0.into_err(settings.pos))?
+ {
+ seg
+ } else {
+ break;
+ };
+
let settings = settings.level_up();
- match segment.as_str() {
+ match token.as_str() {
MARKER_IDENT => match input.next().unwrap() {
(Token::Identifier(s), pos) => {
+ segments.push(s.to_string());
exprs.push(Expr::Variable(Box::new(((s, pos), None, 0, None))));
}
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
@@ -2705,20 +2729,25 @@ fn parse_custom(
}
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
},
- MARKER_EXPR => exprs.push(parse_expr(input, state, lib, settings)?),
+ MARKER_EXPR => {
+ exprs.push(parse_expr(input, state, lib, settings)?);
+ segments.push(MARKER_EXPR.to_string());
+ }
MARKER_BLOCK => {
let stmt = parse_block(input, state, lib, settings)?;
let pos = stmt.position();
- exprs.push(Expr::Stmt(Box::new((stmt, pos))))
+ exprs.push(Expr::Stmt(Box::new((stmt, pos))));
+ segments.push(MARKER_BLOCK.to_string());
}
s => match input.peek().unwrap() {
+ (Token::LexError(err), pos) => return Err(err.into_err(*pos)),
(t, _) if t.syntax().as_ref() == s => {
- input.next().unwrap();
+ segments.push(input.next().unwrap().0.syntax().into_owned());
}
(_, pos) => {
return Err(PERR::MissingToken(
s.to_string(),
- format!("for '{}' expression", key),
+ format!("for '{}' expression", segments[0]),
)
.into_err(*pos))
}
@@ -2745,16 +2774,22 @@ fn parse_expr(
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
// Check if it is a custom syntax.
- if let Some(ref custom) = state.engine.custom_syntax {
+ if !state.engine.custom_syntax.is_empty() {
let (token, pos) = input.peek().unwrap();
let token_pos = *pos;
match token {
- Token::Custom(key) if custom.contains_key(key) => {
- let custom = custom.get_key_value(key).unwrap();
- let (key, syntax) = custom;
- input.next().unwrap();
- return parse_custom(input, state, lib, settings, key, syntax, token_pos);
+ Token::Custom(key) | Token::Reserved(key) | Token::Identifier(key) => {
+ match state.engine.custom_syntax.get_key_value(key) {
+ Some((key, syntax)) => {
+ let key = key.to_string();
+ input.next().unwrap();
+ return parse_custom_syntax(
+ input, state, lib, settings, key, syntax, token_pos,
+ );
+ }
+ _ => (),
+ }
}
_ => (),
}
diff --git a/src/settings.rs b/src/settings.rs
index bd23114a..ce89c3ad 100644
--- a/src/settings.rs
+++ b/src/settings.rs
@@ -240,15 +240,7 @@ impl Engine {
/// ```
#[inline(always)]
pub fn disable_symbol(&mut self, symbol: &str) -> &mut Self {
- if self.disabled_symbols.is_none() {
- self.disabled_symbols = Some(Default::default());
- }
-
- self.disabled_symbols
- .as_mut()
- .unwrap()
- .insert(symbol.into());
-
+ self.disabled_symbols.insert(symbol.into());
self
}
@@ -291,28 +283,14 @@ impl Engine {
// Standard identifiers, reserved keywords and custom keywords are OK
None | Some(Token::Reserved(_)) | Some(Token::Custom(_)) => (),
// Disabled keywords are also OK
- Some(token)
- if !self
- .disabled_symbols
- .as_ref()
- .map(|d| d.contains(token.syntax().as_ref()))
- .unwrap_or(false) =>
- {
- ()
- }
+ Some(token) if !self.disabled_symbols.contains(token.syntax().as_ref()) => (),
// Active standard keywords cannot be made custom
Some(_) => return Err(format!("'{}' is a reserved keyword", keyword).into()),
}
// Add to custom keywords
- if self.custom_keywords.is_none() {
- self.custom_keywords = Some(Default::default());
- }
-
self.custom_keywords
- .as_mut()
- .unwrap()
- .insert(keyword.into(), precedence);
+ .insert(keyword.into(), Some(precedence));
Ok(self)
}
diff --git a/src/syntax.rs b/src/syntax.rs
index c5e9d138..5096547f 100644
--- a/src/syntax.rs
+++ b/src/syntax.rs
@@ -7,13 +7,10 @@ use crate::fn_native::{SendSync, Shared};
use crate::parser::Expr;
use crate::result::EvalAltResult;
use crate::token::{is_valid_identifier, Position, Token};
+use crate::utils::ImmutableString;
use crate::StaticVec;
-use crate::stdlib::{
- boxed::Box,
- fmt, format,
- string::{String, ToString},
-};
+use crate::stdlib::{boxed::Box, format, string::ToString};
/// A general expression evaluation trait object.
#[cfg(not(feature = "sync"))]
@@ -24,6 +21,14 @@ pub type FnCustomSyntaxEval =
pub type FnCustomSyntaxEval =
dyn Fn(&mut EvalContext, &[Expression]) -> Result> + Send + Sync;
+/// A general expression parsing trait object.
+#[cfg(not(feature = "sync"))]
+pub type FnCustomSyntaxParse = dyn Fn(&[&String]) -> Result, ParseError>;
+/// A general expression parsing trait object.
+#[cfg(feature = "sync")]
+pub type FnCustomSyntaxParse =
+ dyn Fn(&[&String]) -> Result , ParseError> + Send + Sync;
+
/// An expression sub-tree in an AST.
#[derive(Debug, Clone, Hash)]
pub struct Expression<'a>(&'a Expr);
@@ -76,20 +81,12 @@ impl EvalContext<'_, '_, '_, '_, '_, '_, '_, '_, '_> {
}
}
-#[derive(Clone)]
pub struct CustomSyntax {
- pub segments: StaticVec,
+ pub parse: Box,
pub func: Shared,
pub scope_delta: isize,
}
-impl fmt::Debug for CustomSyntax {
- #[inline(always)]
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- fmt::Debug::fmt(&self.segments, f)
- }
-}
-
impl Engine {
/// Register a custom syntax with the `Engine`.
///
@@ -106,7 +103,7 @@ impl Engine {
) -> Result<&mut Self, ParseError> {
let keywords = keywords.as_ref();
- let mut segments: StaticVec<_> = Default::default();
+ let mut segments: StaticVec = Default::default();
for s in keywords {
let s = s.as_ref().trim();
@@ -119,47 +116,40 @@ impl Engine {
let seg = match s {
// Markers not in first position
MARKER_EXPR | MARKER_BLOCK | MARKER_IDENT if !segments.is_empty() => s.to_string(),
- // Standard symbols not in first position
+ // Standard or reserved keyword/symbol not in first position
s if !segments.is_empty() && Token::lookup_from_syntax(s).is_some() => {
- // Make it a custom keyword/operator if it is a disabled standard keyword/operator
- // or a reserved keyword/operator.
- if self
- .disabled_symbols
- .as_ref()
- .map(|d| d.contains(s))
- .unwrap_or(false)
- || Token::lookup_from_syntax(s)
- .map(|token| token.is_reserved())
- .unwrap_or(false)
- {
- // If symbol is disabled, make it a custom keyword
- if self.custom_keywords.is_none() {
- self.custom_keywords = Some(Default::default());
- }
-
- if !self.custom_keywords.as_ref().unwrap().contains_key(s) {
- self.custom_keywords.as_mut().unwrap().insert(s.into(), 0);
- }
+ // Make it a custom keyword/symbol
+ if !self.custom_keywords.contains_key(s) {
+ self.custom_keywords.insert(s.into(), None);
}
-
s.into()
}
- // Identifier
- s if is_valid_identifier(s.chars()) => {
- if self.custom_keywords.is_none() {
- self.custom_keywords = Some(Default::default());
+ // Standard keyword in first position
+ s if segments.is_empty()
+ && Token::lookup_from_syntax(s)
+ .map(|v| v.is_keyword() || v.is_reserved())
+ .unwrap_or(false) =>
+ {
+ return Err(LexError::ImproperSymbol(format!(
+ "Improper symbol for custom syntax at position #{}: '{}'",
+ segments.len() + 1,
+ s
+ ))
+ .into_err(Position::none())
+ .into());
+ }
+ // Identifier in first position
+ s if segments.is_empty() && is_valid_identifier(s.chars()) => {
+ if !self.custom_keywords.contains_key(s) {
+ self.custom_keywords.insert(s.into(), None);
}
-
- if !self.custom_keywords.as_ref().unwrap().contains_key(s) {
- self.custom_keywords.as_mut().unwrap().insert(s.into(), 0);
- }
-
s.into()
}
// Anything else is an error
_ => {
return Err(LexError::ImproperSymbol(format!(
- "Improper symbol for custom syntax: '{}'",
+ "Improper symbol for custom syntax at position #{}: '{}'",
+ segments.len() + 1,
s
))
.into_err(Position::none())
@@ -167,7 +157,7 @@ impl Engine {
}
};
- segments.push(seg);
+ segments.push(seg.into());
}
// If the syntax has no keywords, just ignore the registration
@@ -175,24 +165,54 @@ impl Engine {
return Ok(self);
}
- // Remove the first keyword as the discriminator
- let key = segments.remove(0);
+ // The first keyword is the discriminator
+ let key = segments[0].clone();
+ self.register_custom_syntax_raw(
+ key,
+ // Construct the parsing function
+ move |stream| {
+ if stream.len() >= segments.len() {
+ Ok(None)
+ } else {
+ Ok(Some(segments[stream.len()].clone()))
+ }
+ },
+ new_vars,
+ func,
+ );
+
+ Ok(self)
+ }
+
+ /// Register a custom syntax with the `Engine`.
+ ///
+ /// ## WARNING - Low Level API
+ ///
+ /// This function is very low level.
+ ///
+ /// * `new_vars` is the number of new variables declared by this custom syntax, or the number of variables removed (if negative).
+ /// * `parse` is the parsing function.
+ /// * `func` is the implementation function.
+ ///
+ /// All custom keywords must be manually registered via `Engine::register_custom_operator`.
+ /// Otherwise, custom keywords won't be recognized.
+ pub fn register_custom_syntax_raw(
+ &mut self,
+ key: impl Into,
+ parse: impl Fn(&[&String]) -> Result, ParseError> + SendSync + 'static,
+ new_vars: isize,
+ func: impl Fn(&mut EvalContext, &[Expression]) -> Result>
+ + SendSync
+ + 'static,
+ ) -> &mut Self {
let syntax = CustomSyntax {
- segments,
+ parse: Box::new(parse),
func: (Box::new(func) as Box).into(),
scope_delta: new_vars,
};
- if self.custom_syntax.is_none() {
- self.custom_syntax = Some(Default::default());
- }
-
- self.custom_syntax
- .as_mut()
- .unwrap()
- .insert(key, syntax.into());
-
- Ok(self)
+ self.custom_syntax.insert(key.into(), syntax);
+ self
}
}
diff --git a/src/token.rs b/src/token.rs
index 3a27c4cf..82d7c611 100644
--- a/src/token.rs
+++ b/src/token.rs
@@ -18,9 +18,7 @@ use crate::parser::FLOAT;
use crate::stdlib::{
borrow::Cow,
boxed::Box,
- char,
- collections::HashMap,
- fmt, format,
+ char, fmt, format,
iter::Peekable,
str::{Chars, FromStr},
string::{String, ToString},
@@ -610,7 +608,7 @@ impl Token {
}
/// Get the precedence number of the token.
- pub fn precedence(&self, custom: Option<&HashMap>) -> u8 {
+ pub fn precedence(&self) -> u8 {
use Token::*;
match self {
@@ -639,9 +637,6 @@ impl Token {
Period => 240,
- // Custom operators
- Custom(s) => custom.map_or(0, |c| *c.get(s).unwrap()),
-
_ => 0,
}
}
@@ -692,7 +687,7 @@ impl Token {
Import | Export | As => true,
True | False | Let | Const | If | Else | While | Loop | For | In | Continue | Break
- | Return | Throw => true,
+ | Return | Throw | Try | Catch => true,
_ => false,
}
@@ -1647,16 +1642,12 @@ impl<'a> Iterator for TokenIterator<'a, '_> {
type Item = (Token, Position);
fn next(&mut self) -> Option {
- let token = match (
- get_next_token(&mut self.stream, &mut self.state, &mut self.pos),
- self.engine.disabled_symbols.as_ref(),
- self.engine.custom_keywords.as_ref(),
- ) {
+ let token = match get_next_token(&mut self.stream, &mut self.state, &mut self.pos) {
// {EOF}
- (None, _, _) => None,
+ None => None,
// Reserved keyword/symbol
- (Some((Token::Reserved(s), pos)), disabled, custom) => Some((match
- (s.as_str(), custom.map(|c| c.contains_key(&s)).unwrap_or(false))
+ Some((Token::Reserved(s), pos)) => Some((match
+ (s.as_str(), self.engine.custom_keywords.contains_key(&s))
{
("===", false) => Token::LexError(Box::new(LERR::ImproperSymbol(
"'===' is not a valid operator. This is not JavaScript! Should it be '=='?".to_string(),
@@ -1691,21 +1682,19 @@ impl<'a> Iterator for TokenIterator<'a, '_> {
format!("'{}' is a reserved symbol", token)
))),
// Reserved keyword that is not custom and disabled.
- (token, false) if disabled.map(|d| d.contains(token)).unwrap_or(false) => Token::LexError(Box::new(LERR::ImproperSymbol(
+ (token, false) if self.engine.disabled_symbols.contains(token) => Token::LexError(Box::new(LERR::ImproperSymbol(
format!("reserved symbol '{}' is disabled", token)
))),
// Reserved keyword/operator that is not custom.
(_, false) => Token::Reserved(s),
}, pos)),
// Custom keyword
- (Some((Token::Identifier(s), pos)), _, Some(custom)) if custom.contains_key(&s) => {
+ Some((Token::Identifier(s), pos)) if self.engine.custom_keywords.contains_key(&s) => {
Some((Token::Custom(s), pos))
}
// Custom standard keyword - must be disabled
- (Some((token, pos)), Some(disabled), Some(custom))
- if token.is_keyword() && custom.contains_key(token.syntax().as_ref()) =>
- {
- if disabled.contains(token.syntax().as_ref()) {
+ Some((token, pos)) if token.is_keyword() && self.engine.custom_keywords.contains_key(token.syntax().as_ref()) => {
+ if self.engine.disabled_symbols.contains(token.syntax().as_ref()) {
// Disabled standard keyword
Some((Token::Custom(token.syntax().into()), pos))
} else {
@@ -1714,21 +1703,17 @@ impl<'a> Iterator for TokenIterator<'a, '_> {
}
}
// Disabled operator
- (Some((token, pos)), Some(disabled), _)
- if token.is_operator() && disabled.contains(token.syntax().as_ref()) =>
- {
+ Some((token, pos)) if token.is_operator() && self.engine.disabled_symbols.contains(token.syntax().as_ref()) => {
Some((
Token::LexError(Box::new(LexError::UnexpectedInput(token.syntax().into()))),
pos,
))
}
// Disabled standard keyword
- (Some((token, pos)), Some(disabled), _)
- if token.is_keyword() && disabled.contains(token.syntax().as_ref()) =>
- {
+ Some((token, pos)) if token.is_keyword() && self.engine.disabled_symbols.contains(token.syntax().as_ref()) => {
Some((Token::Reserved(token.syntax().into()), pos))
}
- (r, _, _) => r,
+ r => r,
};
match token {
diff --git a/tests/syntax.rs b/tests/syntax.rs
index c0433e9e..447e7126 100644
--- a/tests/syntax.rs
+++ b/tests/syntax.rs
@@ -1,4 +1,4 @@
-use rhai::{Engine, EvalAltResult, EvalContext, Expression, ParseErrorType, INT};
+use rhai::{Engine, EvalAltResult, ParseError, ParseErrorType, Position, INT};
#[test]
fn test_custom_syntax() -> Result<(), Box> {
@@ -19,10 +19,10 @@ fn test_custom_syntax() -> Result<(), Box> {
engine.register_custom_syntax(
&[
- "do", "|", "$ident$", "|", "->", "$block$", "while", "$expr$",
+ "exec", "|", "$ident$", "|", "->", "$block$", "while", "$expr$",
],
1,
- |context: &mut EvalContext, inputs: &[Expression]| {
+ |context, inputs| {
let var_name = inputs[0].get_variable_name().unwrap().to_string();
let stmt = inputs.get(1).unwrap();
let condition = inputs.get(2).unwrap();
@@ -58,7 +58,7 @@ fn test_custom_syntax() -> Result<(), Box> {
assert_eq!(
engine.eval::(
r"
- do |x| -> { x += 1 } while x < 42;
+ exec |x| -> { x += 1 } while x < 42;
x
"
)?,
@@ -71,7 +71,48 @@ fn test_custom_syntax() -> Result<(), Box> {
.register_custom_syntax(&["!"], 0, |_, _| Ok(().into()))
.expect_err("should error")
.0,
- ParseErrorType::BadInput("Improper symbol for custom syntax: '!'".to_string())
+ ParseErrorType::BadInput(
+ "Improper symbol for custom syntax at position #1: '!'".to_string()
+ )
+ );
+
+ Ok(())
+}
+
+#[test]
+fn test_custom_syntax_raw() -> Result<(), Box> {
+ let mut engine = Engine::new();
+
+ engine.register_custom_syntax_raw(
+ "hello",
+ |stream| match stream.len() {
+ 0 => unreachable!(),
+ 1 => Ok(Some("$ident$".into())),
+ 2 => match stream[1].as_str() {
+ "world" | "kitty" => Ok(None),
+ s => Err(ParseError(
+ Box::new(ParseErrorType::BadInput(s.to_string())),
+ Position::none(),
+ )),
+ },
+ _ => unreachable!(),
+ },
+ 0,
+ |_, inputs| {
+ Ok(match inputs[0].get_variable_name().unwrap() {
+ "world" => 123 as INT,
+ "kitty" => 42 as INT,
+ _ => unreachable!(),
+ }
+ .into())
+ },
+ );
+
+ assert_eq!(engine.eval::("hello world")?, 123);
+ assert_eq!(engine.eval::("hello kitty")?, 42);
+ assert_eq!(
+ *engine.compile("hello hey").expect_err("should error").0,
+ ParseErrorType::BadInput("hey".to_string())
);
Ok(())