diff --git a/Cargo.toml b/Cargo.toml index ab0afa3..26ce390 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] } diff --git a/src/main.rs b/bin/e0c.rs similarity index 80% rename from src/main.rs rename to bin/e0c.rs index 6002daf..8eeca3c 100644 --- a/src/main.rs +++ b/bin/e0c.rs @@ -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)?; diff --git a/bin/e0pkg.rs b/bin/e0pkg.rs new file mode 100644 index 0000000..65842bf --- /dev/null +++ b/bin/e0pkg.rs @@ -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(); +} diff --git a/default.nix b/default.nix index ccecb20..d9573d8 100644 --- a/default.nix +++ b/default.nix @@ -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"; diff --git a/flake.nix b/flake.nix index 81e7862..9f0e298 100644 --- a/flake.nix +++ b/flake.nix @@ -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"; diff --git a/scripts/e0c b/scripts/e0c deleted file mode 100755 index bbe5eab..0000000 --- a/scripts/e0c +++ /dev/null @@ -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 diff --git a/src/ast/cranelift.rs b/src/ast/cranelift.rs deleted file mode 100644 index e65aa29..0000000 --- a/src/ast/cranelift.rs +++ /dev/null @@ -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>, -) -> Result { - todo!() -} diff --git a/src/ast/llvm.rs b/src/ast/llvm.rs deleted file mode 100644 index 0b106d7..0000000 --- a/src/ast/llvm.rs +++ /dev/null @@ -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 { - fn into_llvm<'ctx>( - &self, - context: &'ctx Context, - builder: &'ctx Builder, - env: &Env<'_, 'ctx>, - ) -> Result> { - 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::>>()?; - - 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>, -} - -impl<'a, 'ctx> Env<'a, 'ctx> { - pub fn lookup(&self, name: impl AsRef) -> 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, - 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]>, -) -> 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>, -) -> Result { - 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::>(); - 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) -} diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 9f9f4d4..3eb75f9 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -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 { - // 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, diff --git a/src/codegen/llvm_ir/if_else.rs b/src/codegen/llvm_ir/if_else.rs index 4599703..1cf8338 100644 --- a/src/codegen/llvm_ir/if_else.rs +++ b/src/codegen/llvm_ir/if_else.rs @@ -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; diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..bbc6c3f --- /dev/null +++ b/src/lib.rs @@ -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; diff --git a/std/e0pkg.yml b/std/e0pkg.yml new file mode 100644 index 0000000..990b48b --- /dev/null +++ b/std/e0pkg.yml @@ -0,0 +1,4 @@ +name: std +version: 0.1.0 +authors: + - Michael Zhang