Compare commits

..

10 commits

Author SHA1 Message Date
5dc75e5645
zz 2022-07-19 02:10:01 -05:00
2e5b9accc9
Bump dependencies 2022-07-19 02:07:07 -05:00
1c0ab773f1
Roadmap 2022-07-19 02:02:58 -05:00
e546deff64
Annotate with plain text 2022-07-19 01:53:06 -05:00
3ff54b0f44
Vim highlight 2022-07-19 01:51:53 -05:00
273a0dc63b
Use nightly rust 2022-07-19 01:43:51 -05:00
1336d860dc
vim extension 2022-07-19 01:41:30 -05:00
215683173e
Lots of work on revamping the type system for structs, also stubs for future pieces 2022-07-19 01:31:00 -05:00
f7e4bfa8cf
Cleanup 2022-07-18 03:21:39 -05:00
dbb51fd1d0
Some basic conditionals
- No terminator analysis yet
2022-07-17 22:26:11 -05:00
46 changed files with 1512 additions and 1122 deletions

21
.build.yml Normal file
View file

@ -0,0 +1,21 @@
image: archlinux
packages:
- rust-nightly
- rsync
sources:
- https://git.sr.ht/~mzhang/e0
secrets:
- 0b26b413-7901-41c3-a4e2-3c752228ffcb
environment:
CARGO_UNSTABLE_SPARSE_REGISTRY: "true"
tasks:
- setup: |
rustup default nightly
- test: |
cd e0
cargo test --all
- doc-upload: |
cd e0
cargo doc --workspace --no-deps
echo "mzhang.io ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBzBZ+QmM4EO3Fwc1ZcvWV2IY9VF04T0H9brorGj9Udp" >> ~/.ssh/known_hosts
rsync -azvrP target/doc/ sourcehutBuilds@mzhang.io:/mnt/storage/svcdata/blog-public/e0

96
Cargo.lock generated
View file

@ -109,6 +109,16 @@ dependencies = [
"os_str_bytes",
]
[[package]]
name = "codespan-reporting"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
dependencies = [
"termcolor",
"unicode-width",
]
[[package]]
name = "crunchy"
version = "0.2.2"
@ -148,11 +158,17 @@ version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"codespan-reporting",
"frunk",
"lalrpop",
"lalrpop-util",
"tempfile",
]
[[package]]
name = "e0-rt"
version = "0.1.0"
[[package]]
name = "either"
version = "1.7.0"
@ -183,6 +199,70 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "frunk"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cd67cf7d54b7e72d0ea76f3985c3747d74aee43e0218ad993b7903ba7a5395e"
dependencies = [
"frunk_core",
"frunk_derives",
"frunk_proc_macros",
]
[[package]]
name = "frunk_core"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1246cf43ec80bf8b2505b5c360b8fb999c97dabd17dbb604d85558d5cbc25482"
[[package]]
name = "frunk_derives"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dbc4f084ec5a3f031d24ccedeb87ab2c3189a2f33b8d070889073837d5ea09e"
dependencies = [
"frunk_proc_macro_helpers",
"quote",
"syn",
]
[[package]]
name = "frunk_proc_macro_helpers"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99f11257f106c6753f5ffcb8e601fb39c390a088017aaa55b70c526bff15f63e"
dependencies = [
"frunk_core",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "frunk_proc_macros"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a078bd8459eccbb85e0b007b8f756585762a72a9efc53f359b371c3b6351dbcc"
dependencies = [
"frunk_core",
"frunk_proc_macros_impl",
"proc-macro-hack",
]
[[package]]
name = "frunk_proc_macros_impl"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ffba99f0fa4f57e42f57388fbb9a0ca863bc2b4261f3c5570fed579d5df6c32"
dependencies = [
"frunk_core",
"frunk_proc_macro_helpers",
"proc-macro-hack",
"quote",
"syn",
]
[[package]]
name = "getrandom"
version = "0.2.7"
@ -196,9 +276,9 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.12.2"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "607c8a29735385251a339424dd462993c0fed8fa09d378f259377df08c126022"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "heck"
@ -402,6 +482,12 @@ dependencies = [
"version_check",
]
[[package]]
name = "proc-macro-hack"
version = "0.5.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
[[package]]
name = "proc-macro2"
version = "1.0.40"
@ -595,6 +681,12 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7"
[[package]]
name = "unicode-width"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]]
name = "unicode-xid"
version = "0.2.3"

View file

