rhai/src/api/custom_syntax.rs

385 lines
15 KiB
Rust
Raw Normal View History

2020-11-20 16:52:28 +08:00
//! Module implementing custom syntax for [`Engine`].
2022-07-05 22:59:03 +08:00
#![cfg(not(feature = "no_custom_syntax"))]
2020-10-29 11:37:51 +08:00
use crate::ast::Expr;
2022-06-26 14:10:09 +08:00
use crate::func::SendSync;
2021-12-26 12:16:48 +08:00
use crate::parser::ParseResult;
2021-11-13 22:36:23 +08:00
use crate::tokenizer::{is_valid_identifier, Token};
use crate::types::dynamic::Variant;
2020-11-16 23:10:14 +08:00
use crate::{
2022-09-12 12:03:32 +08:00
reify, Dynamic, Engine, EvalContext, Identifier, ImmutableString, LexError, Position,
RhaiResult, StaticVec,
2020-11-16 23:10:14 +08:00
};
2021-04-17 15:15:54 +08:00
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
2022-10-20 15:31:57 +08:00
use std::{borrow::Borrow, ops::Deref};
2020-07-09 19:54:28 +08:00
2021-07-10 15:50:31 +08:00
/// Collection of special markers for custom syntax definition.
pub mod markers {
/// Special marker for matching an expression.
pub const CUSTOM_SYNTAX_MARKER_EXPR: &str = "$expr$";
/// Special marker for matching a statements block.
pub const CUSTOM_SYNTAX_MARKER_BLOCK: &str = "$block$";
/// Special marker for matching an identifier.
pub const CUSTOM_SYNTAX_MARKER_IDENT: &str = "$ident$";
/// Special marker for matching a single symbol.
pub const CUSTOM_SYNTAX_MARKER_SYMBOL: &str = "$symbol$";
/// Special marker for matching a string literal.
pub const CUSTOM_SYNTAX_MARKER_STRING: &str = "$string$";
/// Special marker for matching an integer number.
pub const CUSTOM_SYNTAX_MARKER_INT: &str = "$int$";
/// Special marker for matching a floating-point number.
#[cfg(not(feature = "no_float"))]
pub const CUSTOM_SYNTAX_MARKER_FLOAT: &str = "$float$";
/// Special marker for matching a boolean value.
pub const CUSTOM_SYNTAX_MARKER_BOOL: &str = "$bool$";
2021-10-25 22:41:42 +08:00
/// Special marker for identifying the custom syntax variant.
pub const CUSTOM_SYNTAX_MARKER_SYNTAX_VARIANT: &str = "$$";
2021-07-10 15:50:31 +08:00
}
2020-07-23 15:49:09 +08:00
/// A general expression evaluation trait object.
2020-07-09 19:54:28 +08:00
#[cfg(not(feature = "sync"))]
2022-09-12 12:03:32 +08:00
pub type FnCustomSyntaxEval = dyn Fn(&mut EvalContext, &[Expression], &Dynamic) -> RhaiResult;
2020-07-23 15:49:09 +08:00
/// A general expression evaluation trait object.
2020-07-09 19:54:28 +08:00
#[cfg(feature = "sync")]
2022-09-12 12:03:32 +08:00
pub type FnCustomSyntaxEval =
dyn Fn(&mut EvalContext, &[Expression], &Dynamic) -> RhaiResult + Send + Sync;
2020-07-09 19:54:28 +08:00
2020-10-25 21:57:18 +08:00
/// A general expression parsing trait object.
#[cfg(not(feature = "sync"))]
pub type FnCustomSyntaxParse =
2022-09-12 12:03:32 +08:00
dyn Fn(&[ImmutableString], &str, &mut Dynamic) -> ParseResult<Option<ImmutableString>>;
2020-10-25 21:57:18 +08:00
/// A general expression parsing trait object.
#[cfg(feature = "sync")]
2022-09-12 12:03:32 +08:00
pub type FnCustomSyntaxParse = dyn Fn(&[ImmutableString], &str, &mut Dynamic) -> ParseResult<Option<ImmutableString>>
+ Send
+ Sync;
2020-10-25 21:57:18 +08:00
2020-11-20 16:52:28 +08:00
/// An expression sub-tree in an [`AST`][crate::AST].
2020-11-13 18:32:18 +08:00
#[derive(Debug, Clone)]
pub struct Expression<'a>(&'a Expr);
impl<'a> From<&'a Expr> for Expression<'a> {
2020-10-08 22:25:50 +08:00
#[inline(always)]
fn from(expr: &'a Expr) -> Self {
Self(expr)
}
}
impl Expression<'_> {
2022-05-07 15:54:44 +08:00
/// Evaluate this [expression tree][Expression] within an [evaluation context][`EvalContext`].
///
/// # WARNING - Low Level API
///
/// This function is very low level. It evaluates an expression from an [`AST`][crate::AST].
#[inline(always)]
pub fn eval_with_context(&self, context: &mut EvalContext) -> RhaiResult {
context.eval_expression_tree(self)
}
2022-07-06 12:56:15 +08:00
/// Evaluate this [expression tree][Expression] within an [evaluation context][`EvalContext`].
///
/// The following option is available:
///
2022-07-18 23:01:03 +08:00
/// * whether to rewind the [`Scope`][crate::Scope] after evaluation if the expression is a [`StmtBlock`][crate::ast::StmtBlock]
2022-07-06 12:56:15 +08:00
///
/// # WARNING - Unstable API
///
/// This API is volatile and may change in the future.
///
/// # WARNING - Low Level API
///
/// This function is _extremely_ low level. It evaluates an expression from an [`AST`][crate::AST].
#[inline(always)]
pub fn eval_with_context_raw(
&self,
context: &mut EvalContext,
rewind_scope: bool,
) -> RhaiResult {
#[allow(deprecated)]
context.eval_expression_tree_raw(self, rewind_scope)
}
/// Get the value of this expression if it is a variable name or a string constant.
///
/// Returns [`None`] also if the constant is not of the specified type.
2020-10-08 22:25:50 +08:00
#[inline(always)]
2021-06-12 22:47:43 +08:00
#[must_use]
pub fn get_string_value(&self) -> Option<&str> {
match self.0 {
2022-01-29 11:09:43 +08:00
#[cfg(not(feature = "no_module"))]
2022-03-05 17:57:23 +08:00
Expr::Variable(x, ..) if !x.1.is_empty() => None,
Expr::Variable(x, ..) => Some(x.3.as_str()),
2022-02-08 09:02:15 +08:00
Expr::StringConstant(x, ..) => Some(x.as_str()),
_ => None,
}
}
/// Get the position of this expression.
2020-10-08 22:25:50 +08:00
#[inline(always)]
2021-06-12 22:47:43 +08:00
#[must_use]
2021-06-29 18:41:03 +08:00
pub const fn position(&self) -> Position {
self.0.position()
}
2021-06-10 10:16:39 +08:00
/// Get the value of this expression if it is a literal constant.
2022-02-24 10:36:20 +08:00
///
/// Supports [`INT`][crate::INT], [`FLOAT`][crate::FLOAT], `()`, `char`, `bool` and
/// [`ImmutableString`][crate::ImmutableString].
///
/// Returns [`None`] also if the constant is not of the specified type.
#[inline]
2021-06-12 22:47:43 +08:00
#[must_use]
pub fn get_literal_value<T: Variant>(&self) -> Option<T> {
// Coded this way in order to maximally leverage potentials for dead-code removal.
match self.0 {
2022-02-08 09:25:53 +08:00
Expr::IntegerConstant(x, ..) => reify!(*x => Option<T>),
#[cfg(not(feature = "no_float"))]
2022-02-08 09:25:53 +08:00
Expr::FloatConstant(x, ..) => reify!(*x => Option<T>),
2022-02-08 09:25:53 +08:00
Expr::CharConstant(x, ..) => reify!(*x => Option<T>),
Expr::StringConstant(x, ..) => reify!(x.clone() => Option<T>),
2022-08-27 16:26:41 +08:00
Expr::Variable(x, ..) => reify!(x.3.clone() => Option<T>),
2022-02-08 09:25:53 +08:00
Expr::BoolConstant(x, ..) => reify!(*x => Option<T>),
Expr::Unit(..) => reify!(() => Option<T>),
_ => None,
}
2021-06-10 10:16:39 +08:00
}
}
2022-10-20 15:31:57 +08:00
impl Borrow<Expr> for Expression<'_> {
#[inline(always)]
#[must_use]
fn borrow(&self) -> &Expr {
self.0
}
}
2021-12-06 18:50:37 +08:00
impl AsRef<Expr> for Expression<'_> {
#[inline(always)]
2022-09-25 12:24:03 +08:00
#[must_use]
2021-12-06 18:50:37 +08:00
fn as_ref(&self) -> &Expr {
2022-07-20 21:16:35 +09:00
self.0
2021-12-06 18:50:37 +08:00
}
}
impl Deref for Expression<'_> {
type Target = Expr;
#[inline(always)]
fn deref(&self) -> &Self::Target {
2022-07-20 21:16:35 +09:00
self.0
2021-12-06 18:50:37 +08:00
}
}
2020-11-06 16:27:40 +08:00
/// Definition of a custom syntax definition.
2020-07-09 19:54:28 +08:00
pub struct CustomSyntax {
2021-08-26 23:58:41 +08:00
/// A parsing function to return the next token in a custom syntax based on the
/// symbols parsed so far.
2020-10-25 21:57:18 +08:00
pub parse: Box<FnCustomSyntaxParse>,
2020-11-06 16:27:40 +08:00
/// Custom syntax implementation function.
2022-05-07 15:54:44 +08:00
pub func: Box<FnCustomSyntaxEval>,
/// Any variables added/removed in the scope?
pub scope_may_be_changed: bool,
2020-07-09 19:54:28 +08:00
}
impl Engine {
2020-11-20 16:52:28 +08:00
/// Register a custom syntax with the [`Engine`].
2020-10-11 21:58:11 +08:00
///
2022-07-05 22:59:03 +08:00
/// Not available under `no_custom_syntax`.
///
2022-07-20 21:16:35 +09:00
/// * `symbols` holds a slice of strings that define the custom syntax.
2021-07-04 16:40:15 +08:00
/// * `scope_may_be_changed` specifies variables _may_ be added/removed by this custom syntax.
2020-10-11 21:58:11 +08:00
/// * `func` is the implementation function.
2021-01-14 19:07:03 +08:00
///
2021-08-26 23:58:41 +08:00
/// ## Note on `symbols`
///
/// * Whitespaces around symbols are stripped.
/// * Symbols that are all-whitespace or empty are ignored.
/// * If `symbols` does not contain at least one valid token, then the custom syntax registration
/// is simply ignored.
///
2021-07-04 16:40:15 +08:00
/// ## Note on `scope_may_be_changed`
2021-01-14 19:07:03 +08:00
///
2021-07-04 16:40:15 +08:00
/// If `scope_may_be_changed` is `true`, then _size_ of the current [`Scope`][crate::Scope]
/// _may_ be modified by this custom syntax.
2021-01-14 19:07:03 +08:00
///
2021-07-04 16:40:15 +08:00
/// Adding new variables and/or removing variables count.
///
2021-07-04 16:40:15 +08:00
/// Simply modifying the values of existing variables does NOT count, as the _size_ of the
/// current [`Scope`][crate::Scope] is unchanged, so `false` should be passed.
///
2021-07-04 16:40:15 +08:00
/// Replacing one variable with another (i.e. adding a new variable and removing one variable at
/// the same time so that the total _size_ of the [`Scope`][crate::Scope] is unchanged) also
/// does NOT count, so `false` should be passed.
2022-01-04 15:22:48 +08:00
pub fn register_custom_syntax<S: AsRef<str> + Into<Identifier>>(
2020-07-09 19:54:28 +08:00
&mut self,
2022-01-04 15:22:48 +08:00
symbols: impl AsRef<[S]>,
scope_may_be_changed: bool,
2021-03-02 15:02:28 +08:00
func: impl Fn(&mut EvalContext, &[Expression]) -> RhaiResult + SendSync + 'static,
2021-12-26 12:16:48 +08:00
) -> ParseResult<&mut Self> {
2021-07-10 15:50:31 +08:00
use markers::*;
2021-09-11 19:40:40 +08:00
let mut segments = StaticVec::<ImmutableString>::new();
2020-07-09 19:54:28 +08:00
2022-01-04 15:22:48 +08:00
for s in symbols.as_ref() {
let s = s.as_ref().trim();
2021-08-26 23:58:41 +08:00
// Skip empty symbols
if s.is_empty() {
continue;
}
2022-10-30 22:16:09 +08:00
let token = Token::lookup_symbol_from_syntax(s).or_else(|| {
if Token::is_reserved_keyword(s) {
Some(Token::Reserved(Box::new(s.into())))
} else {
None
}
});
2020-12-26 23:21:09 +08:00
let seg = match s {
2020-07-09 19:54:28 +08:00
// Markers not in first position
2021-06-28 18:06:05 +08:00
CUSTOM_SYNTAX_MARKER_IDENT
2021-07-10 15:50:31 +08:00
| CUSTOM_SYNTAX_MARKER_SYMBOL
2021-06-28 18:06:05 +08:00
| CUSTOM_SYNTAX_MARKER_EXPR
| CUSTOM_SYNTAX_MARKER_BLOCK
| CUSTOM_SYNTAX_MARKER_BOOL
| CUSTOM_SYNTAX_MARKER_INT
| CUSTOM_SYNTAX_MARKER_STRING
2021-06-10 10:16:39 +08:00
if !segments.is_empty() =>
{
s.into()
}
// Markers not in first position
#[cfg(not(feature = "no_float"))]
2021-06-28 18:06:05 +08:00
CUSTOM_SYNTAX_MARKER_FLOAT if !segments.is_empty() => s.into(),
2020-10-25 21:57:18 +08:00
// Standard or reserved keyword/symbol not in first position
2021-08-26 23:58:41 +08:00
_ if !segments.is_empty() && token.is_some() => {
2020-12-26 23:21:09 +08:00
// Make it a custom keyword/symbol if it is disabled or reserved
2022-03-03 13:02:57 +08:00
if ((!self.disabled_symbols.is_empty() && self.disabled_symbols.contains(s))
2021-08-26 23:58:41 +08:00
|| token.map_or(false, |v| v.is_reserved()))
2022-03-03 13:02:57 +08:00
&& (self.custom_keywords.is_empty()
|| !self.custom_keywords.contains_key(s))
2020-12-26 23:21:09 +08:00
{
2020-10-25 21:57:18 +08:00
self.custom_keywords.insert(s.into(), None);
2020-07-10 22:01:47 +08:00
}
s.into()
}
// Standard keyword in first position but not disabled
_ if segments.is_empty()
2022-07-27 18:04:59 +08:00
&& token.as_ref().map_or(false, Token::is_standard_keyword)
2022-03-03 13:02:57 +08:00
&& (self.disabled_symbols.is_empty() || !self.disabled_symbols.contains(s)) =>
2020-10-25 21:57:18 +08:00
{
return Err(LexError::ImproperSymbol(
s.to_string(),
format!(
2022-08-11 19:01:23 +08:00
"Improper symbol for custom syntax at position #{}: '{s}'",
segments.len() + 1,
),
)
2021-07-24 14:11:16 +08:00
.into_err(Position::NONE));
2020-10-25 21:57:18 +08:00
}
// Identifier in first position
2022-10-30 15:45:25 +08:00
_ if segments.is_empty() && is_valid_identifier(s) => {
2020-12-26 23:21:09 +08:00
// Make it a custom keyword/symbol if it is disabled or reserved
2022-03-03 13:02:57 +08:00
if (!self.disabled_symbols.is_empty() && self.disabled_symbols.contains(s))
|| token.map_or(false, |v| v.is_reserved())
2022-07-27 16:04:24 +08:00
&& self.custom_keywords.is_empty()
|| !self.custom_keywords.contains_key(s)
2020-12-26 23:21:09 +08:00
{
2022-07-27 16:04:24 +08:00
self.custom_keywords.insert(s.into(), None);
2020-07-09 19:54:28 +08:00
}
s.into()
}
// Anything else is an error
_ => {
return Err(LexError::ImproperSymbol(
s.to_string(),
format!(
2022-08-11 19:01:23 +08:00
"Improper symbol for custom syntax at position #{}: '{s}'",
segments.len() + 1,
),
)
2021-07-24 14:11:16 +08:00
.into_err(Position::NONE));
}
2020-07-09 19:54:28 +08:00
};
2020-10-27 09:56:37 +08:00
segments.push(seg);
2020-07-09 19:54:28 +08:00
}
2021-08-26 23:58:41 +08:00
// If the syntax has no symbols, just ignore the registration
if segments.is_empty() {
return Ok(self);
}
2020-10-25 21:57:18 +08:00
// The first keyword is the discriminator
let key = segments[0].clone();
2022-09-12 12:03:32 +08:00
self.register_custom_syntax_with_state_raw(
2020-10-25 21:57:18 +08:00
key,
// Construct the parsing function
2022-09-12 12:03:32 +08:00
move |stream, _, _| {
2020-10-25 21:57:18 +08:00
if stream.len() >= segments.len() {
Ok(None)
} else {
Ok(Some(segments[stream.len()].clone()))
}
},
scope_may_be_changed,
2022-09-12 12:03:32 +08:00
move |context, expressions, _| func(context, expressions),
2020-10-25 21:57:18 +08:00
);
Ok(self)
}
2022-09-12 12:03:32 +08:00
/// Register a custom syntax with the [`Engine`] with custom user-defined state.
2020-10-25 21:57:18 +08:00
///
2022-07-05 22:59:03 +08:00
/// Not available under `no_custom_syntax`.
///
2021-01-02 23:30:10 +08:00
/// # WARNING - Low Level API
2020-10-25 21:57:18 +08:00
///
/// This function is very low level.
///
/// * `scope_may_be_changed` specifies variables have been added/removed by this custom syntax.
2020-10-25 21:57:18 +08:00
/// * `parse` is the parsing function.
/// * `func` is the implementation function.
///
2021-08-26 23:58:41 +08:00
/// All custom keywords used as symbols must be manually registered via [`Engine::register_custom_operator`].
/// Otherwise, they won't be recognized.
///
2022-09-12 12:03:32 +08:00
/// # Parsing Function Signature
///
2022-09-12 12:03:32 +08:00
/// The parsing function has the following signature:
///
2022-09-12 12:03:32 +08:00
/// `Fn(symbols: &[ImmutableString], look_ahead: &str, state: &mut Dynamic) -> Result<Option<ImmutableString>, ParseError>`
///
/// where:
/// * `symbols`: a slice of symbols that have been parsed so far, possibly containing `$expr$` and/or `$block$`;
/// `$ident$` and other literal markers are replaced by the actual text
/// * `look_ahead`: a string slice containing the next symbol that is about to be read
2022-09-12 12:03:32 +08:00
/// * `state`: a [`Dynamic`] value that contains a user-defined state
///
/// ## Return value
///
/// * `Ok(None)`: parsing complete and there are no more symbols to match.
/// * `Ok(Some(symbol))`: the next symbol to match, which can also be `$expr$`, `$ident$` or `$block$`.
2021-12-31 23:01:34 +08:00
/// * `Err(ParseError)`: error that is reflected back to the [`Engine`], normally `ParseError(ParseErrorType::BadInput(LexError::ImproperSymbol(message)), Position::NONE)` to indicate a syntax error, but it can be any [`ParseError`][crate::ParseError].
2022-09-12 12:03:32 +08:00
pub fn register_custom_syntax_with_state_raw(
2020-10-25 21:57:18 +08:00
&mut self,
2021-03-29 11:36:02 +08:00
key: impl Into<Identifier>,
2022-09-12 12:03:32 +08:00
parse: impl Fn(&[ImmutableString], &str, &mut Dynamic) -> ParseResult<Option<ImmutableString>>
+ SendSync
+ 'static,
scope_may_be_changed: bool,
2022-09-12 12:03:32 +08:00
func: impl Fn(&mut EvalContext, &[Expression], &Dynamic) -> RhaiResult + SendSync + 'static,
2020-10-25 21:57:18 +08:00
) -> &mut Self {
2021-05-03 13:45:41 +08:00
self.custom_syntax.insert(
key.into(),
2021-06-29 18:25:20 +08:00
CustomSyntax {
2021-05-03 13:45:41 +08:00
parse: Box::new(parse),
2022-05-07 15:54:44 +08:00
func: Box::new(func),
scope_may_be_changed,
2022-07-20 21:28:17 +09:00
},
2021-05-03 13:45:41 +08:00
);
2020-10-25 21:57:18 +08:00
self
2020-07-09 19:54:28 +08:00
}
}