Skip to content
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
19 changes: 19 additions & 0 deletions crates/foxvm/src/mock_host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ pub struct MockHost {
rng: crate::host::SeededRandom,
/// Upper-cased program name -> module id, for `resolve_program`.
pub programs: HashMap<String, u32>,
/// Upper-cased program name -> source, for programs that are not loaded until something
/// asks: `run_with_requests` compiles one when the VM's `LoadProgram` names it, which is
/// what the IDE's host does with a `.prg` in the project.
pub program_sources: HashMap<String, String>,
/// Every request answered by `run_with_requests`, in order.
pub requests: Vec<HostRequest>,
/// Handles released through `ReleaseObject`.
Expand Down Expand Up @@ -118,6 +122,7 @@ impl MockHost {
now: (crate::value::days_from_civil(2026, 9, 7), 12.0 * 3600.0 + 30.0 * 60.0),
rng: crate::host::SeededRandom::default(),
programs: HashMap::new(),
program_sources: HashMap::new(),
requests: Vec::new(),
released: Vec::new(),
files: HashMap::new(),
Expand Down Expand Up @@ -1230,6 +1235,20 @@ pub fn run_with_requests(
Step::Done { value, .. } => return Ok(value),
Step::Error(e) => return Err(e),
Step::Suspend(req) => {
// a program the host holds the source of is compiled and loaded on request
if let HostRequest::LoadProgram { name } = &req
&& let Some(src) = host.program_sources.get(&name.to_ascii_uppercase()).cloned()
{
let upper = name.to_ascii_uppercase();
let module = compiler::compile_program(&src, &upper.to_ascii_lowercase())
.module
.ok_or_else(|| RtError::syntax(format!("{upper} does not compile")))?;
let id = vm.load_module(module);
host.programs.insert(upper, id);
host.requests.push(req);
vm.resume(fiber, Value::number(id as f64));
continue;
}
let answer = match answers.pop_front() {
Some(v) => {
// Scripted answers still apply property writes so later reads see them.
Expand Down
44 changes: 44 additions & 0 deletions crates/foxvm/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2418,6 +2418,17 @@ impl Parser {
let (args, in_prog) = self.do_clauses()?;
return Ok(Some(StmtKind::DoExpr { name, args, in_prog }));
}
// `DO p_cod+"vx_prios.prg"`: measured, a name with a `+` right against it is an
// expression that builds the program's name, and the same line written with spaces
// round the `+` is a syntax error in the product - so only the unspaced form is read so
if let TokKind::Ident(_) = self.peek_kind()
&& matches!(self.peek_at(1).kind, TokKind::Plus)
&& self.peek_at(1).span.start == self.peek().span.end
{
let name = self.expr()?;
let (args, in_prog) = self.do_clauses()?;
return Ok(Some(StmtKind::DoExpr { name, args, in_prog }));
}
let name = self.file_name("program name")?;
let (args, in_prog) = self.do_clauses()?;
Ok(Some(StmtKind::Do { name, args, in_prog }))
Expand Down Expand Up @@ -6437,6 +6448,21 @@ impl Parser {
flags: 0,
}));
}
// `RELEASE PROCEDURE a, b` takes those files off the SET PROCEDURE list. It is carried
// as a setting of its own name, which is where the list is kept.
if self.is_kw("PROCEDURE") && !self.peek_at(1).is_newline() && !matches!(self.peek_at(1).kind, TokKind::Eof) {
let tok = self.advance();
let mut files = Vec::new();
while !self.at_eol() {
files.push(self.file_name_word(tok.span, &[])?);
if !self.eat(&TokKind::Comma) {
break;
}
}
self.skip_line_keep_newline();
let setting = Name::new("RELEASE PROCEDURE", first.span.to(tok.span));
return Ok(Some(StmtKind::Set { setting, value: SetValue::To(files) }));
}
const FORMS: &[&str] = &["LIBRARY", "PROCEDURE", "CLASSLIB"];
if let Some(word) = self.peek().ident().map(|s| s.to_string()) {
let next_is_more = !self.peek_at(1).is_newline();
Expand Down Expand Up @@ -6613,6 +6639,24 @@ impl Parser {
let args = vec![Expr::new(ExprKind::Bool(additive), span), named];
return Ok(StmtKind::Set { setting, value: SetValue::To(args) });
}
// `SET PROCEDURE TO a, b [ADDITIVE]`: the files are names, as a class library's are, and
// `SET PROCEDURE TO` with nothing after it empties the list
if kw_text(&setting.text, "PROCEDURE") && self.is_kw("TO") {
let span = setting.span;
self.advance();
let mut files: Vec<Expr> = Vec::new();
while !self.at_eol() && !self.is_kw("ADDITIVE") {
files.push(self.file_name_word(span, &["ADDITIVE"])?);
if !self.eat(&TokKind::Comma) {
break;
}
}
let additive = self.eat_kw("ADDITIVE");
self.skip_line_keep_newline();
let mut args = vec![Expr::new(ExprKind::Bool(additive), span)];
args.append(&mut files);
return Ok(StmtKind::Set { setting, value: SetValue::To(args) });
}
if kw_text(&setting.text, "CLASSLIB") {
let span = setting.span;
let _ = self.eat_kw("TO");
Expand Down
2 changes: 2 additions & 0 deletions crates/foxvm/src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,8 @@ pub const REMEMBERED: &[(&str, SettingShape, &str)] = &[
("BRSTATUS", SettingShape::Switch, "OFF"),
("CARRY", SettingShape::Switch, "OFF"),
("CLASSLIB", SettingShape::Word, ""),
// the list itself is the VM's; this is only what SET() answers when nothing is on it
("PROCEDURE", SettingShape::Word, ""),
("CLEAR", SettingShape::Switch, "ON"),
("CLOCK", SettingShape::Switch, "OFF"),
("COLLATE", SettingShape::Word, "MACHINE"),
Expand Down
Loading
Loading