Skip to content

Commit

Permalink
Add $func$ to custom syntax.
Browse files Browse the repository at this point in the history
  • Loading branch information
schungx committed May 10, 2024
1 parent ea43adf commit 071b54b
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 50 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Rhai Release Notes
==================

Version 1.18.1
Version 1.19.0
==============

Bug fixes
Expand All @@ -10,6 +10,11 @@ Bug fixes
* Variable resolver now correctly resolves variables that are captured in a closure.
* `NativeCallContext<'_>` (with a lifetime parameter) now parses correctly in the `#[export_module]` macro. This is to allow for `rust_2018_idioms` lints.

New features
------------

* A new symbol, `$func$`, is added to custom syntax to allow parsing of anonymous functions.


Version 1.18.0
==============
Expand Down
8 changes: 8 additions & 0 deletions src/api/custom_syntax.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ pub mod markers {
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 a function body.
#[cfg(not(feature = "no_function"))]
pub const CUSTOM_SYNTAX_MARKER_FUNC: &str = "$func$";
/// Special marker for matching an identifier.
pub const CUSTOM_SYNTAX_MARKER_IDENT: &str = "$ident$";
/// Special marker for matching a single symbol.
Expand Down Expand Up @@ -251,6 +254,11 @@ impl Engine {
{
s.into()
}

// Markers not in first position
#[cfg(not(feature = "no_function"))]
CUSTOM_SYNTAX_MARKER_FUNC if !segments.is_empty() => s.into(),

// Markers not in first position
#[cfg(not(feature = "no_float"))]
CUSTOM_SYNTAX_MARKER_FLOAT if !segments.is_empty() => s.into(),
Expand Down
8 changes: 5 additions & 3 deletions src/eval/stmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ use crate::func::{get_builtin_op_assignment_fn, get_hasher};
use crate::tokenizer::Token;
use crate::types::dynamic::{AccessMode, Union};
use crate::{Dynamic, Engine, RhaiResult, RhaiResultOf, Scope, VarDefInfo, ERR, INT};
use core::num::NonZeroUsize;
use std::hash::{Hash, Hasher};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
Expand Down Expand Up @@ -978,8 +977,11 @@ impl Engine {

let context =
EvalContext::new(self, global, caches, scope, this_ptr.as_deref_mut());
let resolved_var =
resolve_var(&var.name, index.map_or(0, NonZeroUsize::get), context);
let resolved_var = resolve_var(
&var.name,
index.map_or(0, core::num::NonZeroUsize::get),
context,
);

if orig_scope_len != scope.len() {
// The scope is changed, always search from now on
Expand Down
130 changes: 84 additions & 46 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1424,47 +1424,13 @@ impl Engine {
}
#[cfg(not(feature = "no_function"))]
Token::Pipe | Token::Or if settings.has_option(LangOptions::ANON_FN) => {
// Build new parse state
let new_state = &mut ParseState::new(
state.external_constants,
state.input,
state.tokenizer_control.clone(),
state.lib,
);

#[cfg(not(feature = "no_module"))]
{
// Do not allow storing an index to a globally-imported module
// just in case the function is separated from this `AST`.
//
// Keep them in `global_imports` instead so that strict variables
// mode will not complain.
new_state.global_imports.clone_from(&state.global_imports);
new_state.global_imports.extend(state.imports.clone());
}

// Brand new options
#[cfg(not(feature = "no_closure"))]
let options = self.options & !LangOptions::STRICT_VAR; // a capturing closure can access variables not defined locally, so turn off Strict Variables mode
#[cfg(feature = "no_closure")]
let options = self.options | (settings.options & LangOptions::STRICT_VAR);

// Brand new flags, turn on function scope and closure scope
let flags = ParseSettingFlags::FN_SCOPE
| ParseSettingFlags::CLOSURE_SCOPE
| (settings.flags
& (ParseSettingFlags::DISALLOW_UNQUOTED_MAP_PROPERTIES
| ParseSettingFlags::DISALLOW_STATEMENTS_IN_BLOCKS));

let new_settings = ParseSettings {
flags,
options,
..settings
};

let result = self.parse_anon_fn(new_state, new_settings.level_up()?);

let (expr, fn_def, _externals) = result?;
let (expr, fn_def, _externals) = self.parse_anon_fn(
state,
settings.level_up()?,
false,
#[cfg(not(feature = "no_closure"))]
true,
)?;

#[cfg(not(feature = "no_closure"))]
for Ident { name, pos } in &_externals {
Expand Down Expand Up @@ -2556,6 +2522,33 @@ impl Engine {
}
stmt => unreachable!("Stmt::Block expected but gets {:?}", stmt),
},
#[cfg(not(feature = "no_function"))]
CUSTOM_SYNTAX_MARKER_FUNC => {
let skip = match fwd_token {
Token::Or | Token::Pipe => false,
Token::LeftBrace => true,
_ => {
return Err(PERR::MissingSymbol("Expecting '{' or '|'".into())
.into_err(*fwd_pos))
}
};

let (expr, fn_def, _) = self.parse_anon_fn(
state,
settings.level_up()?,
skip,
#[cfg(not(feature = "no_closure"))]
false,
)?;

let hash_script = calc_fn_hash(None, &fn_def.name, fn_def.params.len());
state.lib.insert(hash_script, fn_def);

inputs.push(expr);
let keyword = self.get_interned_string(CUSTOM_SYNTAX_MARKER_FUNC);
segments.push(keyword.clone());
tokens.push(keyword);
}
CUSTOM_SYNTAX_MARKER_BOOL => match state.input.next().unwrap() {
(b @ (Token::True | Token::False), pos) => {
inputs.push(Expr::BoolConstant(b == Token::True, pos));
Expand Down Expand Up @@ -3717,11 +3710,54 @@ impl Engine {
&self,
state: &mut ParseState,
settings: ParseSettings,
skip_parameters: bool,
#[cfg(not(feature = "no_closure"))] allow_capture: bool,
) -> ParseResult<(Expr, Shared<ScriptFuncDef>, ThinVec<Ident>)> {
let settings = settings.level_up()?;
// Build new parse state
let new_state = &mut ParseState::new(

Check warning on line 3717 in src/parser.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,no_module,serde,metadata,internals,debugging, st...

unused variable: `new_state`
state.external_constants,
state.input,
state.tokenizer_control.clone(),
state.lib,
);

#[cfg(not(feature = "no_module"))]
{
// Do not allow storing an index to a globally-imported module
// just in case the function is separated from this `AST`.
//
// Keep them in `global_imports` instead so that strict variables
// mode will not complain.
new_state.global_imports.clone_from(&state.global_imports);
new_state.global_imports.extend(state.imports.clone());
}

// Brand new options
#[cfg(not(feature = "no_closure"))]
let options = self.options & !LangOptions::STRICT_VAR; // a capturing closure can access variables not defined locally, so turn off Strict Variables mode
#[cfg(feature = "no_closure")]
let options = self.options | (settings.options & LangOptions::STRICT_VAR);

// Brand new flags, turn on function scope and closure scope
let flags = ParseSettingFlags::FN_SCOPE
| ParseSettingFlags::CLOSURE_SCOPE
| (settings.flags
& (ParseSettingFlags::DISALLOW_UNQUOTED_MAP_PROPERTIES
| ParseSettingFlags::DISALLOW_STATEMENTS_IN_BLOCKS));

let new_settings = ParseSettings {
flags,
options,
..settings
};

let mut params_list = StaticVec::<ImmutableString>::new_const();

if state.input.next().unwrap().0 != Token::Or && !match_token(state.input, &Token::Pipe).0 {
// Parse parameters
if !skip_parameters
&& state.input.next().unwrap().0 != Token::Or
&& !match_token(state.input, &Token::Pipe).0
{
loop {
match state.input.next().unwrap() {
(Token::Pipe, ..) => break,
Expand Down Expand Up @@ -3762,18 +3798,20 @@ impl Engine {
}

// Parse function body
let body = self.parse_stmt(state, settings)?;
let body = self.parse_stmt(state, new_settings)?;

// External variables may need to be processed in a consistent order,
// so extract them into a list.
#[cfg(not(feature = "no_closure"))]
let (mut params, externals) = {
let (mut params, externals) = if allow_capture {
let externals = std::mem::take(&mut state.external_vars);

let mut params = FnArgsVec::with_capacity(params_list.len() + externals.len());
params.extend(externals.iter().map(|Ident { name, .. }| name.clone()));

(params, externals)
} else {
(FnArgsVec::with_capacity(params_list.len()), ThinVec::new())
};
#[cfg(feature = "no_closure")]
let (mut params, externals) = (FnArgsVec::with_capacity(params_list.len()), ThinVec::new());
Expand Down Expand Up @@ -3807,7 +3845,7 @@ impl Engine {
#[cfg(not(feature = "no_function"))]
fn_def: Some(script.clone()),
};
let expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), settings.pos);
let expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), new_settings.pos);

Ok((expr, script, externals))
}
Expand Down
13 changes: 13 additions & 0 deletions tests/custom_syntax.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,19 @@ fn test_custom_syntax_scope() {
);
}

#[cfg(not(feature = "no_function"))]
#[test]
fn test_custom_syntax_func() {
let mut engine = Engine::new();

engine
.register_custom_syntax(["hello", "$func$"], false, |context, inputs| context.eval_expression_tree(&inputs[0]))
.unwrap();

assert_eq!(engine.eval::<INT>("(hello |x| { x + 1 }).call(41)").unwrap(), 42);
assert_eq!(engine.eval::<INT>("(hello { 42 }).call()").unwrap(), 42);
}

#[test]
fn test_custom_syntax_matrix() {
let mut engine = Engine::new();
Expand Down

0 comments on commit 071b54b

Please sign in to comment.