@ -3,11 +3,24 @@ name = "e0"
version = "0.1.0"
edition = "2021"
[workspace]
members = ["rt"]
[[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"] }
lalrpop-util = { version = "0.19.7", features = ["lexer"] }
anyhow = "1.0.58"
clap = { version = "3.2.12", features = ["derive"] }
codespan-reporting = "0.11.1"
frunk = "0.4.0"
lalrpop-util = { version = "0.19.8", features = ["lexer"] }
tempfile = "3.3.0"
[build-dependencies]
lalrpop = "0.19.7"
lalrpop = "0.19.8"

40
README.md Normal file
View file

@ -0,0 +1,40 @@
# e0: Experimental Language #0
`e0` is an experimental language created for the purpose of practicing making a
simple language ecosystem. The language is strongly typed but with only a
limited set of primitives, and compiles down to LLVM IR.
Compilation process
-------------------
```text
e0 source code => llvm bitcode text => clang => link against e0-rt => binary
```
Currently clang is required as there are no plans for custom codegen yet.
Roadmap
-------
In development:
- Structs
- Vim extension
Near future (probably ordered):
- Modules
- Standard library
- e0pkg
- Arrays
- Strings (UTF-8)
Distant future:
- LSP
- Custom codegen backend
License
-------
MIT / Apache-2.0

94
bin/e0c.rs Normal file
View file

@ -0,0 +1,94 @@
use std::fs::{self, File};
use std::io::Write;
use std::path::PathBuf;
use std::process::ExitCode;
use anyhow::Result;
use clap::Parser;
use codespan_reporting::diagnostic::{Diagnostic, Label};
use codespan_reporting::files::SimpleFiles;
use codespan_reporting::term::termcolor::{ColorChoice, StandardStream};
use codespan_reporting::term::{self, Config as CodespanConfig};
use e0::ast::typeck_bidi::TypeChecker;
use e0::codegen::llvm_ir::LlvmIrCodegen;
use e0::codegen::CodegenBackend;
use e0::parser::ProgramParser;
use lalrpop_util::ParseError;
#[derive(Debug, Parser)]
struct Opt {
path: PathBuf,
/// Path to output the bitcode to.
#[clap(short = 'o', long = "out", name = "bitcode-out")]
out_path: PathBuf,
/// Path to output the AST to, if at all.
#[clap(long = "emit-ast", name = "ast-out")]
emit_ast: Option<PathBuf>,
}
fn main() -> Result<ExitCode> {
let opts = Opt::parse();
// Set up reporting
let mut files = SimpleFiles::new();
let writer = StandardStream::stderr(ColorChoice::Always);
let config = CodespanConfig::default();
let contents = fs::read_to_string(&opts.path)?;
let file_id = files.add(opts.path.display().to_string(), &contents);
let parser = ProgramParser::new();
let ast = match parser.parse(&contents) {
Ok(v) => v,
Err(err) => {
let loc = match err {
ParseError::InvalidToken { location }
| ParseError::UnrecognizedEOF { location, .. } => (location, location),
ParseError::UnrecognizedToken { ref token, .. }
| ParseError::ExtraToken { ref token } => (token.0, token.2),
_ => todo!(),
};
let diagnostic = Diagnostic::error()
.with_labels(vec![Label::primary(file_id, loc.0..loc.1)])
.with_message(err.to_string());
term::emit(&mut writer.lock(), &config, &files, &diagnostic)?;
return Ok(ExitCode::FAILURE);
}
};
let type_checker = TypeChecker::default();
let typed_ast = match type_checker.convert(ast) {
Ok(v) => v,
Err(err) => {
let diagnostic = Diagnostic::error().with_message(err.to_string());
term::emit(&mut writer.lock(), &config, &files, &diagnostic)?;
return Ok(ExitCode::FAILURE);
}
};
if let Some(path) = opts.emit_ast {
let mut file = File::create(&path)?;
file.write(format!("{:#?}", typed_ast).as_bytes())?;
}
{
let file = File::create(&opts.out_path)?;
let mut codegen = LlvmIrCodegen::new(file);
codegen.convert(typed_ast)?;
}
// let ctx = ast::cranelift::convert(
// opts.path.display().to_string(),
// typed_ast,
// )?;
// {
// let file = File::create(&opts.out_path)?;
// ctx.compile_and_emit();
// println!("Emitted.");
// }
Ok(ExitCode::SUCCESS)
}

18
bin/e0pkg.rs Normal file
View 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();
}

View file

@ -1,6 +1,8 @@
{ fenix, makeRustPlatform, lib, nix-gitignore, libffi, libgcc }:
{ toolchain, makeRustPlatform, lib, nix-gitignore, libffi, libgcc }:
let rustPlatform = makeRustPlatform { inherit (fenix.minimal) cargo rustc; };
with lib;
let rustPlatform = makeRustPlatform { inherit (toolchain) cargo rustc; };
in rustPlatform.buildRustPackage {
name = "e0";
@ -10,4 +12,10 @@ in rustPlatform.buildRustPackage {
buildInputs = [ libffi ];
cargoLock = { lockFile = ./Cargo.lock; };
meta = {
description = "Experimental programming language #0";
license = licenses.mit;
homepage = "https://git.sr.ht/~mzhang/e0";
};
}

1
docs/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
book

6
docs/book.toml Normal file
View file

@ -0,0 +1,6 @@
[book]
authors = ["Michael Zhang"]
language = "en"
multilingual = false
src = "src"
title = "e0 Reference"

3
docs/src/SUMMARY.md Normal file
View file

@ -0,0 +1,3 @@
# Summary
- [Chapter 1](./chapter_1.md)

1
docs/src/chapter_1.md Normal file
View file

@ -0,0 +1 @@
# Chapter 1

View file

@ -5,13 +5,7 @@ fn main() -> int {
return 10;
} else if x > 50 {
return 50;
} else {
if x < 20 {
return 20;
} else if x > 40 {
return 40;
} else {
return x;
}
}
return 65;
}

View file

@ -3,11 +3,14 @@ type IntPair = struct {
snd: int,
}
fn main() -> int {
let x = IntPair {
fn getX() -> IntPair {
return new IntPair {
fst: 4,
snd: 6,
};
}
fn main() -> int {
let x = getX();
return x.fst + x.snd;
}

8
ext/e0-vim/default.nix Normal file
View file

@ -0,0 +1,8 @@
{ vimUtils }:
with vimUtils;
buildVimPlugin {
name = "e0-vim";
src = ./.;
}

View file

@ -0,0 +1,2 @@
" Automatic detection of *.e0 files
autocmd BufNewFile,BufRead *.e0 setfiletype e0

View file

7
ext/e0-vim/syntax/e0.vim Normal file
View file

@ -0,0 +1,7 @@
syntax keyword e0Conditional if else
syntax keyword e0PrimitiveType int
syntax keyword e0Keyword fn type return let new
highlight default link e0Conditional Conditional
highlight default link e0PrimitiveType Type
highlight default link e0Keyword Keyword

View file

@ -12,16 +12,30 @@
inherit system;
overlays = [ fenix.overlay ];
};
myPkgs = rec { e0 = pkgs.callPackage ./. { }; };
toolchain = pkgs.fenix.complete;
myPkgs = rec {
e0 = pkgs.callPackage ./. { inherit toolchain; };
e0-vim = pkgs.callPackage ./ext/e0-vim { };
};
in rec {
devShell = pkgs.mkShell {
packages = with pkgs;
with pkgs.llvmPackages_12; [
cargo-edit
cargo-watch
clangUseLLVM
mdbook
(toolchain.withComponents [
"cargo"
"clippy"
"rustc"
"rust-src"
"rustfmt"
])
];
inputsFrom = with myPkgs; [ e0 ];
inputsFrom = with myPkgs; [ e0 e0-vim ];
CARGO_UNSTABLE_SPARSE_REGISTRY = "true";
};

9
rt/Cargo.toml Normal file
View file

@ -0,0 +1,9 @@
[package]
name = "e0-rt"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]

1
rt/src/aarch64/mod.rs Normal file
View file

@ -0,0 +1 @@
pub mod syscall;

View file

8
rt/src/gc.rs Normal file
View file

@ -0,0 +1,8 @@
//! Simple concurrent mark-and-sweep garbage collector.
struct Allocator {
}
#[no_mangle]
pub extern "C" fn __e0_gc_init() {
}

23
rt/src/lib.rs Normal file
View file

@ -0,0 +1,23 @@
#![feature(lang_items)]
#![cfg_attr(not(test), no_std)]
#[cfg(target_arch = "aarch64")]
mod aarch64;
#[cfg(target_arch = "x86_64")]
mod x86_64;
pub mod gc;
pub mod start;
use core::panic::PanicInfo;
#[cfg(not(test))]
#[panic_handler]
fn panic(_panic: &PanicInfo<'_>) -> ! {
loop {}
}
#[cfg(not(test))]
#[lang = "eh_personality"]
extern "C" fn eh_personality() {}

4
rt/src/start.rs Normal file
View file

@ -0,0 +1,4 @@
#[no_mangle]
pub extern "C" fn __e0_rt_init() {
// Set up thread-local storage
}

12
rt/src/x86_64/linux.rs Normal file
View file

@ -0,0 +1,12 @@
use core::mem::size_of;
#[repr(C)]
pub struct CloneArgs {
pub flags: u64,
}
#[no_mangle]
#[inline(always)]
pub extern "C" fn clone3(flags: CloneArgs) {
let size = size_of::<CloneArgs>();
}

4
rt/src/x86_64/mod.rs Normal file
View file

@ -0,0 +1,4 @@
pub mod syscall;
#[cfg(target_os = "linux")]
pub mod linux;

45
rt/src/x86_64/syscall.rs Normal file
View file

@ -0,0 +1,45 @@
use core::arch::asm;
#[no_mangle]
#[inline(always)]
pub extern "C" fn __syscall0(n: u64) -> u64 {
let mut res: u64;
unsafe {
asm!(
"syscall",
in("rax") n,
lateout("rax") res,
out("rcx") _,
out("r11") _,
);
}
res
}
#[no_mangle]
#[inline(always)]
pub extern "C" fn __syscall2(n: u64, a1: u64, a2: u64) -> u64 {
let mut res: u64;
unsafe {
asm!(
"syscall",
in("rax") n,
in("rdi") a1,
in("rsi") a2,
lateout("rax") res,
out("rcx") _,
out("r11") _,
);
}
res
}
#[cfg(test)]
mod tests {
use super::__syscall0;
#[test]
fn run() {
println!("{}", __syscall0(102));
}
}

View file

@ -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

View file

@ -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!()
}

View file

@ -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)
}

View file

@ -1,30 +1,44 @@
// pub mod llvm;
// pub mod cranelift;
pub mod typed;
// pub mod typeck;
pub mod typeck_bidi;
pub mod types;
use self::types::Type;
#[derive(Debug)]
pub enum Decl<T> {
Func(Func<T>),
TypeDef(TypeDef<T>),
}
#[derive(Debug)]
pub struct Func<T> {
pub name: String,
pub args: Vec<Arg>,
pub return_ty: Type,
pub args: Vec<Arg<T>>,
pub return_ty: T,
pub stmts: Vec<Stmt<T>>,
}
#[derive(Debug)]
pub struct TypeDef<T> {
pub name: String,
pub def: T,
}
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub struct Arg<T> {
pub name: String,
pub ty: T,
}
#[derive(Debug, Clone)]
pub struct Arg {
#[derive(Debug)]
pub struct NameValue<T> {
pub name: String,
pub ty: Type,
pub expr: Expr<T>,
}
#[derive(Debug)]
pub enum Stmt<T> {
Let(String, Option<Type>, Expr<T>),
Let(String, T, Expr<T>),
Return(Option<Expr<T>>),
IfElse(IfElse<T>),
}
@ -54,6 +68,9 @@ pub enum ExprKind<T> {
Var(String),
BinOp(Box<Expr<T>>, Op, Box<Expr<T>>),
Call(String, Vec<Expr<T>>),
StructNew(String, Vec<NameValue<T>>),
FieldAccess(Box<Expr<T>>, String),
}
#[derive(Copy, Clone, Debug)]
@ -64,21 +81,14 @@ 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,
}
}
}
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub enum Type {
Int,
Bool,
StructInst(Vec<(String, Type)>),
Func(Vec<Type>, Box<Type>),
}

600
src/ast/typeck.rs Normal file
View file

