dependency time
This commit is contained in:
parent
63bb50f6b3
commit
95c1ca72e6
6 changed files with 2159 additions and 393 deletions
1575
Cargo.lock
generated
1575
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -2,3 +2,8 @@
|
|||
name = "lang"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.4.6", features = ["derive"] }
|
||||
wasm-encoder = "0.33.2"
|
||||
wasmtime = "13.0.0"
|
||||
|
|
407
src/compiler.rs
Normal file
407
src/compiler.rs
Normal file
|
@ -0,0 +1,407 @@
|
|||
/*
|
||||
optimizations
|
||||
|
||||
- [ ] use i32.eqz when comparing to zero
|
||||
- [x] write negative numbers directly instead of as positive + sign flip
|
||||
- [ ] replace `local.set` + `local.get` with `local.tee`
|
||||
- [ ] don't create local variables at all if they're only used once
|
||||
*/
|
||||
|
||||
use crate::data::{BinaryOp, Expr, Func, Literal, Pattern, PrefixOp, Statement, Type};
|
||||
use crate::parser::Context;
|
||||
use crate::Error;
|
||||
// use crate::generator::Generator;
|
||||
|
||||
/// pipeline to convert parsed tree -> wat/wasm
|
||||
pub struct Compiler<'a> {
|
||||
output: Box<&'a mut dyn std::io::Write>,
|
||||
// generator: Generator<'a>,
|
||||
output_type: OutputType,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum OutputType {
|
||||
Wat,
|
||||
WatVerbose,
|
||||
Wasm,
|
||||
}
|
||||
|
||||
impl OutputType {
|
||||
pub fn is_binary(&self) -> bool {
|
||||
match self {
|
||||
Self::Wat | Self::WatVerbose => false,
|
||||
Self::Wasm => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Allocator {
|
||||
count: u32,
|
||||
}
|
||||
|
||||
// const PRELUDE: &str = r#"
|
||||
// ;; begin prelude
|
||||
// (import "wasi_snapshot_preview1" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32)))
|
||||
// (export "memory" (memory 0))
|
||||
// ;; (func $print (param $offset i32) (param $length i32)
|
||||
// (func $print (param $string i32)
|
||||
// ;; (i32.store (i32.const 0) (local.get $offset)) ;; iov.iov_base (pointer to string)
|
||||
// ;; (i32.store (i32.const 4) (local.get $length)) ;; iov.iov_len (length of string)
|
||||
// ;; (call $fd_write
|
||||
// ;; (i32.const 1) ;; fd: stdout = 1
|
||||
// ;; (i32.const 0) ;; data: pointer to memory - this is the first memory we create (index 0)
|
||||
// ;; (i32.const 1) ;; data_len: there's 1 string
|
||||
// ;; (i32.const 0) ;; nwritten: i don't care about this, write it wherever
|
||||
// ;; )
|
||||
// ;; drop
|
||||
// )
|
||||
// ;; end prelude
|
||||
// "#;
|
||||
|
||||
impl<'a> Compiler<'a> {
|
||||
pub fn new(output: Box<&mut dyn std::io::Write>, output_type: OutputType) -> Compiler {
|
||||
Compiler {
|
||||
output,
|
||||
output_type,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_module(&mut self, stmts: &[Statement]) -> Result<(), Error> {
|
||||
if self.output_type.is_binary() {
|
||||
self.output.write_all(&[0x00, 0x61, 0x73, 0x6d])?; // magic
|
||||
self.output.write_all(&[0x01, 0x00, 0x00, 0x00])?; // version
|
||||
} else {
|
||||
writeln!(self.output, "(module")?;
|
||||
}
|
||||
|
||||
let mut ctx = Context::new();
|
||||
// ctx.funcs.insert("print".to_string(), (vec![("message".to_string(), Type::String)], Type::empty()));
|
||||
|
||||
// writeln!(self.output, "{}", PRELUDE)?;
|
||||
|
||||
let mut gctx = GenContext::new();
|
||||
let mut alloc = Allocator::new();
|
||||
get_locals(&Expr::Block(crate::data::Block(stmts.to_vec())), &mut alloc, &mut ctx, &mut gctx);
|
||||
// get_strings(&Expr::Block(crate::data::Block(stmts.to_vec())), &mut gctx);
|
||||
// if !gctx.strings.is_empty() {
|
||||
// writeln!(self.output, "(memory 1)")?;
|
||||
// for (s, offset) in &mut gctx.strings {
|
||||
// writeln!(self.output, "(data $strings (i32.const {}) \"{}\")", offset, s.replace("\n", "\\n").replace("\"", "\\\""))?;
|
||||
// }
|
||||
// }
|
||||
|
||||
for stmt in stmts {
|
||||
match stmt {
|
||||
Statement::Func(func) => self.write_func(&gctx, &ctx, func)?,
|
||||
Statement::Let(..) | Statement::TailExpr(..) | Statement::Expr (..) => {
|
||||
return Err(Error::syn("incorrect top level statement"))
|
||||
}
|
||||
};
|
||||
}
|
||||
writeln!(self.output, ")")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_comment(&mut self, comment: &str) -> Result<(), Error> {
|
||||
if self.output_type == OutputType::WatVerbose {
|
||||
writeln!(self.output, ";; {}", comment)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_func(&mut self, parent_gctx: &GenContext, ctx: &Context, func: &Func) -> Result<(), Error> {
|
||||
write!(self.output, "(func ${}", func.name)?;
|
||||
if func.name == "main" {
|
||||
write!(self.output, " (export \"_start\")")?;
|
||||
} else if func.public {
|
||||
write!(self.output, " (export \"{}\")", func.name)?;
|
||||
}
|
||||
|
||||
let expr = Expr::Block(func.block.clone());
|
||||
let mut ctx = ctx.clone();
|
||||
|
||||
for (name, ty) in &func.params {
|
||||
ctx.locals.insert(name.clone(), ty.clone());
|
||||
write!(self.output, " (param ${} {})", name, ty.string())?;
|
||||
}
|
||||
|
||||
if !func.ret.is_empty() {
|
||||
write!(self.output, " (result {})", func.ret.string())?;
|
||||
}
|
||||
|
||||
writeln!(self.output)?;
|
||||
writeln!(self.output, "(local $match i32)")?;
|
||||
|
||||
let inferred = func.block.infer(&ctx)?;
|
||||
if func.ret != inferred {
|
||||
return Err(Error::TypeError(format!(
|
||||
"fn should return {:?}, but instead returns {inferred:?}",
|
||||
func.ret
|
||||
)));
|
||||
}
|
||||
|
||||
let mut alloc = Allocator::new();
|
||||
let mut gctx = GenContext::new();
|
||||
get_locals(&expr, &mut alloc, &mut ctx, &mut gctx);
|
||||
let exprs = gctx.exprs;
|
||||
|
||||
for (name, expr) in &exprs {
|
||||
let ty = match expr.infer(&ctx).unwrap() {
|
||||
Type::Integer => "i32",
|
||||
Type::Float => "f64",
|
||||
// Type::String => "i32",
|
||||
_ => todo!(),
|
||||
};
|
||||
writeln!(self.output, "(local ${name} {ty})")?;
|
||||
}
|
||||
for (name, expr) in &exprs {
|
||||
self.gen_expr(expr, parent_gctx, &ctx)?;
|
||||
writeln!(self.output, "local.set ${name}")?;
|
||||
}
|
||||
|
||||
self.gen_expr(&expr, parent_gctx, &ctx)?;
|
||||
|
||||
writeln!(self.output, ")")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn gen_expr(&mut self, expr: &Expr, parent_gctx: &GenContext, ctx: &Context) -> Result<(), Error> {
|
||||
match expr {
|
||||
Expr::Literal(lit) => match lit {
|
||||
Literal::Integer(int) => writeln!(self.output, "i32.const {int}")?,
|
||||
Literal::Float(f) => writeln!(self.output, "f64.const {f}")?,
|
||||
Literal::Boolean(b) => writeln!(self.output, "i32.const {}", if *b { 1 } else { 0 })?,
|
||||
// Literal::String(s) => writeln!(self.output, "i32.const {}", parent_gctx.strings.iter().find_map(|(s2, off)| (s == s2).then_some(off)).unwrap())?,
|
||||
_ => todo!(),
|
||||
},
|
||||
Expr::Variable(name) => {
|
||||
writeln!(self.output, "local.get ${name}")?;
|
||||
}
|
||||
Expr::Binary(op, a, b) => {
|
||||
self.gen_expr(a, parent_gctx, ctx)?;
|
||||
self.gen_expr(b, parent_gctx, ctx)?;
|
||||
|
||||
let ty = match expr.infer(ctx).unwrap() {
|
||||
Type::Integer => "i32",
|
||||
Type::Float => "f64",
|
||||
Type::Boolean => "i32",
|
||||
_ => todo!(),
|
||||
};
|
||||
match op {
|
||||
BinaryOp::Add => writeln!(self.output, "{ty}.add")?,
|
||||
BinaryOp::Mul => writeln!(self.output, "{ty}.mul")?,
|
||||
BinaryOp::Sub => writeln!(self.output, "{ty}.sub")?,
|
||||
BinaryOp::Div => writeln!(self.output, "{ty}.div_u")?, // do i _u or _s?
|
||||
BinaryOp::Mod => writeln!(self.output, "{ty}.rem_u")?,
|
||||
BinaryOp::Eq => writeln!(self.output, "{ty}.eq")?,
|
||||
BinaryOp::Neq => writeln!(self.output, "{ty}.neq")?,
|
||||
BinaryOp::Less => writeln!(self.output, "{ty}.lt_u")?,
|
||||
BinaryOp::Greater => writeln!(self.output, "{ty}.gt_u")?,
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
Expr::Unary(op, e) => {
|
||||
if let Expr::Literal(lit) = e.as_ref() {
|
||||
match lit {
|
||||
Literal::Integer(int) => writeln!(self.output, "i32.const {}", -int)?,
|
||||
Literal::Float(f) => writeln!(self.output, "f64.const {}", -f)?,
|
||||
_ => todo!(),
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.gen_expr(e, parent_gctx, ctx)?;
|
||||
match op {
|
||||
PrefixOp::Minus => {
|
||||
// this is so inefficent, but i don't care
|
||||
writeln!(self.output, "i32.const -1")?;
|
||||
writeln!(self.output, "i32.mul")?;
|
||||
}
|
||||
PrefixOp::LogicNot => {
|
||||
writeln!(self.output, "i32.eqz")?;
|
||||
}
|
||||
PrefixOp::BitNot => {
|
||||
// TODO: do i flip the sign bit?
|
||||
writeln!(self.output, "i32.const {}", i32::MAX)?;
|
||||
writeln!(self.output, "i32.xor")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
// FIXME: awful code until i fix patching up parser and lexer
|
||||
Expr::Match(cond, arms) => {
|
||||
self.gen_expr(cond, parent_gctx, ctx)?;
|
||||
writeln!(self.output, "local.set $match")?;
|
||||
|
||||
for (idx, (pat, expr)) in arms.iter().enumerate() {
|
||||
match pat {
|
||||
Pattern::Literal(lit) => {
|
||||
match lit {
|
||||
Literal::Integer(int) => writeln!(self.output, "i32.const {}", int)?,
|
||||
Literal::Boolean(b) => writeln!(self.output, "i32.const {}", if *b { 1 } else { 0 })?,
|
||||
_ => todo!(),
|
||||
}
|
||||
writeln!(self.output, "local.get $match")?;
|
||||
writeln!(self.output, "i32.eq")?;
|
||||
}
|
||||
Pattern::Wildcard => {
|
||||
writeln!(self.output, "i32.const 1")?;
|
||||
}
|
||||
};
|
||||
|
||||
writeln!(self.output, "(if (result i32) (then")?;
|
||||
self.gen_expr(expr, parent_gctx, ctx)?;
|
||||
|
||||
if idx == arms.len() - 1 {
|
||||
// TODO: verify its actually unreachable earlier on
|
||||
writeln!(self.output, ") (else unreachable")?;
|
||||
} else {
|
||||
writeln!(self.output, ") (else")?;
|
||||
}
|
||||
}
|
||||
writeln!(self.output, "{}", ")".repeat(arms.len() * 2))?;
|
||||
}
|
||||
Expr::Block(b) => {
|
||||
let mut ctx = ctx.clone();
|
||||
for (_i, stmt) in b.0.iter().enumerate() {
|
||||
match stmt {
|
||||
Statement::TailExpr(expr) => {
|
||||
self.gen_expr(expr, parent_gctx, &ctx)?;
|
||||
}
|
||||
Statement::Expr(expr) => {
|
||||
self.gen_expr(expr, parent_gctx, &ctx)?;
|
||||
if !expr.infer(&ctx).unwrap().is_empty() {
|
||||
writeln!(self.output, "drop")?;
|
||||
}
|
||||
}
|
||||
Statement::Let(name, expr) => {
|
||||
let ty = expr.infer(&ctx).unwrap();
|
||||
ctx.locals.insert(name.clone(), ty);
|
||||
// exprs.push((name.clone(), expr.clone()));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Expr::Call(func, args) => {
|
||||
expr.infer(ctx)?;
|
||||
for expr in args {
|
||||
self.gen_expr(expr, parent_gctx, ctx)?;
|
||||
}
|
||||
writeln!(self.output, "call ${func}")?;
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: not sure what i was thinking
|
||||
impl Allocator {
|
||||
fn new() -> Allocator {
|
||||
Allocator {
|
||||
count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn alloc_ident(&mut self, name: &str) -> String {
|
||||
// self.count += 1;
|
||||
// format!("{name}_{}", self.count)
|
||||
name.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct GenContext {
|
||||
exprs: Vec<(String, Expr)>,
|
||||
strings: Vec<(String, usize)>,
|
||||
}
|
||||
|
||||
impl GenContext {
|
||||
pub fn new() -> GenContext {
|
||||
GenContext {
|
||||
exprs: vec![],
|
||||
strings: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_locals(expr: &Expr, alloc: &mut Allocator, ctx: &mut Context, gctx: &mut GenContext) {
|
||||
match expr {
|
||||
Expr::Block(b) => {
|
||||
for stmt in &b.0 {
|
||||
match stmt {
|
||||
Statement::Let(name, expr) => {
|
||||
// FIXME: block scoped variables (name collisions)
|
||||
get_locals(expr, alloc, ctx, gctx);
|
||||
|
||||
let ty = expr.infer(ctx).unwrap();
|
||||
ctx.locals.insert(name.clone(), ty);
|
||||
gctx.exprs.push((alloc.alloc_ident(name), expr.clone()));
|
||||
}
|
||||
Statement::TailExpr(expr) => get_locals(expr, alloc, ctx, gctx),
|
||||
Statement::Expr(expr) => get_locals(expr, alloc, ctx, gctx),
|
||||
Statement::Func(Func { name, ret, params, .. }) => {
|
||||
ctx.funcs.insert(name.clone(), (params.clone(), ret.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Expr::Unary(_, expr) => get_locals(expr, alloc, ctx, gctx),
|
||||
Expr::Binary(_, a, b) => {
|
||||
get_locals(a, alloc, ctx, gctx);
|
||||
get_locals(b, alloc, ctx, gctx);
|
||||
}
|
||||
Expr::Literal(Literal::String(s)) => {
|
||||
let offset = gctx.strings.last().map(|(s, off)| off + s.len()).unwrap_or(8);
|
||||
gctx.strings.push((s.clone(), offset));
|
||||
}
|
||||
Expr::Match(pat, arms) => {
|
||||
get_locals(pat, alloc, ctx, gctx);
|
||||
for (_, arm) in arms {
|
||||
get_locals(arm, alloc, ctx, gctx);
|
||||
}
|
||||
}
|
||||
Expr::Call(_, exprs) => {
|
||||
for expr in exprs {
|
||||
get_locals(expr, alloc, ctx, gctx);
|
||||
}
|
||||
}
|
||||
Expr::Variable(..) | Expr::Literal(..) => (),
|
||||
}
|
||||
}
|
||||
|
||||
// fn get_strings(expr: &Expr, gctx: &mut GenContext) {
|
||||
// match expr {
|
||||
// Expr::Block(b) => {
|
||||
// for stmt in &b.0 {
|
||||
// match stmt {
|
||||
// Statement::Let(_, expr) => get_strings(expr, gctx),
|
||||
// Statement::TailExpr(expr) => get_strings(expr, gctx),
|
||||
// Statement::Expr(expr) => get_strings(expr, gctx),
|
||||
// Statement::Func(Func { block, .. }) => get_strings(&Expr::Block(block.clone()), gctx),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// Expr::Unary(_, expr) => get_strings(expr, gctx),
|
||||
// Expr::Binary(_, a, b) => {
|
||||
// get_strings(a, gctx);
|
||||
// get_strings(b, gctx);
|
||||
// }
|
||||
// Expr::Literal(Literal::String(s)) => {
|
||||
// let offset = gctx.strings.last().map(|(s, off)| off + s.len()).unwrap_or(8);
|
||||
// gctx.strings.push((s.clone(), offset));
|
||||
// }
|
||||
// Expr::Match(pat, arms) => {
|
||||
// get_strings(pat, gctx);
|
||||
// for (_, arm) in arms {
|
||||
// get_strings(arm, gctx);
|
||||
// }
|
||||
// }
|
||||
// Expr::Call(_, exprs) => {
|
||||
// for expr in exprs {
|
||||
// get_strings(expr, gctx);
|
||||
// }
|
||||
// }
|
||||
// Expr::Variable(..) | Expr::Literal(..) => (),
|
||||
// }
|
||||
// }
|
387
src/generator.rs
387
src/generator.rs
|
@ -1,371 +1,28 @@
|
|||
/*
|
||||
optimizations
|
||||
// use crate::Error;
|
||||
|
||||
- [ ] use i32.eqz when comparing to zero
|
||||
- [x] write negative numbers directly instead of as positive + sign flip
|
||||
- [ ] replace `local.set` + `local.get` with `local.tee`
|
||||
- [ ] don't create local variables at all if they're only used once
|
||||
*/
|
||||
// /// helper pipeline to convert wat/wasm -> actual text/binary
|
||||
// pub struct Generator<'a> {
|
||||
// output: Box<&'a mut dyn std::io::Write>,
|
||||
|
||||
// }
|
||||
|
||||
use crate::data::{BinaryOp, Expr, Func, Literal, Pattern, PrefixOp, Statement, Type};
|
||||
use crate::parser::Context;
|
||||
use crate::Error;
|
||||
// #[derive(Debug)]
|
||||
// enum OutputFormat {
|
||||
// Wat, Wasm,
|
||||
// }
|
||||
|
||||
pub struct Generator {
|
||||
output: Box<dyn std::io::Write>,
|
||||
}
|
||||
// impl Generator<'_> {
|
||||
// pub fn new(output: Box<&mut dyn std::io::Write>) -> Generator {
|
||||
// Generator { output }
|
||||
// }
|
||||
|
||||
struct Allocator {
|
||||
count: u32,
|
||||
}
|
||||
|
||||
// const PRELUDE: &str = r#"
|
||||
// ;; begin prelude
|
||||
// (import "wasi_snapshot_preview1" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32)))
|
||||
// (export "memory" (memory 0))
|
||||
// ;; (func $print (param $offset i32) (param $length i32)
|
||||
// (func $print (param $string i32)
|
||||
// ;; (i32.store (i32.const 0) (local.get $offset)) ;; iov.iov_base (pointer to string)
|
||||
// ;; (i32.store (i32.const 4) (local.get $length)) ;; iov.iov_len (length of string)
|
||||
// ;; (call $fd_write
|
||||
// ;; (i32.const 1) ;; fd: stdout = 1
|
||||
// ;; (i32.const 0) ;; data: pointer to memory - this is the first memory we create (index 0)
|
||||
// ;; (i32.const 1) ;; data_len: there's 1 string
|
||||
// ;; (i32.const 0) ;; nwritten: i don't care about this, write it wherever
|
||||
// ;; )
|
||||
// ;; drop
|
||||
// )
|
||||
// ;; end prelude
|
||||
// "#;
|
||||
|
||||
impl Generator {
|
||||
pub fn new(output: Box<dyn std::io::Write>) -> Generator {
|
||||
Generator { output }
|
||||
}
|
||||
|
||||
pub fn write_module(&mut self, stmts: &[Statement]) -> Result<(), Error> {
|
||||
writeln!(self.output, "(module")?;
|
||||
let mut ctx = Context::new();
|
||||
// ctx.funcs.insert("print".to_string(), (vec![("message".to_string(), Type::String)], Type::empty()));
|
||||
|
||||
// writeln!(self.output, "{}", PRELUDE)?;
|
||||
|
||||
let mut gctx = GenContext::new();
|
||||
let mut alloc = Allocator::new();
|
||||
get_locals(&Expr::Block(crate::data::Block(stmts.to_vec())), &mut alloc, &mut ctx, &mut gctx);
|
||||
// get_strings(&Expr::Block(crate::data::Block(stmts.to_vec())), &mut gctx);
|
||||
// if !gctx.strings.is_empty() {
|
||||
// writeln!(self.output, "(memory 1)")?;
|
||||
// for (s, offset) in &mut gctx.strings {
|
||||
// writeln!(self.output, "(data $strings (i32.const {}) \"{}\")", offset, s.replace("\n", "\\n").replace("\"", "\\\""))?;
|
||||
// }
|
||||
// }
|
||||
|
||||
for stmt in stmts {
|
||||
match stmt {
|
||||
Statement::Func(func) => self.write_func(&gctx, &ctx, func)?,
|
||||
Statement::Let(..) | Statement::TailExpr(..) | Statement::Expr (..) => {
|
||||
return Err(Error::syn("incorrect top level statement"))
|
||||
}
|
||||
};
|
||||
}
|
||||
writeln!(self.output, ")")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_func(&mut self, parent_gctx: &GenContext, ctx: &Context, func: &Func) -> Result<(), Error> {
|
||||
write!(self.output, "(func ${}", func.name)?;
|
||||
if func.name == "main" {
|
||||
write!(self.output, " (export \"_start\")")?;
|
||||
} else if func.public {
|
||||
write!(self.output, " (export \"{}\")", func.name)?;
|
||||
}
|
||||
|
||||
let expr = Expr::Block(func.block.clone());
|
||||
let mut ctx = ctx.clone();
|
||||
|
||||
for (name, ty) in &func.params {
|
||||
ctx.locals.insert(name.clone(), ty.clone());
|
||||
write!(self.output, " (param ${} {})", name, ty.string())?;
|
||||
}
|
||||
|
||||
if !func.ret.is_empty() {
|
||||
write!(self.output, " (result {})", func.ret.string())?;
|
||||
}
|
||||
|
||||
writeln!(self.output)?;
|
||||
writeln!(self.output, "(local $match i32)")?;
|
||||
|
||||
let inferred = func.block.infer(&ctx)?;
|
||||
if func.ret != inferred {
|
||||
return Err(Error::TypeError(format!(
|
||||
"fn should return {:?}, but instead returns {inferred:?}",
|
||||
func.ret
|
||||
)));
|
||||
}
|
||||
|
||||
let mut alloc = Allocator::new();
|
||||
let mut gctx = GenContext::new();
|
||||
get_locals(&expr, &mut alloc, &mut ctx, &mut gctx);
|
||||
let exprs = gctx.exprs;
|
||||
|
||||
for (name, expr) in &exprs {
|
||||
let ty = match expr.infer(&ctx).unwrap() {
|
||||
Type::Integer => "i32",
|
||||
Type::Float => "f64",
|
||||
// Type::String => "i32",
|
||||
_ => todo!(),
|
||||
};
|
||||
writeln!(self.output, "(local ${name} {ty})")?;
|
||||
}
|
||||
for (name, expr) in &exprs {
|
||||
self.gen_expr(expr, parent_gctx, &ctx)?;
|
||||
writeln!(self.output, "local.set ${name}")?;
|
||||
}
|
||||
|
||||
self.gen_expr(&expr, parent_gctx, &ctx)?;
|
||||
|
||||
writeln!(self.output, ")")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn gen_expr(&mut self, expr: &Expr, parent_gctx: &GenContext, ctx: &Context) -> Result<(), Error> {
|
||||
match expr {
|
||||
Expr::Literal(lit) => match lit {
|
||||
Literal::Integer(int) => writeln!(self.output, "i32.const {int}")?,
|
||||
Literal::Float(f) => writeln!(self.output, "f64.const {f}")?,
|
||||
Literal::Boolean(b) => writeln!(self.output, "i32.const {}", if *b { 1 } else { 0 })?,
|
||||
// Literal::String(s) => writeln!(self.output, "i32.const {}", parent_gctx.strings.iter().find_map(|(s2, off)| (s == s2).then_some(off)).unwrap())?,
|
||||
_ => todo!(),
|
||||
},
|
||||
Expr::Variable(name) => {
|
||||
writeln!(self.output, "local.get ${name}")?;
|
||||
}
|
||||
Expr::Binary(op, a, b) => {
|
||||
self.gen_expr(a, parent_gctx, ctx)?;
|
||||
self.gen_expr(b, parent_gctx, ctx)?;
|
||||
|
||||
let ty = match expr.infer(ctx).unwrap() {
|
||||
Type::Integer => "i32",
|
||||
Type::Float => "f64",
|
||||
Type::Boolean => "i32",
|
||||
_ => todo!(),
|
||||
};
|
||||
match op {
|
||||
BinaryOp::Add => writeln!(self.output, "{ty}.add")?,
|
||||
BinaryOp::Mul => writeln!(self.output, "{ty}.mul")?,
|
||||
BinaryOp::Sub => writeln!(self.output, "{ty}.sub")?,
|
||||
BinaryOp::Div => writeln!(self.output, "{ty}.div_u")?, // do i _u or _s?
|
||||
BinaryOp::Mod => writeln!(self.output, "{ty}.rem_u")?,
|
||||
BinaryOp::Eq => writeln!(self.output, "{ty}.eq")?,
|
||||
BinaryOp::Neq => writeln!(self.output, "{ty}.neq")?,
|
||||
BinaryOp::Less => writeln!(self.output, "{ty}.lt_u")?,
|
||||
BinaryOp::Greater => writeln!(self.output, "{ty}.gt_u")?,
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
Expr::Unary(op, e) => {
|
||||
if let Expr::Literal(lit) = e.as_ref() {
|
||||
match lit {
|
||||
Literal::Integer(int) => writeln!(self.output, "i32.const {}", -int)?,
|
||||
Literal::Float(f) => writeln!(self.output, "f64.const {}", -f)?,
|
||||
_ => todo!(),
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.gen_expr(e, parent_gctx, ctx)?;
|
||||
match op {
|
||||
PrefixOp::Minus => {
|
||||
// this is so inefficent, but i don't care
|
||||
writeln!(self.output, "i32.const -1")?;
|
||||
writeln!(self.output, "i32.mul")?;
|
||||
}
|
||||
PrefixOp::LogicNot => {
|
||||
writeln!(self.output, "i32.eqz")?;
|
||||
}
|
||||
PrefixOp::BitNot => {
|
||||
// TODO: do i flip the sign bit?
|
||||
writeln!(self.output, "i32.const {}", i32::MAX)?;
|
||||
writeln!(self.output, "i32.xor")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
// FIXME: awful code until i fix patching up parser and lexer
|
||||
Expr::Match(cond, arms) => {
|
||||
self.gen_expr(cond, parent_gctx, ctx)?;
|
||||
writeln!(self.output, "local.set $match")?;
|
||||
|
||||
for (idx, (pat, expr)) in arms.iter().enumerate() {
|
||||
match pat {
|
||||
Pattern::Literal(lit) => {
|
||||
match lit {
|
||||
Literal::Integer(int) => writeln!(self.output, "i32.const {}", int)?,
|
||||
Literal::Boolean(b) => writeln!(self.output, "i32.const {}", if *b { 1 } else { 0 })?,
|
||||
_ => todo!(),
|
||||
}
|
||||
writeln!(self.output, "local.get $match")?;
|
||||
writeln!(self.output, "i32.eq")?;
|
||||
}
|
||||
Pattern::Wildcard => {
|
||||
writeln!(self.output, "i32.const 1")?;
|
||||
}
|
||||
};
|
||||
|
||||
writeln!(self.output, "(if (result i32) (then")?;
|
||||
self.gen_expr(expr, parent_gctx, ctx)?;
|
||||
|
||||
if idx == arms.len() - 1 {
|
||||
// TODO: verify its actually unreachable earlier on
|
||||
writeln!(self.output, ") (else unreachable")?;
|
||||
} else {
|
||||
writeln!(self.output, ") (else")?;
|
||||
}
|
||||
}
|
||||
writeln!(self.output, "{}", ")".repeat(arms.len() * 2))?;
|
||||
}
|
||||
Expr::Block(b) => {
|
||||
let mut ctx = ctx.clone();
|
||||
for (_i, stmt) in b.0.iter().enumerate() {
|
||||
match stmt {
|
||||
Statement::TailExpr(expr) => {
|
||||
self.gen_expr(expr, parent_gctx, &ctx)?;
|
||||
}
|
||||
Statement::Expr(expr) => {
|
||||
self.gen_expr(expr, parent_gctx, &ctx)?;
|
||||
if !expr.infer(&ctx).unwrap().is_empty() {
|
||||
writeln!(self.output, "drop")?;
|
||||
}
|
||||
}
|
||||
Statement::Let(name, expr) => {
|
||||
let ty = expr.infer(&ctx).unwrap();
|
||||
ctx.locals.insert(name.clone(), ty);
|
||||
// exprs.push((name.clone(), expr.clone()));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Expr::Call(func, args) => {
|
||||
expr.infer(ctx)?;
|
||||
for expr in args {
|
||||
self.gen_expr(expr, parent_gctx, ctx)?;
|
||||
}
|
||||
writeln!(self.output, "call ${func}")?;
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: not sure what i was thinking
|
||||
impl Allocator {
|
||||
fn new() -> Allocator {
|
||||
Allocator {
|
||||
count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn alloc_ident(&mut self, name: &str) -> String {
|
||||
// self.count += 1;
|
||||
// format!("{name}_{}", self.count)
|
||||
name.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct GenContext {
|
||||
exprs: Vec<(String, Expr)>,
|
||||
strings: Vec<(String, usize)>,
|
||||
}
|
||||
|
||||
impl GenContext {
|
||||
pub fn new() -> GenContext {
|
||||
GenContext {
|
||||
exprs: vec![],
|
||||
strings: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_locals(expr: &Expr, alloc: &mut Allocator, ctx: &mut Context, gctx: &mut GenContext) {
|
||||
match expr {
|
||||
Expr::Block(b) => {
|
||||
for stmt in &b.0 {
|
||||
match stmt {
|
||||
Statement::Let(name, expr) => {
|
||||
// FIXME: block scoped variables (name collisions)
|
||||
get_locals(expr, alloc, ctx, gctx);
|
||||
|
||||
let ty = expr.infer(ctx).unwrap();
|
||||
ctx.locals.insert(name.clone(), ty);
|
||||
gctx.exprs.push((alloc.alloc_ident(name), expr.clone()));
|
||||
}
|
||||
Statement::TailExpr(expr) => get_locals(expr, alloc, ctx, gctx),
|
||||
Statement::Expr(expr) => get_locals(expr, alloc, ctx, gctx),
|
||||
Statement::Func(Func { name, ret, params, .. }) => {
|
||||
ctx.funcs.insert(name.clone(), (params.clone(), ret.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Expr::Unary(_, expr) => get_locals(expr, alloc, ctx, gctx),
|
||||
Expr::Binary(_, a, b) => {
|
||||
get_locals(a, alloc, ctx, gctx);
|
||||
get_locals(b, alloc, ctx, gctx);
|
||||
}
|
||||
Expr::Literal(Literal::String(s)) => {
|
||||
let offset = gctx.strings.last().map(|(s, off)| off + s.len()).unwrap_or(8);
|
||||
gctx.strings.push((s.clone(), offset));
|
||||
}
|
||||
Expr::Match(pat, arms) => {
|
||||
get_locals(pat, alloc, ctx, gctx);
|
||||
for (_, arm) in arms {
|
||||
get_locals(arm, alloc, ctx, gctx);
|
||||
}
|
||||
}
|
||||
Expr::Call(_, exprs) => {
|
||||
for expr in exprs {
|
||||
get_locals(expr, alloc, ctx, gctx);
|
||||
}
|
||||
}
|
||||
Expr::Variable(..) | Expr::Literal(..) => (),
|
||||
}
|
||||
}
|
||||
|
||||
// fn get_strings(expr: &Expr, gctx: &mut GenContext) {
|
||||
// match expr {
|
||||
// Expr::Block(b) => {
|
||||
// for stmt in &b.0 {
|
||||
// match stmt {
|
||||
// Statement::Let(_, expr) => get_strings(expr, gctx),
|
||||
// Statement::TailExpr(expr) => get_strings(expr, gctx),
|
||||
// Statement::Expr(expr) => get_strings(expr, gctx),
|
||||
// Statement::Func(Func { block, .. }) => get_strings(&Expr::Block(block.clone()), gctx),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// Expr::Unary(_, expr) => get_strings(expr, gctx),
|
||||
// Expr::Binary(_, a, b) => {
|
||||
// get_strings(a, gctx);
|
||||
// get_strings(b, gctx);
|
||||
// }
|
||||
// Expr::Literal(Literal::String(s)) => {
|
||||
// let offset = gctx.strings.last().map(|(s, off)| off + s.len()).unwrap_or(8);
|
||||
// gctx.strings.push((s.clone(), offset));
|
||||
// }
|
||||
// Expr::Match(pat, arms) => {
|
||||
// get_strings(pat, gctx);
|
||||
// for (_, arm) in arms {
|
||||
// get_strings(arm, gctx);
|
||||
// }
|
||||
// }
|
||||
// Expr::Call(_, exprs) => {
|
||||
// for expr in exprs {
|
||||
// get_strings(expr, gctx);
|
||||
// }
|
||||
// }
|
||||
// Expr::Variable(..) | Expr::Literal(..) => (),
|
||||
// pub fn write_comment(&mut self, comment: &str) -> Result<(), Error> {
|
||||
// writeln!(self.output, ";; {}", comment)?;
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
// pub fn write_raw(&mut self, text: &str) -> Result<(), Error> {
|
||||
// write!(self.output, "{}", text)?;
|
||||
// Ok(())
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
pub mod data;
|
||||
pub mod error;
|
||||
pub mod compiler;
|
||||
pub mod generator;
|
||||
pub mod lexer;
|
||||
pub mod parser;
|
||||
|
|
177
src/main.rs
177
src/main.rs
|
@ -4,41 +4,162 @@ a second time when generating (so the types are known), there should be
|
|||
a better way
|
||||
*/
|
||||
|
||||
use lang::{generator, lexer, parser};
|
||||
use generator::Generator;
|
||||
use std::{path::PathBuf, fs::OpenOptions};
|
||||
|
||||
use clap::Parser;
|
||||
use lang::{compiler::{self, OutputType}, lexer, parser};
|
||||
use compiler::Compiler;
|
||||
|
||||
#[derive(Debug, clap::Parser)]
|
||||
enum Cli {
|
||||
/// Compile the immediately execute some code
|
||||
Run {
|
||||
/// The file containing the code you want to run
|
||||
file: PathBuf,
|
||||
},
|
||||
|
||||
/// Compile code into webassembly binary or text
|
||||
Compile {
|
||||
#[arg(short, long)]
|
||||
/// What format to output as
|
||||
format: Option<OutputFormat>,
|
||||
|
||||
#[arg(short, long)]
|
||||
/// Where to output to
|
||||
output: Option<PathBuf>,
|
||||
|
||||
/// The file containing the code you want to compile
|
||||
file: PathBuf,
|
||||
},
|
||||
|
||||
// future work:
|
||||
//
|
||||
// /// Start a read-print-eval loop
|
||||
// Repl,
|
||||
//
|
||||
// /// Check/lint the source code without running it
|
||||
// Check,
|
||||
//
|
||||
// /// Run tests and/or benchmarks
|
||||
// Test,
|
||||
//
|
||||
// /// Generate documentation
|
||||
// Doc,
|
||||
//
|
||||
// /// Create a new project
|
||||
// Create,
|
||||
//
|
||||
// /// Format source code nicely
|
||||
// Fmt,
|
||||
//
|
||||
// /// Start a language server
|
||||
// // NOTE: this is VERY HARD to implement and require an overhaul of the lexer/parser/checker
|
||||
// Lsp,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, clap::ValueEnum)]
|
||||
enum OutputFormat {
|
||||
Wat, Wasm,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let source = std::fs::read_to_string(std::env::args().skip(1).next().expect("no filename!")).expect("no source!");
|
||||
let lexer = lexer::Lexer::new(&source);
|
||||
let args = Cli::parse();
|
||||
match args {
|
||||
Cli::Run { file } => {
|
||||
let source = std::fs::read_to_string(file).expect("no source!");
|
||||
let lexer = lexer::Lexer::new(&source);
|
||||
|
||||
let tokens = match lexer.collect::<Result<Vec<_>, _>>() {
|
||||
Ok(tokens) => tokens,
|
||||
Err(error) => {
|
||||
eprintln!("error: {:?}", error);
|
||||
return;
|
||||
let tokens = match lexer.collect::<Result<Vec<_>, _>>() {
|
||||
Ok(tokens) => tokens,
|
||||
Err(error) => {
|
||||
eprintln!("error: {:?}", error);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut parser = parser::Parser::new(tokens);
|
||||
let mut statements = vec![];
|
||||
loop {
|
||||
match parser.next() {
|
||||
Ok(None) => break,
|
||||
Ok(Some(tree)) => statements.push(tree),
|
||||
Err(error) => {
|
||||
eprintln!("error: {:?}", error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut wat = vec![];
|
||||
let mut gen = Compiler::new(Box::new(&mut wat), OutputType::WatVerbose);
|
||||
|
||||
if let Err(err) = gen.write_module(&statements) {
|
||||
panic!("{:?}", err);
|
||||
}
|
||||
|
||||
let wat = String::from_utf8(wat).unwrap();
|
||||
let engine = wasmtime::Engine::default();
|
||||
let module = wasmtime::Module::new(&engine, wat).unwrap();
|
||||
let linker = wasmtime::Linker::new(&engine);
|
||||
let mut store = wasmtime::Store::new(&engine, 4);
|
||||
let instance = linker.instantiate(&mut store, &module).unwrap();
|
||||
let start = instance.get_typed_func::<(), i32>(&mut store, "_start").unwrap();
|
||||
dbg!(start.call(&mut store, ()).unwrap());
|
||||
}
|
||||
};
|
||||
Cli::Compile { file, format, output } => {
|
||||
let source = std::fs::read_to_string(&file).expect("no source!");
|
||||
let lexer = lexer::Lexer::new(&source);
|
||||
|
||||
let tokens = match lexer.collect::<Result<Vec<_>, _>>() {
|
||||
Ok(tokens) => tokens,
|
||||
Err(error) => {
|
||||
eprintln!("error: {:?}", error);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut parser = parser::Parser::new(tokens);
|
||||
let mut statements = vec![];
|
||||
loop {
|
||||
match parser.next() {
|
||||
Ok(None) => break,
|
||||
Ok(Some(tree)) => statements.push(tree),
|
||||
Err(error) => {
|
||||
eprintln!("error: {:?}", error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let format = format.unwrap_or_else(|| {
|
||||
if file.extension().is_some_and(|ext| ext == "wasm") {
|
||||
OutputFormat::Wasm
|
||||
} else {
|
||||
OutputFormat::Wat
|
||||
}
|
||||
});
|
||||
|
||||
let mut output: Box<dyn std::io::Write> = match output {
|
||||
Some(path) => Box::new(OpenOptions::new().create(true).write(true).open(path).expect("failed to open file")),
|
||||
None => Box::new(std::io::stdout()),
|
||||
};
|
||||
|
||||
match format {
|
||||
OutputFormat::Wat => {
|
||||
let mut gen = Compiler::new(Box::new(&mut output), OutputType::WatVerbose);
|
||||
|
||||
// dbg!(&tokens);
|
||||
if let Err(err) = gen.write_module(&statements) {
|
||||
panic!("{:?}", err);
|
||||
}
|
||||
}
|
||||
OutputFormat::Wasm => {
|
||||
let mut gen = Compiler::new(Box::new(&mut output), OutputType::Wasm);
|
||||
|
||||
let mut parser = parser::Parser::new(tokens);
|
||||
let mut statements = vec![];
|
||||
loop {
|
||||
match parser.next() {
|
||||
Ok(None) => break,
|
||||
Ok(Some(tree)) => statements.push(tree),
|
||||
Err(error) => {
|
||||
eprintln!("error: {:?}", error);
|
||||
return;
|
||||
if let Err(err) = gen.write_module(&statements) {
|
||||
panic!("{:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dbg!(&statements);
|
||||
|
||||
let mut gen = Generator::new(Box::new(std::io::stdout()));
|
||||
|
||||
if let Err(err) = gen.write_module(&statements) {
|
||||
panic!("{:?}", err);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue