Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add $func$ to custom syntax. #871

Merged
merged 6 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -5,9 +5,9 @@
spanned::Spanned,
};

#[cfg(no_std)]

Check warning on line 8 in codegen/src/function.rs

View workflow job for this annotation

GitHub Actions / Build (nightly, ubuntu-latest, true, --features unstable)

unexpected `cfg` condition name: `no_std`

Check warning on line 8 in codegen/src/function.rs

View workflow job for this annotation

GitHub Actions / NoStdBuild (ubuntu-latest, --profile unix, false)

unexpected `cfg` condition name: `no_std`

Check warning on line 8 in codegen/src/function.rs

View workflow job for this annotation

GitHub Actions / Check Formatting

unexpected `cfg` condition name: `no_std`

Check warning on line 8 in codegen/src/function.rs

View workflow job for this annotation

GitHub Actions / NoStdBuild (windows-latest, --profile windows, true)

unexpected `cfg` condition name: `no_std`
use alloc::format;
#[cfg(not(no_std))]

Check warning on line 10 in codegen/src/function.rs

View workflow job for this annotation

GitHub Actions / Build (nightly, ubuntu-latest, true, --features unstable)

unexpected `cfg` condition name: `no_std`

Check warning on line 10 in codegen/src/function.rs

View workflow job for this annotation

GitHub Actions / NoStdBuild (ubuntu-latest, --profile unix, false)

unexpected `cfg` condition name: `no_std`

Check warning on line 10 in codegen/src/function.rs

View workflow job for this annotation

GitHub Actions / Check Formatting

unexpected `cfg` condition name: `no_std`

Check warning on line 10 in codegen/src/function.rs

View workflow job for this annotation

GitHub Actions / NoStdBuild (windows-latest, --profile windows, true)

unexpected `cfg` condition name: `no_std`
use std::format;

use std::borrow::Cow;
Expand Down Expand Up @@ -287,8 +287,12 @@
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 @@
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 @@ -6,7 +6,7 @@
use crate::engine::{FN_IDX_GET, FN_IDX_SET};
use crate::types::dynamic::Union;
use crate::{
calc_fn_hash, Dynamic, Engine, ExclusiveRange, FnArgsVec, InclusiveRange, OnceCell, Position,

Check warning on line 9 in src/eval/chaining.rs

View workflow job for this annotation

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

unused imports: `ExclusiveRange`, `InclusiveRange`
RhaiResult, RhaiResultOf, Scope, ERR,
};
use std::hash::Hash;
Expand Down Expand Up @@ -389,8 +389,8 @@
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
Loading