Skip to content

Commit 81c614c

Browse files
committed
Implement syntactic function calls such as is_def_var and is_def_fn.
1 parent 8c8c0db commit 81c614c

4 files changed

Lines changed: 196 additions & 68 deletions

File tree

src/func/call.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1201,7 +1201,7 @@ impl Engine {
12011201
first_arg: Option<&Expr>,
12021202
args_expr: &[Expr],
12031203
hashes: FnCallHashes,
1204-
capture_scope: bool,
1204+
capture_parent_scope: bool,
12051205
pos: Position,
12061206
) -> RhaiResult {
12071207
let mut first_arg = first_arg;
@@ -1430,7 +1430,7 @@ impl Engine {
14301430
//
14311431
// If so, do it separately because we cannot convert the first argument (if it is a simple
14321432
// variable access) to &mut because `scope` is needed.
1433-
if capture_scope && !scope.is_empty() {
1433+
if capture_parent_scope && !scope.is_empty() {
14341434
for expr in first_arg.iter().copied().chain(args_expr.iter()) {
14351435
let (value, ..) =
14361436
self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), expr)?;

src/grain/compile/mod.rs

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1932,38 +1932,24 @@ impl Lowering {
19321932

19331933
/// Whether a call can go through generic dispatch.
19341934
///
1935-
/// Rhai resolves a handful of names syntactically in `eval_fn_call_expr`
1936-
/// before dispatch ever happens (`func/call.rs:1109-1340`), so routing
1937-
/// those through `call_fn_raw` would change what they mean. A call that
1938-
/// captures the enclosing scope is closure construction, and a qualified
1939-
/// name resolves against imported modules; neither is a plain call.
1935+
/// Rhai resolves a handful of names syntactically before dispatch ever happens,
1936+
/// so routing those through `call_fn_raw` would change what they mean.
1937+
/// A call that captures the enclosing scope is closure construction,
1938+
/// and a qualified name resolves against imported modules;
1939+
/// neither is a plain call.
19401940
fn is_lowerable_call(&self, call: &FnCallExpr) -> bool {
1941+
// These are handled by `is_syntactic_call` above, but only at the
1942+
// arities Rhai treats syntactically — at any other arity it falls
1943+
// through to ordinary dispatch, and so must catch them here.
19411944
const SYNTACTIC: &[&str] = &[
19421945
crate::engine::KEYWORD_EVAL,
1943-
crate::engine::KEYWORD_IS_DEF_VAR,
1944-
#[cfg(not(feature = "no_function"))]
1945-
crate::engine::KEYWORD_IS_DEF_FN,
1946-
];
1947-
1948-
// `is_shared` belongs here for a sharper reason than the rest: Rhai
1949-
// answers it syntactically in both call positions (`func/call.rs:1240`
1950-
// and `:929`) and registers no function for it anywhere, so a lowered
1951-
// call raises `ErrorFunctionNotFound` where the walker returns a bool.
1952-
const FN_CALL: &[&str] = &[
19531946
crate::engine::KEYWORD_FN_PTR,
19541947
crate::engine::KEYWORD_FN_PTR_CALL,
19551948
crate::engine::KEYWORD_FN_PTR_CURRY,
19561949
#[cfg(not(feature = "no_closure"))]
19571950
crate::engine::KEYWORD_IS_SHARED,
19581951
];
19591952

1960-
// These are handled by `fn_ptr_call` above, but only at the arities
1961-
// Rhai treats syntactically — at any other arity it falls through to
1962-
// ordinary dispatch, and so must this.
1963-
if FN_CALL.contains(&call.name.as_str()) {
1964-
return false;
1965-
}
1966-
19671953
!call_has_namespace!(call)
19681954
&& call.args.len() <= u8::MAX as usize
19691955
&& !SYNTACTIC.contains(&call.name.as_str())

src/grain/vm/mod.rs

Lines changed: 182 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2238,6 +2238,125 @@ impl<'e> Vm<'e> {
22382238
Ok(())
22392239
}
22402240

2241+
// Some syntactic calls can be self-implemented or short-circuited.
2242+
fn call_syntactic(
2243+
&mut self,
2244+
program: &Program,
2245+
name: &str,
2246+
argc: usize,
2247+
first: usize,
2248+
scope: &mut Scope,
2249+
pos: Position,
2250+
) -> Result<Option<Dynamic>, Box<EvalAltResult>> {
2251+
match name {
2252+
#[cfg(not(feature = "no_closure"))]
2253+
crate::engine::KEYWORD_IS_SHARED => {
2254+
return Err(EvalAltResult::ErrorFunctionNotFound(name.to_string(), pos).into())
2255+
}
2256+
crate::engine::KEYWORD_IS_DEF_VAR => {
2257+
if argc != 1 {
2258+
return Err(EvalAltResult::ErrorFunctionNotFound(name.to_string(), pos).into());
2259+
}
2260+
let var_name = self.stack[first].as_immutable_string_ref().map_err(|typ| {
2261+
self.engine
2262+
.make_type_mismatch_err::<ImmutableString>(typ, pos)
2263+
})?;
2264+
return Ok(Some(scope.contains(&var_name).into()));
2265+
}
2266+
#[cfg(not(feature = "no_function"))]
2267+
crate::engine::KEYWORD_IS_DEF_FN => {
2268+
let (this_type, fn_name, arity) = match argc {
2269+
2 => {
2270+
let var_name = self.stack[first]
2271+
.as_immutable_string_ref()
2272+
.as_deref()
2273+
.cloned()
2274+
.map_err(|typ| {
2275+
self.engine
2276+
.make_type_mismatch_err::<ImmutableString>(typ, pos)
2277+
})?;
2278+
let arity = self.stack[first + 1]
2279+
.as_int()
2280+
.map_err(|typ| self.engine.make_type_mismatch_err::<INT>(typ, pos))?;
2281+
(None, var_name, arity as usize)
2282+
}
2283+
3 => {
2284+
let this_type = self.stack[first]
2285+
.as_immutable_string_ref()
2286+
.as_deref()
2287+
.cloned()
2288+
.map_err(|typ| {
2289+
self.engine
2290+
.make_type_mismatch_err::<ImmutableString>(typ, pos)
2291+
})?;
2292+
let var_name = self.stack[first + 1]
2293+
.as_immutable_string_ref()
2294+
.as_deref()
2295+
.cloned()
2296+
.map_err(|typ| {
2297+
self.engine
2298+
.make_type_mismatch_err::<ImmutableString>(typ, pos)
2299+
})?;
2300+
let arity = self.stack[first + 2]
2301+
.as_int()
2302+
.map_err(|typ| self.engine.make_type_mismatch_err::<INT>(typ, pos))?;
2303+
(Some(this_type), var_name, arity as usize)
2304+
}
2305+
_ => {
2306+
return Err(
2307+
EvalAltResult::ErrorFunctionNotFound(name.to_string(), pos).into()
2308+
)
2309+
}
2310+
};
2311+
2312+
// Check if there is a compiled function.
2313+
for f in program.functions() {
2314+
let local_name = program
2315+
.name(f.name)
2316+
.ok_or_else(|| malformed(format!("no name {}", f.name)))?;
2317+
2318+
if local_name == fn_name.as_str() && f.params.len() == arity {
2319+
if let Some(ref this_type) = this_type {
2320+
if let Some(local_this_type_index) = f.this_type {
2321+
let local_this_type_name =
2322+
program.name(local_this_type_index).ok_or_else(|| {
2323+
malformed(format!("no name {local_this_type_index}"))
2324+
})?;
2325+
2326+
if local_this_type_name == this_type {
2327+
return Ok(Some(Dynamic::TRUE));
2328+
}
2329+
}
2330+
} else if f.this_type.is_none() {
2331+
return Ok(Some(Dynamic::TRUE));
2332+
}
2333+
}
2334+
}
2335+
2336+
// Call into Rhai.
2337+
let mut args = self.stack[first..].iter_mut().collect::<FnArgsVec<_>>();
2338+
2339+
return self
2340+
.engine
2341+
.exec_syntactic_fn_call(
2342+
&mut self.global,
2343+
&mut self.caches,
2344+
name,
2345+
&mut args,
2346+
pos,
2347+
)
2348+
.map_err(|err| dispatch_failure(err, pos))?
2349+
.ok_or_else(|| {
2350+
EvalAltResult::ErrorFunctionNotFound(name.to_string(), pos).into()
2351+
})
2352+
.map(Some);
2353+
}
2354+
_ => {}
2355+
}
2356+
2357+
Ok(None)
2358+
}
2359+
22412360
/// Call `name` with `argc` arguments sitting contiguously from `first` up.
22422361
///
22432362
/// A function this compiler lowered is called directly, with no hash and no
@@ -2248,15 +2367,13 @@ impl<'e> Vm<'e> {
22482367
&mut self,
22492368
program: &Program,
22502369
name_index: u32,
2370+
name: &str,
22512371
argc: usize,
22522372
first: usize,
22532373
scope: &mut Scope,
22542374
pos: Position,
22552375
) -> VmResult {
2256-
let name = program
2257-
.name(name_index)
2258-
.ok_or_else(|| malformed(format!("no name {name_index}")))?;
2259-
2376+
// Run compiled function if available.
22602377
if let Some(function) = program.function(name_index, argc) {
22612378
return self.call_compiled(
22622379
program,
@@ -2299,17 +2416,14 @@ impl<'e> Vm<'e> {
22992416
&mut self,
23002417
program: &Program,
23012418
name_index: u32,
2419+
name: &str,
23022420
argc: usize,
23032421
receiver: Receiver,
23042422
scope: &mut Scope,
23052423
base: usize,
23062424
capture_parent_scope: bool,
23072425
pos: Position,
23082426
) -> VmResult {
2309-
let name = program
2310-
.name(name_index)
2311-
.ok_or_else(|| malformed(format!("no name {name_index}")))?;
2312-
23132427
// Every argument count here includes the receiver, so zero of them
23142428
// names no receiver at all and the instruction is nonsense. Only an
23152429
// artifact can say it; the compiler emits one of these for a call that
@@ -2323,17 +2437,15 @@ impl<'e> Vm<'e> {
23232437
// The register is not a scope entry, so it takes a path of its own
23242438
// rather than a third [`Site`].
23252439
if let Receiver::This = receiver {
2326-
let mut new_scope;
2327-
let use_scope = if capture_parent_scope {
2328-
// Reuse parent scope.
2329-
scope
2330-
} else {
2331-
// Create detached scope.
2332-
new_scope = Scope::new();
2333-
&mut new_scope
2334-
};
2335-
2336-
return self.call_by_this(program, name_index, name, argc, use_scope, pos);
2440+
return self.call_by_this(
2441+
program,
2442+
name_index,
2443+
name,
2444+
argc,
2445+
scope,
2446+
capture_parent_scope,
2447+
pos,
2448+
);
23372449
}
23382450

23392451
// A named receiver's value is already argument zero — [`Op::LoadNamed`]
@@ -2387,20 +2499,23 @@ impl<'e> Vm<'e> {
23872499
let value = scope.get_mut_by_index(index).flatten_clone();
23882500
self.stack.insert(first, value);
23892501
}
2390-
2391-
let mut new_scope;
2392-
let use_scope = if capture_parent_scope {
2393-
// Reuse parent scope.
2394-
scope
2395-
} else {
2396-
// Create detached scope.
2397-
new_scope = Scope::new();
2398-
&mut new_scope
2399-
};
2400-
2401-
let value = self.call_stacked(program, name_index, argc, first, use_scope, pos);
2502+
// Check if it is a built-in syntactic function.
2503+
let value =
2504+
if let Some(value) = self.call_syntactic(program, name, argc, first, scope, pos)? {
2505+
value
2506+
} else {
2507+
// Detach the scope with a new one if not capturing the parent's.
2508+
let mut detached;
2509+
let scope = if !capture_parent_scope {
2510+
detached = Scope::new();
2511+
&mut detached
2512+
} else {
2513+
scope
2514+
};
2515+
self.call_stacked(program, name_index, name, argc, first, scope, pos)?
2516+
};
24022517
self.stack.truncate(first);
2403-
return value;
2518+
return Ok(value);
24042519
}
24052520

24062521
let value = {
@@ -2459,6 +2574,7 @@ impl<'e> Vm<'e> {
24592574
name: &str,
24602575
argc: usize,
24612576
scope: &mut Scope,
2577+
capture_parent_scope: bool,
24622578
pos: Position,
24632579
) -> VmResult {
24642580
let first = self
@@ -2477,9 +2593,23 @@ impl<'e> Vm<'e> {
24772593
&& program.function(name_index, argc).is_none();
24782594

24792595
if !by_reference {
2480-
let value = self.call_stacked(program, name_index, argc, first, scope, pos);
2596+
// Check if it is a built-in syntactic function.
2597+
let value =
2598+
if let Some(value) = self.call_syntactic(program, name, argc, first, scope, pos)? {
2599+
value
2600+
} else {
2601+
// Detach the scope with a new one if not capturing the parent's.
2602+
let mut detached;
2603+
let scope = if !capture_parent_scope {
2604+
detached = Scope::new();
2605+
&mut detached
2606+
} else {
2607+
scope
2608+
};
2609+
self.call_stacked(program, name_index, name, argc, first, scope, pos)?
2610+
};
24812611
self.stack.truncate(first);
2482-
return value;
2612+
return Ok(value);
24832613
}
24842614

24852615
let value = {
@@ -3431,18 +3561,22 @@ impl<'e> Vm<'e> {
34313561
}
34323562
}
34333563

3434-
let mut new_scope;
3435-
let use_scope = if capture_parent_scope {
3436-
// Reuse parent scope.
3437-
&mut *scope
3564+
// Check if it is a built-in syntactic function.
3565+
let value = if let Some(value) =
3566+
self.call_syntactic(program, name, argc, first, scope, pos())?
3567+
{
3568+
value
34383569
} else {
3439-
// Create detached scope.
3440-
new_scope = Scope::new();
3441-
&mut new_scope
3570+
// Detach the scope with a new one if not capturing the parent's.
3571+
let mut detached;
3572+
let scope = if !capture_parent_scope {
3573+
detached = Scope::new();
3574+
&mut detached
3575+
} else {
3576+
&mut *scope
3577+
};
3578+
self.call_stacked(program, name_index, name, argc, first, scope, pos())?
34423579
};
3443-
3444-
let value =
3445-
self.call_stacked(program, name_index, argc, first, use_scope, pos())?;
34463580
self.stack.truncate(first);
34473581
self.stack.push(value);
34483582
}
@@ -3454,6 +3588,9 @@ impl<'e> Vm<'e> {
34543588
| code::tag::CALL_THIS_REF
34553589
| code::tag::CALL_THIS_REF_CAPTURE => {
34563590
let name_index = u32::from(small(1)?);
3591+
let name = program
3592+
.name(name_index)
3593+
.ok_or_else(|| malformed(format!("no name {name_index}")))?;
34573594
let argc = code[pc + 3] as usize;
34583595
// `this` is a register, so this one carries no operand for
34593596
// the receiver and is two bytes shorter.
@@ -3479,6 +3616,7 @@ impl<'e> Vm<'e> {
34793616
let value = self.call_by_reference(
34803617
program,
34813618
name_index,
3619+
name,
34823620
argc,
34833621
receiver,
34843622
scope,

0 commit comments

Comments
 (0)