@ -0,0 +1,600 @@
//! Hindley-Milner-shaped type-checker.
use std::collections::{HashMap, HashSet};
use anyhow::Result;
use crate::{ast::Expr, utils::LayeredEnv};
use super::{
types::{TypeS, TypeTC},
Arg, NameValue,
};
use super::{Decl, ElseClause, ExprKind, Func, IfElse, Op, Stmt, Type};
type Assignments = HashMap<usize, TypeTC>;
#[derive(Default)]
pub struct TypeChecker {
assignments: Assignments,
ctr: usize,
struct_env: LayeredEnv<String, Vec<Arg<TypeTC>>>,
type_env: LayeredEnv<String, TypeTC>,
constraints: HashSet<Constraint2>,
}
impl TypeChecker {
/// Convert an AST with source types to an AST with final types.
pub fn convert(mut self, ast: Vec<Decl<TypeS>>) -> Result<Vec<Decl<Type>>> {
// First pass, gather all of the type signatures in the top level
self.type_env.push();
for decl in ast.iter() {
match decl {
Decl::Func(func) => {
let name = func.name.clone();
let args_ty = func.args.iter().map(|arg| arg.ty.clone()).collect();
let ty = TypeS::Func(args_ty, Box::new(func.return_ty.clone()));
let ty = self.convert_TypeS_to_TypeTC(ty);
self.type_env.insert(name, ty);
}
Decl::TypeDef(typedef) => match &typedef.def {
TypeS::Struct(strct) => {
let strct = strct
.iter()
.map(|arg| Arg {
name: arg.name.clone(),
ty: self.convert_TypeS_to_TypeTC(arg.ty.clone()),
})
.collect();
self.struct_env.insert(typedef.name.clone(), strct);
}
_ => {}
},
}
}
// Now, type-check each function separately
let mut new_decl = Vec::new();
for decl in ast.iter() {
match decl {
Decl::Func(func) => {
self.type_env.push();
for arg in func.args.iter() {
let arg_ty = self.convert_TypeS_to_TypeTC(arg.ty.clone());
self.type_env.insert(arg.name.clone(), arg_ty);
}
let constraints = HashSet::new();
let new_stmts = self.annotate_stmts(&func.stmts)?;
let mut args_ty = Vec::new();
let mut args_func = Vec::new();
for arg in func.args.iter() {
let ty = self.convert_TypeS_to_TypeTC(arg.ty.clone());
args_ty.push(ty.clone());
args_func.push(Arg {
name: arg.name.clone(),
ty,
});
}
let return_ty = self.convert_TypeS_to_TypeTC(func.return_ty.clone());
let decorated_func: Func<TypeTC> = Func {
name: func.name.clone(),
args: args_func,
return_ty,
stmts: new_stmts,
};
self.type_env.pop();
self.unify_constraints(&constraints)?;
println!("Func: {:?}", decorated_func);
println!("Constraints:");
for Constraint(left, right) in constraints {
println!("- {:?} == {:?}", left, right);
}
println!("Assignments:");
for (num, ty) in self.assignments.iter() {
println!("- {} : {:?}", num, ty);
}
let typed_func = self.substitute_in_func(decorated_func)?;
new_decl.push(Decl::Func(typed_func));
}
_ => {}
}
}
self.type_env.pop();
Ok(new_decl)
}
/// Convert a type from the source code into a type used for type-checking.
#[allow(non_snake_case)]
fn convert_TypeS_to_TypeTC(&mut self, ty: TypeS) -> TypeTC {
match ty {
TypeS::Infer => self.type_var(),
TypeS::Named(name) => match self.struct_env.lookup(&name) {
Some(ty) => TypeTC::Struct(ty.clone()),
None => self.type_var(),
},
TypeS::Unit => TypeTC::Unit,
TypeS::Int => TypeTC::Int,
TypeS::Bool => TypeTC::Bool,
TypeS::Func(args, ret) => {
let args = args
.into_iter()
.map(|arg| self.convert_TypeS_to_TypeTC(arg))
.collect();
let ret = self.convert_TypeS_to_TypeTC(*ret);
TypeTC::Func(args, Box::new(ret))
}
TypeS::Struct(fields) => TypeTC::Struct(
fields
.into_iter()
.map(|arg| Arg {
name: arg.name,
ty: self.convert_TypeS_to_TypeTC(arg.ty),
})
.collect(),
),
}
}
#[allow(non_snake_case)]
fn convert_TypeTC_to_Type(&self, ty: &TypeTC) -> Result<Type> {
Ok(match ty {
TypeTC::Var(n) => match self.assignments.get(&n) {
Some(v) => self.convert_TypeTC_to_Type(&v)?,
None => bail!("Unsolved constraint variable {n}"),
},
TypeTC::Unit => Type::Unit,
TypeTC::Int => Type::Int,
TypeTC::Bool => Type::Bool,
TypeTC::Func(args, ret) => {
let args = args
.into_iter()
.map(|arg| self.convert_TypeTC_to_Type(&arg))
.collect::<Result<_>>()?;
let ret = self.convert_TypeTC_to_Type(&ret)?;
Type::Func(args, Box::new(ret))
}
TypeTC::Struct(fields) => Type::Struct(
fields
.into_iter()
.map(|arg| {
Ok(Arg {
name: arg.name.clone(),
ty: self.convert_TypeTC_to_Type(&arg.ty)?,
})
})
.collect::<Result<_>>()?,
),
})
}
fn annotate_stmts(
&mut self,
stmts: impl AsRef<[Stmt<TypeS>]>,
) -> Result<Vec<Stmt<TypeTC>>> {
let stmts = stmts.as_ref();
let mut new_stmts = Vec::new();
self.type_env.push();
for stmt in stmts.iter() {
match stmt {
Stmt::Return(ret_val) => {
let new_stmt = match ret_val {
Some(v) => Some(self.annotate_expr(v)?),
None => None,
};
new_stmts.push(Stmt::Return(new_stmt));
}
Stmt::Let(name, ty, body) => {
let ty = self.convert_TypeS_to_TypeTC(ty.clone());
let new_stmt =
Stmt::Let(name.clone(), ty.clone(), self.annotate_expr(body)?);
self.type_env.insert(name.to_owned(), ty);
new_stmts.push(new_stmt);
}
Stmt::IfElse(if_else) => {
let new_stmt = Stmt::IfElse(self.annotate_if_else(&if_else)?);
new_stmts.push(new_stmt);
}
}
}
self.type_env.pop();
Ok(new_stmts)
}
fn annotate_expr(&mut self, expr: &Expr<TypeS>) -> Result<Expr<TypeTC>> {
Ok(match &expr.kind {
ExprKind::Int(n) => Expr {
kind: ExprKind::Int(*n),
ty: TypeTC::Int,
},
ExprKind::Var(name) => {
let ty = match self.type_env.lookup(name) {
Some(v) => v.clone(),
None => self.type_var(),
};
Expr {
kind: ExprKind::Var(name.clone()),
ty,
}
}
ExprKind::BinOp(left, op, right) => {
let left = self.annotate_expr(left)?;
let right = self.annotate_expr(right)?;
let output = self.type_var();
op.constraints(self, &left.ty, &right.ty, &output);
Expr {
kind: ExprKind::BinOp(Box::new(left), *op, Box::new(right)),
ty: output,
}
}
ExprKind::Call(func_name, args) => {
let mut args_annot = Vec::new();
let ret_ty = self.type_var();
let (func_args_ty, func_ret_ty) = match self.type_env.lookup(func_name)
{
Some(TypeTC::Func(args, ret)) => (args.clone(), *ret.clone()),
Some(_) => bail!("Not a function"),
None => bail!("Name not found"),
};
self.constrain(ret_ty.clone(), func_ret_ty);
for (arg, expected_ty) in args.iter().zip(func_args_ty.iter()) {
let arg_annot = self.annotate_expr(arg)?;
self.constrain(arg_annot.ty.clone(), expected_ty.clone());
args_annot.push(arg_annot);
}
Expr {
kind: ExprKind::Call(func_name.to_string(), args_annot),
ty: ret_ty,
}
}
ExprKind::StructNew(struct_ref, values) => {
let struct_def = match self.struct_env.lookup(struct_ref) {
Some(v) => v.clone(),
None => bail!("No struct named {struct_ref:?}"),
};
let mut values2 = Vec::new();
for (arg, value) in struct_def.iter().zip(values.iter()) {
let ty = self.type_var();
let expr = self.annotate_expr(&value.expr)?;
self.constrain(arg.ty.clone(), ty.clone());
values2.push(NameValue {
name: value.name.clone(),
expr,
});
}
Expr {
kind: ExprKind::StructNew(struct_ref.to_owned(), values2),
ty: TypeTC::Struct(struct_def.to_vec()),
}
}
ExprKind::FieldAccess(expr, field_name) => {
let struct_ty = self.type_var();
let field_ty = self.type_var();
let expr = self.annotate_expr(&expr)?;
self.require_field(struct_ty, field_name, field_ty.clone());
Expr {
kind: ExprKind::FieldAccess(Box::new(expr), field_name.to_owned()),
ty: field_ty,
}
}
})
}
fn annotate_if_else(
&mut self,
if_else: &IfElse<TypeS>,
) -> Result<IfElse<TypeTC>> {
let converted_cond = self.annotate_expr(&if_else.cond)?;
let converted_body = self.annotate_stmts(&if_else.body)?;
let else_clause = match &if_else.else_clause {
Some(ElseClause::If(if_else2)) => {
Some(ElseClause::If(Box::new(self.annotate_if_else(&if_else2)?)))
}
Some(ElseClause::Body(stmts)) => {
Some(ElseClause::Body(self.annotate_stmts(&stmts)?))
}
None => None,
};
Ok(IfElse {
cond: converted_cond,
body: converted_body,
else_clause,
})
}
fn substitute_in_expr_kind(
&self,
expr_kind: ExprKind<TypeTC>,
) -> Result<ExprKind<Type>> {
Ok(match expr_kind {
ExprKind::Int(n) => ExprKind::Int(n),
ExprKind::Var(name) => ExprKind::Var(name),
ExprKind::BinOp(left, op, right) => {
let left = self.substitute_in_expr(*left)?;
let right = self.substitute_in_expr(*right)?;
ExprKind::BinOp(Box::new(left), op, Box::new(right))
}
ExprKind::Call(func, args) => {
let args = args
.into_iter()
.map(|arg| self.substitute_in_expr(arg))
.collect::<Result<_>>()?;
ExprKind::Call(func, args)
}
ExprKind::StructNew(struct_ref, values) => {
let values = values
.into_iter()
.map(|nv| {
Ok(NameValue {
name: nv.name,
expr: self.substitute_in_expr(nv.expr)?,
})
})
.collect::<Result<_>>()?;
ExprKind::StructNew(struct_ref, values)
}
ExprKind::FieldAccess(expr, field_name) => {
let expr = self.substitute_in_expr(*expr)?;
ExprKind::FieldAccess(Box::new(expr), field_name)
}
})
}
fn substitute_in_expr(&self, expr: Expr<TypeTC>) -> Result<Expr<Type>> {
Ok(Expr {
kind: self.substitute_in_expr_kind(expr.kind)?,
ty: self.convert_TypeTC_to_Type(&expr.ty)?,
})
}
fn substitute_in_if_else(
&self,
if_else: IfElse<TypeTC>,
) -> Result<IfElse<Type>> {
let cond = self.substitute_in_expr(if_else.cond)?;
let body = self.substitute_in_stmts(if_else.body)?;
let else_clause = match if_else.else_clause {
Some(ElseClause::If(if_else2)) => Some(ElseClause::If(Box::new(
self.substitute_in_if_else(*if_else2)?,
))),
Some(ElseClause::Body(body)) => {
Some(ElseClause::Body(self.substitute_in_stmts(body)?))
}
None => None,
};
Ok(IfElse {
cond,
body,
else_clause,
})
}
fn substitute_in_stmts(
&self,
stmts: Vec<Stmt<TypeTC>>,
) -> Result<Vec<Stmt<Type>>> {
stmts
.into_iter()
.map(|stmt| {
Ok(match stmt {
Stmt::Let(name, ty, body) => {
let ty = self.convert_TypeTC_to_Type(&ty)?;
Stmt::Let(name, ty, self.substitute_in_expr(body)?)
}
Stmt::Return(ret_val) => Stmt::Return(match ret_val {
Some(v) => Some(self.substitute_in_expr(v)?),
None => None,
}),
Stmt::IfElse(if_else) => {
Stmt::IfElse(self.substitute_in_if_else(if_else)?)
}
})
})
.collect()
}
fn substitute_in_func(&self, func: Func<TypeTC>) -> Result<Func<Type>> {
let args = func
.args
.into_iter()
.map(|arg| {
Ok(Arg {
name: arg.name,
ty: self.convert_TypeTC_to_Type(&arg.ty)?,
})
})
.collect::<Result<_>>()?;
let return_ty = self.convert_TypeTC_to_Type(&func.return_ty)?;
Ok(Func {
name: func.name,
args,
return_ty,
stmts: self.substitute_in_stmts(func.stmts)?,
})
}
fn substitute_types(&mut self, ty: &TypeTC) -> TypeTC {
match ty {
TypeTC::Var(n) => match self.assignments.get(&n) {
Some(ty2) => ty2.clone(),
None => ty.clone(),
},
TypeTC::Func(args, ret) => {
let args = args
.into_iter()
.map(|arg| self.substitute_types(arg))
.collect();
let ret = self.substitute_types(&*ret);
TypeTC::Func(args, Box::new(ret))
}
TypeTC::Struct(fields) => TypeTC::Struct(fields.to_vec()),
TypeTC::Unit | TypeTC::Int | TypeTC::Bool => ty.clone(),
}
}
fn unify_constraints(
&mut self,
constraints: &HashSet<Constraint2>,
) -> Result<()> {
for constraint in constraints.iter() {
match constraint {
Constraint2::Eq(left, right) => {
let left = self.substitute_types(left);
let right = self.substitute_types(right);
self.unify_single(left, right)?;
}
Constraint2::HasField(struct_ty, field_name, field_ty) => {
let struct_ty = self.substitute_types(struct_ty);
let field_ty = self.substitute_types(field_ty);
match struct_ty {
TypeTC::Struct(fields) => {}
TypeTC::Var(n) => {}
_ => bail!(
"Expected struct with field {field_name}, got {struct_ty:?}"
),
}
}
}
}
Ok(())
}
fn unify_single(&mut self, left: TypeTC, right: TypeTC) -> Result<()> {
match (left, right) {
// Unify basic types
(TypeTC::Unit, TypeTC::Unit)
| (TypeTC::Int, TypeTC::Int)
| (TypeTC::Bool, TypeTC::Bool) => {}
// Unify variables
(TypeTC::Var(n), o) | (o, TypeTC::Var(n)) => {
self.assignments.insert(n, o);
}
// Unify functions
(
TypeTC::Func(left_args, left_ret),
TypeTC::Func(right_args, right_ret),
) => {
let mut new_constraints = HashSet::new();
for (left_arg, right_arg) in
left_args.into_iter().zip(right_args.into_iter())
{
new_constraints.insert(Constraint2::Eq(left_arg, right_arg));
}
new_constraints.insert(Constraint2::Eq(*left_ret, *right_ret));
self.unify_constraints(&new_constraints)?;
}
(left, right) => bail!("Mismatching types {left:?} vs. {right:?}"),
};
Ok(())
}
fn type_var(&mut self) -> TypeTC {
TypeTC::Var(self.gen_int())
}
fn gen_int(&mut self) -> usize {
let id = self.ctr;
self.ctr += 1;
id
}
fn constrain(&mut self, left: TypeTC, right: TypeTC) {
if left == right {
// No op, return now
return;
}
self.constraints.insert(Constraint2::Eq(left, right));
}
fn require_field(
&mut self,
left: TypeTC,
right_name: &str,
right_ty: TypeTC,
) {
self.constraints.insert(Constraint2::HasField(
left,
right_name.to_owned(),
right_ty,
));
}
}
impl Op {
fn constraints(
&self,
tc: &mut TypeChecker,
left: &TypeTC,
right: &TypeTC,
output: &TypeTC,
) {
match self {
Op::Plus => {
tc.constrain(left.clone(), TypeTC::Int);
tc.constrain(right.clone(), TypeTC::Int);
tc.constrain(output.clone(), TypeTC::Int);
}
Op::LessThan | Op::GreaterThan => {
tc.constrain(left.clone(), TypeTC::Int);
tc.constrain(right.clone(), TypeTC::Int);
tc.constrain(output.clone(), TypeTC::Bool);
}
}
}
}
#[derive(Debug, Hash, Eq, PartialEq)]
struct Constraint(TypeTC, TypeTC);
#[derive(Debug, Hash, Eq, PartialEq)]
enum Constraint2 {
Eq(TypeTC, TypeTC),
HasField(TypeTC, String, TypeTC),
}

