Files
RustAst/src/ast/environment.rs
T
Brummel 99b540c84e Refactor series implementation into its own module
This commit reorganizes the series-related code by moving the
`RingBuffer`, `ScalarSeries`, `ValueSeries`, `RecordSeries`,
`SeriesView`, and `SharedSeries` structs into a new
`src/ast/rtl/series/data.rs` file.

The `src/ast/rtl/series/mod.rs` file now contains the module
declaration, re-exports, and the `register` function for registering
series-related native functions with the environment. This improves code
organization and maintainability.
2026-03-22 21:22:56 +01:00

845 lines
31 KiB
Rust

use crate::ast::compiler::analyzer::Analyzer;
use crate::ast::compiler::binder::{Binder, CompilerScope, LocalInfo};
use crate::ast::compiler::{CapturePass, TypeChecker, TypedNode};
use crate::ast::nodes::{AnalyzedPhase, Symbol, SyntaxKind, SyntaxNode};
use crate::ast::parser::Parser;
use crate::ast::closure::Closure;
use crate::ast::vm::{TracingObserver, VM};
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf};
use std::rc::Rc;
use crate::ast::nodes::{
Address, AnalyzedNode, ExecNode, GlobalAnalyzedRegistry, GlobalFunctionRegistry, GlobalIdx,
LambdaBinding, Node, NodeKind, VirtualId,
};
use crate::ast::compiler::dumper::Dumper;
use crate::ast::compiler::lambda_collector::LambdaCollector;
use crate::ast::compiler::lowering::Lowering;
use crate::ast::compiler::macros::{MacroEvaluator, MacroExpander, MacroRegistry};
use crate::ast::compiler::optimizer::Optimizer;
use crate::ast::compiler::specializer::{FunctionRegistry, MonoCache, RtlLookupFunc, Specializer};
use crate::ast::rtl::{self, intrinsics};
use crate::ast::types::{
NativeFunction, NodeIdentity, Purity, SourceLocation, StaticType, Value,
};
use crate::ast::diagnostics::{DiagnosticLevel, Diagnostics};
pub type PipelineGenerator = Box<dyn FnMut() -> bool>;
const SYSTEM_LIB_SOURCE: &str = include_str!("rtl/prelude.myc");
const SYSTEM_LIB_PATH: &str = "<embedded>/prelude.myc";
fn make_rtl_lookup() -> RtlLookupFunc {
Rc::new(|name: &str, args: &[StaticType]| intrinsics::lookup(name, args))
}
pub struct CompilationResult {
pub ast: Option<TypedNode>,
pub diagnostics: Diagnostics,
}
impl CompilationResult {
pub fn success(ast: TypedNode) -> Self {
Self {
ast: Some(ast),
diagnostics: Diagnostics::new(),
}
}
pub fn error(msg: impl Into<String>) -> Self {
let mut diag = Diagnostics::new();
diag.push_error(msg, None);
Self {
ast: None,
diagnostics: diag,
}
}
pub fn into_result(self) -> Result<TypedNode, String> {
if self.diagnostics.has_errors() {
let errors: Vec<String> = self
.diagnostics
.items
.into_iter()
.filter(|d| d.level == DiagnosticLevel::Error)
.map(|d| d.message)
.collect();
Err(errors.join("\n"))
} else if let Some(ast) = self.ast {
Ok(ast)
} else {
Err("Compilation failed without diagnostics".to_string())
}
}
}
pub struct Environment {
pub root_types: Rc<RefCell<Vec<StaticType>>>,
pub root_purity: Rc<RefCell<Vec<Purity>>>,
pub root_values: Rc<RefCell<Vec<Value>>>,
pub fixed_scope_idx: i32,
pub root_scopes: Rc<RefCell<Vec<CompilerScope>>>,
pub root_slot_count: Rc<RefCell<u32>>,
pub function_registry: Rc<RefCell<GlobalFunctionRegistry>>,
pub typed_function_registry: Rc<RefCell<GlobalAnalyzedRegistry>>,
pub monomorph_cache: Rc<RefCell<MonoCache>>,
pub debug_mode: bool,
pub optimization: bool,
pub macro_registry: Rc<RefCell<MacroRegistry>>,
pub pipeline_generators: Rc<RefCell<Vec<PipelineGenerator>>>,
pub search_paths: Rc<RefCell<Vec<PathBuf>>>,
pub loaded_modules: Rc<RefCell<HashSet<PathBuf>>>,
}
struct EnvFunctionRegistry {
analyzed_registry: Rc<RefCell<GlobalAnalyzedRegistry>>,
}
impl FunctionRegistry for EnvFunctionRegistry {
fn resolve(&self, addr: Address<VirtualId>) -> Option<Rc<Node<AnalyzedPhase>>> {
if let Address::Global(idx) = addr {
self.analyzed_registry.borrow().get(&idx).cloned()
} else {
None
}
}
}
struct RuntimeMacroEvaluator {
root_scopes: Rc<RefCell<Vec<CompilerScope>>>,
root_slot_count: Rc<RefCell<u32>>,
root_types: Rc<RefCell<Vec<StaticType>>>,
root_values: Rc<RefCell<Vec<Value>>>,
root_purity: Rc<RefCell<Vec<Purity>>>,
fixed_scope_idx: i32,
}
impl MacroEvaluator for RuntimeMacroEvaluator {
fn evaluate(
&self,
node: &SyntaxNode,
bindings: &HashMap<Rc<str>, SyntaxNode>,
) -> Result<Value, String> {
if let SyntaxKind::Identifier { symbol: sym, .. } = &node.kind
&& let Some(arg_node) = bindings.get(&sym.name)
{
return Ok(Value::Quote(Rc::new(arg_node.clone())));
}
let mut diag = Diagnostics::new();
let initial_scopes = self.root_scopes.borrow().clone();
let initial_slot_count = *self.root_slot_count.borrow();
let (bound_ast, captures, _, _) = Binder::bind_root(initial_scopes, initial_slot_count, self.fixed_scope_idx, node, &mut diag)?;
let bound_ast = CapturePass::apply(bound_ast, &captures);
let checker = TypeChecker::new(self.root_types.clone());
let typed_ast = checker.check(&bound_ast, &[], &mut diag);
if diag.has_errors() {
return Err(diag
.items
.into_iter()
.map(|d| d.message)
.collect::<Vec<_>>()
.join("\n"));
}
let exec_ast = Lowering::lower(Analyzer::analyze(&typed_ast, &self.root_purity.borrow())); // Minimal analysis for macro eval
let mut vm = VM::new(self.root_values.clone());
vm.run(&exec_ast)
}
}
impl Default for Environment {
fn default() -> Self {
Self::new()
}
}
impl Environment {
pub fn new() -> Self {
let env = Self {
root_types: Rc::new(RefCell::new(Vec::new())),
root_purity: Rc::new(RefCell::new(Vec::new())),
root_values: Rc::new(RefCell::new(Vec::new())),
fixed_scope_idx: -1,
root_scopes: Rc::new(RefCell::new(vec![CompilerScope::new()])),
root_slot_count: Rc::new(RefCell::new(0)),
function_registry: Rc::new(RefCell::new(HashMap::new())),
typed_function_registry: Rc::new(RefCell::new(HashMap::new())),
monomorph_cache: Rc::new(RefCell::new(HashMap::new())),
debug_mode: false,
optimization: true,
macro_registry: Rc::new(RefCell::new(MacroRegistry::new())),
pipeline_generators: Rc::new(RefCell::new(Vec::new())),
search_paths: Rc::new(RefCell::new(Vec::new())),
loaded_modules: Rc::new(RefCell::new(HashSet::new())),
};
rtl::register(&env);
let mut env = env;
env.fixed_scope_idx = 0;
// Push the first mutable user scope (Level 1)
env.root_scopes.borrow_mut().push(CompilerScope::new());
// Automatically add standard search paths (CWD and CWD/rtl)
if let Ok(cwd) = std::env::current_dir() {
env.add_search_path(&cwd);
let rtl_path = cwd.join("rtl");
if rtl_path.exists() {
env.add_search_path(rtl_path);
}
}
env
}
pub fn add_search_path(&self, path: impl AsRef<Path>) {
self.search_paths.borrow_mut().push(path.as_ref().to_path_buf());
}
pub fn set_debug_mode(&mut self, enabled: bool) {
self.debug_mode = enabled;
}
/// Pumps data through all registered pipeline generators until they are exhausted.
pub fn run_pipeline(&self) {
let mut generators = self.pipeline_generators.borrow_mut();
let mut any_active = true;
while any_active {
any_active = false;
for generator in generators.iter_mut() {
if generator() {
any_active = true;
}
}
}
}
fn get_expander(&self) -> MacroExpander<RuntimeMacroEvaluator> {
let evaluator = RuntimeMacroEvaluator {
root_scopes: self.root_scopes.clone(),
root_slot_count: self.root_slot_count.clone(),
root_types: self.root_types.clone(),
root_values: self.root_values.clone(),
root_purity: self.root_purity.clone(),
fixed_scope_idx: self.fixed_scope_idx,
};
MacroExpander::new(self.macro_registry.borrow().clone(), evaluator)
}
/// Resolves a #use module path relative to a base path, then falls back to search paths.
/// Returns a list of all matching .myc files (single file, or all files in a directory).
fn resolve_module_paths(&self, module_path: &str, base_path: &Path) -> Result<Vec<PathBuf>, String> {
let relative_path = module_path.replace("->", std::path::MAIN_SEPARATOR_STR);
let file_name = format!("{}.myc", relative_path);
let check_path = |base: &Path| -> Option<Vec<PathBuf>> {
// Check for file first
let mut file_p = base.to_path_buf();
file_p.push(&file_name);
if file_p.is_file() && let Ok(canon) = file_p.canonicalize() {
return Some(vec![canon]);
}
// Check for directory
let mut dir_p = base.to_path_buf();
dir_p.push(&relative_path);
if dir_p.is_dir() {
let mut files = Vec::new();
if let Ok(entries) = std::fs::read_dir(&dir_p) {
let mut valid_entries: Vec<_> = entries.filter_map(|e| e.ok()).collect();
valid_entries.sort_by_key(|e| e.path());
for entry in valid_entries {
let p = entry.path();
if p.is_file() && p.extension().is_some_and(|ext| ext == "myc") && let Ok(canon) = p.canonicalize() {
files.push(canon);
}
}
}
if !files.is_empty() {
return Some(files);
}
}
None
};
// 1. Try relative to base_path
if let Some(paths) = check_path(base_path) {
return Ok(paths);
}
// 2. Try search paths
for sp in self.search_paths.borrow().iter() {
if let Some(paths) = check_path(sp) {
return Ok(paths);
}
}
Err(format!("Could not find module or directory '{}'", module_path))
}
fn collect_dependencies(
&self,
source: &str,
base_path: &Path,
all_files: &mut Vec<(PathBuf, SyntaxNode)>,
) -> Result<(), String> {
let directives = self.extract_use_directives(source);
for module_path in directives {
let abs_paths = self.resolve_module_paths(&module_path, base_path)?;
for abs_path in abs_paths {
if self.loaded_modules.borrow().contains(&abs_path) {
continue;
}
self.loaded_modules.borrow_mut().insert(abs_path.clone());
let lib_source = std::fs::read_to_string(&abs_path)
.map_err(|e| format!("Failed to read module {:?}: {}", abs_path, e))?;
// Pre-process dependencies of the library BEFORE adding it, ensuring topological order
let lib_base = abs_path.parent().unwrap_or(Path::new("."));
self.collect_dependencies(&lib_source, lib_base, all_files)?;
let mut parser = Parser::new(&lib_source);
let syntax_ast = parser.parse_expression();
if parser.diagnostics.has_errors() {
return Err(format!(
"Parser error in module {:?}:\n{}",
abs_path,
parser.diagnostics.items[0].message
));
}
all_files.push((abs_path, syntax_ast));
}
}
Ok(())
}
fn extract_use_directives(&self, source: &str) -> Vec<String> {
let mut paths = Vec::new();
for line in source.lines() {
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with(';') {
continue;
}
if let Some(stripped) = trimmed.strip_prefix("#use ") {
let path = stripped.trim();
// Strip optional quotes if they somehow got in, though user spec says no spaces/quotes.
let clean_path = if (path.starts_with('"') && path.ends_with('"'))
|| (path.starts_with('\'') && path.ends_with('\''))
{
&path[1..path.len() - 1]
} else {
path
};
paths.push(clean_path.to_string());
} else {
// Stop at first non-directive/non-comment line
break;
}
}
paths
}
/// Used to pre-load all dependencies of a script before compiling it.
/// It returns the base path which can be used to resolve further things if necessary.
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 files: Vec<(PathBuf, 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 syntax_ast = parser.parse_expression();
if parser.diagnostics.has_errors() {
return Err(format!(
"Failed to parse embedded system library:\n{}",
parser.diagnostics.items[0].message
));
}
files.push((system_path, syntax_ast));
}
// 2. Collect dependencies from the main source
self.collect_dependencies(source, base_path, &mut files)?;
// Pass 1: Discovery (Globals and Macros)
for (_, syntax_ast) in &files {
self.discover_globals(syntax_ast);
}
// Pass 2: Compilation and Initialization
for (path, syntax_ast) in files {
let typed_ast = self.compile_syntax(syntax_ast).map_err(|e: String| {
format!("Compilation error in {}:\n{}", path.display(), e)
})?;
self.run_script_compiled(typed_ast).map_err(|e: String| {
format!("Initialization error in {}:\n{}", path.display(), e)
})?;
}
Ok(())
}
fn discover_globals(&self, node: &SyntaxNode) {
match &node.kind {
SyntaxKind::Def { pattern, .. } => {
if let SyntaxKind::Identifier { symbol: sym, .. } = &pattern.kind {
let mut root_scopes = self.root_scopes.borrow_mut();
let last_idx = root_scopes.len() - 1;
let current_scope = &mut root_scopes[last_idx];
if !current_scope.locals.contains_key(sym) {
let mut slot_count = self.root_slot_count.borrow_mut();
let slot = VirtualId(*slot_count);
current_scope.locals.insert(
sym.clone(),
LocalInfo {
addr: Address::Local(slot),
identity: node.identity.clone(),
_ty: StaticType::Any,
purity: Purity::Impure,
},
);
*slot_count += 1;
}
}
}
SyntaxKind::MacroDecl { name, params, body } => {
let mut registry = self.macro_registry.borrow_mut();
fn extract_names(node: &SyntaxNode) -> Vec<Rc<str>> {
match &node.kind {
SyntaxKind::Identifier { symbol: sym, .. } => vec![sym.name.clone()],
SyntaxKind::Tuple { elements } => {
elements.iter().flat_map(|e| extract_names(e)).collect()
}
_ => vec![],
}
}
let p_names = extract_names(params);
registry.define(name.name.clone(), p_names, body.as_ref().clone());
}
SyntaxKind::Block { exprs } => {
for expr in exprs {
self.discover_globals(expr);
}
}
_ => {}
}
}
fn compile_pipeline(&self, syntax_ast: SyntaxNode, diagnostics: &mut Diagnostics) -> Option<TypedNode> {
let expanded_ast = match self.get_expander().expand(syntax_ast) {
Ok(ast) => ast,
Err(e) => {
diagnostics.push_error(e, None);
return None;
}
};
let initial_scopes = self.root_scopes.borrow().clone();
let initial_slot_count = *self.root_slot_count.borrow();
let (bound_ast, captures, final_scopes, final_slot_count) =
match Binder::bind_root(initial_scopes, initial_slot_count, self.fixed_scope_idx, &expanded_ast, diagnostics) {
Ok(res) => res,
Err(e) => {
diagnostics.push_error(e, None);
return None;
}
};
// Update environment state with new bindings from this script
*self.root_scopes.borrow_mut() = final_scopes;
*self.root_slot_count.borrow_mut() = final_slot_count;
let bound_ast = CapturePass::apply(bound_ast, &captures);
// Pre-allocate global slots to prevent out-of-bounds during specialization/optimization
{
let mut values = self.root_values.borrow_mut();
let count = *self.root_slot_count.borrow();
if (count as usize) > values.len() {
values.resize(count as usize, Value::Void);
}
}
LambdaCollector::collect(&bound_ast, &mut self.function_registry.borrow_mut());
let checker = TypeChecker::new(self.root_types.clone());
let wrapped_ast = if let NodeKind::Lambda { .. } = bound_ast.kind {
bound_ast
} else {
Node {
identity: bound_ast.identity.clone(),
kind: NodeKind::Lambda {
params: std::rc::Rc::new(Node {
identity: bound_ast.identity.clone(),
kind: NodeKind::Tuple { elements: vec![] },
ty: (),
}),
body: std::rc::Rc::new(bound_ast),
info: LambdaBinding {
upvalues: vec![],
positional_count: Some(0),
},
},
ty: (),
}
};
Some(checker.check(&wrapped_ast, &[], diagnostics))
}
fn compile_syntax(&self, syntax_ast: SyntaxNode) -> Result<TypedNode, String> {
let mut diagnostics = Diagnostics::new();
let typed_ast_opt = self.compile_pipeline(syntax_ast, &mut diagnostics);
if diagnostics.has_errors() || typed_ast_opt.is_none() {
return Err(diagnostics
.items
.into_iter()
.map(|d| d.message)
.collect::<Vec<_>>()
.join("\n"));
}
Ok(typed_ast_opt.unwrap())
}
pub fn register_native(
&self,
name: &str,
ty: StaticType,
func: Rc<NativeFunction>,
) {
let mut types = self.root_types.borrow_mut();
let mut values = self.root_values.borrow_mut();
let mut purity = self.root_purity.borrow_mut();
let identity = NodeIdentity::new(SourceLocation { line: 0, col: 0 });
// populate root_scopes[0]
let mut root_scopes = self.root_scopes.borrow_mut();
let mut slot_count = self.root_slot_count.borrow_mut();
let global_idx = GlobalIdx(*slot_count);
root_scopes[0].locals.insert(
Symbol::from(name),
LocalInfo {
addr: Address::Global(global_idx),
identity,
_ty: ty.clone(),
purity: func.purity,
},
);
*slot_count += 1;
types.push(ty);
purity.push(func.purity);
values.push(Value::Function(func));
}
pub fn register_native_fn(
&self,
name: &str,
ty: StaticType,
purity_level: Purity,
func: impl Fn(&[Value]) -> Value + 'static,
) {
self.register_native(
name,
ty,
Rc::new(NativeFunction {
func: Rc::new(func),
purity: purity_level,
}),
);
}
pub fn register_constant(&self, name: &str, ty: StaticType, val: Value) {
let mut types = self.root_types.borrow_mut();
let mut values = self.root_values.borrow_mut();
let mut purity = self.root_purity.borrow_mut();
let identity = NodeIdentity::new(SourceLocation { line: 0, col: 0 });
// populate root_scopes[0]
let mut root_scopes = self.root_scopes.borrow_mut();
let mut slot_count = self.root_slot_count.borrow_mut();
let global_idx = GlobalIdx(*slot_count);
root_scopes[0].locals.insert(
Symbol::from(name),
LocalInfo {
addr: Address::Global(global_idx),
identity,
_ty: ty.clone(),
purity: Purity::Pure,
},
);
*slot_count += 1;
types.push(ty);
purity.push(Purity::Pure);
values.push(val);
}
pub fn dump_ast(&self, source: &str) -> Result<String, String> {
self.preload_dependencies(source, None)?;
let compiled = self.compile(source).into_result()?;
let linked = self.link(compiled);
Ok(Dumper::dump(&linked))
}
pub fn compile(&self, source: &str) -> CompilationResult {
if let Err(e) = self.preload_dependencies(source, None) {
return CompilationResult::error(format!("Dependency Error: {}", e));
}
let mut parser = Parser::new(source);
let syntax_ast = parser.parse_expression();
if !parser.at_eof() {
parser
.diagnostics
.push_error("Unexpected trailing expressions in script.", None);
return CompilationResult {
ast: None,
diagnostics: parser.diagnostics,
};
}
let mut diagnostics = parser.diagnostics;
let typed_ast = self.compile_pipeline(syntax_ast, &mut diagnostics);
CompilationResult {
ast: typed_ast,
diagnostics,
}
}
pub fn link(&self, node: TypedNode) -> ExecNode {
// 1. Analyze
let analyzed = Analyzer::analyze(&node, &self.root_purity.borrow());
// 2. Collect Analyzed Lambdas
LambdaCollector::collect(&analyzed, &mut self.typed_function_registry.borrow_mut());
// 3. Specialize
let specialized = self.specialize_node(analyzed);
// 4. Optimize
let optimizer = Optimizer::new(self.optimization)
.with_globals(self.root_values.clone())
.with_purity(self.root_purity.clone())
.with_registry(self.typed_function_registry.clone());
let optimized = optimizer.optimize(specialized);
// 5. Lowering
Lowering::lower(optimized)
}
pub fn instantiate(&self, node: ExecNode) -> Rc<NativeFunction> {
let root_values = self.root_values.clone();
if let NodeKind::Lambda {
params,
body,
info,
} = &node.kind
&& info.upvalues.is_empty()
{
let closure = Rc::new(Closure::new(
params.clone(),
body.ty.original.clone(),
body.clone(),
Vec::new(),
info.positional_count,
node.ty.stack_size,
));
return Rc::new(NativeFunction {
purity: Purity::Impure,
func: Rc::new(move |args| {
let mut vm = VM::new(root_values.clone());
match vm.run_with_args(closure.clone(), args) {
Ok(v) => v,
Err(e) => panic!("Myc Runtime Error: {}", e),
}
}),
});
}
let exec_node = Rc::new(node);
Rc::new(NativeFunction {
purity: Purity::Impure,
func: Rc::new(move |args| {
let mut vm = VM::new(root_values.clone());
let res = match vm.run(&exec_node) {
Ok(v) => v,
Err(e) => panic!("Myc Runtime Error: {}", e),
};
if let Value::Closure(obj) = &res {
match vm.run_with_args(obj.clone(), args) {
Ok(v) => v,
Err(e) => panic!("Myc Runtime Error (Closure): {}", e),
}
} else {
res
}
}),
})
}
fn specialize_node(&self, node: AnalyzedNode) -> AnalyzedNode {
let registry = Rc::new(EnvFunctionRegistry {
analyzed_registry: self.typed_function_registry.clone(),
});
let rtl_lookup = make_rtl_lookup();
let typed_reg = self.typed_function_registry.clone();
let mono_cache = self.monomorph_cache.clone();
let root_values = self.root_values.clone();
let root_types = self.root_types.clone();
let root_purity = self.root_purity.clone();
let optimization = self.optimization;
let compiler = Rc::new(
move |func_template: Rc<Node<AnalyzedPhase>>,
arg_types: &[StaticType]|
-> Result<(Value, StaticType), String> {
let mut diag = Diagnostics::new();
let checker = TypeChecker::new(root_types.clone());
// For specialization, we re-type-check the analyzed node.
// Note: AnalyzedNode.ty.original is the TypedNode.
// But TypeChecker expects Node<BoundPhase>. We need to go from TypedNode -> BoundNode.
// However, since TypedNode is just Node<TypedPhase>, we can't easily "un-type" it.
// Instead, we access the original AST from the binder if we had it,
// OR we make the TypeChecker generic.
// For now, we assume the TypedNode can be used where a BoundNode is expected
// if we strip the metadata.
// A better way is to store the BoundNode in NodeMetrics as well.
// Temporary fix: Re-binding from source would be too expensive.
// Let's assume for now that we can specialize directly on the TypedNode
// or that we need a small helper to transform TypedNode -> BoundNode.
// Realizing that TypedNode (StaticType) is very similar to BoundNode (()),
// we can just use the internal transform.
let retyped_ast = checker.check_node_as_bound(func_template.ty.original.as_ref(), arg_types, &mut diag);
if diag.has_errors() {
return Err(diag
.items
.into_iter()
.map(|d| d.message)
.collect::<Vec<_>>()
.join("\n"));
}
let analyzed = Analyzer::analyze(&retyped_ast, &root_purity.borrow());
let sub_registry = Rc::new(EnvFunctionRegistry {
analyzed_registry: typed_reg.clone(),
});
let sub_rtl_lookup = make_rtl_lookup();
let sub_specializer = Specializer::new(
Some(sub_registry),
None,
Some(sub_rtl_lookup),
Some(mono_cache.clone()),
);
let specialized_ast = sub_specializer.specialize(analyzed);
let optimizer = Optimizer::new(optimization)
.with_globals(root_values.clone())
.with_purity(root_purity.clone());
let optimized_ast = optimizer.optimize(specialized_ast);
let exec_ast = Lowering::lower(optimized_ast);
let mut vm = VM::new(root_values.clone());
let compiled_val = match vm.run(&exec_ast) {
Ok(v) => v,
Err(e) => return Err(format!("VM Error during specialization: {}", e)),
};
let ret_type = exec_ast.ty.ty.clone();
Ok((compiled_val, ret_type))
},
);
let specializer = Specializer::new(
Some(registry),
Some(compiler),
Some(rtl_lookup),
Some(self.monomorph_cache.clone()),
);
specializer.specialize(node)
}
pub fn run_script(&self, source: &str) -> Result<Value, String> {
self.preload_dependencies(source, None)?;
if self.debug_mode {
let (res, logs) = self.run_debug(source)?;
for line in logs {
println!("{}", line);
}
res
} else {
self.compile(source)
.into_result()
.and_then(|ast| self.run_script_compiled(ast))
}
}
pub fn run_script_compiled(&self, compiled: TypedNode) -> Result<Value, String> {
let linked = self.link(compiled);
let func = self.instantiate(linked);
let res = (func.func)(&[]);
self.run_pipeline();
Ok(res)
}
pub fn run_debug(&self, source: &str) -> Result<(Result<Value, String>, Vec<String>), String> {
self.preload_dependencies(source, None)?;
let compiled = self.compile(source).into_result()?;
let linked = self.link(compiled);
let mut vm = VM::new(self.root_values.clone());
let mut observer = TracingObserver::new();
// 1. Run the script wrapper (returns a closure representing the script)
let result = vm.run_with_observer(&mut observer, &linked);
// 2. Execute the root closure immediately to get the actual script result.
// All Myc scripts are wrapped in a parameterless lambda for consistency.
let mut final_result = result;
if let Ok(Value::Closure(obj)) = &final_result {
final_result = vm.run_with_args_observed(&mut observer, obj.clone(), &[]);
}
self.run_pipeline();
Ok((final_result, observer.logs))
}
}