Cleanup
This commit is contained in:
parent
dbb51fd1d0
commit
f7e4bfa8cf
12 changed files with 55 additions and 409 deletions
|
@ -3,6 +3,14 @@ name = "e0"
|
|||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[[bin]]
|
||||
name = "e0c"
|
||||
path = "./bin/e0c.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "e0pkg"
|
||||
path = "./bin/e0pkg.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.56"
|
||||
clap = { version = "3.1.8", features = ["derive"] }
|
||||
|
|
|
@ -1,23 +1,12 @@
|
|||
#[macro_use]
|
||||
extern crate lalrpop_util;
|
||||
#[macro_use]
|
||||
extern crate anyhow;
|
||||
|
||||
lalrpop_mod!(parser);
|
||||
|
||||
mod ast;
|
||||
pub mod codegen;
|
||||
mod utils;
|
||||
|
||||
use std::fs::{self, File};
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
|
||||
use crate::codegen::{llvm_ir::LlvmIrCodegen, CodegenBackend};
|
||||
use crate::parser::ProgramParser;
|
||||
use e0::codegen::CodegenBackend;
|
||||
use e0::codegen::llvm_ir::LlvmIrCodegen;
|
||||
use e0::parser::ProgramParser;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct Opt {
|
||||
|
@ -40,7 +29,7 @@ fn main() -> Result<()> {
|
|||
let parser = ProgramParser::new();
|
||||
let ast = parser.parse(&contents).unwrap();
|
||||
|
||||
let typed_ast = ast::typed::convert(ast)?;
|
||||
let typed_ast = e0::ast::typed::convert(ast)?;
|
||||
|
||||
if let Some(path) = opts.emit_ast {
|
||||
let mut file = File::create(&path)?;
|
18
bin/e0pkg.rs
Normal file
18
bin/e0pkg.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
use clap::{Parser, Subcommand};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct Opt {
|
||||
#[clap(subcommand)]
|
||||
command: Command,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
enum Command {
|
||||
/// Compile an e0 package
|
||||
#[clap(name = "build", alias = "b")]
|
||||
Build,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let opts = Opt::parse();
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{ fenix, makeRustPlatform, lib, nix-gitignore, libffi, libgcc }:
|
||||
{ toolchain, makeRustPlatform, lib, nix-gitignore, libffi, libgcc }:
|
||||
|
||||
let rustPlatform = makeRustPlatform { inherit (fenix.minimal) cargo rustc; };
|
||||
let rustPlatform = makeRustPlatform { inherit (toolchain) cargo rustc; };
|
||||
|
||||
in rustPlatform.buildRustPackage {
|
||||
name = "e0";
|
||||
|
|
|
@ -12,14 +12,17 @@
|
|||
inherit system;
|
||||
overlays = [ fenix.overlay ];
|
||||
};
|
||||
myPkgs = rec { e0 = pkgs.callPackage ./. { }; };
|
||||
toolchain = pkgs.fenix.complete;
|
||||
myPkgs = rec { e0 = pkgs.callPackage ./. { inherit toolchain; }; };
|
||||
|
||||
in rec {
|
||||
devShell = pkgs.mkShell {
|
||||
packages = with pkgs;
|
||||
with pkgs.llvmPackages_12; [
|
||||
cargo-edit
|
||||
cargo-watch
|
||||
clangUseLLVM
|
||||
(toolchain.withComponents [ "clippy" "rustc" "rust-src" ])
|
||||
];
|
||||
inputsFrom = with myPkgs; [ e0 ];
|
||||
CARGO_UNSTABLE_SPARSE_REGISTRY = "true";
|
||||
|
|
25
scripts/e0c
25
scripts/e0c
|
@ -1,25 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
SOURCE=$?
|
||||
BITCODE=$(mktemp -d)
|
||||
OUTPUT=./a.out
|
||||
|
||||
usage() { echo "$0 options:" && grep " .)\ #" $0; exit 0; }
|
||||
|
||||
[ $# -eq 0 ] && usage
|
||||
while getopts ":hs:p:" arg; do
|
||||
case $arg in
|
||||
o) # Binary output
|
||||
OUTPUT=${OPTARG}
|
||||
;;
|
||||
h | *) # Display help.
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
cargo run -- -o $BITCODE $SOURCE
|
||||
clang -o $OUTPUT $BITCODE
|
|
@ -1,12 +0,0 @@
|
|||
use anyhow::Result;
|
||||
use cranelift_codegen::Context as ClifContext;
|
||||
|
||||
use super::{Decl, Type};
|
||||
|
||||
pub fn convert(
|
||||
file_name: String,
|
||||
context: &Context,
|
||||
program: Vec<Decl<Type>>,
|
||||
) -> Result<ClifContext> {
|
||||
todo!()
|
||||
}
|
349
src/ast/llvm.rs
349
src/ast/llvm.rs
|
@ -1,349 +0,0 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Result;
|
||||
use inkwell::{
|
||||
builder::Builder,
|
||||
context::Context,
|
||||
module::Module,
|
||||
types::{BasicMetadataTypeEnum, BasicTypeEnum, FunctionType},
|
||||
values::{BasicValue, BasicValueEnum, FunctionValue, IntValue},
|
||||
IntPredicate,
|
||||
};
|
||||
|
||||
use crate::utils::{
|
||||
convert_type_to_metadata_type, convert_value_to_metadata_value,
|
||||
};
|
||||
|
||||
use super::{Decl, ElseClause, Expr, ExprKind, IfElse, Op, Stmt, Type};
|
||||
|
||||
impl Expr<Type> {
|
||||
fn into_llvm<'ctx>(
|
||||
&self,
|
||||
context: &'ctx Context,
|
||||
builder: &'ctx Builder,
|
||||
env: &Env<'_, 'ctx>,
|
||||
) -> Result<BasicValueEnum<'ctx>> {
|
||||
let int_ty = context.i64_type();
|
||||
let bool_ty = context.bool_type();
|
||||
|
||||
match &self.kind {
|
||||
ExprKind::Var(name) => Ok(match env.lookup(&name) {
|
||||
Some(v) => match v.kind {
|
||||
EnvValueKind::Local(l) => l,
|
||||
EnvValueKind::Func(f) => {
|
||||
let ptr = f.as_global_value().as_pointer_value();
|
||||
builder.build_load(ptr, "")
|
||||
}
|
||||
},
|
||||
None => bail!("Unbound name {name:?}"),
|
||||
}),
|
||||
|
||||
ExprKind::Int(n) => {
|
||||
Ok(BasicValueEnum::IntValue(int_ty.const_int(*n as u64, false)))
|
||||
}
|
||||
|
||||
ExprKind::BinOp(left, op, right) => {
|
||||
if !op.check_types(&left.ty, &right.ty) {
|
||||
// TODO: detailed error message
|
||||
bail!("Invalid types on operation.");
|
||||
}
|
||||
|
||||
match op {
|
||||
Op::Plus => {
|
||||
let left_val =
|
||||
left.into_llvm(context, builder, env)?.into_int_value();
|
||||
let right_val =
|
||||
right.into_llvm(context, builder, env)?.into_int_value();
|
||||
let result: IntValue =
|
||||
builder.build_int_add(left_val, right_val, "");
|
||||
Ok(BasicValueEnum::IntValue(result))
|
||||
}
|
||||
|
||||
Op::LessThan => {
|
||||
let left_val =
|
||||
left.into_llvm(context, builder, env)?.into_int_value();
|
||||
let right_val =
|
||||
right.into_llvm(context, builder, env)?.into_int_value();
|
||||
let result: IntValue = builder.build_int_compare(
|
||||
IntPredicate::SLT,
|
||||
left_val,
|
||||
right_val,
|
||||
"",
|
||||
);
|
||||
Ok(BasicValueEnum::IntValue(result))
|
||||
}
|
||||
|
||||
Op::GreaterThan => {
|
||||
let left_val =
|
||||
left.into_llvm(context, builder, env)?.into_int_value();
|
||||
let right_val =
|
||||
right.into_llvm(context, builder, env)?.into_int_value();
|
||||
let result: IntValue = builder.build_int_compare(
|
||||
IntPredicate::SGT,
|
||||
left_val,
|
||||
right_val,
|
||||
"",
|
||||
);
|
||||
Ok(BasicValueEnum::IntValue(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ExprKind::Call(func, args) => match env.lookup(func) {
|
||||
Some(EnvValue {
|
||||
ty: func_ty,
|
||||
kind: EnvValueKind::Func(func_ptr),
|
||||
}) => {
|
||||
let args_llvm = args
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
arg
|
||||
.into_llvm(context, builder, env)
|
||||
.map(convert_value_to_metadata_value)
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
let call_site =
|
||||
builder.build_call(*func_ptr, args_llvm.as_slice(), "");
|
||||
|
||||
let value = call_site.try_as_basic_value().unwrap_left();
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
_ => bail!("No function with name {func:?}"),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Type {
|
||||
pub fn into_llvm_basic_type<'ctx>(
|
||||
&self,
|
||||
context: &'ctx Context,
|
||||
) -> BasicTypeEnum<'ctx> {
|
||||
match self {
|
||||
Type::Int => BasicTypeEnum::IntType(context.i64_type()),
|
||||
Type::Bool => BasicTypeEnum::IntType(context.bool_type()),
|
||||
_ => panic!("Tried to convert a function type into a LLVM basic type"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fn_type_basic<'ctx>(
|
||||
return_ty: BasicTypeEnum<'ctx>,
|
||||
args: &[BasicMetadataTypeEnum<'ctx>],
|
||||
is_var_args: bool,
|
||||
) -> FunctionType<'ctx> {
|
||||
match return_ty {
|
||||
BasicTypeEnum::ArrayType(ty) => ty.fn_type(args, is_var_args),
|
||||
BasicTypeEnum::FloatType(ty) => ty.fn_type(args, is_var_args),
|
||||
BasicTypeEnum::IntType(ty) => ty.fn_type(args, is_var_args),
|
||||
BasicTypeEnum::PointerType(ty) => ty.fn_type(args, is_var_args),
|
||||
BasicTypeEnum::StructType(ty) => ty.fn_type(args, is_var_args),
|
||||
BasicTypeEnum::VectorType(ty) => ty.fn_type(args, is_var_args),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum EnvValueKind<'ctx> {
|
||||
Func(FunctionValue<'ctx>),
|
||||
Local(BasicValueEnum<'ctx>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct EnvValue<'a, 'ctx> {
|
||||
ty: &'a Type,
|
||||
kind: EnvValueKind<'ctx>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct Env<'a, 'ctx> {
|
||||
parent: Option<&'a Env<'a, 'ctx>>,
|
||||
local_type_map: HashMap<String, EnvValue<'a, 'ctx>>,
|
||||
}
|
||||
|
||||
impl<'a, 'ctx> Env<'a, 'ctx> {
|
||||
pub fn lookup(&self, name: impl AsRef<str>) -> Option<&EnvValue<'a, 'ctx>> {
|
||||
match self.local_type_map.get(name.as_ref()) {
|
||||
Some(v) => Some(v),
|
||||
None => match &self.parent {
|
||||
Some(p) => p.lookup(name),
|
||||
None => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_if_else(
|
||||
context: &Context,
|
||||
module: &Module,
|
||||
builder: &Builder,
|
||||
function: &FunctionValue,
|
||||
if_else: &IfElse<Type>,
|
||||
env: &Env,
|
||||
) -> Result<()> {
|
||||
let success_branch = context.append_basic_block(*function, "success");
|
||||
let fail_branch = context.append_basic_block(*function, "fail");
|
||||
let exit_block = context.append_basic_block(*function, "exit");
|
||||
|
||||
let cond = if_else
|
||||
.cond
|
||||
.into_llvm(context, builder, env)?
|
||||
.into_int_value();
|
||||
builder.build_conditional_branch(cond, success_branch, fail_branch);
|
||||
|
||||
// build success branch
|
||||
builder.position_at_end(success_branch);
|
||||
convert_stmts(context, module, function, builder, env, &if_else.body)?;
|
||||
// builder.build_unconditional_branch(exit_branch);
|
||||
|
||||
// build fail branch
|
||||
builder.position_at_end(fail_branch);
|
||||
match &if_else.else_clause {
|
||||
Some(ElseClause::If(if_else2)) => {
|
||||
convert_if_else(context, module, builder, function, if_else2, env)?;
|
||||
}
|
||||
Some(ElseClause::Body(body)) => {
|
||||
convert_stmts(context, module, function, builder, env, body)?;
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
builder.position_at_end(fail_branch);
|
||||
builder.build_unconditional_branch(exit_block);
|
||||
|
||||
builder.position_at_end(exit_block);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn convert_stmts(
|
||||
context: &Context,
|
||||
module: &Module,
|
||||
function: &FunctionValue,
|
||||
builder: &Builder,
|
||||
parent_env: &Env,
|
||||
stmts: impl AsRef<[Stmt<Type>]>,
|
||||
) -> Result<()> {
|
||||
let stmts = stmts.as_ref();
|
||||
let mut scope_env = Env {
|
||||
parent: Some(parent_env),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
for stmt in stmts.iter() {
|
||||
match stmt {
|
||||
Stmt::Let(name, _, expr) => {
|
||||
let ty = &expr.ty;
|
||||
let llvm_ty = ty.into_llvm_basic_type(context);
|
||||
// Empty variable name gets LLVM to generate a unique name
|
||||
let alloca = builder.build_alloca(llvm_ty, "");
|
||||
|
||||
let expr_val = expr.into_llvm(context, builder, &scope_env)?;
|
||||
builder.build_store(alloca, expr_val);
|
||||
|
||||
scope_env.local_type_map.insert(
|
||||
name.clone(),
|
||||
EnvValue {
|
||||
ty,
|
||||
kind: EnvValueKind::Local(alloca.as_basic_value_enum()),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Stmt::Return(ret_val) => {
|
||||
if let Some(ret_val) = ret_val {
|
||||
let value = ret_val.into_llvm(context, builder, &scope_env)?;
|
||||
builder.build_return(Some(&value));
|
||||
} else {
|
||||
builder.build_return(None);
|
||||
}
|
||||
}
|
||||
|
||||
Stmt::IfElse(if_else) => {
|
||||
convert_if_else(
|
||||
context, module, builder, function, if_else, &scope_env,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn convert(
|
||||
file_name: String,
|
||||
context: &Context,
|
||||
program: Vec<Decl<Type>>,
|
||||
) -> Result<Module> {
|
||||
let module = context.create_module(&file_name);
|
||||
let builder = context.create_builder();
|
||||
|
||||
let mut env = Env::default();
|
||||
for func in program.iter().filter_map(|decl| match decl {
|
||||
Decl::Func(v) => Some(v),
|
||||
_ => None,
|
||||
}) {
|
||||
let return_ty = func.return_ty.into_llvm_basic_type(context);
|
||||
let args_ty = func
|
||||
.args
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
convert_type_to_metadata_type(arg.ty.into_llvm_basic_type(context))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let llvm_func_ty = fn_type_basic(return_ty, &args_ty, false);
|
||||
let llvm_func = module.add_function(&func.name, llvm_func_ty, None);
|
||||
|
||||
env.local_type_map.insert(
|
||||
func.name.clone(),
|
||||
EnvValue {
|
||||
ty: &func.ty,
|
||||
kind: EnvValueKind::Func(llvm_func),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
for func in program.iter().filter_map(|decl| match decl {
|
||||
Decl::Func(v) => Some(v),
|
||||
_ => None,
|
||||
}) {
|
||||
let llvm_func = match env.local_type_map.get(&func.name) {
|
||||
Some(EnvValue {
|
||||
kind: EnvValueKind::Func(func),
|
||||
..
|
||||
}) => func.clone(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let entry_block = context.append_basic_block(llvm_func, "entry");
|
||||
|
||||
builder.position_at_end(entry_block);
|
||||
|
||||
let mut scoped_env = Env {
|
||||
parent: Some(&env),
|
||||
local_type_map: HashMap::new(),
|
||||
};
|
||||
|
||||
for (arg, param) in func.args.iter().zip(llvm_func.get_params().into_iter())
|
||||
{
|
||||
scoped_env.local_type_map.insert(
|
||||
arg.name.clone(),
|
||||
EnvValue {
|
||||
ty: &arg.ty,
|
||||
kind: EnvValueKind::Local(param),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
convert_stmts(
|
||||
&context,
|
||||
&module,
|
||||
&llvm_func,
|
||||
&builder,
|
||||
&scoped_env,
|
||||
&func.stmts,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(module)
|
||||
}
|
|
@ -1,5 +1,3 @@
|
|||
// pub mod llvm;
|
||||
// pub mod cranelift;
|
||||
pub mod typed;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -64,9 +62,11 @@ pub enum Op {
|
|||
}
|
||||
|
||||
impl Op {
|
||||
/// Check that the types match the current operation. If so, return the type
|
||||
/// that is expected as a result. Otherwise, return None.
|
||||
pub fn check_types(&self, left_ty: &Type, right_ty: &Type) -> Option<Type> {
|
||||
// TODO: since only binops work on integers right now, just check that the two sides are both
|
||||
// integers. this will have to change once && gets added.
|
||||
// TODO: since only binops work on integers right now, just check that the
|
||||
// two sides are both integers. this will have to change once && gets added.
|
||||
match (left_ty, right_ty) {
|
||||
(Type::Int, Type::Int) => Some(Type::Int),
|
||||
_ => None,
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::io::Write;
|
|||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::ast::{ElseClause, IfElse, Stmt, Type};
|
||||
use crate::ast::{ElseClause, IfElse, Type};
|
||||
|
||||
use super::LlvmIrCodegen;
|
||||
|
||||
|
|
10
src/lib.rs
Normal file
10
src/lib.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
#[macro_use]
|
||||
extern crate lalrpop_util;
|
||||
#[macro_use]
|
||||
extern crate anyhow;
|
||||
|
||||
lalrpop_mod!(pub parser);
|
||||
|
||||
pub mod ast;
|
||||
pub mod codegen;
|
||||
mod utils;
|
4
std/e0pkg.yml
Normal file
4
std/e0pkg.yml
Normal file
|
@ -0,0 +1,4 @@
|
|||
name: std
|
||||
version: 0.1.0
|
||||
authors:
|
||||
- Michael Zhang <mail@mzhang.io>
|
Loading…
Reference in a new issue