149
src/ast/typeck_bidi.rs Normal file
View file

@ -0,0 +1,149 @@
use std::collections::{HashSet, VecDeque};
use anyhow::Result;
use crate::utils::LayeredEnv;
use super::types::{Type, TypeS, TypeTC};
use super::{Arg, Decl, Expr, ExprKind, Stmt};
#[derive(Default)]
pub struct TypeChecker {
ctr: usize,
type_env: LayeredEnv<String, TypeTC>,
struct_env: LayeredEnv<String, Vec<Arg<TypeTC>>>,
}
impl TypeChecker {
pub fn convert(mut self, ast: Vec<Decl<TypeS>>) -> Result<Vec<Decl<Type>>> {
// First pass, gather all of the type signatures in the top level
self.type_env.push();
for decl in ast.iter() {
match decl {
Decl::Func(func) => {
let name = func.name.clone();
let args_ty = func.args.iter().map(|arg| arg.ty.clone()).collect();
let ty = TypeS::Func(args_ty, Box::new(func.return_ty.clone()));
let ty = self.convert_TypeS_to_TypeTC(ty);
self.type_env.insert(name, ty);
}
Decl::TypeDef(typedef) => match &typedef.def {
TypeS::Struct(strct) => {
let strct = strct
.iter()
.map(|arg| Arg {
name: arg.name.clone(),
ty: self.convert_TypeS_to_TypeTC(arg.ty.clone()),
})
.collect();
self.struct_env.insert(typedef.name.clone(), strct);
}
_ => {}
},
}
}
// Now, type-check each function separately
// let mut new_decl = Vec::new();
for decl in ast.iter() {
if let Decl::Func(func) = decl {
self.type_env.push();
// Stmt queue. If there's not enough information to figure out the type,
// punt it to the back of the queue. Eventually we stitch it back
// together in order.
let mut stmts: VecDeque<_> = func.stmts.iter().enumerate().collect();
while !stmts.is_empty() {
let next_stmt = stmts.pop_front();
// self.infer_stmt(next_stmt);
}
self.type_env.pop();
}
}
self.type_env.pop();
todo!()
}
fn infer_stmt(&mut self, stmt: Stmt<TypeS>) -> Result<Stmt<TypeTC>> {
todo!()
}
fn infer_expr(&mut self, expr: Expr<TypeS>) -> Result<Expr<TypeTC>> {
Ok(match expr.kind {
ExprKind::Int(n) => Expr {
kind: ExprKind::Int(n),
ty: TypeTC::Int,
},
ExprKind::Var(_) => todo!(),
ExprKind::BinOp(_, _, _) => todo!(),
ExprKind::Call(_, _) => todo!(),
ExprKind::StructNew(_, _) => todo!(),
ExprKind::FieldAccess(_, _) => todo!(),
})
}
fn check_expr(
&mut self,
expr: Expr<TypeS>,
ty: TypeTC,
) -> Result<Expr<TypeTC>> {
Ok(match expr.kind {
ExprKind::Int(_) => self.infer_expr(expr)?,
ExprKind::Var(_) => todo!(),
ExprKind::BinOp(_, _, _) => todo!(),
ExprKind::Call(_, _) => todo!(),
ExprKind::StructNew(_, _) => todo!(),
ExprKind::FieldAccess(_, _) => todo!(),
})
}
/// Convert a type from the source code into a type used for type-checking.
#[allow(non_snake_case)]
fn convert_TypeS_to_TypeTC(&mut self, ty: TypeS) -> TypeTC {
match ty {
TypeS::Infer => self.type_var(),
TypeS::Named(name) => match self.struct_env.lookup(&name) {
Some(ty) => TypeTC::Struct(ty.clone()),
None => self.type_var(),
},
TypeS::Unit => TypeTC::Unit,
TypeS::Int => TypeTC::Int,
TypeS::Bool => TypeTC::Bool,
TypeS::Func(args, ret) => {
let args = args
.into_iter()
.map(|arg| self.convert_TypeS_to_TypeTC(arg))
.collect();
let ret = self.convert_TypeS_to_TypeTC(*ret);
TypeTC::Func(args, Box::new(ret))
}
TypeS::Struct(fields) => TypeTC::Struct(
fields
.into_iter()
.map(|arg| Arg {
name: arg.name,
ty: self.convert_TypeS_to_TypeTC(arg.ty),
})
.collect(),
),
}
}
fn type_var(&mut self) -> TypeTC {
TypeTC::Var(self.gen_int())
}
fn gen_int(&mut self) -> usize {
let id = self.ctr;
self.ctr += 1;
id
}
}

