Refactor environment loading and program execution
The `preload_dependencies` function has been refactored to directly run parsed programs instead of collecting all forms into a single `Program` node. This simplifies the compilation pipeline by allowing each dependency to be processed individually. Additionally, the `VM::run_with_observer` method has been updated to manage its stack and frames more cleanly, avoiding the need for `eval_in_frame`. The main loop now correctly extracts expressions from the `Program` node for source code emission.
This commit is contained in:
+26
-34
@@ -5,7 +5,7 @@ use crate::ast::compiler::{CapturePass, TypeChecker, TypedNode};
|
||||
use crate::ast::nodes::{AnalyzedPhase, BoundPhase, Symbol, SyntaxKind, SyntaxNode};
|
||||
use crate::ast::parser::Parser;
|
||||
use crate::ast::closure::Closure;
|
||||
use crate::ast::vm::{self, GlobalStore, TracingObserver, VM};
|
||||
use crate::ast::vm::{GlobalStore, TracingObserver, VM};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -334,23 +334,20 @@ impl Environment {
|
||||
/// Used to pre-load all dependencies of a script before compiling it.
|
||||
pub fn preload_dependencies(&self, source: &str, file_path: Option<&Path>) -> Result<(), String> {
|
||||
let base_path = file_path.and_then(|p| p.parent()).unwrap_or_else(|| Path::new("."));
|
||||
let mut all_forms: Vec<Rc<SyntaxNode>> = Vec::new();
|
||||
|
||||
// 1. Always load the embedded system library first as a virtual module
|
||||
let system_path = PathBuf::from(SYSTEM_LIB_PATH);
|
||||
if !self.loaded_modules.borrow().contains(&system_path) {
|
||||
self.loaded_modules.borrow_mut().insert(system_path.clone());
|
||||
let mut parser = Parser::new(SYSTEM_LIB_SOURCE);
|
||||
let forms = parser.parse_program();
|
||||
let program = parser.parse_program();
|
||||
if parser.diagnostics.has_errors() {
|
||||
return Err(format!(
|
||||
"Failed to parse embedded system library:\n{}",
|
||||
parser.diagnostics.items[0].message
|
||||
));
|
||||
}
|
||||
for form in forms {
|
||||
all_forms.push(Rc::new(form));
|
||||
}
|
||||
self.run_program(program)?;
|
||||
}
|
||||
|
||||
// 2. Collect user dependencies (file I/O + parse, topological order)
|
||||
@@ -358,43 +355,38 @@ impl Environment {
|
||||
Rc::clone(&self.search_paths),
|
||||
Rc::clone(&self.loaded_modules),
|
||||
);
|
||||
for (_, syntax_ast) in loader.collect_dependency_files(source, base_path)? {
|
||||
all_forms.push(Rc::new(syntax_ast));
|
||||
for (path, syntax_ast) in loader.collect_dependency_files(source, base_path)? {
|
||||
let compiled = self.compile_pipeline(syntax_ast, &mut Diagnostics::new())
|
||||
.ok_or_else(|| format!("Compilation error in {}", path.display()))?;
|
||||
self.run_script_compiled(compiled).map_err(|e| {
|
||||
format!("Initialization error in {}:\n{}", path.display(), e)
|
||||
})?;
|
||||
}
|
||||
|
||||
// Compile and initialize all forms as a single program (no scope boundary)
|
||||
if !all_forms.is_empty() {
|
||||
let block = SyntaxNode {
|
||||
identity: NodeIdentity::anonymous(),
|
||||
kind: SyntaxKind::Program { exprs: all_forms },
|
||||
ty: (),
|
||||
comments: Rc::from([]),
|
||||
};
|
||||
let mut diagnostics = Diagnostics::new();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// 1. Expand macros
|
||||
let expanded = self.expand(block, &mut diagnostics)
|
||||
.ok_or_else(|| diagnostics.format_errors())?;
|
||||
/// Compiles and executes a `Program` node without lambda wrapping.
|
||||
/// Definitions at the top level receive `Address::Global` and persist
|
||||
/// in the GlobalStore for subsequent compilation units.
|
||||
fn run_program(&self, program: SyntaxNode) -> Result<(), String> {
|
||||
let mut diagnostics = Diagnostics::new();
|
||||
|
||||
// 2. Bind names and update root scopes
|
||||
let bound = self.bind_and_update(&expanded, &mut diagnostics)
|
||||
.ok_or_else(|| diagnostics.format_errors())?;
|
||||
let expanded = self.expand(program, &mut diagnostics)
|
||||
.ok_or_else(|| diagnostics.format_errors())?;
|
||||
|
||||
// 3. Type-check (without lambda wrapping)
|
||||
let typed = self.type_check(&bound, &mut diagnostics);
|
||||
let bound = self.bind_and_update(&expanded, &mut diagnostics)
|
||||
.ok_or_else(|| diagnostics.format_errors())?;
|
||||
|
||||
if diagnostics.has_errors() {
|
||||
return Err(diagnostics.format_errors());
|
||||
}
|
||||
let typed = self.type_check(&bound, &mut diagnostics);
|
||||
|
||||
// 4. Link and execute
|
||||
let linked = self.link(typed);
|
||||
let mut vm = self.create_vm();
|
||||
let stack = vec![Value::Void; linked.ty.stack_size as usize];
|
||||
let result = vm.eval_in_frame(&mut vm::NoOpObserver, &linked, stack)?;
|
||||
vm.resolve_tail_calls(&mut vm::NoOpObserver, Ok(result))?;
|
||||
if diagnostics.has_errors() {
|
||||
return Err(diagnostics.format_errors());
|
||||
}
|
||||
|
||||
let linked = self.link(typed);
|
||||
let mut vm = self.create_vm();
|
||||
vm.run(&linked)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
+11
-4
@@ -119,13 +119,20 @@ impl<'a> Parser<'a> {
|
||||
matches!(self.current_token.kind, TokenKind::EOF)
|
||||
}
|
||||
|
||||
/// Parses a complete program as a sequence of top-level expressions.
|
||||
pub fn parse_program(&mut self) -> Vec<SyntaxNode> {
|
||||
/// Parses a complete program as a sequence of top-level expressions
|
||||
/// wrapped in a `Program` node.
|
||||
pub fn parse_program(&mut self) -> SyntaxNode {
|
||||
let identity = NodeIdentity::new(SourceLocation { line: 1, col: 1 });
|
||||
let mut expressions = Vec::new();
|
||||
while !self.at_eof() {
|
||||
expressions.push(self.parse_expression());
|
||||
expressions.push(Rc::new(self.parse_expression()));
|
||||
}
|
||||
SyntaxNode {
|
||||
identity,
|
||||
kind: SyntaxKind::Program { exprs: expressions },
|
||||
ty: (),
|
||||
comments: Rc::from([]),
|
||||
}
|
||||
expressions
|
||||
}
|
||||
|
||||
fn synchronize(&mut self) {
|
||||
|
||||
+10
-14
@@ -266,27 +266,23 @@ impl VM {
|
||||
self.resolve_tail_calls(observer, result)
|
||||
}
|
||||
|
||||
pub fn eval_in_frame<O: VMObserver>(&mut self, observer: &mut O, root: &ExecNode, stack: Vec<Value>) -> Result<Value, String> {
|
||||
self.stack = stack;
|
||||
self.frames.clear();
|
||||
self.frames.push(CallFrame {
|
||||
stack_base: 0,
|
||||
closure: None,
|
||||
});
|
||||
let result = self.eval_internal(observer, root);
|
||||
self.frames.pop();
|
||||
result
|
||||
}
|
||||
|
||||
pub fn run_with_observer<O: VMObserver>(
|
||||
&mut self,
|
||||
observer: &mut O,
|
||||
root: &ExecNode,
|
||||
) -> Result<Value, String> {
|
||||
let mut stack = Vec::new();
|
||||
stack.resize(root.ty.stack_size as usize, Value::Void);
|
||||
self.stack.clear();
|
||||
self.frames.clear();
|
||||
|
||||
let result = self.eval_in_frame(observer, root, stack);
|
||||
self.stack.resize(root.ty.stack_size as usize, Value::Void);
|
||||
|
||||
self.frames.push(CallFrame {
|
||||
stack_base: 0,
|
||||
closure: None,
|
||||
});
|
||||
let result = self.eval_internal(observer, root);
|
||||
self.frames.pop();
|
||||
self.resolve_tail_calls(observer, result)
|
||||
}
|
||||
|
||||
|
||||
+7
-1
@@ -2,6 +2,7 @@ use eframe::egui;
|
||||
use egui_extras::{Column, TableBuilder};
|
||||
use myc::ast::compiler::emitter;
|
||||
use myc::ast::environment::Environment;
|
||||
use myc::ast::nodes::SyntaxKind;
|
||||
use myc::ast::parser::Parser;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -198,7 +199,12 @@ impl CompilerApp {
|
||||
self.active_tab = AppTab::Output;
|
||||
return;
|
||||
}
|
||||
self.source_code = expressions.iter().map(emitter::emit).collect::<Vec<_>>().join("\n");
|
||||
let exprs = if let SyntaxKind::Program { exprs } = &expressions.kind {
|
||||
exprs.as_slice()
|
||||
} else {
|
||||
&[]
|
||||
};
|
||||
self.source_code = exprs.iter().map(|e| emitter::emit(e)).collect::<Vec<_>>().join("\n");
|
||||
self.output_log = "Roundtrip complete — source reformatted.".to_string();
|
||||
self.active_tab = AppTab::Output;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user