Add no_custom_syntax.

This commit is contained in:
Stephen Chung 2022-07-05 22:59:03 +08:00
parent b6528bd51d
commit b4dbc7619a
18 changed files with 121 additions and 43 deletions

View File

@ -27,6 +27,7 @@ jobs:
- "--features no_float,serde,metadata,internals,debugging"
- "--features f32_float,serde,metadata,internals,debugging"
- "--features decimal,serde,metadata,internals,debugging"
- "--features no_custom_syntax,serde,metadata,internals,debugging"
- "--features no_float,decimal"
- "--tests --features only_i32,serde,metadata,internals,debugging"
- "--features only_i64,serde,metadata,internals,debugging"
@ -36,8 +37,8 @@ jobs:
- "--features no_module,serde,metadata,internals,debugging"
- "--features no_closure,serde,metadata,internals,debugging"
- "--features unicode-xid-ident,serde,metadata,internals,debugging"
- "--features sync,no_function,no_float,no_position,no_optimize,no_module,no_closure,metadata,serde,unchecked,debugging"
- "--features no_function,no_float,no_position,no_index,no_object,no_optimize,no_module,no_closure,unchecked"
- "--features sync,no_function,no_float,no_position,no_optimize,no_module,no_closure,no_custom_syntax,metadata,serde,unchecked,debugging"
- "--features no_function,no_float,no_position,no_index,no_object,no_optimize,no_module,no_closure,no_custom_syntax,unchecked"
toolchain: [stable]
experimental: [false]
include:

View File

@ -4,6 +4,11 @@ Rhai Release Notes
Version 1.9.0
=============
New features
------------
* A new feature, `no_custom_syntax`, is added to remove custom syntax support from Rhai for applications that do not require it (which should be most).
Enhancements
------------

View File

@ -53,6 +53,7 @@ no_object = [] # no custom objects
no_function = ["no_closure"] # no script-defined functions (meaning no closures)
no_closure = [] # no automatic sharing and capture of anonymous functions to external variables
no_module = [] # no modules
no_custom_syntax = [] # no custom syntax or custom operators
unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers.
metadata = ["serde", "serde_json", "rhai_codegen/metadata", "smartstring/serde"] # enable exporting functions metadata
internals = [] # expose internal data structures

View File