View file

@ -1,532 +0,0 @@
use std::{
collections::{HashMap, HashSet},
mem,
};
use anyhow::Result;
use crate::ast::Expr;
use super::{Decl, ElseClause, ExprKind, Func, IfElse, Op, Stmt, Type};
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub enum Type_ {
Var(usize),
Int,
Bool,
StructInst(Vec<(String, Type_)>),
Func(Vec<Type_>, Box<Type_>),
}
impl Type_ {
fn from_type(ty: Type) -> Self {
match ty {
Type::Int => Type_::Int,
Type::Bool => Type_::Bool,
Type::Func(args, ret) => {
let args = args.into_iter().map(|arg| Type_::from_type(arg)).collect();
let ret = Type_::from_type(*ret);
Type_::Func(args, Box::new(ret))
}
Type::StructInst(fields) => Type_::StructInst(
fields
.into_iter()
.map(|(name, ty)| (name, Type_::from_type(ty)))
.collect(),
),
}
}
fn convert(&self, assignments: &Assignments) -> Result<Type> {
Ok(match self {
Type_::Var(n) => match assignments.get(&n) {
Some(v) => v.convert(assignments)?,
None => bail!("Unsolved constraint variable {n}"),
},
Type_::Int => Type::Int,
Type_::Bool => Type::Bool,
Type_::Func(args, ret) => {
let args = args
.into_iter()
.map(|arg| arg.convert(assignments))
.collect::<Result<_>>()?;
let ret = ret.convert(assignments)?;
Type::Func(args, Box::new(ret))
}
Type_::StructInst(fields) => Type::StructInst(
fields
.into_iter()
.map(|(name, ty)| Ok((name.clone(), ty.convert(assignments)?)))
.collect::<Result<_>>()?,
),
})
}
}
impl Op {
fn constraints(
&self,
ctx: &mut AnnotationContext,
left: &Type_,
right: &Type_,
output: &Type_,
) {
match self {
Op::Plus => {
ctx.constrain(left.clone(), Type_::Int);
ctx.constrain(right.clone(), Type_::Int);
ctx.constrain(output.clone(), Type_::Int);
}
Op::LessThan | Op::GreaterThan => {
ctx.constrain(left.clone(), Type_::Int);
ctx.constrain(right.clone(), Type_::Int);
ctx.constrain(output.clone(), Type_::Bool);
}
}
}
}
#[derive(Debug, Hash, Eq, PartialEq)]
struct Constraint(Type_, Type_);
#[derive(Debug, Default)]
struct Env {
parent: Option<Box<Env>>,
local_type_map: HashMap<String, Type_>,
}
impl Env {
pub fn lookup(&self, name: impl AsRef<str>) -> Option<&Type_> {
match self.local_type_map.get(name.as_ref()) {
Some(v) => Some(v),
None => match &self.parent {
Some(p) => p.lookup(name),
None => None,
},
}
}
}
struct AnnotationContext<'a> {
counter: usize,
constraints: &'a mut HashSet<Constraint>,
current_env: Option<Env>,
}
impl<'a> AnnotationContext<'a> {
pub fn type_var(&mut self) -> Type_ {
Type_::Var(self.gen_int())
}
pub fn gen_int(&mut self) -> usize {
let id = self.counter;
self.counter += 1;
id
}
pub fn constrain(&mut self, left: Type_, right: Type_) {
if left == right {
// No op, return now
return;
}
self.constraints.insert(Constraint(left, right));
}
pub fn lookup(&self, name: impl AsRef<str>) -> Option<&Type_> {
self.current_env.as_ref().unwrap().lookup(name)
}
pub fn define_var(&mut self, name: impl AsRef<str>, ty: Type_) {
self
.current_env
.as_mut()
.unwrap()
.local_type_map
.insert(name.as_ref().to_string(), ty);
}
pub fn push_scope(&mut self) {
self.current_env = Some(Env {
parent: Some(Box::new(self.current_env.take().unwrap())),
local_type_map: Default::default(),
});
}
pub fn pop_scope(&mut self) {
self.current_env =
Some(*self.current_env.take().unwrap().parent.take().unwrap());
}
}
fn annotate_stmts(
ctx: &mut AnnotationContext,
stmts: impl AsRef<[Stmt<()>]>,
) -> Result<Vec<Stmt<Type_>>> {
let stmts = stmts.as_ref();
let mut new_stmts = Vec::new();
ctx.push_scope();
for stmt in stmts.iter() {
match stmt {
Stmt::Return(ret_val) => {
let new_stmt = match ret_val {
Some(v) => Some(annotate_expr(ctx, v)?),
None => None,
};
new_stmts.push(Stmt::Return(new_stmt));
}
Stmt::Let(name, ty, body) => {
let new_stmt =
Stmt::Let(name.clone(), ty.clone(), annotate_expr(ctx, body)?);
let ty = match ty {
Some(v) => Type_::from_type(v.clone()),
None => ctx.type_var(),
};
ctx.define_var(name, ty);
new_stmts.push(new_stmt);
}
Stmt::IfElse(if_else) => {
let new_stmt = Stmt::IfElse(annotate_if_else(ctx, &if_else)?);
new_stmts.push(new_stmt);
}
}
}
ctx.pop_scope();
Ok(new_stmts)
}
fn annotate_expr(
ctx: &mut AnnotationContext,
expr: &Expr<()>,
) -> Result<Expr<Type_>> {
Ok(match &expr.kind {
ExprKind::Int(n) => Expr {
kind: ExprKind::Int(*n),
ty: Type_::Int,
},
ExprKind::Var(name) => {
let ty = match ctx.lookup(name) {
Some(v) => v.clone(),
None => ctx.type_var(),
};
Expr {
kind: ExprKind::Var(name.clone()),
ty,
}
}
ExprKind::BinOp(left, op, right) => {
let left = annotate_expr(ctx, left)?;
let right = annotate_expr(ctx, right)?;
let output = ctx.type_var();
op.constraints(ctx, &left.ty, &right.ty, &output);
Expr {
kind: ExprKind::BinOp(Box::new(left), *op, Box::new(right)),
ty: output,
}
}
ExprKind::Call(func_name, args) => {
let mut args_annot = Vec::new();
let ret_ty = ctx.type_var();
let (func_args_ty, func_ret_ty) =
match ctx.current_env.as_ref().unwrap().lookup(func_name) {
Some(Type_::Func(args, ret)) => (args.clone(), *ret.clone()),
Some(_) => bail!("Not a function"),
None => bail!("Name not found"),
};
ctx.constrain(ret_ty.clone(), func_ret_ty);
for (arg, expected_ty) in args.iter().zip(func_args_ty.iter()) {
let arg_annot = annotate_expr(ctx, arg)?;
ctx.constrain(arg_annot.ty.clone(), expected_ty.clone());
args_annot.push(arg_annot);
}
Expr {
kind: ExprKind::Call(func_name.to_string(), args_annot),
ty: ret_ty,
}
}
})
}
fn annotate_if_else(
ctx: &mut AnnotationContext,
if_else: &IfElse<()>,
) -> Result<IfElse<Type_>> {
let converted_cond = annotate_expr(ctx, &if_else.cond)?;
let converted_body = annotate_stmts(ctx, &if_else.body)?;
let else_clause = match &if_else.else_clause {
Some(ElseClause::If(if_else2)) => {
Some(ElseClause::If(Box::new(annotate_if_else(ctx, &if_else2)?)))
}
Some(ElseClause::Body(stmts)) => {
Some(ElseClause::Body(annotate_stmts(ctx, &stmts)?))
}
None => None,
};
Ok(IfElse {
cond: converted_cond,
body: converted_body,
else_clause,
})
}
fn collect_info(
env: Env,
func: &Func<()>,
) -> Result<(Func<Type_>, HashSet<Constraint>, Env)> {
let mut constraints = HashSet::new();
let mut ctx = AnnotationContext {
counter: 0,
constraints: &mut constraints,
current_env: Some(env),
};
let new_stmts = annotate_stmts(&mut ctx, &func.stmts)?;
let args_ = func
.args
.iter()
.cloned()
.map(|arg| Type_::from_type(arg.ty))
.collect();
let total_ty =
Type_::Func(args_, Box::new(Type_::from_type(func.return_ty.clone())));
let func = Func {
name: func.name.clone(),
args: func.args.clone(),
return_ty: func.return_ty.clone(),
stmts: new_stmts,
ty: total_ty,
};
let env = ctx.current_env.unwrap();
mem::drop(ctx.constraints);
Ok((func, constraints, env))
}
type Assignments = HashMap<usize, Type_>;
fn substitute_types(assignments: &Assignments, ty: &Type_) -> Type_ {
match ty {
Type_::Var(n) => match assignments.get(&n) {
Some(ty2) => ty2.clone(),
None => ty.clone(),
},
Type_::Func(args, ret) => {
let args = args
.into_iter()
.map(|arg| substitute_types(assignments, arg))
.collect();
let ret = substitute_types(assignments, &*ret);
Type_::Func(args, Box::new(ret))
}
Type_::StructInst(fields) => Type_::StructInst(
fields
.into_iter()
.map(|(name, ty)| (name.clone(), substitute_types(assignments, ty)))
.collect(),
),
Type_::Int | Type_::Bool => ty.clone(),
}
}
fn unify_constraints(constraints: &HashSet<Constraint>) -> Result<Assignments> {
let mut assignments = HashMap::new();
for Constraint(left, right) in constraints {
let left = substitute_types(&assignments, left);
let right = substitute_types(&assignments, right);
unify_single(&mut assignments, left, right)?;
}
Ok(assignments)
}
fn unify_single(
assignments: &mut Assignments,
left: Type_,
right: Type_,
) -> Result<()> {
match (left, right) {
(Type_::Int, Type_::Int) | (Type_::Bool, Type_::Bool) => {}
(Type_::Var(n), o) | (o, Type_::Var(n)) => {
assignments.insert(n, o);
}
(Type_::Func(left_args, left_ret), Type_::Func(right_args, right_ret)) => {
let mut new_constraints = HashSet::new();
for (left_arg, right_arg) in
left_args.into_iter().zip(right_args.into_iter())
{
new_constraints.insert(Constraint(left_arg, right_arg));
}
new_constraints.insert(Constraint(*left_ret, *right_ret));
assignments.extend(unify_constraints(&new_constraints)?);
}
(left, right) => bail!("Mismatching types {left:?} vs. {right:?}"),
};
Ok(())
}
fn substitute_in_expr_kind(
assignments: &Assignments,
expr_kind: ExprKind<Type_>,
) -> Result<ExprKind<Type>> {
Ok(match expr_kind {
ExprKind::Int(n) => ExprKind::Int(n),
ExprKind::Var(name) => ExprKind::Var(name),
ExprKind::BinOp(left, op, right) => {
let left = substitute_in_expr(assignments, *left)?;
let right = substitute_in_expr(assignments, *right)?;
ExprKind::BinOp(Box::new(left), op, Box::new(right))
}
ExprKind::Call(func, args) => {
let args = args
.into_iter()
.map(|arg| substitute_in_expr(assignments, arg))
.collect::<Result<_>>()?;
ExprKind::Call(func, args)
}
})
}
fn substitute_in_expr(
assignments: &Assignments,
expr: Expr<Type_>,
) -> Result<Expr<Type>> {
Ok(Expr {
kind: substitute_in_expr_kind(assignments, expr.kind)?,
ty: expr.ty.convert(assignments)?,
})
}
fn substitute_in_if_else(
assignments: &Assignments,
if_else: IfElse<Type_>,
) -> Result<IfElse<Type>> {
let cond = substitute_in_expr(assignments, if_else.cond)?;
let body = substitute_in_stmts(assignments, if_else.body)?;
let else_clause = match if_else.else_clause {
Some(ElseClause::If(if_else2)) => Some(ElseClause::If(Box::new(
substitute_in_if_else(assignments, *if_else2)?,
))),
Some(ElseClause::Body(body)) => {
Some(ElseClause::Body(substitute_in_stmts(assignments, body)?))
}
None => None,
};
Ok(IfElse {
cond,
body,
else_clause,
})
}
fn substitute_in_stmts(
assignments: &Assignments,
stmts: Vec<Stmt<Type_>>,
) -> Result<Vec<Stmt<Type>>> {
stmts
.into_iter()
.map(|stmt| {
Ok(match stmt {
Stmt::Let(name, ty, body) => {
Stmt::Let(name, ty, substitute_in_expr(assignments, body)?)
}
Stmt::Return(ret_val) => Stmt::Return(match ret_val {
Some(v) => Some(substitute_in_expr(assignments, v)?),
None => None,
}),
Stmt::IfElse(if_else) => {
Stmt::IfElse(substitute_in_if_else(assignments, if_else)?)
}
})
})
.collect()
}
fn substitute_in_func(
assignments: &Assignments,
func: Func<Type_>,
) -> Result<Func<Type>> {
Ok(Func {
name: func.name,
args: func.args,
return_ty: func.return_ty,
stmts: substitute_in_stmts(assignments, func.stmts)?,
ty: func.ty.convert(assignments)?,
})
}
pub fn convert(ast: Vec<Decl<()>>) -> Result<Vec<Decl<Type>>> {
// First pass, gather all of the type signatures in the top level
let mut top_level_env = Env::default();
for decl in ast.iter() {
match decl {
super::Decl::Func(func) => {
let name = func.name.clone();
let args_ty = func.args.iter().map(|arg| arg.ty.clone()).collect();
let ty = Type::Func(args_ty, Box::new(func.return_ty.clone()));
top_level_env
.local_type_map
.insert(name, Type_::from_type(ty));
}
}
}
// Now, type-check each function separately
let mut new_decl = Vec::new();
let mut env = top_level_env;
for decl in ast.iter() {
match decl {
Decl::Func(func) => {
let mut scoped_env = Env {
parent: Some(Box::new(env)),
local_type_map: HashMap::new(),
};
for arg in func.args.iter() {
scoped_env
.local_type_map
.insert(arg.name.clone(), Type_::from_type(arg.ty.clone()));
}
let (decorated_func, constraints, env2) =
collect_info(scoped_env, func)?;
env = *env2.parent.unwrap();
let assignments = unify_constraints(&constraints)?;
let typed_func = substitute_in_func(&assignments, decorated_func)?;
new_decl.push(Decl::Func(typed_func));
}
}
}
Ok(new_decl)
}

