mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-13 21:17:48 +00:00
Scripts: $.fileName names the file of the code that reads it
$.fileName gave the file being run, so a function defined in a file that #include inserted, in a file $.evalFile ran, or in an earlier run of a named engine reported the calling file instead of its own. - vectorcraft_js: HostItem::SiteGetter gives a getter the CallSite (source tag, line, column) of the code that reads it. - $.fileName maps that place through the run's source maps, or the maps a named engine kept, to the file the code is in. It is empty in code from a script given as source and in kept code whose map was dropped. Code that eval or Function compiled still gets the file being run. Docs: docs/mcp.md, docs/target-app-parity.md.
This commit is contained in:
@@ -321,7 +321,8 @@ impl Files {
|
||||
std::env::var("JSINCLUDE").map(|v| v.split(';').map(str::trim).filter(|p| !p.is_empty()).map(PathBuf::from).collect()).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The file being run now, as `$.fileName` gives it (empty for a script given as text).
|
||||
/// The file being run now: the script, or the file `$.evalFile` is running (empty for a script
|
||||
/// given as text). `$.fileName` gives it to code that `eval` or `Function` compiled.
|
||||
pub(crate) fn file_name(&self) -> String {
|
||||
self.frames.last().map(|f| f.shown.to_string()).unwrap_or_default()
|
||||
}
|
||||
|
||||
@@ -822,6 +822,29 @@ impl Host {
|
||||
}
|
||||
}
|
||||
|
||||
/// `$.fileName` read by the code at `site`: the file that code is in, by the source map of the
|
||||
/// source it came from (this run's script, a file `$.evalFile` gave, or code an earlier run of
|
||||
/// the named engine compiled), so a function keeps the file it was defined in. Code from a file
|
||||
/// `#include` inserted is in that file. A script given as text has no name, and neither has
|
||||
/// code whose map the named engine no longer keeps. Code that `eval` or `Function` compiled has
|
||||
/// no map, and is given the file being run now.
|
||||
fn file_name_at(&self, site: vectorcraft_js::CallSite) -> String {
|
||||
let name = |m: &include::SourceMap| m.locate(site.line, site.column).1.to_string();
|
||||
let tag = site.source;
|
||||
if tag == vectorcraft_js::UNKNOWN_SOURCE {
|
||||
return self.files.borrow().file_name();
|
||||
}
|
||||
let script = self.script_tag.get();
|
||||
match tag.checked_sub(script).filter(|_| script != vectorcraft_js::UNKNOWN_SOURCE) {
|
||||
Some(0) => name(&self.script_map.borrow()),
|
||||
Some(n) => self.eval_maps.borrow().get(n as usize - 1).map(name).unwrap_or_default(),
|
||||
None => {
|
||||
let kept = self.kept_maps.as_ref().map(|k| k.borrow());
|
||||
kept.as_ref().and_then(|k| k.get(tag)).map(|k| name(&k.map)).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a line to the run's console and, during a slice, to the session's.
|
||||
fn line(&self, entry: console::Entry, cut: bool) {
|
||||
let line = console::ConsoleLine { run: self.run, name: self.name.clone(), entry };
|
||||
@@ -1254,9 +1277,9 @@ fn each_line(text: &str, mut done: impl FnMut(String, bool)) {
|
||||
|
||||
/// The `$` object: `$.write(...values)` writes the values to the Script Console as `String(value)`
|
||||
/// gives them, one after another, and `$.writeln(...values)` does the same and ends the line.
|
||||
/// `$.evalFile(path[, timeout])` runs a file and gives its last value, `$.fileName` is the file
|
||||
/// being run and `$.includePath` the `#includepath` list. Its other members are
|
||||
/// [`dollar::members`].
|
||||
/// `$.evalFile(path[, timeout])` runs a file and gives its last value, `$.fileName` is the file the
|
||||
/// code reading it is in ([`Host::file_name_at`]) and `$.includePath` the `#includepath` list. Its
|
||||
/// other members are [`dollar::members`].
|
||||
fn dollar_object(link: &Link, info: dollar::RunInfo, localization: &Rc<vectorcraft_js::Localization>) -> Vec<(String, HostItem)> {
|
||||
let writer = |end: bool| {
|
||||
let l = link.clone();
|
||||
@@ -1278,6 +1301,15 @@ fn dollar_object(link: &Link, info: dollar::RunInfo, localization: &Rc<vectorcra
|
||||
Ok(Data::String(text))
|
||||
}))
|
||||
};
|
||||
let file_name = {
|
||||
let l = link.clone();
|
||||
HostItem::SiteGetter(Rc::new(move |site| {
|
||||
let h = current(&l)?;
|
||||
let text = h.file_name_at(site);
|
||||
h.charge_call(text.len() as u64 + 2)?;
|
||||
Ok(Data::String(text))
|
||||
}))
|
||||
};
|
||||
let eval_file = {
|
||||
let (l, end, strict) = (link.clone(), link.clone(), link.clone());
|
||||
HostItem::Evaluator {
|
||||
@@ -1294,7 +1326,7 @@ fn dollar_object(link: &Link, info: dollar::RunInfo, localization: &Rc<vectorcra
|
||||
("write".into(), writer(false)),
|
||||
("writeln".into(), writer(true)),
|
||||
("evalFile".into(), eval_file),
|
||||
("fileName".into(), getter(include::Files::file_name)),
|
||||
("fileName".into(), file_name),
|
||||
("includePath".into(), getter(include::Files::include_path)),
|
||||
];
|
||||
items.extend(dollar::members(link, info, localization));
|
||||
|
||||
@@ -202,6 +202,52 @@ fn an_error_in_code_an_earlier_run_kept_is_at_that_runs_file_and_line() {
|
||||
let _ = std::fs::remove_dir_all(base);
|
||||
}
|
||||
|
||||
/// `$.fileName` in code an earlier run of a named engine kept names the file that code is in: that
|
||||
/// run's script, a file it included or a file `$.evalFile` gave, not the file of the run that calls
|
||||
/// it. Code from a script given as text, and code whose place the engine no longer keeps, has no
|
||||
/// name; a dropped engine takes its functions with it.
|
||||
#[test]
|
||||
fn a_kept_function_reads_the_file_name_of_the_file_it_is_in() {
|
||||
let base: PathBuf = std::env::temp_dir().join(format!("vc-script-engines-file-name-{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&base);
|
||||
for dir in ["a", "b"] {
|
||||
std::fs::create_dir_all(base.join(dir)).unwrap();
|
||||
}
|
||||
std::fs::write(base.join("a/inc.jsxinc"), "function fromInclude() { return $.fileName; }").unwrap();
|
||||
std::fs::write(base.join("a/ev.jsxinc"), "\nfunction fromEval() { return $.fileName; }").unwrap();
|
||||
let def = base.join("a/def.jsx");
|
||||
std::fs::write(&def, "#targetengine e\nfunction where() { return $.fileName; }\n#include \"inc.jsxinc\"\n$.evalFile('ev.jsxinc');").unwrap();
|
||||
let caller = base.join("b/caller.jsx");
|
||||
let call = |s: &mut Session, text: &str| -> Vec<String> {
|
||||
std::fs::write(&caller, text).unwrap();
|
||||
let r = s.execute("script.run", &json!({"path": caller.to_string_lossy()})).unwrap();
|
||||
r["log"][0].as_array().unwrap().iter().map(|v| v.as_str().unwrap().replace('\\', "/")).collect()
|
||||
};
|
||||
let mut s = Session::new();
|
||||
s.execute("script.run", &json!({"path": def.to_string_lossy()})).unwrap();
|
||||
let from_file = call(&mut s, "#targetengine e\nvc.log([where(), fromInclude(), fromEval(), $.fileName]);");
|
||||
let from_text = log(&mut s, "#targetengine e\nvc.log([where(), $.fileName, eval('where()'), eval('$.fileName')]);");
|
||||
run(&mut s, "#targetengine e\nfunction fromText() { return $.fileName; }").unwrap();
|
||||
let text_kept = call(&mut s, "#targetengine e\nvc.log([fromText(), $.fileName]);");
|
||||
// Each run's own place takes the room of the ones before, so def.jsx's places go.
|
||||
TEST_KEPT_MAP_BYTES.with(|b| b.set(Some(300)));
|
||||
for _ in 0..20 {
|
||||
run(&mut s, "#targetengine e\nvar x = 1;").unwrap();
|
||||
}
|
||||
let evicted = call(&mut s, "#targetengine e\nvc.log([where(), fromEval(), $.fileName]);");
|
||||
TEST_KEPT_MAP_BYTES.with(|b| b.set(None));
|
||||
s.execute("script.engine.drop", &json!({"name": "e"})).unwrap();
|
||||
let dropped = log(&mut s, "#targetengine e\nvc.log(typeof where);");
|
||||
let _ = std::fs::remove_dir_all(&base);
|
||||
let ends = |names: &[String], ends: &[&str]| names.len() == ends.len() && names.iter().zip(ends).all(|(n, e)| n.ends_with(e));
|
||||
assert!(ends(&from_file, &["a/def.jsx", "a/inc.jsxinc", "a/ev.jsxinc", "b/caller.jsx"]), "{from_file:?}");
|
||||
let shown: Vec<String> = from_text[0].as_array().unwrap().iter().map(|v| v.as_str().unwrap().replace('\\', "/")).collect();
|
||||
assert!(ends(&shown, &["a/def.jsx", "", "a/def.jsx", ""]) && shown[1].is_empty() && shown[3].is_empty(), "{shown:?}");
|
||||
assert!(text_kept[0].is_empty() && text_kept[1].ends_with("b/caller.jsx"), "{text_kept:?}");
|
||||
assert!(evicted[0].is_empty() && evicted[1].is_empty() && evicted[2].ends_with("b/caller.jsx"), "{evicted:?}");
|
||||
assert_eq!(dropped, json!(["undefined"]));
|
||||
}
|
||||
|
||||
/// A named engine keeps the places of its earlier runs' code within its budget, the oldest going
|
||||
/// first, and the code still runs once its place is gone.
|
||||
#[test]
|
||||
|
||||
@@ -374,6 +374,48 @@ fn script_eval_file_returns_last_value_and_shares_globals() {
|
||||
let _ = std::fs::remove_dir_all(base);
|
||||
}
|
||||
|
||||
/// `$.fileName` names the file the code reading it is in: a function `$.evalFile` gave names its
|
||||
/// file after the file has returned, through nested files, and code an included file inserted names
|
||||
/// that file. Code that `eval` compiled names the file being run.
|
||||
#[test]
|
||||
fn script_file_name_names_the_file_of_the_code_that_reads_it() {
|
||||
let base = folder("filename");
|
||||
write(&base.join("main/inc.jsxinc"), "function fromInclude() { return $.fileName; }\nvar includedTop = $.fileName;");
|
||||
write(
|
||||
&base.join("main/sub/outer.jsx"),
|
||||
"function fromOuter() { return $.fileName; }\nvar before = $.fileName;\n$.evalFile('inner.jsx');\nvar after = $.fileName;\nfromInner();",
|
||||
);
|
||||
write(&base.join("main/sub/inner.jsx"), "function fromInner() { return $.fileName; }\n$.fileName;");
|
||||
let r = run(
|
||||
&base,
|
||||
"#include \"inc.jsxinc\"\nvar nested = $.evalFile('sub/outer.jsx');\n\
|
||||
vc.log([fromInclude(), includedTop, before, after, nested, fromOuter(), fromInner(), $.fileName, eval('$.fileName')]);",
|
||||
)
|
||||
.unwrap();
|
||||
let names: Vec<String> = r["log"][0].as_array().unwrap().iter().map(|v| v.as_str().unwrap().replace('\\', "/")).collect();
|
||||
let ends = [
|
||||
"main/inc.jsxinc",
|
||||
"main/inc.jsxinc",
|
||||
"sub/outer.jsx",
|
||||
"sub/outer.jsx",
|
||||
"sub/inner.jsx",
|
||||
"sub/outer.jsx",
|
||||
"sub/inner.jsx",
|
||||
"main/main.jsx",
|
||||
"main/main.jsx",
|
||||
];
|
||||
assert_eq!(names.len(), ends.len(), "{names:?}");
|
||||
for (name, end) in names.iter().zip(ends) {
|
||||
assert!(name.ends_with(end), "{name} ends with {end}: {names:?}");
|
||||
}
|
||||
// A script given as text has no name, and neither have the functions it defines.
|
||||
let r = Session::new()
|
||||
.execute("script.run", &json!({"source": "function f() { return $.fileName; }\nvc.log([f(), $.fileName]);", "name": "t.js"}))
|
||||
.unwrap();
|
||||
assert_eq!(r["log"], json!([["", ""]]));
|
||||
let _ = std::fs::remove_dir_all(base);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn script_eval_file_outside_scope_refused() {
|
||||
let base = folder("evaloutside");
|
||||
|
||||
+56
-2
@@ -74,6 +74,21 @@ pub enum HostError {
|
||||
/// A function the host gives a script, with its arguments converted.
|
||||
pub type HostFunction = Rc<dyn Fn(Vec<Data>) -> Result<Data, HostError>>;
|
||||
|
||||
/// The place in the script's code that reads a [`HostItem::SiteGetter`] property: the source tag
|
||||
/// of that code, as [`crate::Realm::error_tag`] gives one, and the line and column there, counted
|
||||
/// from 1. A function an earlier script defined keeps its script's tag; code that `eval` or
|
||||
/// `Function` compiled has [`crate::UNKNOWN_SOURCE`]. A read with none of the script's code
|
||||
/// running has [`crate::UNKNOWN_SOURCE`] and line and column 0.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct CallSite {
|
||||
pub source: u32,
|
||||
pub line: u32,
|
||||
pub column: u32,
|
||||
}
|
||||
|
||||
/// A getter the host gives a script that is told where the code reading it is.
|
||||
pub type SiteFunction = Rc<dyn Fn(CallSite) -> Result<Data, HostError>>;
|
||||
|
||||
/// How one argument of a [`HostItem::Converted`] function is read.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Arg {
|
||||
@@ -96,6 +111,8 @@ pub(crate) enum ArgRead {
|
||||
Text,
|
||||
/// One by one as the list says; those past its end are not read.
|
||||
Each(Rc<[Arg]>),
|
||||
/// None: the function is given the [`CallSite`] of the code that calls it, as three numbers.
|
||||
Site,
|
||||
}
|
||||
|
||||
/// A member of an object the host adds to the global environment.
|
||||
@@ -114,6 +131,9 @@ pub enum HostItem {
|
||||
/// A property whose value the function gives each time it is read, frozen as
|
||||
/// [`HostItem::Value`] gives its value.
|
||||
FrozenGetter(HostFunction),
|
||||
/// A property whose value the function gives each time it is read, given the [`CallSite`] of
|
||||
/// the code that reads it.
|
||||
SiteGetter(SiteFunction),
|
||||
/// A property whose value `get` gives each time it is read, and that `set` is given each value
|
||||
/// assigned to it, as its one argument; an error from `set` is thrown to the script.
|
||||
Property {
|
||||
@@ -357,7 +377,10 @@ impl Interp {
|
||||
/// Calls host function `f`: reads its arguments as `how` says, runs it, and gives its result to
|
||||
/// the script.
|
||||
pub(crate) fn call_data(&mut self, f: &crate::heap::DataFn, name: &Rc<str>, args: &[Value], how: &ArgRead) -> JsResult<Value> {
|
||||
let data = self.read_args(name, args, how)?;
|
||||
let data = match how {
|
||||
ArgRead::Site => site_data(self.call_site()),
|
||||
_ => self.read_args(name, args, how)?,
|
||||
};
|
||||
// Only the script's own code, in a run that can pause, can wait for a result.
|
||||
let can_wait = self.in_slice && self.reentry == 0;
|
||||
self.can_wait.set(can_wait);
|
||||
@@ -375,6 +398,18 @@ impl Interp {
|
||||
}
|
||||
}
|
||||
|
||||
/// Where the innermost frame of the script's code is: its source's tag and the line and column
|
||||
/// of the instruction it is running.
|
||||
fn call_site(&self) -> CallSite {
|
||||
match self.script_frames().last() {
|
||||
Some((code, pc, _)) => {
|
||||
let (line, column) = code.line_column(pc.saturating_sub(1));
|
||||
CallSite { source: code.source.tag, line, column }
|
||||
}
|
||||
None => CallSite { source: crate::UNKNOWN_SOURCE, line: 0, column: 0 },
|
||||
}
|
||||
}
|
||||
|
||||
/// Calls evaluator `f` ([`HostItem::Evaluator`]): reads its arguments, runs it, and runs the
|
||||
/// source it gives as global code, as an indirect `eval` does, giving that code's completion
|
||||
/// value. A result that is not a string is given to the script as it is. `end` is called once
|
||||
@@ -442,7 +477,7 @@ impl Interp {
|
||||
for (i, a) in args.iter().enumerate() {
|
||||
reading.path.push(Step::Index(u32::try_from(i).unwrap_or(u32::MAX)));
|
||||
let d = match how {
|
||||
ArgRead::Data | ArgRead::Frozen => self.read_data(a, &mut reading),
|
||||
ArgRead::Data | ArgRead::Frozen | ArgRead::Site => self.read_data(a, &mut reading),
|
||||
ArgRead::Text => self.read_text(a, &mut reading),
|
||||
ArgRead::Each(list) => match list.get(i) {
|
||||
Some(Arg::TextOrUndefined) if matches!(a, Value::Undefined) => Ok(Data::Undefined),
|
||||
@@ -531,6 +566,10 @@ impl Interp {
|
||||
HostItem::Value(d) => Prop::Data { value: self.write_data(d, 0, true)?, flags: ENUMERABLE },
|
||||
HostItem::Getter(f) => Prop::Accessor { get: Some(self.host_function(f, &full, ArgRead::Data)), set: None, flags: ENUMERABLE },
|
||||
HostItem::FrozenGetter(f) => Prop::Accessor { get: Some(self.host_function(f, &full, ArgRead::Frozen)), set: None, flags: ENUMERABLE },
|
||||
HostItem::SiteGetter(f) => {
|
||||
let get: HostFunction = Rc::new(move |data| f(read_site(&data)));
|
||||
Prop::Accessor { get: Some(self.host_function(get, &full, ArgRead::Site)), set: None, flags: ENUMERABLE }
|
||||
}
|
||||
HostItem::Property { get, set } => Prop::Accessor {
|
||||
get: Some(self.host_function(get, &full, ArgRead::Data)),
|
||||
set: Some(self.host_function(set, &full, ArgRead::Data)),
|
||||
@@ -577,3 +616,18 @@ enum Slot {
|
||||
Index(usize),
|
||||
Name(String),
|
||||
}
|
||||
|
||||
/// `site` as the three numbers [`ArgRead::Site`] gives its function.
|
||||
fn site_data(site: CallSite) -> Vec<Data> {
|
||||
vec![Data::Number(f64::from(site.source)), Data::Number(f64::from(site.line)), Data::Number(f64::from(site.column))]
|
||||
}
|
||||
|
||||
/// The [`CallSite`] that [`site_data`] gave as `data`.
|
||||
fn read_site(data: &[Data]) -> CallSite {
|
||||
let at = |i: usize, missing: u32| match data.get(i) {
|
||||
// Each number is a `u32` that `site_data` gave, so the conversion is exact.
|
||||
Some(Data::Number(n)) => *n as u32,
|
||||
_ => missing,
|
||||
};
|
||||
CallSite { source: at(0, crate::UNKNOWN_SOURCE), line: at(1, 0), column: at(2, 0) }
|
||||
}
|
||||
|
||||
@@ -271,6 +271,41 @@ fn a_frozen_getter_gives_a_frozen_value_each_time() {
|
||||
assert_eq!(r.run(source).unwrap(), JsValue::String("true,true,true,1,false,false".into()));
|
||||
}
|
||||
|
||||
/// A site getter is told the source tag, line and column of the code that reads it: a function an
|
||||
/// earlier script or an evaluator's source defined keeps that source's tag, and code that `eval`
|
||||
/// compiled has none.
|
||||
#[test]
|
||||
fn a_site_getter_is_told_where_the_code_that_reads_it_is() {
|
||||
let mut r = realm(10_000_000);
|
||||
let seen: Rc<RefCell<Vec<CallSite>>> = Rc::default();
|
||||
let log = seen.clone();
|
||||
let site: SiteFunction = Rc::new(move |s| {
|
||||
log.borrow_mut().push(s);
|
||||
Ok(Data::Number(f64::from(s.line)))
|
||||
});
|
||||
let source: HostFunction = Rc::new(|_| Ok(Data::String("\n\nfunction inEval() { return host.site; }".into())));
|
||||
let load = HostItem::Evaluator { source, end: Rc::new(|| {}), strict_writes: Rc::new(|| None) };
|
||||
r.define_host_object("host", vec![("site".into(), HostItem::SiteGetter(site)), ("load".into(), load)]).unwrap();
|
||||
r.run("function early() {\n return host.site;\n}\nhost.load();").unwrap();
|
||||
let first = r.script_tag();
|
||||
assert_eq!(r.run("\n\n\n\nhost.site + early() + inEval() + eval('host.site')").unwrap(), JsValue::Number(5.0 + 2.0 + 3.0 + 1.0));
|
||||
let second = r.script_tag();
|
||||
let sites = seen.borrow().clone();
|
||||
assert_eq!(
|
||||
sites,
|
||||
vec![
|
||||
CallSite { source: second, line: 5, column: 1 },
|
||||
CallSite { source: first, line: 2, column: 11 },
|
||||
CallSite { source: first + 1, line: 3, column: 28 },
|
||||
CallSite { source: UNKNOWN_SOURCE, line: 1, column: 1 },
|
||||
]
|
||||
);
|
||||
seen.borrow_mut().clear();
|
||||
// A getter a built-in calls is at the place of the code that called the built-in.
|
||||
assert_eq!(r.run("\nObject.getOwnPropertyDescriptor(host, 'site').get.call(host)").unwrap(), JsValue::Number(2.0));
|
||||
assert_eq!(seen.borrow().first().map(|s| s.source), Some(r.script_tag()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_text_function_takes_each_argument_as_string_gives_it() {
|
||||
let mut r = realm(1_000_000);
|
||||
|
||||
@@ -80,7 +80,7 @@ pub use ast::{
|
||||
};
|
||||
pub use builtins::extensions::Localization;
|
||||
pub use error::{SyntaxError, TOO_DEEP, TOO_LONG};
|
||||
pub use host::{Arg, Data, DataLimits, HostError, HostFunction, HostItem, ScriptItem};
|
||||
pub use host::{Arg, CallSite, Data, DataLimits, HostError, HostFunction, HostItem, ScriptItem, SiteFunction};
|
||||
pub use lexer::{Goal, Lexer};
|
||||
pub use number::to_string as number_to_string;
|
||||
pub use realm::{Interrupt, JsValue, LimitKind, Limits, MAX_UNBROKEN_FUEL, Progress, Realm, RunError, RunErrorKind, UNKNOWN_SOURCE};
|
||||
|
||||
+7
-2
@@ -1947,8 +1947,10 @@ app through the global object `vc`, and writes to the Script Console and runs ot
|
||||
file). Its preprocessor lines are read as the script's are. `timeout` is a number of milliseconds from 0; the file
|
||||
is read at once and `timeout` is not waited on. The run's fuel and limits apply to the file. An empty path, a
|
||||
missing file and a refused path throw errors the script can catch.
|
||||
- `$.fileName` is the file being run, which is the file `$.evalFile` is running while it runs, and is empty for a
|
||||
script given as `source`. `$.includePath` is the current `#includepath` list, separated by `;`. `$.engineName` is
|
||||
- `$.fileName` is the file of the code that reads it: the script, a file it included with `#include`, or a file
|
||||
`$.evalFile` ran, also when a function that file defined is called after `$.evalFile` returned. It is empty in code
|
||||
from a script given as `source`. In code that `eval` or `Function` compiled it is the file being run, which is the
|
||||
file `$.evalFile` is running while it runs. `$.includePath` is the current `#includepath` list, separated by `;`. `$.engineName` is
|
||||
the name `#targetengine` gave the engine the script runs in (`VectorCraft` without the line) and is read-only.
|
||||
- `alert(message[, title, errorIcon])`, `confirm(message[, noAsDflt, title])` and `prompt(message, preset[, title])`
|
||||
open a dialog, described below. `vc.api.has("dialogs")` is true.
|
||||
@@ -2092,6 +2094,9 @@ no engines, and the engines it left are dropped when their own thread next uses
|
||||
for 8 MiB of source maps, the oldest going first, and a run whose script does not compile keeps none. An error in
|
||||
code whose place is gone, or in code that `eval` or `Function` compiled, is reported without a line and column, and
|
||||
the code still runs.
|
||||
- `$.fileName` in code an earlier run compiled names the file that code is in, by the same places: the earlier run's
|
||||
script, a file it included or a file its `$.evalFile` ran, not the calling run's file. It is empty in code from an
|
||||
earlier script given as `source`, even when that run had a name, and in code whose place is gone.
|
||||
- `vc`, `$`, `alert`, `confirm` and `prompt` act for the run that calls them, including a copy an earlier run kept in a
|
||||
global. `vc.args` is the calling run's `args`, through a `vc` object an earlier run kept too. An engine
|
||||
holds no document: `vc.document` is a copy, read when it is called. What a run sets on `$` (`$.locale`,
|
||||
|
||||
@@ -780,8 +780,9 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
- Scripts: an error `$.evalFile` throws for a file it cannot run is reported at that file's line and column only while no `catch` has taken it. Caught and thrown again later, in the script or in another file, it is reported where it was thrown again, and a new error whose message holds its text is reported where that error was thrown. VectorCraft's own rule. `crates/engine/src/tests/script_include.rs`, `crates/js/src/host_tests.rs`.
|
||||
- Scripts: an uncaught error that one or more `finally` blocks pass on unchanged, in the script or in a function it called, is reported at the line and column where it was thrown, in `script.run`'s report and the Script Console. An error a `finally` block throws is reported at that throw, and one its `return`, `break` or `continue` discards is not reported. VectorCraft's own rule. `crates/engine/src/tests/script_include.rs`, `crates/js/src/host_tests.rs`.
|
||||
- Scripts: an error a `finally` block passes on is still reported where it was thrown when the block's allocations collect garbage under a small heap limit, and a `finally` block that `return`, `break` or `continue` leaves drops the saved location of its error as it leaves, so a collection no longer counts that code as live. VectorCraft's own rule. `crates/js/src/host_tests.rs`.
|
||||
- Scripts: a run reads its preprocessor lines before any of it runs: `#include` and `//@include` put another file in place of the line, looked for beside the including file, in the `#includepath` folders and in `JSINCLUDE`; `#script` names the run; `#target` and `#strict off` are taken, `#strict on` turns on strict write checks (below), `#targetengine` names the engine (below), and `#engine` is refused. `$.evalFile` runs a file with the same globals and returns its last value, and `$.fileName` and `$.includePath` give the file being run and the include folders. A script run from a file reads these files only in its folder and below, without `..` names or links that lead out, and an error in one is reported at its own file, line and column ([preprocessor directives](https://extendscript.docsforadobe.dev/extendscript-tools-features/preprocessor-directives/), [the `$` object](https://extendscript.docsforadobe.dev/extendscript-tools-features/dollar-object/); `crates/engine/src/tests/script_include.rs`).
|
||||
- Scripts: a run reads its preprocessor lines before any of it runs: `#include` and `//@include` put another file in place of the line, looked for beside the including file, in the `#includepath` folders and in `JSINCLUDE`; `#script` names the run; `#target` and `#strict off` are taken, `#strict on` turns on strict write checks (below), `#targetengine` names the engine (below), and `#engine` is refused. `$.evalFile` runs a file with the same globals and returns its last value, and `$.fileName` and `$.includePath` give the current script's file and the include folders. A script run from a file reads these files only in its folder and below, without `..` names or links that lead out, and an error in one is reported at its own file, line and column ([preprocessor directives](https://extendscript.docsforadobe.dev/extendscript-tools-features/preprocessor-directives/), [the `$` object](https://extendscript.docsforadobe.dev/extendscript-tools-features/dollar-object/); `crates/engine/src/tests/script_include.rs`).
|
||||
- Scripts: `#targetengine name` (and `//@targetengine name`) runs the script in a named engine that lasts for the session, and a later script that names it sees the globals and functions earlier ones left ([preprocessor directives](https://extendscript.docsforadobe.dev/extendscript-tools-features/preprocessor-directives/)); `$.engineName` gives the name ([the `$` object](https://extendscript.docsforadobe.dev/extendscript-tools-features/dollar-object/)). VectorCraft's own rules: a script without the line and `#targetengine main` get a new engine each run, the line comes once in the script itself before its code, a session keeps 8 named engines with 64 MiB of heap each, an error or a stop keeps the globals the run set while the documents go back, kept host functions act for the run that calls them, `script.engines` lists the engines and `script.engine.drop` drops one, and the web build has none. `crates/engine/src/tests/script_engines.rs`.
|
||||
- Scripts: `$.fileName` is a read-only name of the script file in use ([the help page](https://extendscript.docsforadobe.dev/extendscript-tools-features/dollar-object/)). VectorCraft's own rules: it names the file of the code that reads it, so a function a file included with `#include` or run with `$.evalFile` defined, or one an earlier run of a named engine kept, gives its own file and not the calling file; code from a script given as `source`, and kept code whose place the engine dropped, gives an empty name, and code that `eval` or `Function` compiled gives the file being run. `crates/engine/src/tests/script_include.rs`, `crates/engine/src/tests/script_engines.rs`.
|
||||
- Scripts: a runtime error is shown at the line that was running ([the help page](https://extendscript.docsforadobe.dev/extendscript-toolkit/debugging-in-the-toolkit/)). VectorCraft's own rules: an error in code an earlier run of a named engine compiled, such as a function it defined, is reported at that code's file, line and column (the earlier run's script, a file it included or a file its `$.evalFile` ran), while the report and its console line keep the calling run's name; a script given as text is named by its run's name, and each engine keeps these places for 8 MiB of source maps, the oldest going first. An error in code whose place is gone, or in code that `eval` or `Function` compiled, is reported without a line and column, and the code still runs. `crates/engine/src/tests/script_engines.rs`.
|
||||
- Scripts: `File` and `Folder` ([File object](https://extendscript.docsforadobe.dev/file-system-access/file-object/), [Folder object](https://extendscript.docsforadobe.dev/file-system-access/folder-object/)) name files and folders by platform or URI path, with `name`, `fsName`, `absoluteURI`, `relativeURI`, `parent`, `Folder.current` and `encode`/`decode`, and read them: `exists`, `length`, the dates, `getFiles` with a mask or a function, and `open('r')` with `read`, `readln`, `readch`, `seek`, `tell` and `close`, decoding by byte order mark, `encoding`, UTF-8 or CP1252. VectorCraft's own rules: a script reads only in its own folder and below (and inside the automation read roots when they are set); any other path and a network or device path are refused before anything is looked up, and a link that leads out is refused as well (`false` or `null`, with `error` set); a file is read whole up to 8 MiB and a run reads up to 32 MiB; writing, the file dialogs and `execute` are not available yet. `crates/engine/src/tests/script_io.rs`.
|
||||
- Scripts: `#strict on` (and `//@strict on`) runs the script with the checks the [`$` object](https://extendscript.docsforadobe.dev/extendscript-tools-features/dollar-object/) help page gives for `$.strict`: an assignment to a read-only property throws a `TypeError`, and so does a new property on an object that takes none. VectorCraft's own rules: a getter-only property counts as read-only, the objects that take no new properties are the non-extensible ones (`Object.preventExtensions`, `seal`, `freeze`, and the frozen `vc` and `$` objects), the last `#strict` line of the script and its includes sets the whole run, a file `$.evalFile` runs keeps its own line's checks for its code, and the rest of ES5 strict mode stays with `'use strict'`. Without the line, runs are unchanged. `crates/engine/src/tests/script_include.rs` (`strict_on_*`, `strict_mode_in_eval_file_is_scoped`), `crates/js/src/interp_tests.rs` (`strict_writes_are_separate_from_es5_strict`).
|
||||
|
||||
Reference in New Issue
Block a user