reid-llvm/reid/src/lib.rs

270 lines
7.4 KiB
Rust

//! Reid is a toy-language compiler I'm working on to learn LLVM (and compiler
//! development).
//!
//! Reid only uses [llvm-sys](https://gitlab.com/taricorp/llvm-sys.rs), which
//! provide very minimal bindings from the LLVM C-API to Rust. `reid_llvm`-crate
//! contains the relevant abstraction to produce a more Rust'y API from that.
//!
//! Much of the syntax in Reid is directly inspired by rust, but mostly it is
//! driven by simplicity.
//!
//! Reid is currently able to (non-exhaustively):
//! - Do basic algebra (e.g. Add, Sub, Mult)
//! - Resolve complex one-liners correctly using PEDMAS (e.g. `5 + 2 * 5 - 5 *
//! 5` is calculated correctly)
//! - Declare and call functions with varying parameters and return types
//! - Perform type-checking and type-inference such that return-types and
//! parameter types must always match.
//! - Do simple logic-operations (e.g. If/And/Or)
//!
//! An example program of Reid, that calculates the 5th fibonacci number (and
//! uses Rust for highlighting) is:
//! ```reid
//! fn main() -> u16 {
//! return fibonacci(5);
//! }
//!
//! fn fibonacci(n: u16) -> u16 {
//! if n <= 2 {
//! return 1;
//! }
//! return fibonacci(n-1) + fibonacci(n-2);
//! }
//! ```
//!
//! Currently missing relevant features (TODOs) are:
//! - ~~Arrays~~ (DONE)
//! - Structs (and custom types as such)
//! - ~~Extern functions~~ (DONE)
//! - ~~Strings~~ (DONE)
//! - Loops
//! - Debug Symbols
//! ```
use std::path::PathBuf;
use error_raporting::{ErrorKind as ErrorRapKind, ErrorModules, ReidError};
use intrinsics::{form_intrinsic_binops, form_intrinsics};
use lexer::FullToken;
use mir::{
linker::LinkerPass, typecheck::TypeCheck, typeinference::TypeInference, typerefs::TypeRefs,
};
use reid_lib::{compile::CompileOutput, Context};
use crate::{ast::TopLevelStatement, lexer::Token, token_stream::TokenStream};
mod allocator;
mod ast;
mod codegen;
pub mod error_raporting;
pub mod intrinsics;
pub mod ld;
mod lexer;
pub mod mir;
mod pad_adapter;
mod token_stream;
mod util;
pub fn parse_module<'map, T: Into<String>>(
source: &str,
name: T,
map: &'map mut ErrorModules,
) -> Result<(mir::SourceModuleId, Vec<FullToken>), ReidError> {
let id = map.add_module(name.into()).unwrap();
map.set_source(id, source.to_owned());
let tokens = ReidError::from_lexer(lexer::tokenize(source), map.clone(), id)?;
map.set_tokens(id, tokens.clone());
#[cfg(debug_assertions)]
println!("{:#?}", &tokens);
Ok((id, tokens))
}
pub fn compile_module<'map>(
module_id: mir::SourceModuleId,
tokens: Vec<FullToken>,
map: &'map mut ErrorModules,
path: Option<PathBuf>,
is_main: bool,
) -> Result<mir::Module, ReidError> {
let module = map.module(&module_id).cloned().unwrap();
let mut token_stream = TokenStream::from(&tokens);
let mut statements = Vec::new();
while !matches!(token_stream.peek().unwrap_or(Token::Eof), Token::Eof) {
let statement = ReidError::from_parser(
token_stream.parse::<TopLevelStatement>(),
map.clone(),
module_id,
)?;
statements.push(statement);
}
drop(token_stream);
let ast_module = ast::Module {
name: module.name,
tokens: tokens,
top_level_statements: statements,
path,
is_main,
};
#[cfg(debug_assertions)]
dbg!(&ast_module);
Ok(ast_module.process(module_id))
}
pub fn perform_all_passes<'map>(
context: &mut mir::Context,
module_map: &'map mut ErrorModules,
) -> Result<(), ReidError> {
#[cfg(debug_assertions)]
dbg!(&context);
for module in &mut context.modules {
for intrinsic in form_intrinsic_binops() {
module.1.binop_defs.insert(0, intrinsic);
}
}
for module in &mut context.modules {
for intrinsic in form_intrinsics() {
module.1.functions.insert(0, intrinsic);
}
}
#[cfg(debug_assertions)]
println!("{}", &context);
let state = context.pass(&mut LinkerPass {
module_map,
is_lib: true,
})?;
#[cfg(debug_assertions)]
println!("{:-^100}", "LINKER OUTPUT");
#[cfg(debug_assertions)]
println!("{}", &context);
#[cfg(debug_assertions)]
dbg!(&state);
if !state.errors.is_empty() {
return Err(ReidError::from_kind(
state.errors.iter().map(|e| e.clone().into()).collect(),
module_map.clone(),
));
}
let refs = TypeRefs::default();
let state = context.pass(&mut TypeInference { refs: &refs })?;
#[cfg(debug_assertions)]
println!("{:-^100}", "TYPE INFERRER OUTPUT");
#[cfg(debug_assertions)]
println!("{}", &refs);
#[cfg(debug_assertions)]
println!("{}", &context);
#[cfg(debug_assertions)]
dbg!(&state);
if !state.errors.is_empty() {
return Err(ReidError::from_kind(
state
.errors
.iter()
.map(|e| ErrorRapKind::TypeInferenceError(e.clone()))
.collect::<Vec<_>>(),
module_map.clone(),
));
}
let state = context.pass(&mut TypeCheck { refs: &refs })?;
#[cfg(debug_assertions)]
println!("{:-^100}", "TYPECHECKER OUTPUT");
#[cfg(debug_assertions)]
println!("{}", &context);
#[cfg(debug_assertions)]
dbg!(&state);
if !state.errors.is_empty() {
return Err(ReidError::from_kind(
state
.errors
.iter()
.map(|e| ErrorRapKind::TypeCheckError(e.clone()))
.collect::<Vec<_>>(),
module_map.clone(),
));
}
Ok(())
}
/// Takes in a bit of source code, parses and compiles it and produces `hello.o`
/// and `hello.asm` from it, which can be linked using `ld` to produce an
/// executable file.
pub fn compile_and_pass<'map>(
source: &str,
path: PathBuf,
module_map: &'map mut ErrorModules,
cpu: Option<String>,
features: Vec<String>,
) -> Result<(CompileOutput, CustomIRs), ReidError> {
let path = path.canonicalize().unwrap();
let name = path.file_name().unwrap().to_str().unwrap().to_owned();
let (id, tokens) = parse_module(source, name, module_map)?;
let module = compile_module(id, tokens, module_map, Some(path.clone()), true)?;
let mut mir_context = mir::Context::from(vec![module], path.parent().unwrap().to_owned());
perform_all_passes(&mut mir_context, module_map)?;
#[cfg(debug_assertions)]
println!("{:-^100}", "FINAL OUTPUT");
#[cfg(debug_assertions)]
println!("{}", &mir_context);
let mut context = Context::new(format!("Reid ({})", env!("CARGO_PKG_VERSION")));
let codegen_modules = match mir_context.codegen(&mut context) {
Ok(modules) => modules,
Err(e) => Err(ReidError::from_kind(vec![e.into()], module_map.clone()))?,
};
#[cfg(debug_assertions)]
println!("{}", &codegen_modules.context);
let compiled = codegen_modules.compile(cpu, features);
Ok((
compiled.output(),
CustomIRs {
llir: format!("{}", codegen_modules.context),
mir: format!("{}", mir_context),
},
))
}
pub struct CustomIRs {
pub llir: String,
pub mir: String,
}
pub fn compile_simple(
source: &str,
path: PathBuf,
cpu: Option<String>,
features: Vec<String>,
) -> Result<(CompileOutput, CustomIRs), ReidError> {
let mut map = ErrorModules::default();
compile_and_pass(source, path, &mut map, cpu, features)
}