Merge pull request #772 from vangel76/script-760-evalfile-bridgetalk

#760: $.evalFile and a minimal BridgeTalk host identification

# Conflicts:
#	crates/script/src/runtime.rs
#	docs/scripting-parity.md
This commit is contained in:
Brandon Thomas
2026-10-12 03:20:49 -04:00
5 changed files with 191 additions and 4 deletions
+44 -2
View File
@@ -72,11 +72,13 @@ pub(crate) struct Active {
/// Running on a script-host thread: modal dialogs hand the session back to the caller
/// through this link while they wait.
pub(crate) link: Option<crate::ui::Link>,
/// The files `$.evalFile` is evaluating, outermost first.
pub(crate) loading: Vec<std::path::PathBuf>,
}
impl Active {
pub(crate) fn new(session: Session, name: &str, host: u32) -> Active {
Active { session, output: vec![], groups: vec![], name: name.to_string(), host, link: None }
Active { session, output: vec![], groups: vec![], name: name.to_string(), host, link: None, loading: vec![] }
}
}
@@ -411,18 +413,58 @@ fn native_file(_: &JsValue, args: &[JsValue], ctx: &mut Context) -> JsResult<JsV
}
}
/// `__evalFile(path)`: ExtendScript's `$.evalFile`. Evaluates a script file in the global scope
/// and returns its last value. Relative paths resolve against the folder of the file being
/// evaluated (the running script's for the outermost call, else the project's); reads follow the
/// file gate `#include` uses ([`crate::scriptfile`]) and calls nest at most
/// [`MAX_DEPTH`](crate::scriptfile::MAX_DEPTH) deep.
fn native_eval_file(_: &JsValue, args: &[JsValue], ctx: &mut Context) -> JsResult<JsValue> {
use crate::scriptfile::{self, MAX_DEPTH};
let name = arg_string(args, 0, ctx)?;
if name.is_empty() || name == "undefined" {
return Err(throw("$.evalFile needs a file"));
}
let (path, text) = with_active(|a| {
if a.loading.len() >= MAX_DEPTH {
return Err(format!("$.evalFile(\"{name}\"): files are nested too deeply (at most {MAX_DEPTH})"));
}
let s = &a.session;
let root = scriptfile::script_dir(s, &a.name);
let dir = a
.loading
.last()
.and_then(|p| p.parent().map(std::path::Path::to_path_buf))
.or_else(|| root.clone())
.or_else(|| project_dir(s).map(|d| normalize(&d)));
let path = scriptfile::resolve(s, &name, dir.as_deref(), &[]).map_err(|e| format!("$.evalFile {e}"))?;
scriptfile::check_read(s, &path, root.as_deref())?;
let text = scriptfile::read(s, &path)?;
check_nesting(&text)?;
a.loading.push(path.clone());
Ok((path, text))
})
.map_err(throw)?;
let r = ctx.eval(Source::from_bytes(text.as_bytes()).with_path(&path));
let _ = with_active(|a| {
a.loading.pop();
Ok(())
});
r
}
pub(crate) fn new_context() -> Result<Context, String> {
let mut ctx = Context::default();
let limits = ctx.runtime_limits_mut();
limits.set_loop_iteration_limit(200_000_000);
limits.set_recursion_limit(2000);
let natives: [(JsString, usize, fn(&JsValue, &[JsValue], &mut Context) -> JsResult<JsValue>); 10] = [
let natives: [(JsString, usize, fn(&JsValue, &[JsValue], &mut Context) -> JsResult<JsValue>); 11] = [
(js_string!("__exec"), 2, native_exec),
(js_string!("__query"), 2, native_query),
(js_string!("__print"), 1, native_print),
(js_string!("__undo"), 2, native_undo),
(js_string!("__task"), 4, native_task),
(js_string!("__file"), 3, native_file),
(js_string!("__evalFile"), 2, native_eval_file),
(js_string!("__sock"), 3, crate::socket::native_sock),
(js_string!("__uiNewId"), 0, crate::ui::native_new_id),
(js_string!("__uiModal"), 1, crate::ui::native_modal),