@ -1,4 +1,5 @@
//! Module implementing custom syntax for [`Engine`].
#![cfg(not(feature = "no_custom_syntax"))]
use crate::ast::Expr;
use crate::func::SendSync;
@ -153,6 +154,8 @@ pub struct CustomSyntax {
impl Engine {
/// Register a custom syntax with the [`Engine`].
///
/// Not available under `no_custom_syntax`.
///
/// * `symbols` holds a slice of strings that define the custom syntax.
/// * `scope_may_be_changed` specifies variables _may_ be added/removed by this custom syntax.
/// * `func` is the implementation function.
@ -296,6 +299,8 @@ impl Engine {
}
/// Register a custom syntax with the [`Engine`].
///
/// Not available under `no_custom_syntax`.
///
/// # WARNING - Low Level API
///
/// This function is very low level.

View File

@ -1,8 +1,8 @@
//! Module containing all deprecated API that will be removed in the next major version.
use crate::{
Dynamic, Engine, EvalAltResult, Expression, FnPtr, ImmutableString, NativeCallContext,
Position, RhaiResult, RhaiResultOf, Scope, AST,
Dynamic, Engine, EvalAltResult, FnPtr, ImmutableString, NativeCallContext, Position,
RhaiResult, RhaiResultOf, Scope, AST,
};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -312,7 +312,8 @@ impl FnPtr {
}
}
impl Expression<'_> {
#[cfg(not(feature = "no_custom_syntax"))]
impl crate::Expression<'_> {
/// If this expression is a variable name, return it. Otherwise [`None`].
///
/// # Deprecated

View File

@ -28,10 +28,11 @@ pub mod custom_syntax;
pub mod deprecated;
use crate::engine::Precedence;
use crate::tokenizer::Token;
use crate::{Dynamic, Engine, Identifier};
#[cfg(not(feature = "no_custom_syntax"))]
use crate::{engine::Precedence, tokenizer::Token};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -121,6 +122,8 @@ impl Engine {
/// Register a custom operator with a precedence into the language.
///
/// Not available under `no_custom_syntax`.
///
/// The operator can be a valid identifier, a reserved symbol, a disabled operator or a disabled keyword.
///
/// The precedence cannot be zero.
@ -147,6 +150,7 @@ impl Engine {
/// # Ok(())
/// # }
/// ```
#[cfg(not(feature = "no_custom_syntax"))]
pub fn register_custom_operator(
&mut self,
keyword: impl AsRef<str>,
@ -161,8 +165,11 @@ impl Engine {
let keyword = keyword.as_ref();
match Token::lookup_from_syntax(keyword) {
// Standard identifiers, reserved keywords and custom keywords are OK
None | Some(Token::Reserved(..)) | Some(Token::Custom(..)) => (),
// Standard identifiers and reserved keywords are OK
None | Some(Token::Reserved(..)) => (),
// custom keywords are OK
#[cfg(not(feature = "no_custom_syntax"))]
Some(Token::Custom(..)) => (),
// Active standard keywords cannot be made custom
// Disabled keywords are OK
Some(token) if token.is_standard_keyword() => {

View File

@ -48,6 +48,9 @@ impl From<(Expr, Expr)> for BinaryExpr {
/// _(internals)_ A custom syntax expression.
/// Exported under the `internals` feature only.
///
/// Not available under `no_custom_syntax`.
#[cfg(not(feature = "no_custom_syntax"))]
#[derive(Debug, Clone, Hash)]
pub struct CustomExpr {
/// List of keywords.
@ -61,6 +64,7 @@ pub struct CustomExpr {
pub self_terminated: bool,
}
#[cfg(not(feature = "no_custom_syntax"))]
impl CustomExpr {
/// Is this custom syntax self-terminated (i.e. no need for a semicolon terminator)?
///
@ -421,6 +425,7 @@ pub enum Expr {
/// lhs `??` rhs
Coalesce(Box<BinaryExpr>, Position),
/// Custom syntax
#[cfg(not(feature = "no_custom_syntax"))]
Custom(Box<CustomExpr>, Position),
}
@ -530,6 +535,7 @@ impl fmt::Debug for Expr {
.field("rhs", &x.rhs)
.finish()
}
#[cfg(not(feature = "no_custom_syntax"))]
Self::Custom(x, ..) => f.debug_tuple("Custom").field(x).finish(),
}?;
@ -703,10 +709,12 @@ impl Expr {
| Self::Coalesce(.., pos)
| Self::Index(.., pos)
| Self::Dot(.., pos)
| Self::Custom(.., pos)
| Self::InterpolatedString(.., pos)
| Self::Property(.., pos) => *pos,
#[cfg(not(feature = "no_custom_syntax"))]
Self::Custom(.., pos) => *pos,
Self::FnCall(x, ..) | Self::MethodCall(x, ..) => x.pos,
Self::Stmt(x) => x.position(),
@ -761,10 +769,12 @@ impl Expr {
| Self::Variable(.., pos)
| Self::FnCall(.., pos)
| Self::MethodCall(.., pos)
| Self::Custom(.., pos)
| Self::InterpolatedString(.., pos)
| Self::Property(.., pos) => *pos = new_pos,
#[cfg(not(feature = "no_custom_syntax"))]
Self::Custom(.., pos) => *pos = new_pos,
Self::Stmt(x) => x.set_position(new_pos, Position::NONE),
}
@ -853,8 +863,10 @@ impl Expr {
| Self::Dot(..)
| Self::Index(..)
| Self::Array(..)
| Self::Map(..)
| Self::Custom(..) => false,
| Self::Map(..) => false,
#[cfg(not(feature = "no_custom_syntax"))]
Self::Custom(..) => false,
Self::Variable(..) => match token {
Token::LeftParen => true,
@ -925,6 +937,7 @@ impl Expr {
}
}
}
#[cfg(not(feature = "no_custom_syntax"))]
Self::Custom(x, ..) => {
for e in &x.inputs {
if !e.walk(path, on_node) {

View File

@ -9,7 +9,9 @@ pub mod script_fn;
pub mod stmt;
pub use ast::{ASTNode, AST};
pub use expr::{BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes};
#[cfg(not(feature = "no_custom_syntax"))]
pub use expr::CustomExpr;
pub use expr::{BinaryExpr, Expr, FnCallExpr, FnCallHashes};
pub use flags::{ASTFlags, FnAccess};
pub use ident::Ident;
#[cfg(not(feature = "no_module"))]

View File

@ -713,6 +713,7 @@ impl Stmt {
Self::Noop(..) => false,
Self::Expr(e) => match &**e {
#[cfg(not(feature = "no_custom_syntax"))]
Expr::Custom(x, ..) if x.is_self_terminated() => true,
_ => false,
},

View File

@ -1,6 +1,5 @@
//! Main module defining the script evaluation [`Engine`].
use crate::api::custom_syntax::CustomSyntax;
use crate::api::options::LangOptions;
use crate::func::native::{
OnDebugCallback, OnDefVarCallback, OnParseTokenCallback, OnPrintCallback, OnVarCallback,
@ -111,9 +110,11 @@ pub struct Engine {
/// A set of symbols to disable.
pub(crate) disabled_symbols: BTreeSet<Identifier>,
/// A map containing custom keywords and precedence to recognize.
#[cfg(not(feature = "no_custom_syntax"))]
pub(crate) custom_keywords: BTreeMap<Identifier, Option<Precedence>>,
/// Custom syntax.
pub(crate) custom_syntax: BTreeMap<Identifier, CustomSyntax>,
#[cfg(not(feature = "no_custom_syntax"))]
pub(crate) custom_syntax: BTreeMap<Identifier, crate::api::custom_syntax::CustomSyntax>,
/// Callback closure for filtering variable definition.
pub(crate) def_var_filter: Option<Box<OnDefVarCallback>>,
/// Callback closure for resolving variable access.
@ -160,17 +161,19 @@ impl fmt::Debug for Engine {
#[cfg(not(feature = "no_module"))]
f.field("global_sub_modules", &self.global_sub_modules);
f.field("disabled_symbols", &self.disabled_symbols)
.field("custom_keywords", &self.custom_keywords)
.field(
f.field("disabled_symbols", &self.disabled_symbols);
#[cfg(not(feature = "no_custom_syntax"))]
f.field("custom_keywords", &self.custom_keywords).field(
"custom_syntax",
&self
.custom_syntax
.keys()
.map(|s| s.as_str())
.collect::<String>(),
)
.field("def_var_filter", &self.def_var_filter.is_some())
);
f.field("def_var_filter", &self.def_var_filter.is_some())
.field("resolve_var", &self.resolve_var.is_some())
.field("token_mapper", &self.token_mapper.is_some());
@ -268,7 +271,9 @@ impl Engine {
empty_string: ImmutableString::new(),
disabled_symbols: BTreeSet::new(),
#[cfg(not(feature = "no_custom_syntax"))]
custom_keywords: BTreeMap::new(),
#[cfg(not(feature = "no_custom_syntax"))]
custom_syntax: BTreeMap::new(),
def_var_filter: None,

View File

@ -1,12 +1,12 @@
//! Evaluation context.
use super::{Caches, GlobalRuntimeState};
use crate::{Dynamic, Engine, Expression, Module, RhaiResult, Scope};
use crate::{Dynamic, Engine, Module, Scope};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
/// Context of a script evaluation process.
#[derive(Debug)]
#[allow(dead_code)]
pub struct EvalContext<'a, 's, 'ps, 'g, 'pg, 'c, 'pc, 't, 'pt> {
/// The current [`Engine`].
engine: &'a Engine,
@ -142,13 +142,14 @@ impl<'a, 's, 'ps, 'g, 'pg, 'c, 'pc, 't, 'pt> EvalContext<'a, 's, 'ps, 'g, 'pg, '
self.level
}
/// Evaluate an [expression tree][Expression] within this [evaluation context][`EvalContext`].
/// Evaluate an [expression tree][crate::Expression] within this [evaluation context][`EvalContext`].
///
/// # WARNING - Low Level API
///
/// This function is very low level. It evaluates an expression from an [`AST`][crate::AST].
#[cfg(not(feature = "no_custom_syntax"))]
#[inline(always)]
pub fn eval_expression_tree(&mut self, expr: &Expression) -> RhaiResult {
pub fn eval_expression_tree(&mut self, expr: &crate::Expression) -> crate::RhaiResult {
let mut new_caches = Caches::new();
let caches = match self.caches.as_mut() {

View File

@ -4,7 +4,7 @@ use super::{Caches, EvalContext, GlobalRuntimeState, Target};
use crate::ast::{Expr, FnCallExpr, OpAssignment};
use crate::engine::{KEYWORD_THIS, OP_CONCAT};
use crate::types::dynamic::AccessMode;
use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, StaticVec, ERR};
use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, ERR};
use std::num::NonZeroUsize;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -475,8 +475,10 @@ impl Engine {
}
}
#[cfg(not(feature = "no_custom_syntax"))]
Expr::Custom(custom, pos) => {
let expressions: StaticVec<_> = custom.inputs.iter().map(Into::into).collect();
let expressions: crate::StaticVec<_> =
custom.inputs.iter().map(Into::into).collect();
// The first token acts as the custom syntax's key
let key_token = custom.tokens.first().unwrap();
// The key should exist, unless the AST is compiled in a different Engine

View File

@ -165,7 +165,6 @@ type ExclusiveRange = std::ops::Range<INT>;
/// An inclusive integer range.
type InclusiveRange = std::ops::RangeInclusive<INT>;
pub use api::custom_syntax::Expression;
pub use api::events::VarDefInfo;
pub use ast::{FnAccess, AST};
pub use engine::{Engine, OP_CONTAINS, OP_EQUALS};
@ -179,6 +178,9 @@ pub use types::{
Dynamic, EvalAltResult, FnPtr, ImmutableString, LexError, ParseError, ParseErrorType, Scope,
};
#[cfg(not(feature = "no_custom_syntax"))]
pub use api::custom_syntax::Expression;
/// _(debugging)_ Module containing types for debugging.
/// Exported under the `debugging` feature only.
#[cfg(feature = "debugging")]
@ -282,11 +284,14 @@ pub use parser::ParseState;
#[cfg(feature = "internals")]
pub use ast::{
ASTFlags, ASTNode, BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr,
FnCallHashes, Ident, OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlock, SwitchCases,
TryCatchBlock,
ASTFlags, ASTNode, BinaryExpr, ConditionalStmtBlock, Expr, FnCallExpr, FnCallHashes, Ident,
OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlock, SwitchCases, TryCatchBlock,
};
#[cfg(feature = "internals")]
#[cfg(not(feature = "no_custom_syntax"))]
pub use ast::CustomExpr;
#[cfg(feature = "internals")]
#[cfg(not(feature = "no_module"))]
pub use ast::Namespace;

View File

@ -1231,6 +1231,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
}
// Custom syntax
#[cfg(not(feature = "no_custom_syntax"))]
Expr::Custom(x, ..) => {
if x.scope_may_be_changed {
state.propagate_constants = false;

View File

@ -1,10 +1,9 @@
//! Main module defining the lexer and parser.
use crate::api::custom_syntax::{markers::*, CustomSyntax};
use crate::api::events::VarDefInfo;
use crate::api::options::LangOptions;
use crate::ast::{
ASTFlags, BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident,
ASTFlags, BinaryExpr, ConditionalStmtBlock, Expr, FnCallExpr, FnCallHashes, Ident,
OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlockContainer, SwitchCases, TryCatchBlock,
};
use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS};
@ -428,6 +427,7 @@ fn parse_var_name(input: &mut TokenStream) -> ParseResult<(SmartString, Position
}
/// Parse a symbol.
#[cfg(not(feature = "no_custom_syntax"))]
#[inline]
fn parse_symbol(input: &mut TokenStream) -> ParseResult<(SmartString, Position)> {
match input.next().expect(NEVER_ENDS) {
@ -1449,6 +1449,7 @@ impl Engine {
Token::MapStart => self.parse_map_literal(input, state, lib, settings.level_up())?,
// Custom syntax.
#[cfg(not(feature = "no_custom_syntax"))]
Token::Custom(key) | Token::Reserved(key) | Token::Identifier(key)
if !self.custom_syntax.is_empty() && self.custom_syntax.contains_key(&**key) =>
{
@ -2184,6 +2185,7 @@ impl Engine {
}
let precedence = match current_op {
#[cfg(not(feature = "no_custom_syntax"))]
Token::Custom(c) => self
.custom_keywords
.get(c)
@ -2208,6 +2210,7 @@ impl Engine {
let (next_op, next_pos) = input.peek().expect(NEVER_ENDS);
let next_precedence = match next_op {
#[cfg(not(feature = "no_custom_syntax"))]
Token::Custom(c) => self
.custom_keywords
.get(c)
@ -2317,6 +2320,7 @@ impl Engine {
.into_fn_call_expr(pos)
}
#[cfg(not(feature = "no_custom_syntax"))]
Token::Custom(s)
if self
.custom_keywords
@ -2347,6 +2351,7 @@ impl Engine {
}
/// Parse a custom syntax.
#[cfg(not(feature = "no_custom_syntax"))]
fn parse_custom_syntax(
&self,
input: &mut TokenStream,
@ -2354,9 +2359,11 @@ impl Engine {
lib: &mut FnLib,
settings: ParseSettings,
key: impl Into<ImmutableString>,
syntax: &CustomSyntax,
syntax: &crate::api::custom_syntax::CustomSyntax,
pos: Position,
) -> ParseResult<Expr> {
use crate::api::custom_syntax::markers::*;
let mut settings = settings;
let mut inputs = StaticVec::<Expr>::new();
let mut segments = StaticVec::new_const();
@ -2520,7 +2527,7 @@ impl Engine {
};
Ok(Expr::Custom(
CustomExpr {
crate::ast::CustomExpr {
inputs,
tokens,
scope_may_be_changed: syntax.scope_may_be_changed,

View File

@ -562,6 +562,9 @@ pub enum Token {
/// A reserved symbol.
Reserved(SmartString),
/// A custom keyword.
///
/// Not available under the `no_custom_syntax` feature.
#[cfg(not(feature = "no_custom_syntax"))]
Custom(SmartString),
/// End of the input stream.
EOF,
@ -682,6 +685,7 @@ impl Token {
CharConstant(c) => c.to_string().into(),
Identifier(s) => s.to_string().into(),
Reserved(s) => s.to_string().into(),
#[cfg(not(feature = "no_custom_syntax"))]
Custom(s) => s.to_string().into(),
LexError(err) => err.to_string().into(),
Comment(s) => s.to_string().into(),
@ -1069,12 +1073,15 @@ impl Token {
#[inline]
pub(crate) fn into_function_name_for_override(self) -> Result<SmartString, Self> {
match self {
Self::Custom(s) | Self::Identifier(s) if is_valid_function_name(&s) => Ok(s),
#[cfg(not(feature = "no_custom_syntax"))]
Self::Custom(s) if is_valid_function_name(&s) => Ok(s),
Self::Identifier(s) if is_valid_function_name(&s) => Ok(s),
_ => Err(self),
}
}
/// Is this token a custom keyword?
#[cfg(not(feature = "no_custom_syntax"))]
#[inline(always)]
#[must_use]
pub const fn is_custom(&self) -> bool {
@ -2329,7 +2336,12 @@ impl<'a> Iterator for TokenIterator<'a> {
}
// Reserved keyword/symbol
Some((Token::Reserved(s), pos)) => (match
(&*s, !self.engine.custom_keywords.is_empty() && self.engine.custom_keywords.contains_key(&*s))
(&*s,
#[cfg(not(feature = "no_custom_syntax"))]
(!self.engine.custom_keywords.is_empty() && self.engine.custom_keywords.contains_key(&*s)),
#[cfg(feature = "no_custom_syntax")]
false
)
{
("===", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
"'===' is not a valid operator. This is not JavaScript! Should it be '=='?".to_string(),
@ -2358,7 +2370,10 @@ impl<'a> Iterator for TokenIterator<'a> {
"'#' is not a valid symbol. Should it be '#{'?".to_string(),
).into()),
// Reserved keyword/operator that is custom.
#[cfg(not(feature = "no_custom_syntax"))]
(.., true) => Token::Custom(s),
#[cfg(feature = "no_custom_syntax")]
(.., true) => unreachable!("no custom operators"),
// Reserved keyword that is not custom and disabled.
(token, false) if !self.engine.disabled_symbols.is_empty() && self.engine.disabled_symbols.contains(token) => {
let msg = format!("reserved {} '{}' is disabled", if is_valid_identifier(token.chars()) { "keyword"} else {"symbol"}, token);
@ -2368,10 +2383,12 @@ impl<'a> Iterator for TokenIterator<'a> {
(.., false) => Token::Reserved(s),
}, pos),
// Custom keyword
#[cfg(not(feature = "no_custom_syntax"))]
Some((Token::Identifier(s), pos)) if !self.engine.custom_keywords.is_empty() && self.engine.custom_keywords.contains_key(&*s) => {
(Token::Custom(s), pos)
}
// Custom keyword/symbol - must be disabled
#[cfg(not(feature = "no_custom_syntax"))]
Some((token, pos)) if !self.engine.custom_keywords.is_empty() && self.engine.custom_keywords.contains_key(token.literal_syntax()) => {
if !self.engine.disabled_symbols.is_empty() && self.engine.disabled_symbols.contains(token.literal_syntax()) {
// Disabled standard keyword/symbol

View File

@ -1,3 +1,5 @@
#![cfg(not(feature = "no_custom_syntax"))]
use rhai::{
Dynamic, Engine, EvalAltResult, ImmutableString, LexError, ParseErrorType, Position, Scope, INT,
};

View File

@ -30,6 +30,7 @@ fn test_tokens_disabled() {
));
}
#[cfg(not(feature = "no_custom_syntax"))]
#[test]
fn test_tokens_custom_operator_identifiers() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
@ -60,6 +61,7 @@ fn test_tokens_custom_operator_identifiers() -> Result<(), Box<EvalAltResult>> {
Ok(())
}
#[cfg(not(feature = "no_custom_syntax"))]
#[test]
fn test_tokens_custom_operator_symbol() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();