rhai/src/syntax.rs

231 lines
7.7 KiB
Rust
Raw Normal View History

2020-11-20 09:52:28 +01:00
//! Module implementing custom syntax for [`Engine`].
2020-10-29 04:37:51 +01:00
use crate::ast::Expr;
2020-11-16 16:10:14 +01:00
use crate::engine::{EvalContext, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT};
use crate::fn_native::SendSync;
2020-10-25 15:08:02 +01:00
use crate::stdlib::{
boxed::Box,
format,
string::{String, ToString},
};
2020-11-16 16:10:14 +01:00
use crate::token::{is_valid_identifier, Token};
use crate::{
Dynamic, Engine, EvalAltResult, ImmutableString, LexError, ParseError, Position, Shared,
2020-11-20 09:52:28 +01:00
StaticVec,
2020-11-16 16:10:14 +01:00
};
2020-07-09 13:54:28 +02:00
2020-07-23 09:49:09 +02:00
/// A general expression evaluation trait object.
2020-07-09 13:54:28 +02:00
#[cfg(not(feature = "sync"))]
2020-10-11 15:58:11 +02:00
pub type FnCustomSyntaxEval =
dyn Fn(&mut EvalContext, &[Expression]) -> Result<Dynamic, Box<EvalAltResult>>;
2020-07-23 09:49:09 +02:00
/// A general expression evaluation trait object.
2020-07-09 13:54:28 +02:00
#[cfg(feature = "sync")]
pub type FnCustomSyntaxEval =
dyn Fn(&mut EvalContext, &[Expression]) -> Result<Dynamic, Box<EvalAltResult>> + Send + Sync;
2020-07-09 13:54:28 +02:00
2020-10-25 14:57:18 +01:00
/// A general expression parsing trait object.
#[cfg(not(feature = "sync"))]
2020-10-27 02:56:37 +01:00
pub type FnCustomSyntaxParse = dyn Fn(&[String]) -> Result<Option<String>, ParseError>;
2020-10-25 14:57:18 +01:00
/// A general expression parsing trait object.
#[cfg(feature = "sync")]
pub type FnCustomSyntaxParse =
2020-10-27 02:56:37 +01:00
dyn Fn(&[String]) -> Result<Option<String>, ParseError> + Send + Sync;
2020-10-25 14:57:18 +01:00
2020-11-20 09:52:28 +01:00
/// An expression sub-tree in an [`AST`][crate::AST].
2020-11-13 11:32:18 +01:00
#[derive(Debug, Clone)]
pub struct Expression<'a>(&'a Expr);
impl<'a> From<&'a Expr> for Expression<'a> {
2020-10-08 16:25:50 +02:00
#[inline(always)]
fn from(expr: &'a Expr) -> Self {
Self(expr)
}
}
impl Expression<'_> {
2020-11-20 09:52:28 +01:00
/// If this expression is a variable name, return it. Otherwise [`None`].
2020-10-08 16:25:50 +02:00
#[inline(always)]
pub fn get_variable_name(&self) -> Option<&str> {
self.0.get_variable_access(true)
}
/// Get the expression.
2020-10-08 16:25:50 +02:00
#[inline(always)]
pub(crate) fn expr(&self) -> &Expr {
&self.0
}
/// Get the position of this expression.
2020-10-08 16:25:50 +02:00
#[inline(always)]
pub fn position(&self) -> Position {
self.0.position()
}
}
2020-10-20 04:54:32 +02:00
impl EvalContext<'_, '_, '_, '_, '_, '_, '_, '_, '_> {
2020-11-25 02:36:06 +01:00
/// Evaluate an [expression tree][Expression].
2020-10-11 15:58:11 +02:00
///
/// ## WARNING - Low Level API
///
2020-11-20 09:52:28 +01:00
/// This function is very low level. It evaluates an expression from an [`AST`][crate::AST].
2020-10-11 15:58:11 +02:00
#[inline(always)]
pub fn eval_expression_tree(
&mut self,
expr: &Expression,
) -> Result<Dynamic, Box<EvalAltResult>> {
self.engine.eval_expr(
self.scope,
2020-10-11 15:58:11 +02:00
self.mods,
self.state,
self.lib,
2020-10-11 15:58:11 +02:00
self.this_ptr,
expr.expr(),
self.level,
2020-10-11 15:58:11 +02:00
)
}
}
2020-11-06 09:27:40 +01:00
/// Definition of a custom syntax definition.
2020-07-09 13:54:28 +02:00
pub struct CustomSyntax {
2020-11-06 09:27:40 +01:00
/// A parsing function to return the next keyword in a custom syntax based on the
/// keywords parsed so far.
2020-10-25 14:57:18 +01:00
pub parse: Box<FnCustomSyntaxParse>,
2020-11-06 09:27:40 +01:00
/// Custom syntax implementation function.
2020-07-09 13:54:28 +02:00
pub func: Shared<FnCustomSyntaxEval>,
2020-11-06 09:27:40 +01:00
/// Delta number of variables in the scope.
2020-07-09 13:54:28 +02:00
pub scope_delta: isize,
}
impl Engine {
2020-11-20 09:52:28 +01:00
/// Register a custom syntax with the [`Engine`].
2020-10-11 15:58:11 +02:00
///
/// * `keywords` holds a slice of strings that define the custom syntax.
2020-11-20 09:52:28 +01:00
/// * `new_vars` is the number of new variables declared by this custom syntax, or the number of variables removed (if negative).
2020-10-11 15:58:11 +02:00
/// * `func` is the implementation function.
2020-07-10 16:01:47 +02:00
pub fn register_custom_syntax<S: AsRef<str> + ToString>(
2020-07-09 13:54:28 +02:00
&mut self,
keywords: impl AsRef<[S]>,
2020-10-11 15:58:11 +02:00
new_vars: isize,
func: impl Fn(&mut EvalContext, &[Expression]) -> Result<Dynamic, Box<EvalAltResult>>
2020-07-09 13:54:28 +02:00
+ SendSync
+ 'static,
) -> Result<&mut Self, ParseError> {
let keywords = keywords.as_ref();
2020-10-27 02:56:37 +01:00
let mut segments: StaticVec<_> = Default::default();
2020-07-09 13:54:28 +02:00
for s in keywords {
let s = s.as_ref().trim();
// skip empty keywords
if s.is_empty() {
continue;
}
let seg = match s {
2020-07-09 13:54:28 +02:00
// Markers not in first position
2020-07-10 16:01:47 +02:00
MARKER_EXPR | MARKER_BLOCK | MARKER_IDENT if !segments.is_empty() => s.to_string(),
2020-10-25 14:57:18 +01:00
// Standard or reserved keyword/symbol not in first position
2020-07-10 16:01:47 +02:00
s if !segments.is_empty() && Token::lookup_from_syntax(s).is_some() => {
2020-10-25 14:57:18 +01:00
// Make it a custom keyword/symbol
if !self.custom_keywords.contains_key(s) {
self.custom_keywords.insert(s.into(), None);
2020-07-10 16:01:47 +02:00
}
s.into()
}
2020-10-25 14:57:18 +01:00
// 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(
s.to_string(),
format!(
"Improper symbol for custom syntax at position #{}: '{}'",
segments.len() + 1,
s
),
)
2020-11-20 09:52:28 +01:00
.into_err(Position::NONE)
2020-10-25 14:57:18 +01:00
.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);
2020-07-09 13:54:28 +02:00
}
s.into()
}
// Anything else is an error
_ => {
return Err(LexError::ImproperSymbol(
s.to_string(),
format!(
"Improper symbol for custom syntax at position #{}: '{}'",
segments.len() + 1,
s
),
)
2020-11-20 09:52:28 +01:00
.into_err(Position::NONE)
.into());
}
2020-07-09 13:54:28 +02:00
};
2020-10-27 02:56:37 +01:00
segments.push(seg);
2020-07-09 13:54:28 +02:00
}
// If the syntax has no keywords, just ignore the registration
if segments.is_empty() {
return Ok(self);
}
2020-10-25 14:57:18 +01:00
// 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)
}
2020-11-20 09:52:28 +01:00
/// Register a custom syntax with the [`Engine`].
2020-10-25 14:57:18 +01:00
///
/// ## WARNING - Low Level API
///
/// This function is very low level.
///
2020-11-20 09:52:28 +01:00
/// * `new_vars` is the number of new variables declared by this custom syntax, or the number of variables removed (if negative).
2020-10-25 14:57:18 +01:00
/// * `parse` is the parsing function.
/// * `func` is the implementation function.
///
2020-11-20 09:52:28 +01:00
/// All custom keywords must be manually registered via [`register_custom_operator`][Engine::register_custom_operator].
2020-10-25 14:57:18 +01:00
/// Otherwise, custom keywords won't be recognized.
pub fn register_custom_syntax_raw(
&mut self,
key: impl Into<ImmutableString>,
2020-10-27 02:56:37 +01:00
parse: impl Fn(&[String]) -> Result<Option<String>, ParseError> + SendSync + 'static,
2020-10-25 14:57:18 +01:00
new_vars: isize,
func: impl Fn(&mut EvalContext, &[Expression]) -> Result<Dynamic, Box<EvalAltResult>>
+ SendSync
+ 'static,
) -> &mut Self {
2020-07-09 13:54:28 +02:00
let syntax = CustomSyntax {
2020-10-25 14:57:18 +01:00
parse: Box::new(parse),
func: (Box::new(func) as Box<FnCustomSyntaxEval>).into(),
2020-10-11 15:58:11 +02:00
scope_delta: new_vars,
2020-07-09 13:54:28 +02:00
};
2020-10-25 14:57:18 +01:00
self.custom_syntax.insert(key.into(), syntax);
self
2020-07-09 13:54:28 +02:00
}
}