dependency time

This commit is contained in:
tezlm 2023-10-03 20:18:44 -07:00
parent 63bb50f6b3
commit 95c1ca72e6
Signed by: tezlm
GPG key ID: 649733FCD94AFBBA
6 changed files with 2159 additions and 393 deletions

1575
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -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
View 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(..) => (),
// }
// }

View file

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

View file

@ -2,6 +2,7 @@
pub mod data;
pub mod error;
pub mod compiler;
pub mod generator;
pub mod lexer;
pub mod parser;

View file

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