57
src/ast/types.rs Normal file
View file

@ -0,0 +1,57 @@
use super::Arg;
pub trait IType {
fn func_ty(args: impl AsRef<[Self]>, ret: Self) -> Self
where
Self: Sized;
}
/// The type, as written in the source code. Notably, this includes type
/// variables that cannot appear in the final type.
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub enum TypeS {
/// Type is not given in the source, will infer it later.
Infer,
Unit,
Bool,
Int,
Struct(Vec<Arg<TypeS>>),
Func(Vec<TypeS>, Box<TypeS>),
Named(String),
}
impl IType for TypeS {
fn func_ty(args: impl AsRef<[Self]>, ret: Self) -> Self
where
Self: Sized,
{
TypeS::Func(args.as_ref().to_vec(), Box::new(ret))
}
}
/// The "real" type, that is modeled.
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub enum Type {
Unit,
Bool,
Int,
Struct(Vec<Arg<Type>>),
Func(Vec<Type>, Box<Type>),
}
/// Types used during type-checking. Notably, this includes the constraint
/// variable Var
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub enum TypeTC {
Var(usize),
Unit,
Bool,
Int,
Struct(Vec<Arg<TypeTC>>),
Func(Vec<TypeTC>, Box<TypeTC>),
}

View file

