use std::collections::HashMap;
use crate::{
Error, ErrorKind, TulispContext, TulispObject, TulispValue,
bytecode::{Bytecode, Instruction},
object::wrappers::generic::SharedMut,
};
use super::forms::{VMCompilers, compile_form};
#[derive(Default, Clone)]
pub(crate) struct VMDefunParams {
pub required: Vec<TulispObject>,
pub optional: Vec<TulispObject>,
pub rest: Option<TulispObject>,
}
#[allow(dead_code)]
pub(crate) struct Compiler {
pub vm_compilers: VMCompilers,
pub defun_args: HashMap<usize, VMDefunParams>, pub bytecode: Bytecode,
pub keep_result: bool,
pub current_defun: Option<TulispObject>,
pub active_let_scopes: Vec<TulispObject>,
label_counter: usize,
}
impl Compiler {
pub fn new(vm_compilers: VMCompilers) -> Self {
Compiler {
vm_compilers,
defun_args: HashMap::new(),
bytecode: Bytecode::new(),
keep_result: true,
current_defun: None,
active_let_scopes: Vec::new(),
label_counter: 0,
}
}
pub fn new_label(&mut self) -> TulispObject {
self.label_counter += 1;
TulispObject::symbol(format!(":{}", self.label_counter), true)
}
pub fn reset_label_counter(&mut self) {
self.label_counter = 0;
}
}
pub fn compile(ctx: &mut TulispContext, value: &TulispObject) -> Result<Bytecode, Error> {
let before: std::collections::HashSet<usize> = ctx
.compiler
.as_ref()
.unwrap()
.bytecode
.functions
.keys()
.copied()
.collect();
let output = compile_progn(ctx, value)?;
let (output, global_trace_ranges) = crate::bytecode::bytecode::strip_trace_markers(output);
let global_trace_ranges =
crate::object::wrappers::generic::Shared::new_sized(global_trace_ranges);
let compiler = ctx.compiler.as_mut().unwrap();
compiler.bytecode.global = SharedMut::new(output);
compiler.bytecode.global_trace_ranges = global_trace_ranges.clone();
let new_functions = compiler
.bytecode
.functions
.iter()
.filter(|(k, _)| !before.contains(k))
.map(|(k, v)| (*k, v.clone()))
.collect();
Ok(Bytecode {
global: compiler.bytecode.global.clone(),
global_trace_ranges,
functions: new_functions,
})
}
pub fn compile_progn(
ctx: &mut TulispContext,
value: &TulispObject,
) -> Result<Vec<Instruction>, Error> {
pre_register_defun_arities(ctx, value);
let mut result = vec![];
let mut prev = None;
let compiler = ctx.compiler.as_mut().unwrap();
let keep_result = compiler.keep_result;
compiler.keep_result = false;
#[allow(dropping_references)]
drop(compiler);
for expr in value.base_iter() {
if let Some(prev) = prev {
result.append(&mut compile_expr(ctx, &prev)?);
}
prev = Some(expr);
}
let compiler = ctx.compiler.as_mut().unwrap();
compiler.keep_result = keep_result;
#[allow(dropping_references)]
drop(compiler);
if let Some(prev) = prev {
result.append(&mut compile_expr(ctx, &prev)?);
} else if keep_result {
result.push(Instruction::Push(false.into()));
}
Ok(result)
}
fn pre_register_defun_arities(ctx: &mut TulispContext, body: &TulispObject) {
for expr in body.base_iter() {
try_pre_register_one(ctx, &expr);
}
}
fn try_pre_register_one(ctx: &mut TulispContext, expr: &TulispObject) {
if !expr.consp() {
return;
}
let Ok(head) = expr.car() else { return };
let Ok(head_sym) = head.as_symbol() else {
return;
};
if head_sym != "defun" {
return;
}
let Ok(after_defun) = expr.cdr() else { return };
let Ok(name_obj) = after_defun.car() else {
return;
};
let Ok(after_name) = after_defun.cdr() else {
return;
};
let Ok(params) = after_name.car() else { return };
let mut required = Vec::new();
let mut optional = Vec::new();
let mut rest_param: Option<TulispObject> = None;
let mut is_optional = false;
let mut is_rest = false;
for p in params.base_iter() {
if p.eq(&ctx.keywords.amp_optional) {
is_optional = true;
} else if p.eq(&ctx.keywords.amp_rest) {
is_optional = false;
is_rest = true;
} else if is_rest {
rest_param = Some(p.clone());
} else if is_optional {
optional.push(p.clone());
} else {
required.push(p.clone());
}
}
let params_struct = VMDefunParams {
required,
optional,
rest: rest_param,
};
let compiler = ctx.compiler.as_mut().unwrap();
compiler
.defun_args
.insert(name_obj.addr_as_usize(), params_struct);
}
pub(crate) fn compile_expr_keep_result(
ctx: &mut TulispContext,
expr: &TulispObject,
) -> Result<Vec<Instruction>, Error> {
let compiler = ctx.compiler.as_mut().unwrap();
let keep_result = compiler.keep_result;
compiler.keep_result = true;
#[allow(dropping_references)]
drop(compiler);
let ret = compile_expr(ctx, expr);
ctx.compiler.as_mut().unwrap().keep_result = keep_result;
ret
}
pub(crate) fn compile_progn_keep_result(
ctx: &mut TulispContext,
expr: &TulispObject,
) -> Result<Vec<Instruction>, Error> {
let compiler = ctx.compiler.as_mut().unwrap();
let keep_result = compiler.keep_result;
compiler.keep_result = true;
#[allow(dropping_references)]
drop(compiler);
let ret = compile_progn(ctx, expr);
ctx.compiler.as_mut().unwrap().keep_result = keep_result;
ret
}
fn compile_back_quote(
ctx: &mut TulispContext,
value: &TulispObject,
depth: u32,
) -> Result<Vec<Instruction>, Error> {
let compiler = ctx.compiler.as_mut().unwrap();
if !compiler.keep_result {
return Ok(vec![]);
}
match &*value.inner_ref() {
(TulispValue::Quote { value }, _) => {
return compile_back_quote(ctx, value, depth).map(|mut v| {
v.push(Instruction::Quote);
v
});
}
(TulispValue::Unquote { value }, _) => {
if depth == 1 {
return compile_expr(ctx, value).map_err(|e| e.with_trace(value.clone()));
}
let mut v = compile_back_quote(ctx, value, depth - 1)?;
v.push(Instruction::WrapUnquote);
return Ok(v);
}
(TulispValue::Splice { value }, _) => {
if depth == 1 {
return Err(Error::new(
crate::ErrorKind::SyntaxError,
"Splice must be within a backquoted list.".to_string(),
));
}
let mut v = compile_back_quote(ctx, value, depth - 1)?;
v.push(Instruction::WrapSplice);
return Ok(v);
}
(TulispValue::Backquote { value }, _) => {
let mut v = compile_back_quote(ctx, value, depth + 1)?;
v.push(Instruction::WrapBackquote);
return Ok(v);
}
(TulispValue::List { .. }, _) => {}
_ => return Ok(vec![Instruction::Push(value.clone())]),
}
let mut result = vec![];
let mut value = value.clone();
let mut items = 0;
let mut need_list = true;
let mut need_append = false;
loop {
value.car_and_then(|first| {
let first_inner = &*first.inner_ref();
if let (TulispValue::Unquote { value }, _) = first_inner {
items += 1;
if depth == 1 {
result.append(
&mut compile_expr(ctx, value).map_err(|e| e.with_trace(first.clone()))?,
);
} else {
result.append(&mut compile_back_quote(ctx, value, depth - 1)?);
result.push(Instruction::WrapUnquote);
}
} else if let (TulispValue::Splice { value }, _) = first_inner {
if depth == 1 {
let mut splice_result = compile_expr(ctx, value)?;
let list_inst = splice_result.pop().unwrap();
if let Instruction::List(n) = list_inst {
result.append(&mut splice_result);
items += n;
} else if let Instruction::Load(idx) = list_inst {
result.append(&mut splice_result);
result.push(Instruction::List(items));
if need_append {
result.push(Instruction::Append(2));
}
result.append(&mut vec![Instruction::Load(idx), Instruction::Append(2)]);
need_append = true;
items = 0;
} else {
if !value.consp() {
return Err(Error::new(
ErrorKind::SyntaxError,
format!(
"Can only splice an inplace-list or a variable binding: {}",
value
),
)
.with_trace(first.clone()));
}
result.push(Instruction::List(items));
if need_append {
result.push(Instruction::Append(2));
}
result.append(&mut splice_result);
result.push(list_inst);
result.push(Instruction::Append(2));
need_append = true;
items = 0;
}
} else {
items += 1;
result.append(&mut compile_back_quote(ctx, value, depth - 1)?);
result.push(Instruction::WrapSplice);
}
} else if let (TulispValue::Backquote { value }, _) = first_inner {
items += 1;
result.append(&mut compile_back_quote(ctx, value, depth + 1)?);
result.push(Instruction::WrapBackquote);
} else {
items += 1;
result.append(&mut compile_back_quote(ctx, first, depth)?);
}
Ok(())
})?;
let rest = value.cdr()?;
if let (TulispValue::Unquote { value }, _) = &*rest.inner_ref() {
if depth == 1 {
result.append(&mut compile_expr(ctx, value)?);
result.push(Instruction::Cons);
need_list = false;
break;
}
result.append(&mut compile_back_quote(ctx, value, depth - 1)?);
result.push(Instruction::WrapUnquote);
result.push(Instruction::Cons);
need_list = false;
break;
}
if !rest.consp() {
if !rest.null() {
result.push(Instruction::Push(rest.clone()));
result.push(Instruction::Cons);
need_list = false;
}
break;
}
value = rest;
}
if need_list {
result.push(Instruction::List(items));
}
if need_append {
result.push(Instruction::Append(2));
}
Ok(result)
}
pub(crate) fn compile_expr(
ctx: &mut TulispContext,
expr: &TulispObject,
) -> Result<Vec<Instruction>, Error> {
let expr_ref = expr.inner_ref();
let compiler = ctx.compiler.as_mut().unwrap();
match &*expr_ref {
(TulispValue::Number { .. }, _) => {
if compiler.keep_result {
Ok(vec![Instruction::Push(expr.clone())])
} else {
Ok(vec![])
}
}
(TulispValue::Nil, _) | (TulispValue::T, _) => {
if compiler.keep_result {
Ok(vec![Instruction::Push(expr.clone())])
} else {
Ok(vec![])
}
}
(TulispValue::String { .. }, _) | (TulispValue::Any(_), _) => {
if compiler.keep_result {
Ok(vec![Instruction::Push(expr.clone())])
} else {
Ok(vec![])
}
}
(TulispValue::Lambda { .. }, _)
| (TulispValue::Func(_), _)
| (TulispValue::Defun { .. }, _)
| (TulispValue::CompiledDefun { .. }, _)
| (TulispValue::Macro(_), _)
| (TulispValue::Defmacro { .. }, _)
| (TulispValue::Bounce, _) => Ok(vec![]),
(TulispValue::Backquote { value }, _) => {
compile_back_quote(ctx, value, 1).map_err(|e| e.with_trace(expr.clone()))
}
(TulispValue::Quote { value }, _) | (TulispValue::Sharpquote { value }, _) => {
if compiler.keep_result {
Ok(vec![Instruction::Push(value.clone())])
} else {
Ok(vec![])
}
}
(TulispValue::List { .. }, _) => {
drop(expr_ref);
let mut inner = compile_form(ctx, expr).map_err(|e| e.with_trace(expr.clone()))?;
if inner.is_empty() {
return Ok(inner);
}
let mut wrapped = Vec::with_capacity(inner.len() + 2);
wrapped.push(Instruction::PushTrace(expr.clone()));
wrapped.append(&mut inner);
wrapped.push(Instruction::PopTrace);
Ok(wrapped)
}
(TulispValue::Symbol { .. }, _) | (TulispValue::LexicalBinding { .. }, _) => {
if !compiler.keep_result {
return Ok(vec![]);
}
Ok(vec![if expr.keywordp() {
Instruction::Push(expr.clone())
} else {
Instruction::Load(expr.clone())
}])
}
(TulispValue::Unquote { .. }, _) => Err(Error::new(
crate::ErrorKind::SyntaxError,
"Unquote without backquote".to_string(),
)),
(TulispValue::Splice { .. }, _) => Err(Error::new(
crate::ErrorKind::SyntaxError,
"Splice without backquote".to_string(),
)),
}
}