pub mod error;
pub mod primitives;
pub mod runtime;
pub use error::{CodegenError, CodegenResult};
use crate::ast::{Expr, Program, WordDef};
use inkwell::builder::Builder;
use inkwell::context::Context;
use inkwell::module::Module;
use inkwell::types::PointerType;
use inkwell::values::{FunctionValue, PointerValue};
use inkwell::AddressSpace;
use std::collections::HashMap;
pub struct CodeGen<'ctx> {
pub context: &'ctx Context,
pub module: Module<'ctx>,
pub builder: Builder<'ctx>,
stack_cell_type: PointerType<'ctx>,
functions: HashMap<String, FunctionValue<'ctx>>,
}
impl<'ctx> CodeGen<'ctx> {
pub fn new(context: &'ctx Context, module_name: &str) -> Self {
let module = context.create_module(module_name);
let builder = context.create_builder();
let stack_cell_type = context.ptr_type(AddressSpace::default());
CodeGen {
context,
module,
builder,
stack_cell_type,
functions: HashMap::new(),
}
}
pub fn stack_type(&self) -> PointerType<'ctx> {
self.stack_cell_type
}
pub fn compile_program(&mut self, program: &Program) -> CodegenResult<()> {
self.declare_runtime_functions()?;
for word in &program.word_defs {
self.declare_word(word)?;
}
for word in &program.word_defs {
self.compile_word(word)?;
}
if let Err(err) = self.module.verify() {
return Err(CodegenError::VerificationError {
message: err.to_string(),
});
}
Ok(())
}
fn declare_word(&mut self, word: &WordDef) -> CodegenResult<()> {
let fn_type = self.stack_type().fn_type(&[self.stack_type().into()], false);
let function = self.module.add_function(&word.name, fn_type, None);
function.get_nth_param(0).unwrap().set_name("stack");
self.functions.insert(word.name.clone(), function);
Ok(())
}
fn compile_word(&mut self, word: &WordDef) -> CodegenResult<()> {
let function = self.functions.get(&word.name).ok_or_else(|| {
CodegenError::UnknownWord {
name: word.name.clone(),
location: None,
}
})?;
let entry_block = self.context.append_basic_block(*function, "entry");
self.builder.position_at_end(entry_block);
let mut stack = function.get_nth_param(0).unwrap().into_pointer_value();
for expr in &word.body {
stack = self.compile_expr(expr, stack)?;
}
self.builder.build_return(Some(&stack)).map_err(|e| {
CodegenError::LlvmError {
operation: "build_return".to_string(),
details: e.to_string(),
}
})?;
Ok(())
}
fn compile_expr(
&mut self,
expr: &Expr,
stack: PointerValue<'ctx>,
) -> CodegenResult<PointerValue<'ctx>> {
match expr {
Expr::IntLit(n) => {
self.compile_push_int(*n, stack)
}
Expr::BoolLit(b) => {
self.compile_push_bool(*b, stack)
}
Expr::StringLit(s) => {
self.compile_push_string(s, stack)
}
Expr::WordCall(name) => {
self.compile_word_call(name, stack)
}
Expr::Quotation(_exprs) => Err(CodegenError::Unimplemented {
feature: "quotations".to_string(),
}),
Expr::Match { branches: _ } => Err(CodegenError::Unimplemented {
feature: "pattern matching".to_string(),
}),
Expr::If {
then_branch: _,
else_branch: _,
} => Err(CodegenError::Unimplemented {
feature: "if expressions".to_string(),
}),
Expr::While {
condition: _,
body: _,
} => Err(CodegenError::Unimplemented {
feature: "while loops".to_string(),
}),
}
}
fn compile_word_call(
&mut self,
name: &str,
stack: PointerValue<'ctx>,
) -> CodegenResult<PointerValue<'ctx>> {
if let Some(new_stack) = self.compile_builtin(name, stack)? {
return Ok(new_stack);
}
let function = self.functions.get(name).ok_or_else(|| {
CodegenError::UnknownWord {
name: name.to_string(),
location: None,
}
})?;
let result = self
.builder
.build_call(*function, &[stack.into()], "call")
.map_err(|e| CodegenError::LlvmError {
operation: "build_call".to_string(),
details: e.to_string(),
})?;
Ok(result
.try_as_basic_value()
.left()
.unwrap()
.into_pointer_value())
}
fn call_runtime<'a>(
&mut self,
fn_name: &str,
args: &[inkwell::values::BasicMetadataValueEnum<'ctx>],
) -> CodegenResult<PointerValue<'ctx>> {
let function = self.get_runtime_function(fn_name)?;
let result = self
.builder
.build_call(function, args, fn_name)
.map_err(|e| CodegenError::LlvmError {
operation: fn_name.to_string(),
details: e.to_string(),
})?;
Ok(result
.try_as_basic_value()
.left()
.unwrap()
.into_pointer_value())
}
fn compile_push_int(
&mut self,
value: i64,
stack: PointerValue<'ctx>,
) -> CodegenResult<PointerValue<'ctx>> {
let int_val = self.context.i64_type().const_int(value as u64, true);
self.call_runtime("push_int", &[stack.into(), int_val.into()])
}
fn compile_push_bool(
&mut self,
value: bool,
stack: PointerValue<'ctx>,
) -> CodegenResult<PointerValue<'ctx>> {
let bool_val = self.context.bool_type().const_int(value as u64, false);
self.call_runtime("push_bool", &[stack.into(), bool_val.into()])
}
fn compile_push_string(
&mut self,
value: &str,
stack: PointerValue<'ctx>,
) -> CodegenResult<PointerValue<'ctx>> {
let string_global = self
.builder
.build_global_string_ptr(value, "str")
.map_err(|e| CodegenError::LlvmError {
operation: "build_global_string_ptr".to_string(),
details: e.to_string(),
})?;
self.call_runtime(
"push_string",
&[stack.into(), string_global.as_pointer_value().into()],
)
}
pub fn emit_ir(&self) -> String {
self.module.print_to_string().to_string()
}
pub fn emit_to_file(&self, path: &str) -> CodegenResult<()> {
self.module
.print_to_file(path)
.map_err(|e| CodegenError::LlvmError {
operation: "write_to_file".to_string(),
details: e.to_string(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{Expr, WordDef};
use crate::ast::types::{Effect, StackType, Type};
#[test]
fn test_codegen_basic() {
let context = Context::create();
let mut codegen = CodeGen::new(&context, "test_module");
let word = WordDef {
name: "five".to_string(),
effect: Effect {
inputs: StackType::Empty,
outputs: StackType::Empty.push(Type::Int),
},
body: vec![Expr::IntLit(5)],
};
let program = Program {
type_defs: vec![],
word_defs: vec![word],
};
codegen.compile_program(&program).unwrap();
let ir = codegen.emit_ir();
assert!(ir.contains("define"));
assert!(ir.contains("@five"));
assert!(ir.contains("push_int"));
}
#[test]
fn test_codegen_word_call() {
let context = Context::create();
let mut codegen = CodeGen::new(&context, "test_module");
let word = WordDef {
name: "double".to_string(),
effect: Effect {
inputs: StackType::Empty.push(Type::Int),
outputs: StackType::Empty.push(Type::Int),
},
body: vec![
Expr::WordCall("dup".to_string()),
Expr::WordCall("+".to_string()),
],
};
let program = Program {
type_defs: vec![],
word_defs: vec![word],
};
codegen.compile_program(&program).unwrap();
let ir = codegen.emit_ir();
assert!(ir.contains("@double"));
}
#[test]
fn test_codegen_boolean() {
let context = Context::create();
let mut codegen = CodeGen::new(&context, "test_module");
let word = WordDef {
name: "truth".to_string(),
effect: Effect {
inputs: StackType::Empty,
outputs: StackType::Empty.push(Type::Bool),
},
body: vec![Expr::BoolLit(true)],
};
let program = Program {
type_defs: vec![],
word_defs: vec![word],
};
codegen.compile_program(&program).unwrap();
let ir = codegen.emit_ir();
assert!(ir.contains("@truth"));
assert!(ir.contains("push_bool"));
}
#[test]
fn test_codegen_string() {
let context = Context::create();
let mut codegen = CodeGen::new(&context, "test_module");
let word = WordDef {
name: "hello".to_string(),
effect: Effect {
inputs: StackType::Empty,
outputs: StackType::Empty.push(Type::String),
},
body: vec![Expr::StringLit("world".to_string())],
};
let program = Program {
type_defs: vec![],
word_defs: vec![word],
};
codegen.compile_program(&program).unwrap();
let ir = codegen.emit_ir();
assert!(ir.contains("@hello"));
assert!(ir.contains("push_string"));
assert!(ir.contains("world"));
}
#[test]
fn test_codegen_multiple_words() {
let context = Context::create();
let mut codegen = CodeGen::new(&context, "test_module");
let program = Program {
type_defs: vec![],
word_defs: vec![
WordDef {
name: "five".to_string(),
effect: Effect {
inputs: StackType::Empty,
outputs: StackType::Empty.push(Type::Int),
},
body: vec![Expr::IntLit(5)],
},
WordDef {
name: "ten".to_string(),
effect: Effect {
inputs: StackType::Empty,
outputs: StackType::Empty.push(Type::Int),
},
body: vec![Expr::IntLit(10)],
},
WordDef {
name: "add_them".to_string(),
effect: Effect {
inputs: StackType::Empty,
outputs: StackType::Empty.push(Type::Int),
},
body: vec![
Expr::WordCall("five".to_string()),
Expr::WordCall("ten".to_string()),
Expr::WordCall("+".to_string()),
],
},
],
};
codegen.compile_program(&program).unwrap();
let ir = codegen.emit_ir();
assert!(ir.contains("@five"));
assert!(ir.contains("@ten"));
assert!(ir.contains("@add_them"));
}
#[test]
fn test_codegen_unknown_word_error() {
let context = Context::create();
let mut codegen = CodeGen::new(&context, "test_module");
let word = WordDef {
name: "bad".to_string(),
effect: Effect {
inputs: StackType::Empty,
outputs: StackType::Empty.push(Type::Int),
},
body: vec![Expr::WordCall("unknown_word".to_string())],
};
let program = Program {
type_defs: vec![],
word_defs: vec![word],
};
let result = codegen.compile_program(&program);
assert!(result.is_err());
if let Err(CodegenError::UnknownWord { name, .. }) = result {
assert_eq!(name, "unknown_word");
} else {
panic!("Expected UnknownWord error");
}
}
#[test]
fn test_codegen_unimplemented_features() {
let context = Context::create();
let mut codegen = CodeGen::new(&context, "test_module");
let word = WordDef {
name: "test_quot".to_string(),
effect: Effect {
inputs: StackType::Empty,
outputs: StackType::Empty,
},
body: vec![Expr::Quotation(vec![Expr::IntLit(42)])],
};
let program = Program {
type_defs: vec![],
word_defs: vec![word],
};
let result = codegen.compile_program(&program);
assert!(result.is_err());
if let Err(CodegenError::Unimplemented { feature }) = result {
assert_eq!(feature, "quotations");
} else {
panic!("Expected Unimplemented error");
}
}
#[test]
fn test_emit_to_file() {
use std::fs;
let context = Context::create();
let mut codegen = CodeGen::new(&context, "test_module");
let word = WordDef {
name: "test".to_string(),
effect: Effect {
inputs: StackType::Empty,
outputs: StackType::Empty.push(Type::Int),
},
body: vec![Expr::IntLit(42)],
};
let program = Program {
type_defs: vec![],
word_defs: vec![word],
};
codegen.compile_program(&program).unwrap();
let temp_file = "/tmp/test_cem_output.ll";
codegen.emit_to_file(temp_file).unwrap();
assert!(fs::metadata(temp_file).is_ok());
let contents = fs::read_to_string(temp_file).unwrap();
assert!(contents.contains("@test"));
fs::remove_file(temp_file).ok();
}
}