@ -2,7 +2,7 @@ use std::io::Write;
use anyhow::Result;
use crate::ast::{Expr, ExprKind, Op, Type};
use crate::ast::{types::Type, Expr, ExprKind, Op};
use super::{type_to_llvm, LlvmIrCodegen};
@ -25,32 +25,42 @@ impl<W: Write> LlvmIrCodegen<W> {
match &expr.kind {
ExprKind::Int(n) => {
expr_ref = format!("{}", n);
// writeln!(self.writer, "%{} = alloca {}", expr_id, ty_str)?;
// writeln!(
// self.writer,
// "store {} {}, {}* %{}",
// ty_str, n, ty_str, expr_id
// )?;
}
ExprKind::Var(name) => match self.var_env.lookup(name) {
Some((_, name)) => {
expr_ref = name.clone();
}
None => bail!("Unbound name {name:?}"),
},
ExprKind::BinOp(left, op, right) => {
let result_ty = match op.check_types(&left.ty, &right.ty) {
Some(v) => type_to_llvm(&v),
None => bail!("Invalid types on operation."),
};
let common_ty = type_to_llvm(&left.ty);
let left = self.convert_expr(left)?;
let right = self.convert_expr(right)?;
match op {
Op::LessThan => todo!(),
Op::GreaterThan => todo!(),
Op::LessThan => writeln!(
self.writer,
"{} = icmp slt {} {}, {}",
expr_ref,
common_ty,
left.as_str(),
right.as_str(),
)?,
Op::GreaterThan => writeln!(
self.writer,
"{} = icmp sgt {} {}, {}",
expr_ref,
common_ty,
left.as_str(),
right.as_str(),
)?,
Op::Plus => writeln!(
self.writer,
"{} = add {} {}, {}",
@ -61,6 +71,7 @@ impl<W: Write> LlvmIrCodegen<W> {
)?,
}
}
ExprKind::Call(func, args) => match self.var_env.lookup(func) {
Some((Type::Func(func_args_ty, func_ret_ty), func_name)) => {
// Clone these so we aren't depending on a reference
@ -92,6 +103,13 @@ impl<W: Write> LlvmIrCodegen<W> {
}
_ => bail!("No function with name {func:?}"),
},
ExprKind::StructNew(struct_ref, values) => {
todo!()
}
ExprKind::FieldAccess(expr, field_name) => {
todo!()
}
}
Ok(ExprLlvm(expr_ref, expr))

View file

@ -2,7 +2,7 @@ use std::io::Write;
use anyhow::Result;
use crate::ast::{IfElse, Stmt, Type};
use crate::ast::{ElseClause, IfElse, types::Type};
use super::LlvmIrCodegen;
@ -11,9 +11,42 @@ impl<W: Write> LlvmIrCodegen<W> {
&mut self,
if_else: &IfElse<Type>,
) -> Result<()> {
// Build condition
let cond = self.convert_expr(&if_else.cond);
// Use a common identifier so they have the same number
let cond = self.gensym(None, "cond");
let success_label = format!("{cond}.success");
let fail_label = format!("{cond}.fail");
let exit_label = format!("{cond}.exit");
todo!()
// Build condition
let cond = self.convert_expr(&if_else.cond)?;
writeln!(
self.writer,
"br i1 {}, label %{}, label %{}",
cond.as_str(),
success_label,
fail_label
)?;
// If true
writeln!(self.writer, "{}:", success_label)?;
self.convert_stmts(&if_else.body)?;
writeln!(self.writer, "br label %{}", exit_label)?;
// If false
writeln!(self.writer, "{}:", fail_label)?;
match &if_else.else_clause {
Some(ElseClause::If(if_else2)) => self.convert_if_else(if_else2)?,
Some(ElseClause::Body(body)) => self.convert_stmts(body)?,
None => {}
}
writeln!(self.writer, "br label %{}", exit_label)?;
// Exit branch
writeln!(self.writer, "{}:", exit_label)?;
// No-op in case this is the last statement
let nop = self.gensym(Some("%"), "nop");
writeln!(self.writer, "{nop} = add i1 0, 0")?;
Ok(())
}
}

View file

@ -6,7 +6,8 @@ use std::io::Write;
use anyhow::Result;
use crate::ast::{Decl, Type};
use crate::ast::Arg;
use crate::ast::{types::Type, Decl};
use crate::utils::LayeredEnv;
use super::CodegenBackend;
@ -14,15 +15,17 @@ use super::CodegenBackend;
pub struct LlvmIrCodegen<W> {
ctr: usize,
var_env: LayeredEnv<String, (Type, String)>,
/// Mapping from source code name to type def and LLVM struct name
struct_env: LayeredEnv<String, (Vec<Arg<Type>>, String)>,
writer: W,
}
impl<W> LlvmIrCodegen<W> {
pub fn new(writer: W) -> Self {
let env = LayeredEnv::new();
LlvmIrCodegen {
ctr: 0,
var_env: env,
var_env: LayeredEnv::default(),
struct_env: LayeredEnv::default(),
writer,
}
}
@ -45,30 +48,45 @@ impl<W> LlvmIrCodegen<W> {
fn type_to_llvm(ty: &Type) -> String {
match ty {
Type::Unit => String::from("void"),
Type::Int => String::from("i32"),
Type::Bool => String::from("i1"),
Type::StructInst(_) => todo!(),
Type::Struct(_) => todo!(),
Type::Func(_, _) => todo!(),
}
}
impl<W: Write> CodegenBackend for LlvmIrCodegen<W> {
fn convert(&mut self, program: Vec<Decl<Type>>) -> Result<()> {
// First, create a global environment and add all functions, so they can be
// called within other functions
// First, create a global environment and add all functions and types, so
// they can be referenced within other functions
//
// This is in a separate loop so there's no dependency on function order
self.var_env = LayeredEnv::new();
for func in program.iter().filter_map(|decl| match decl {
Decl::Func(v) => Some(v),
_ => None,
}) {
let func_name = match func.name.as_str() {
"main" => String::from("@main"),
_ => self.gensym(Some("@"), format!("func.{}", func.name)),
};
let value = (func.ty.clone(), func_name);
self.var_env.insert(func.name.clone(), value);
// This is in a separate loop so there's no dependency on order
for decl in program.iter() {
match decl {
Decl::Func(func) => {
let func_name = match func.name.as_str() {
"main" => String::from("@main"),
_ => self.gensym(Some("@"), format!("func.{}", func.name)),
};
let func_ty = Type::Func(
func.args.iter().map(|arg| arg.ty.clone()).collect(),
Box::new(func.return_ty.clone()),
);
let value = (func_ty, func_name);
self.var_env.insert(func.name.clone(), value);
}
Decl::TypeDef(typedef) => match &typedef.def {
Type::Struct(strct) => {
let name =
self.gensym(Some("%"), format!("struct.{}", typedef.name));
let value = (strct.clone(), name);
self.struct_env.insert(typedef.name.clone(), value);
}
_ => {}
},
}
}
// Convert all functions
@ -76,10 +94,7 @@ impl<W: Write> CodegenBackend for LlvmIrCodegen<W> {
Decl::Func(v) => Some(v),
_ => None,
}) {
let (_, ret_ty) = match &func.ty {
Type::Func(args, ret) => (args, ret),
_ => unreachable!(""),
};
let ret_ty = &func.return_ty;
let (_, func_name) =
self.var_env.lookup(&func.name).expect("Just inserted.");
let func_name = func_name.to_owned();
@ -100,7 +115,7 @@ impl<W: Write> CodegenBackend for LlvmIrCodegen<W> {
writeln!(
self.writer,
"define {} {} ({}) {{",
type_to_llvm(ret_ty),
type_to_llvm(&ret_ty),
func_name,
args_str.join(", "),
)?;

View file

@ -2,7 +2,7 @@ use std::io::Write;
use anyhow::Result;
use crate::ast::{Stmt, Type};
use crate::ast::{Stmt, types::Type};
use super::{type_to_llvm, LlvmIrCodegen};

View file

@ -1,8 +1,13 @@
//! This module contains everything related to codegen.
//!
//! Although written in a style that could possibly support multiple backends,
//! the only planned backend is the [LLVM IR][llvm_ir] backend.
pub mod llvm_ir;
use anyhow::Result;
use crate::ast::{Decl, Type};
use crate::ast::{Decl, types::Type};
pub trait CodegenBackend {
fn convert(&mut self, program: Vec<Decl<Type>>) -> Result<()>;

16
src/lib.rs Normal file
View file

@ -0,0 +1,16 @@
#![doc = include_str!("../README.md")]
#[macro_use]
extern crate lalrpop_util;
#[macro_use]
extern crate anyhow;
/// This module contains an auto-generated parser created by LALRPOP.
pub mod parser {
lalrpop_mod!(parser);
pub use self::parser::*;
}
pub mod ast;
pub mod codegen;
mod utils;

View file

@ -1,68 +0,0 @@
#[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;
#[derive(Debug, Parser)]
struct Opt {
path: PathBuf,
/// Path to output the bitcode to.
#[clap(short = 'o', long = "out", name = "bitcode-out")]
out_path: PathBuf,
/// Path to output the AST to, if at all.
#[clap(long = "emit-ast", name = "ast-out")]
emit_ast: Option<PathBuf>,
}
fn main() -> Result<()> {
let opts = Opt::parse();
let contents = fs::read_to_string(&opts.path)?;
let parser = ProgramParser::new();
let ast = parser.parse(&contents).unwrap();
let typed_ast = ast::typed::convert(ast)?;
if let Some(path) = opts.emit_ast {
let mut file = File::create(&path)?;
file.write(format!("{:#?}", typed_ast).as_bytes())?;
}
{
let file = File::create(&opts.out_path)?;
let mut codegen = LlvmIrCodegen::new(file);
codegen.convert(typed_ast)?;
}
// let ctx = ast::cranelift::convert(
// opts.path.display().to_string(),
// typed_ast,
// )?;
// {
// let file = File::create(&opts.out_path)?;
// ctx.compile_and_emit();
// println!("Emitted.");
// }
Ok(())
}

View file

@ -1,67 +1,93 @@
use crate::ast::*;
use crate::ast::{*, types::*};
grammar;
pub Program: Vec<Decl<()>> = Decl* => <>;
pub Program: Vec<Decl<TypeS>> = Decl* => <>;
Decl: Decl<()> = {
Decl: Decl<TypeS> = {
Func => Decl::Func(<>),
TypeDef => Decl::TypeDef(<>),
};
Func: Func<()> = {
Func: Func<TypeS> = {
"fn" <name:Ident> "(" <args:Args> ")" "->" <return_ty:Type> "{" <stmts:Stmt*> "}" =>
Func { name, args, return_ty, stmts, ty: (), },
Func { name, args, return_ty, stmts },
"fn" <name:Ident> "(" <args:Args> ")" "{" <stmts:Stmt*> "}" =>
Func { name, args, return_ty: TypeS::Unit, stmts },
};
Args: Vec<Arg> = Punct<",", Arg>? => <>.unwrap_or_else(|| Vec::new());
TypeDef: TypeDef<TypeS> = {
"type" <name:Ident> "=" <def:Type> => TypeDef { name, def },
};
Arg: Arg = <name:Ident> ":" <ty:Type> => Arg { name, ty };
Args: Vec<Arg<TypeS>> = Punct<",", Arg>? => <>.unwrap_or_else(|| Vec::new());
Stmt: Stmt<()> = {
Arg: Arg<TypeS> = <name:Ident> ":" <ty:Type> => Arg { name, ty };
NameValue: NameValue<TypeS> = <name:Ident> ":" <expr:Expr> => NameValue { name, expr };
Stmt: Stmt<TypeS> = {
"let" <name:Ident> <ty:ColonType?> "=" <expr:Expr> ";" =>
Stmt::Let(name, ty, expr),
Stmt::Let(name, ty.unwrap_or(TypeS::Infer), expr),
"return" <expr:Expr?> ";" => Stmt::Return(expr),
IfElse => Stmt::IfElse(<>),
};
ColonType: Type = ":" <ty:Type> => ty;
ColonType: TypeS = ":" <ty:Type> => ty;
IfElse: IfElse<()> =
IfElse: IfElse<TypeS> =
"if" <cond:Expr> "{" <body:Stmt*> "}" <else_clause:Else?> =>
IfElse { cond, body, else_clause };
Else: ElseClause<()> = "else" <else_clause:Else_> => else_clause;
Else_: ElseClause<()> = {
Else: ElseClause<TypeS> = "else" <else_clause:Else_> => else_clause;
Else_: ElseClause<TypeS> = {
IfElse => ElseClause::If(Box::new(<>)),
"{" <body:Stmt*> "}" => ElseClause::Body(body),
};
Expr: Expr<()> = {
Expr: Expr<TypeS> = {
#[precedence(level = "0")]
"(" <expr:Expr> ")" => expr,
#[precedence(level = "0")]
r"[0-9]+" => Expr { kind: ExprKind::Int(<>.parse::<i64>().unwrap()), ty: () },
r"-?[0-9]+" => Expr {
kind: ExprKind::Int(<>.parse::<i64>().unwrap()),
ty: TypeS::Infer,
},
#[precedence(level = "0")]
Ident => Expr { kind: ExprKind::Var(<>), ty: () },
"new" <name:Ident> "{" <values:Punct<",", NameValue>> "}" => Expr {
kind: ExprKind::StructNew(name, values),
ty: TypeS::Infer,
},
#[precedence(level = "1")]
Ident => Expr { kind: ExprKind::Var(<>), ty: TypeS::Infer },
#[precedence(level = "2")]
#[assoc(side = "left")]
<expr:Expr> "." <field:Ident> =>
Expr { kind: ExprKind::FieldAccess(Box::new(expr), field), ty: TypeS::Infer },
#[precedence(level = "3")]
<func:Ident> "(" <args:Punct<",", Expr>?> ")" =>
Expr { kind: ExprKind::Call(func, args.unwrap_or_else(|| vec![])), ty: () },
Expr {
kind: ExprKind::Call(func, args.unwrap_or_else(|| vec![])),
ty: TypeS::Infer,
},
#[precedence(level = "8")]
#[assoc(side = "left")]
<left:Expr> <op:AddOp> <right:Expr> => Expr {
kind: ExprKind::BinOp(Box::new(left), op, Box::new(right)),
ty: (),
ty: TypeS::Infer,
},
#[precedence(level = "13")]
#[assoc(side = "none")]
<left:Expr> <op:CompareOp> <right:Expr> => Expr {
kind: ExprKind::BinOp(Box::new(left), op, Box::new(right)),
ty: (),
ty: TypeS::Infer,
},
};
@ -74,22 +100,24 @@ CompareOp: Op = {
">" => Op::GreaterThan,
};
Type: Type = {
"int" => Type::Int,
Type: TypeS = {
"int" => TypeS::Int,
"struct" "{" <fields:Punct<",", Arg>> "}" => TypeS::Struct(fields),
<name:Ident> => TypeS::Named(name),
};
Ident: String = r"([A-Za-z][A-Za-z0-9_]*)|(_[A-Za-z0-9_]+)" => <>.to_string();
Punct<P, T>: Vec<T> = {
P => vec![],
<first:T> <rest:(P Punct<P, T>)?> => {
match rest {
Some(rest) => {
let (_, mut vec) = rest;
vec.insert(0, first);
vec
}
None => vec![first],
}
},
T => vec![<>],
<first:T> P => vec![first],
<first:T> P <rest:Punct<P, T>> => {
let mut vec = rest;
vec.insert(0, first);
vec
}
};

View file

@ -1,11 +1,47 @@
use std::collections::HashMap;
use std::hash::Hash;
/// Layered environment that lets us push and pop frames and lookup across
/// layers.
#[derive(Debug)]
pub struct LayeredEnv<K, V> {
inner: Option<InnerEnv<K, V>>,
}
impl<K: Hash + Eq, V> Default for LayeredEnv<K, V> {
fn default() -> Self {
LayeredEnv {
inner: Some(InnerEnv::new()),
}
}
}
impl<K: Hash + Eq, V> LayeredEnv<K, V> {
pub fn insert(&mut self, key: K, value: V) {
self.inner.as_mut().unwrap().map.insert(key, value);
}
pub fn push(&mut self) {
let inner = self.inner.take().unwrap();
self.inner = Some(InnerEnv::with_parent(inner));
}
pub fn pop(&mut self) {
let inner = self.inner.take().unwrap();
self.inner = Some(*inner.parent.unwrap());
}
/// Look up the key within the environment.
///
/// If a key exists in multiple layers at once, the top-most one (of the
/// stack) will shadow all the others.
///
/// TODO: Make a "lookup" function that traverses all environments.
pub fn lookup(&self, key: &K) -> Option<&V> {
self.inner.as_ref().unwrap().lookup(key)
}
}
#[derive(Debug)]
struct InnerEnv<K, V> {
parent: Option<Box<InnerEnv<K, V>>>,
@ -37,29 +73,3 @@ impl<K: Hash + Eq, V> InnerEnv<K, V> {
}
}
}
impl<K: Hash + Eq, V> LayeredEnv<K, V> {
pub fn new() -> Self {
LayeredEnv {
inner: Some(InnerEnv::new()),
}
}
pub fn insert(&mut self, key: K, value: V) {
self.inner.as_mut().unwrap().map.insert(key, value);
}
pub fn push(&mut self) {
let inner = self.inner.take().unwrap();
self.inner = Some(InnerEnv::with_parent(inner));
}
pub fn pop(&mut self) {
let inner = self.inner.take().unwrap();
self.inner = Some(*inner.parent.unwrap());
}
pub fn lookup(&self, key: &K) -> Option<&V> {
self.inner.as_ref().unwrap().lookup(key)
}
}

4
std/e0pkg.yml Normal file
View file

@ -0,0 +1,4 @@
name: std
version: 0.1.0
authors:
- Michael Zhang <mail@mzhang.io>

0
std/prelude.e0 Normal file
View file