use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::Arc;
use doge_compiler as dc;
use doge_runtime::{DogeError, DogeResult, ErrorKind, Value};
mod analyze;
mod call;
mod exec;
mod expr;
mod natives;
mod pack;
#[cfg(test)]
mod tests;
pub use dc::{ClassInfo, ReplParse, SessionScope};
type Cell = doge_runtime::Cell;
type Scope = Rc<RefCell<HashMap<String, Cell>>>;
fn scope() -> Scope {
Rc::new(RefCell::new(HashMap::new()))
}
fn cell(value: Value) -> Cell {
Rc::new(RefCell::new(value))
}
#[derive(Clone, Copy)]
enum ModuleRef {
Stdlib(&'static dc::Module),
User(u32),
}
struct FileScope {
globals: Scope,
imports: HashMap<String, ModuleRef>,
}
struct Template {
name: String,
file_id: u32,
params: dc::Params,
body: Rc<[dc::Stmt]>,
capture_names: Vec<String>,
method_class: Option<u32>,
}
struct Native {
name: String,
runtime_fn: &'static str,
arity: Arity,
}
#[derive(Clone, Copy)]
enum Arity {
Exact(usize),
OneOrTwo,
ZeroOrOne,
}
enum Callable {
User(Template),
Native(Native),
Ctor(u32),
}
struct ClassData {
name: String,
file_id: u32,
parent: Option<u32>,
methods: HashMap<String, usize>,
ctor_fn_id: usize,
}
enum Flow {
Normal,
Return(Value),
Break,
Continue,
}
pub struct Interp {
callables: Vec<Rc<Callable>>,
classes: Vec<Rc<ClassData>>,
file_scopes: Vec<FileScope>,
file_funcs: Vec<HashMap<String, usize>>,
file_class_ids: Vec<HashMap<String, u32>>,
builtin_ids: HashMap<String, usize>,
module_fn_ids: HashMap<(String, String), usize>,
file_paths: Vec<Rc<str>>,
depth: usize,
cur_fid: u32,
cur_line: u32,
current_method_class: Option<u32>,
pending_module_error: Option<DogeError>,
session_consts: Vec<String>,
program: Option<Arc<dc::Program>>,
}
pub fn run_program(program: Arc<dc::Program>) -> DogeResult<()> {
let mut interp = Interp::new();
interp.run(program)
}
impl Default for Interp {
fn default() -> Self {
Interp::new()
}
}
impl Interp {
pub fn run(&mut self, program: Arc<dc::Program>) -> DogeResult<()> {
self.program = Some(program.clone());
self.integrate_program(&program);
self.run_entry(&program)
}
pub fn prepare(&mut self, program: Arc<dc::Program>) -> DogeResult<()> {
self.program = Some(program.clone());
self.integrate_program(&program);
match self.pending_module_error.take() {
Some(err) => Err(err),
None => Ok(()),
}
}
pub fn error_site(&self) -> (usize, u32) {
(self.cur_fid as usize, self.cur_line)
}
pub fn new() -> Interp {
let mut interp = Interp {
callables: Vec::new(),
classes: Vec::new(),
file_scopes: vec![FileScope {
globals: scope(),
imports: HashMap::new(),
}],
file_funcs: vec![HashMap::new()],
file_class_ids: vec![HashMap::new()],
builtin_ids: HashMap::new(),
module_fn_ids: HashMap::new(),
file_paths: vec![Rc::from("<repl>")],
depth: 0,
cur_fid: 0,
cur_line: 0,
current_method_class: None,
pending_module_error: None,
session_consts: Vec::new(),
program: None,
};
interp.register_natives();
interp
}
fn globals(&self, fid: u32) -> Scope {
self.file_scopes[fid as usize].globals.clone()
}
fn import_ref(&self, fid: u32, name: &str) -> Option<ModuleRef> {
self.file_scopes[fid as usize].imports.get(name).copied()
}
fn lookup(&self, frame: &Scope, fid: u32, name: &str) -> Option<Cell> {
if let Some(c) = frame.borrow().get(name) {
return Some(c.clone());
}
self.file_scopes[fid as usize]
.globals
.borrow()
.get(name)
.cloned()
}
fn class_id_in(&self, fid: u32, name: &str) -> Option<u32> {
self.file_class_ids[fid as usize].get(name).copied()
}
fn mark(&mut self, fid: u32, span: dc::Span) {
self.cur_fid = fid;
self.cur_line = span.line;
}
fn cur_path(&self) -> Rc<str> {
self.file_paths
.get(self.cur_fid as usize)
.cloned()
.unwrap_or_else(|| Rc::from("<repl>"))
}
fn integrate_program(&mut self, program: &dc::Program) {
for file in &program.files {
let fid = file.file_id as usize;
while self.file_scopes.len() <= fid {
self.file_scopes.push(FileScope {
globals: scope(),
imports: HashMap::new(),
});
self.file_funcs.push(HashMap::new());
self.file_class_ids.push(HashMap::new());
self.file_paths.push(Rc::from("<repl>"));
}
self.file_paths[fid] = Rc::from(file.path.as_str());
self.resolve_imports(file);
}
for file in &program.files {
self.analyze_file(&file.script.stmts, file.file_id);
self.hoist_file(&file.script.stmts, file.file_id);
}
for &fid in &program.init_order {
let stmts = program.files[fid as usize].script.stmts.clone();
if let Err(err) = self.init_module(&stmts, fid) {
self.pending_module_error = Some(err);
break;
}
}
}
fn resolve_imports(&mut self, file: &dc::ProgramFile) {
let fid = file.file_id as usize;
for (name, module) in &file.stdlib_imports {
self.file_scopes[fid]
.imports
.insert(name.clone(), ModuleRef::Stdlib(module));
}
for (name, target) in &file.user_imports {
self.file_scopes[fid]
.imports
.insert(name.clone(), ModuleRef::User(*target));
}
}
fn hoist_file(&mut self, stmts: &[dc::Stmt], fid: u32) {
let globals = self.globals(fid);
for name in dc::hoisted_names(stmts) {
globals
.borrow_mut()
.entry(name)
.or_insert_with(|| cell(Value::None));
}
self.hoist_functions(stmts, &globals, fid);
}
fn init_module(&mut self, stmts: &[dc::Stmt], fid: u32) -> DogeResult<()> {
let globals = self.globals(fid);
for stmt in stmts {
if matches!(stmt, dc::Stmt::ConstDecl { .. }) {
self.exec_stmt(stmt, &globals, fid)?;
}
}
Ok(())
}
fn run_entry(&mut self, program: &dc::Program) -> DogeResult<()> {
if let Some(err) = self.pending_module_error.take() {
return Err(err);
}
let entry = &program.files[0];
let globals = self.globals(0);
for stmt in &entry.script.stmts {
if matches!(
stmt,
dc::Stmt::FuncDef { .. } | dc::Stmt::ObjDef { .. } | dc::Stmt::Import { .. }
) {
continue;
}
match self.exec_stmt(stmt, &globals, 0)? {
Flow::Normal => {}
_ => break,
}
}
Ok(())
}
pub fn session_scope(&self) -> SessionScope {
let mut globals: Vec<String> = self.file_scopes[0]
.globals
.borrow()
.keys()
.cloned()
.collect();
globals.extend(self.file_scopes[0].imports.keys().cloned());
globals.extend(self.file_class_ids[0].keys().cloned());
let classes = self.file_class_ids[0]
.iter()
.map(|(name, id)| {
let data = &self.classes[*id as usize];
ClassInfo {
name: name.clone(),
parent: data.parent.map(|p| self.classes[p as usize].name.clone()),
methods: data.methods.keys().cloned().collect(),
}
})
.collect();
SessionScope {
globals,
consts: self.session_consts.clone(),
classes,
}
}
pub fn eval_snippet(&mut self, path: &str, script: &dc::Script) -> DogeResult<Option<Value>> {
self.file_paths[0] = Rc::from(path);
for stmt in &script.stmts {
if let dc::Stmt::Import { module, .. } = stmt {
self.register_repl_import(module)?;
}
}
self.analyze_file(&script.stmts, 0);
self.hoist_file(&script.stmts, 0);
let globals = self.globals(0);
let count = script.stmts.len();
for (i, stmt) in script.stmts.iter().enumerate() {
match stmt {
dc::Stmt::FuncDef { .. } | dc::Stmt::ObjDef { .. } | dc::Stmt::Import { .. } => {}
dc::Stmt::ExprStmt { expr } if i + 1 == count => {
self.mark(0, expr.span());
return Ok(Some(self.eval(expr, &globals, 0)?));
}
dc::Stmt::ConstDecl { name, .. } => {
self.exec_stmt(stmt, &globals, 0)?;
if !self.session_consts.iter().any(|c| c == name) {
self.session_consts.push(name.clone());
}
}
_ => {
self.exec_stmt(stmt, &globals, 0)?;
}
}
}
Ok(None)
}
fn register_repl_import(&mut self, module: &str) -> DogeResult<()> {
if self.file_scopes[0].imports.contains_key(module) {
return Ok(());
}
match dc::stdlib_module(module) {
Some(m) => {
self.file_scopes[0]
.imports
.insert(module.to_string(), ModuleRef::Stdlib(m));
Ok(())
}
None => Err(DogeError::new(
ErrorKind::ValueError,
format!(
"user modules aren't available in the repl yet — run the file instead (doge bark <script>.doge) to use {module}"
),
)),
}
}
}