Skip to content

Commit

Permalink
Merge pull request #871 from schungx/master
Browse files Browse the repository at this point in the history
Add $func$ to custom syntax.
  • Loading branch information
schungx committed May 10, 2024
2 parents 390d55a + e6caf3a commit 1d5f54c
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 58 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
Rhai Release Notes
==================

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

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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ members = [".", "codegen"]

[package]
name = "rhai"
version = "1.18.0"
version = "1.19.0"
rust-version = "1.66.0"
edition = "2018"
resolver = "2"
Expand Down
9 changes: 8 additions & 1 deletion codegen/src/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,8 +287,12 @@ impl Parse for ExportedFn {
let str_type_path = syn::parse2::<syn::Path>(quote! { str }).unwrap();

let context_type_path1 = syn::parse2::<syn::Path>(quote! { NativeCallContext }).unwrap();
let context_type_path1x =
syn::parse2::<syn::Path>(quote! { NativeCallContext<'_> }).unwrap();
let context_type_path2 =
syn::parse2::<syn::Path>(quote! { rhai::NativeCallContext }).unwrap();
let context_type_path2x =
syn::parse2::<syn::Path>(quote! { rhai::NativeCallContext<'_> }).unwrap();
let mut pass_context = false;

let cfg_attrs = crate::attrs::collect_cfg_attr(&fn_all.attrs);
Expand All @@ -299,7 +303,10 @@ impl Parse for ExportedFn {
if let Some(syn::FnArg::Typed(syn::PatType { ref ty, .. })) = fn_all.sig.inputs.first() {
match flatten_type_groups(ty.as_ref()) {
syn::Type::Path(p)
if p.path == context_type_path1 || p.path == context_type_path2 =>
if p.path == context_type_path1
|| p.path == context_type_path1x
|| p.path == context_type_path2
|| p.path == context_type_path2x =>
{
pass_context = true;
}
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
4 changes: 2 additions & 2 deletions src/eval/chaining.rs
Original file line number Diff line number Diff line change
Expand Up @@ -389,8 +389,8 @@ impl Engine {
Ok(Target::StringSlice {
source: target,
value: value.into(),
start: start,
end: end,
start,
end,
exclusive: true,
})
}
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
138 changes: 88 additions & 50 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,
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,
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,13 +3710,56 @@ 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(
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
&& new_state.input.next().unwrap().0 != Token::Or
&& !match_token(new_state.input, &Token::Pipe).0
{
loop {
match state.input.next().unwrap() {
match new_state.input.next().unwrap() {
(Token::Pipe, ..) => break,
(Token::Identifier(s), pos) => {
if params_list.iter().any(|p| p == &*s) {
Expand All @@ -3733,7 +3769,7 @@ impl Engine {
}

let s = self.get_interned_string(*s);
state.stack.push(s.clone(), ());
new_state.stack.push(s.clone(), ());
params_list.push(s);
}
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
Expand All @@ -3746,7 +3782,7 @@ impl Engine {
}
}

match state.input.next().unwrap() {
match new_state.input.next().unwrap() {
(Token::Pipe, ..) => break,
(Token::Comma, ..) => (),
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
Expand All @@ -3762,18 +3798,20 @@ impl Engine {
}

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

// 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 externals = std::mem::take(&mut state.external_vars);
let (mut params, externals) = if allow_capture {
let externals = std::mem::take(&mut new_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>("call(hello |x| { x + 1 }, 41)").unwrap(), 42);
assert_eq!(engine.eval::<INT>("call(hello { 42 })").unwrap(), 42);
}

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

0 comments on commit 1d5f54c

Please sign in to comment.