Skip to main content

gc/
lib.rs

1#![forbid(unsafe_code)]
2
3#[cfg(test)]
4mod debugger_test;
5#[cfg(test)]
6mod gc_test;
7#[cfg(test)]
8mod report_test;
9#[cfg(test)]
10mod runner_test;
11#[cfg(test)]
12mod value_test;
13#[cfg(test)]
14mod vm_test;
15
16pub mod debugger;
17pub mod frame;
18pub mod header;
19pub mod heap;
20pub mod list;
21pub mod malloc;
22pub mod report;
23pub mod runner;
24// The file is named gc_runtime.rs for editor clarity, but the module keeps
25// its historical public path `gc::runtime`.
26#[path = "gc_runtime.rs"]
27pub mod runtime;
28pub mod value;
29pub mod vm;
30
31pub use debugger::{
32    CaptureView, DebuggerHit, FrameView, HeapEdgeView, HeapMemberView, HeapObjectView, HeapView,
33    SlotView, StackSlotView, ValueView, MAX_DEBUGGER_DISPLAY_CHARS, MAX_DEBUGGER_EDGES,
34    MAX_DEBUGGER_HITS, MAX_DEBUGGER_MEMBERS, MAX_DEBUGGER_OBJECTS, MAX_DEBUGGER_SUMMARY_DEPTH,
35};
36pub use frame::Frame;
37pub use header::{GcId, GcObjectHeader, GcObjectType, GcPhase, RefCountHeader, RefCountId};
38pub use heap::{GcHeap, GcRef};
39pub use malloc::{MallocState, DEFAULT_GC_THRESHOLD, MALLOC_OVERHEAD};
40pub use report::{
41    EdgeRelation, FinalFate, FreeCycleStats, GcCollectionReport, GcObjectSummary, GcPhaseStats,
42    GcStatsBundle, GlobalRoot, HashKeyKind, HeapSnapshot, ObjectDecision, RestorationWitness,
43    ScanStats, TrialDecision, TrialDeletionStats, ValueKindCounts, VisitedEdge,
44};
45pub use runner::{compile_source, run_bytecode, run_bytecode_with_output};
46pub use runtime::{GcObject, GcRuntime, MarkFunc};
47pub use value::{
48    export_object, import_object, try_export_object, value_to_string, GcClosure, Value, ValueKind,
49};
50pub use vm::{
51    GcClassifiedRuntimeError, GcRuntimeError, GcRuntimeErrorKind, GcVM, DEFAULT_INSTRUCTION_BUDGET,
52};
53
54use compiler::compiler::{Bytecode, Compiler};
55use object::Object;
56use parser::ast::Node;
57use parser::lexer::token::Span;
58use serde::Serialize;
59
60#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
61#[serde(rename_all = "lowercase")]
62pub enum GcRunStage {
63    Parse,
64    Compile,
65    Runtime,
66}
67
68#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
69#[serde(rename_all = "camelCase")]
70pub struct GcRunSuccess {
71    pub result: String,
72    pub report: GcCollectionReport,
73}
74
75/// Parse, compile, or runtime failure returned by the established report API.
76#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
77#[serde(rename_all = "camelCase")]
78pub struct GcRunError {
79    pub stage: GcRunStage,
80    pub message: String,
81    pub span: Option<Span>,
82}
83
84/// Report failure with a stable, machine-readable error category.
85#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
86#[serde(rename_all = "camelCase")]
87pub struct GcClassifiedRunError {
88    pub stage: GcRunStage,
89    pub kind: String,
90    pub message: String,
91    pub span: Option<Span>,
92}
93
94impl From<GcClassifiedRunError> for GcRunError {
95    fn from(error: GcClassifiedRunError) -> Self {
96        Self {
97            stage: error.stage,
98            message: error.message,
99            span: error.span,
100        }
101    }
102}
103
104/// Compile Monkey source using the existing bytecode compiler.
105pub fn compile(program: &Node) -> Result<Bytecode, String> {
106    let mut compiler = Compiler::new();
107    compiler.compile(program)
108}
109
110/// Compile and execute on the GC-backed VM.
111pub fn eval(program: &Node) -> Result<Object, String> {
112    let bytecode = compile(program)?;
113    let mut vm = GcVM::new(bytecode);
114    vm.run_with_budget(usize::MAX)
115        .map_err(|error| error.message)?;
116    vm.try_export_last_result()
117}
118
119/// Parse, compile, and execute Monkey source.
120pub fn eval_source(source: &str) -> Result<Object, String> {
121    let program = parser::parse(source).map_err(|errors| errors[0].clone())?;
122    eval(&program)
123}
124
125/// Parse, compile, execute with deterministic GC settings, then collect cycles.
126pub fn run_source_with_report(
127    source: &str,
128    instruction_budget: usize,
129) -> Result<GcRunSuccess, GcRunError> {
130    run_source_with_report_classified(source, instruction_budget).map_err(Into::into)
131}
132
133/// Parse, compile, and execute Monkey source while classifying failures at
134/// their raise sites.
135pub fn run_source_with_report_classified(
136    source: &str,
137    instruction_budget: usize,
138) -> Result<GcRunSuccess, GcClassifiedRunError> {
139    let program = parser::parse(source).map_err(|errors| GcClassifiedRunError {
140        stage: GcRunStage::Parse,
141        kind: "syntax".to_string(),
142        message: errors
143            .first()
144            .cloned()
145            .unwrap_or_else(|| "unknown parse error".to_string()),
146        span: None,
147    })?;
148    let mut compiler = Compiler::new();
149    let bytecode = compiler
150        .compile(&program)
151        .map_err(|message| GcClassifiedRunError {
152            stage: GcRunStage::Compile,
153            kind: "compile".to_string(),
154            message,
155            span: None,
156        })?;
157    let global_bindings = compiler.global_bindings();
158    let mut vm = GcVM::new(bytecode);
159    vm.set_global_bindings(global_bindings);
160    vm.heap_mut().set_gc_threshold(usize::MAX);
161    vm.run_with_budget_classified(instruction_budget)
162        .map_err(|error| GcClassifiedRunError {
163            stage: GcRunStage::Runtime,
164            kind: error.kind.as_str().to_string(),
165            message: error.message,
166            span: error.span,
167        })?;
168    let result = vm.last_result_string();
169    let report = vm.collect_garbage();
170    Ok(GcRunSuccess {
171        result,
172        report,
173    })
174}
175
176/// Outcome of a debugger run. Unlike the report API this is not a `Result`:
177/// hits recorded before a runtime failure are part of the story the debugger
178/// tells, so the error arm carries them too.
179#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
180#[serde(rename_all = "camelCase")]
181pub enum GcDebuggerRunOutcome {
182    Ok {
183        result: String,
184        stdout: String,
185        hits: Vec<DebuggerHit>,
186        dropped_hits: usize,
187    },
188    Error {
189        error: GcClassifiedRunError,
190        stdout: String,
191        hits: Vec<DebuggerHit>,
192        dropped_hits: usize,
193    },
194}
195
196/// Parse, compile, and execute Monkey source, recording a snapshot at every
197/// `debugger;` statement. GC stays disabled (threshold `usize::MAX`) so heap
198/// ids remain stable across the hits of one run.
199pub fn run_source_with_debugger_classified(
200    input: &str,
201    instruction_budget: usize,
202) -> GcDebuggerRunOutcome {
203    let error_outcome = |stage: GcRunStage, kind: &str, message: String| {
204        return GcDebuggerRunOutcome::Error {
205            error: GcClassifiedRunError {
206                stage,
207                kind: kind.to_string(),
208                message,
209                span: None,
210            },
211            stdout: String::new(),
212            hits: Vec::new(),
213            dropped_hits: 0,
214        };
215    };
216    let program = match parser::parse(input) {
217        Ok(program) => program,
218        Err(errors) => {
219            return error_outcome(
220                GcRunStage::Parse,
221                "syntax",
222                errors
223                    .first()
224                    .cloned()
225                    .unwrap_or_else(|| "unknown parse error".to_string()),
226            );
227        }
228    };
229    let mut compiler = Compiler::new();
230    let bytecode = match compiler.compile(&program) {
231        Ok(bytecode) => bytecode,
232        Err(message) => return error_outcome(GcRunStage::Compile, "compile", message),
233    };
234    let global_bindings = compiler.global_bindings();
235    let mut vm = GcVM::new(bytecode);
236    vm.set_global_bindings(global_bindings);
237    vm.set_capture_output(true);
238    vm.heap_mut().set_gc_threshold(usize::MAX);
239    let run = vm.run_with_budget_classified(instruction_budget);
240    let stdout = vm.take_output();
241    let (hits, dropped_hits) = vm.take_debugger_hits();
242    match run {
243        Ok(()) => GcDebuggerRunOutcome::Ok {
244            result: vm.last_result_string(),
245            stdout,
246            hits,
247            dropped_hits,
248        },
249        Err(error) => GcDebuggerRunOutcome::Error {
250            error: GcClassifiedRunError {
251                stage: GcRunStage::Runtime,
252                kind: error.kind.as_str().to_string(),
253                message: error.message,
254                span: error.span,
255            },
256            stdout,
257            hits,
258            dropped_hits,
259        },
260    }
261}