#![allow(clippy::cognitive_complexity)]
#![warn(absolute_paths_not_starting_with_crate)]
#![warn(explicit_outlives_requirements)]
#![warn(unreachable_pub)]
#![warn(deprecated_in_future)]
#![deny(unsafe_code)]
#![deny(unused_extern_crates)]
use std::collections::{HashMap, VecDeque};
use std::io;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::rc::Rc;
pub use codespan;
#[cfg(feature = "codegen")]
use cranelift_module::{Backend, Module};
#[cfg(feature = "codegen")]
pub use ir::initialize_aot_module;
#[cfg(all(feature = "color-backtrace", not(feature = "cc")))]
compile_error!(concat!(
"The color-backtrace feature does nothing unless used by the `",
env!("CARGO_PKG_DIR"),
"` binary."
));
#[derive(Debug, Clone)]
pub struct Source {
pub code: Rc<str>,
pub path: PathBuf,
}
impl AsRef<str> for Source {
fn as_ref(&self) -> &str {
self.code.as_ref()
}
}
pub type Files = codespan::Files<Source>;
#[cfg(feature = "codegen")]
pub type Product = <cranelift_object::ObjectBackend as Backend>::Product;
pub struct Program<T, E = VecDeque<CompileError>> {
pub result: Result<T, E>,
pub warnings: VecDeque<CompileWarning>,
pub files: Files,
}
impl<T, E> Program<T, E> {
fn from_cpp(mut cpp: PreProcessor, result: Result<T, E>) -> Self {
Program {
result,
warnings: cpp.warnings(),
files: cpp.into_files(),
}
}
}
pub use analyze::{Analyzer, PureAnalyzer};
pub use data::*;
#[allow(unreachable_pub)]
pub use lex::{Definition, Lexer, PreProcessor, PreProcessorBuilder};
pub use parse::Parser;
#[macro_use]
mod macros;
mod analyze;
mod arch;
pub mod data;
mod fold;
pub mod intern;
#[cfg(feature = "codegen")]
mod ir;
mod lex;
mod parse;
pub use lex::replace;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("{}", .0.iter().map(|err| err.data.to_string()).collect::<Vec<_>>().join("\n"))]
Source(VecDeque<CompileError>),
#[cfg(feature = "codegen")]
#[error("linking error: {0}")]
Platform(cranelift_object::object::write::Error),
#[error("io error: {0}")]
IO(#[from] io::Error),
}
impl From<CompileError> for Error {
fn from(err: CompileError) -> Error {
Error::Source(vec_deque![err])
}
}
impl From<VecDeque<CompileError>> for Error {
fn from(errs: VecDeque<CompileError>) -> Self {
Error::Source(errs)
}
}
#[derive(Clone, Debug, Default)]
struct RecursionGuard(Rc<()>);
impl RecursionGuard {
#[cfg(debug_assertions)]
const MAX_DEPTH: usize = 1000;
#[cfg(not(debug_assertions))]
const MAX_DEPTH: usize = 10000;
fn recursion_check(&self, error_handler: &mut ErrorHandler) -> RecursionGuard {
let guard = self.clone();
let depth = Rc::strong_count(&guard.0);
if depth > Self::MAX_DEPTH {
Self::recursion_check_failed(depth, error_handler);
}
guard
}
#[cold]
#[inline(never)]
fn recursion_check_failed(depth: usize, mut error_handler: &mut ErrorHandler) -> ! {
eprintln!(
"fatal: maximum recursion depth exceeded ({} > {})",
depth,
Self::MAX_DEPTH
);
if !error_handler.is_empty() {
println!("pending errors:");
for error in &mut error_handler {
println!("- error: {}", error.data);
}
for warning in &mut error_handler.warnings {
println!("- warning: {}", warning.data);
}
}
std::process::exit(102);
}
}
#[derive(Clone, Default)]
pub struct Opt {
pub debug_lex: bool,
pub debug_ast: bool,
pub debug_hir: bool,
pub debug_asm: bool,
pub no_link: bool,
#[cfg(feature = "jit")]
pub jit: bool,
pub max_errors: Option<std::num::NonZeroUsize>,
pub search_path: Vec<PathBuf>,
pub definitions: HashMap<InternedStr, Definition>,
pub filename: PathBuf,
}
pub fn preprocess(buf: &str, opt: Opt) -> Program<VecDeque<Locatable<Token>>> {
let path = opt.search_path.iter().map(|p| p.into());
let mut cpp = PreProcessor::new(buf, opt.filename, opt.debug_lex, path, opt.definitions);
let mut tokens = VecDeque::new();
let mut errs = VecDeque::new();
for result in &mut cpp {
match result {
Ok(token) => tokens.push_back(token),
Err(err) => errs.push_back(err),
}
}
let result = if errs.is_empty() {
Ok(tokens)
} else {
Err(errs)
};
Program {
result,
warnings: cpp.warnings(),
files: cpp.into_files(),
}
}
pub fn check_semantics(buf: &str, opt: Opt) -> Program<Vec<Locatable<hir::Declaration>>> {
let path = opt.search_path.iter().map(|p| p.into());
let mut cpp = PreProcessor::new(buf, opt.filename, opt.debug_lex, path, opt.definitions);
let mut errs = VecDeque::new();
let mut hir = vec![];
let mut parser = Analyzer::new(Parser::new(&mut cpp, opt.debug_ast), opt.debug_hir);
for res in &mut parser {
match res {
Ok(decl) => hir.push(decl),
Err(err) => {
errs.push_back(err);
if let Some(max) = opt.max_errors {
if errs.len() >= max.into() {
return Program::from_cpp(cpp, Err(errs));
}
}
}
}
}
let mut warnings = parser.inner.warnings();
warnings.extend(cpp.warnings());
if hir.is_empty() && errs.is_empty() {
errs.push_back(cpp.eof().error(SemanticError::EmptyProgram));
}
let result = if !errs.is_empty() { Err(errs) } else { Ok(hir) };
Program {
result,
warnings,
files: cpp.into_files(),
}
}
#[cfg(feature = "codegen")]
pub fn compile<B: Backend>(module: Module<B>, buf: &str, opt: Opt) -> Program<Module<B>> {
let debug_asm = opt.debug_asm;
let mut program = check_semantics(buf, opt);
let hir = match program.result {
Ok(hir) => hir,
Err(err) => {
return Program {
result: Err(err),
warnings: program.warnings,
files: program.files,
}
}
};
let (result, ir_warnings) = ir::compile(module, hir, debug_asm);
program.warnings.extend(ir_warnings);
Program {
result: result.map_err(|errs| vec_deque![errs]),
warnings: program.warnings,
files: program.files,
}
}
#[cfg(feature = "codegen")]
pub fn assemble(product: Product, output: &Path) -> Result<(), Error> {
use io::Write;
use std::fs::File;
let bytes = product.emit().map_err(Error::Platform)?;
File::create(output)?
.write_all(&bytes)
.map_err(io::Error::into)
}
pub fn link(obj_file: &Path, output: &Path) -> Result<(), io::Error> {
use std::io::{Error, ErrorKind};
let status = Command::new("cc")
.args(&[&obj_file, Path::new("-o"), output])
.status()
.map_err(|err| {
if err.kind() == ErrorKind::NotFound {
Error::new(
ErrorKind::NotFound,
"could not find host cc (for linking). Is it on your PATH?",
)
} else {
err
}
})?;
if !status.success() {
Err(Error::new(ErrorKind::Other, "linking program failed"))
} else {
Ok(())
}
}
#[cfg(feature = "jit")]
pub use jit::*;
#[cfg(feature = "jit")]
mod jit {
use super::*;
use crate::ir::get_isa;
use cranelift_simplejit::{SimpleJITBackend, SimpleJITBuilder};
use std::convert::TryFrom;
pub fn initialize_jit_module() -> Module<SimpleJITBackend> {
let libcall_names = cranelift_module::default_libcall_names();
Module::new(SimpleJITBuilder::with_isa(get_isa(true), libcall_names))
}
pub struct JIT {
module: Module<SimpleJITBackend>,
}
impl From<Module<SimpleJITBackend>> for JIT {
fn from(module: Module<SimpleJITBackend>) -> Self {
Self { module }
}
}
impl TryFrom<Rc<str>> for JIT {
type Error = Error;
fn try_from(source: Rc<str>) -> Result<JIT, Self::Error> {
JIT::from_string(source, Opt::default()).result
}
}
impl JIT {
pub fn from_string<R: Into<Rc<str>>>(source: R, opt: Opt) -> Program<Self, Error> {
let source = source.into();
let module = initialize_jit_module();
let program = compile(module, &source, opt);
let result = match program.result {
Ok(module) => Ok(JIT::from(module)),
Err(errs) => Err(errs.into()),
};
Program {
result,
warnings: program.warnings,
files: program.files,
}
}
pub fn finalize(&mut self) {
self.module.finalize_definitions();
}
pub fn get_compiled_function(&mut self, name: &str) -> Option<*const u8> {
use cranelift_module::FuncOrDataId;
let name = self.module.get_name(name);
if let Some(FuncOrDataId::Func(id)) = name {
Some(self.module.get_finalized_function(id))
} else {
None
}
}
pub fn get_compiled_data(&mut self, name: &str) -> Option<(*mut u8, usize)> {
use cranelift_module::FuncOrDataId;
let name = self.module.get_name(name);
if let Some(FuncOrDataId::Data(id)) = name {
Some(self.module.get_finalized_data(id))
} else {
None
}
}
#[allow(unsafe_code)]
pub unsafe fn run_main(&mut self) -> Option<i32> {
self.finalize();
let main = self.get_compiled_function("main")?;
let args = std::env::args().skip(1);
let argc = args.len() as i32;
let vec_args = args
.map(|string| std::ffi::CString::new(string).unwrap())
.collect::<Vec<_>>();
let argv = vec_args
.iter()
.map(|cstr| cstr.as_ptr() as *const u8)
.collect::<Vec<_>>();
assert_ne!(main, std::ptr::null());
let main: unsafe extern "C" fn(i32, *const *const u8) -> i32 =
std::mem::transmute(main);
Some(main(argc, argv.as_ptr() as *const *const u8))
}
}
}
impl<T: Into<Rc<str>>> From<T> for Source {
fn from(src: T) -> Self {
Self {
code: src.into(),
path: PathBuf::new(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn compile(src: &str) -> Result<Vec<hir::Declaration>, Error> {
let options = Opt::default();
let res = super::check_semantics(src, options).result;
match res {
Ok(decls) => Ok(decls.into_iter().map(|l| l.data).collect()),
Err(errs) => Err(Error::Source(errs)),
}
}
fn compile_err(src: &str) -> VecDeque<CompileError> {
match compile(src).err().unwrap() {
Error::Source(errs) => errs,
_ => unreachable!(),
}
}
#[test]
fn empty() {
let mut lex_errs = compile_err("`\n");
assert!(lex_errs.pop_front().unwrap().data.is_lex_err());
assert!(lex_errs.is_empty());
let mut empty_errs = compile_err("");
let err = empty_errs.pop_front().unwrap().data;
assert_eq!(err, SemanticError::EmptyProgram.into());
assert!(err.is_semantic_err());
assert!(empty_errs.is_empty());
let mut parse_err = compile_err("+++\n");
let err = parse_err.pop_front();
assert!(parse_err.is_empty());
assert!(err.unwrap().data.is_syntax_err());
}
}