pub(super) use std::cell::{Cell, RefCell};
pub(super) use std::collections::{HashMap, HashSet};
pub(super) use crate::ast::{
child_funcdefs, hoisted_names, toplevel_hoisted, BinOp, Expr, InterpPart, Params, Stmt, UnOp,
};
pub(super) use crate::builtins::{BuiltinFn, BuiltinShape};
pub(super) use crate::diagnostics::Diagnostic;
pub(super) use crate::modules::Program;
pub(super) use crate::stdlib::Module;
pub(super) use crate::token::Span;
mod analysis;
mod callable;
mod calls;
mod dispatch;
mod expr;
mod names;
mod program;
mod stmt;
#[cfg(test)]
mod tests;
use analysis::*;
use names::*;
pub fn generate_program(program: &Program) -> Result<String, Diagnostic> {
let files = program
.files
.iter()
.map(|f| FileText {
path: f.path.clone(),
lines: crate::diagnostics::split_source_lines(&f.source),
})
.collect();
let codegen = Codegen {
files,
multifile: program.files.len() > 1,
cur: Cell::new(0),
};
codegen.program(program)
}
struct FileText {
path: String,
lines: Vec<String>,
}
pub(super) fn binop_call(op: BinOp) -> &'static str {
match op {
BinOp::Add => "add",
BinOp::Sub => "sub",
BinOp::Mul => "mul",
BinOp::Div => "div",
BinOp::FloorDiv => "floordiv",
BinOp::Rem => "rem",
BinOp::Pow => "pow",
BinOp::BitAnd => "bitand",
BinOp::BitOr => "bitor",
BinOp::BitXor => "bitxor",
BinOp::Shl => "shl",
BinOp::Shr => "shr",
BinOp::Eq => "eq",
BinOp::NotEq => "ne",
BinOp::Lt => "lt",
BinOp::LtEq => "le",
BinOp::Gt => "gt",
BinOp::GtEq => "ge",
BinOp::In => "in_",
BinOp::NotIn => "not_in",
BinOp::And | BinOp::Or => {
unreachable!("compiler bug: and/or are emitted inline, not as a call")
}
}
}
struct Codegen {
files: Vec<FileText>,
multifile: bool,
cur: Cell<u32>,
}
static EMPTY_PARAMS: Params = Params {
params: Vec::new(),
vararg: None,
};
struct Class {
file_id: u32,
name: String,
id: u32,
parent: Option<u32>,
methods: Vec<(String, Params)>,
}
impl Class {
fn own_init(&self) -> Option<&Params> {
self.methods
.iter()
.find(|(name, _)| name == "init")
.map(|(_, params)| params)
}
}
fn ancestry<'a>(classes: &'a [Class], class: &'a Class) -> Vec<&'a Class> {
let mut chain = Vec::new();
let mut seen = HashSet::new();
let mut cur = Some(class);
while let Some(c) = cur {
if !seen.insert(c.id) {
break;
}
chain.push(c);
cur = c
.parent
.and_then(|pid| classes.iter().find(|x| x.id == pid));
}
chain
}
fn effective_init<'a>(classes: &'a [Class], class: &'a Class) -> Option<(&'a Class, &'a Params)> {
ancestry(classes, class)
.into_iter()
.find_map(|c| c.own_init().map(|params| (c, params)))
}
fn init_params<'a>(classes: &'a [Class], class: &'a Class) -> &'a Params {
effective_init(classes, class)
.map(|(_, params)| params)
.unwrap_or(&EMPTY_PARAMS)
}
fn effective_methods<'a>(
classes: &'a [Class],
class: &'a Class,
) -> Vec<(&'a str, &'a Class, &'a Params)> {
let mut out: Vec<(&str, &Class, &Params)> = Vec::new();
for c in ancestry(classes, class) {
for (name, params) in &c.methods {
if !out.iter().any(|(seen, _, _)| *seen == name.as_str()) {
out.push((name.as_str(), c, params));
}
}
}
out
}
struct Emit<'a> {
tables: &'a [FileTable],
file_id: u32,
classes: &'a [Class],
current_class: Option<u32>,
analysis: &'a Analysis,
uses_method_call: Cell<bool>,
uses_call_function: Cell<bool>,
uses_attr_read: Cell<bool>,
materialized: RefCell<HashSet<u32>>,
locals: HashMap<String, Local>,
local_funcs: HashMap<String, Params>,
counter: u32,
try_stack: Vec<u32>,
loop_stack: Vec<u32>,
}
impl Emit<'_> {
fn table(&self) -> &FileTable {
&self.tables[self.file_id as usize]
}
fn class(&self, name: &str) -> Option<&Class> {
self.class_in(self.file_id, name)
}
fn class_in(&self, file_id: u32, name: &str) -> Option<&Class> {
self.classes
.iter()
.find(|c| c.file_id == file_id && c.name == name)
}
fn module(&self, name: &str) -> Option<&'static Module> {
if self.locals.contains_key(name) {
None
} else {
self.table().stdlib_imports.get(name).copied()
}
}
fn user_module(&self, name: &str) -> Option<u32> {
if self.locals.contains_key(name) {
None
} else {
self.table().user_imports.get(name).copied()
}
}
fn func_value_id(&self, name: &str) -> Option<u32> {
self.analysis
.top_func_ids
.get(&(self.file_id, name.to_string()))
.copied()
.or_else(|| self.analysis.builtin_ids.get(name).copied())
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Local {
Plain,
Cell,
}
impl Codegen {
fn enter_file(&self, emit: &mut Emit, file_id: u32) {
self.cur.set(file_id);
emit.file_id = file_id;
}
fn emit_source_tables(&self, out: &mut String) {
if !self.multifile {
out.push_str("static LINES: &[&str] = &[\n");
for line in &self.files[0].lines {
out.push_str(&format!(" \"{}\",\n", escape_str(line)));
}
out.push_str("];\n\n");
return;
}
for (id, file) in self.files.iter().enumerate() {
out.push_str(&format!("static L{id}: &[&str] = &[\n"));
for line in &file.lines {
out.push_str(&format!(" \"{}\",\n", escape_str(line)));
}
out.push_str("];\n");
}
out.push_str("static FILES: &[(&str, &[&str])] = &[\n");
for (id, file) in self.files.iter().enumerate() {
out.push_str(&format!(" (\"{}\", L{id}),\n", escape_str(&file.path)));
}
out.push_str("];\n\n");
}
fn emit_env_and_main(&self, env_fields: &[String], out: &mut String) {
out.push_str("struct Env {\n");
out.push_str(" cur_line: u32,\n");
if self.multifile {
out.push_str(" cur_file: u32,\n");
}
out.push_str(" depth: usize,\n");
for field in env_fields {
out.push_str(&format!(" {field}: Value,\n"));
}
out.push_str("}\n\n");
out.push_str("fn main() -> std::process::ExitCode {\n");
out.push_str(" set_script_args(std::env::args().skip(1).collect());\n");
out.push_str(" let mut env = Env {\n");
out.push_str(" cur_line: 0,\n");
if self.multifile {
out.push_str(" cur_file: 0,\n");
}
out.push_str(" depth: 0,\n");
for field in env_fields {
out.push_str(&format!(" {field}: Value::None,\n"));
}
out.push_str(" };\n");
out.push_str(" match run(&mut env) {\n");
out.push_str(" Ok(()) => std::process::ExitCode::SUCCESS,\n");
out.push_str(" Err(e) => {\n");
if self.multifile {
out.push_str(" let (f_path, f_lines) = FILES[env.cur_file as usize];\n");
out.push_str(
" let line = f_lines.get((env.cur_line as usize).saturating_sub(1)).copied().unwrap_or(\"\");\n",
);
out.push_str(&format!(
" eprintln!(\"{}\\n\\n {{}}:{{}}\\n {{}}\\n {{}}\", f_path, env.cur_line, line, e);\n",
RUNTIME_ERROR_HEADLINE,
));
} else {
out.push_str(
" let line = LINES.get((env.cur_line as usize).saturating_sub(1)).copied().unwrap_or(\"\");\n",
);
out.push_str(&format!(
" eprintln!(\"{}\\n\\n {}:{{}}\\n {{line}}\\n {{e}}\", env.cur_line);\n",
RUNTIME_ERROR_HEADLINE,
escape_str(&self.files[0].path)
));
}
out.push_str(" std::process::ExitCode::FAILURE\n");
out.push_str(" }\n");
out.push_str(" }\n");
out.push_str("}\n\n");
}
fn fn_info(&self, emit: &Emit, span: Span) -> FnInfoView {
let info = emit
.analysis
.fn_info
.get(&(emit.file_id, span))
.expect("compiler bug: definition was not analyzed");
FnInfoView {
name: info.name.clone(),
params: info.params.clone(),
body: info.body.clone(),
captures: info.captures.clone(),
cell_names: info.cell_names.clone(),
}
}
fn diag(&self, span: Span, message: impl Into<String>) -> Diagnostic {
let file = &self.files[self.cur.get() as usize];
let source_line = file
.lines
.get((span.line as usize).saturating_sub(1))
.cloned()
.unwrap_or_default();
Diagnostic::new(&file.path, span.line, span.col, source_line, message)
}
}