rework some errors and stuff
This commit is contained in:
parent
2392087c4b
commit
298e69611f
7 changed files with 363 additions and 150 deletions
|
@ -9,7 +9,7 @@ optimizations
|
|||
|
||||
use std::io::Write;
|
||||
|
||||
use crate::data::{BinaryOp, Expr, Func, Literal, Pattern, PrefixOp, Statement, Type};
|
||||
use crate::data::{BinaryOp, ExprData, Func, Literal, Pattern, PrefixOp, Statement, Type, Expr};
|
||||
use crate::generator::OutputFormat;
|
||||
use crate::parser::Context;
|
||||
use crate::Error;
|
||||
|
@ -59,18 +59,11 @@ impl<'a> Compiler<'a> {
|
|||
let mut gctx = GenContext::new();
|
||||
let mut alloc = Allocator::new();
|
||||
get_locals(
|
||||
&Expr::Block(crate::data::Block(stmts.to_vec())),
|
||||
&Expr::new(ExprData::Block(crate::data::Block(stmts.to_vec())), (0, 1), &ctx)?,
|
||||
&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 {
|
||||
|
@ -78,8 +71,9 @@ impl<'a> Compiler<'a> {
|
|||
Statement::Let(..) | Statement::TailExpr(..) | Statement::Expr(..) => {
|
||||
return Err(Error::OtherError("incorrect top level statement".into()))
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
writeln!(self.output, ")")?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -98,11 +92,11 @@ impl<'a> Compiler<'a> {
|
|||
write!(self.output, " (export \"{}\")", func.name)?;
|
||||
}
|
||||
|
||||
let expr = Expr::Block(func.block.clone());
|
||||
let expr = Expr::new(ExprData::Block(func.block.clone()), (0, 1), ctx)?;
|
||||
let mut ctx = ctx.clone();
|
||||
|
||||
for (name, ty) in &func.params {
|
||||
ctx.locals.insert(name.clone(), ty.clone());
|
||||
ctx.register(name, ty.clone());
|
||||
write!(self.output, " (param ${} {})", name, ty.string())?;
|
||||
}
|
||||
|
||||
|
@ -129,6 +123,7 @@ impl<'a> Compiler<'a> {
|
|||
for (name, expr) in &exprs {
|
||||
let ty = match expr.infer(&ctx).unwrap() {
|
||||
Type::Integer => "i32",
|
||||
Type::Boolean => "i32",
|
||||
Type::Float => "f64",
|
||||
// Type::String => "i32",
|
||||
_ => todo!(),
|
||||
|
@ -153,8 +148,8 @@ impl<'a> Compiler<'a> {
|
|||
parent_gctx: &GenContext,
|
||||
ctx: &Context,
|
||||
) -> Result<(), Error> {
|
||||
match expr {
|
||||
Expr::Literal(lit) => match lit {
|
||||
match &expr.data {
|
||||
ExprData::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) => {
|
||||
|
@ -163,10 +158,10 @@ impl<'a> Compiler<'a> {
|
|||
// 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) => {
|
||||
ExprData::Variable(name) => {
|
||||
writeln!(self.output, "local.get ${name}")?;
|
||||
}
|
||||
Expr::Binary(op, a, b) => {
|
||||
ExprData::Binary(op, a, b) => {
|
||||
self.gen_expr(a, parent_gctx, ctx)?;
|
||||
self.gen_expr(b, parent_gctx, ctx)?;
|
||||
|
||||
|
@ -189,14 +184,16 @@ impl<'a> Compiler<'a> {
|
|||
_ => 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!(),
|
||||
ExprData::Unary(op, e) => {
|
||||
if op == &PrefixOp::Minus {
|
||||
if let ExprData::Literal(lit) = &e.as_ref().data {
|
||||
match lit {
|
||||
Literal::Integer(int) => writeln!(self.output, "i32.const {}", -int)?,
|
||||
Literal::Float(f) => writeln!(self.output, "f64.const {}", -f)?,
|
||||
_ => todo!(),
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.gen_expr(e, parent_gctx, ctx)?;
|
||||
|
@ -217,11 +214,11 @@ impl<'a> Compiler<'a> {
|
|||
}
|
||||
}
|
||||
// FIXME: awful code until i fix patching up parser and lexer
|
||||
Expr::Match(cond, arms) => {
|
||||
self.gen_expr(cond, parent_gctx, ctx)?;
|
||||
ExprData::Match(mat) => {
|
||||
self.gen_expr(&mat.expr, parent_gctx, ctx)?;
|
||||
writeln!(self.output, "local.set $match")?;
|
||||
|
||||
for (idx, (pat, expr)) in arms.iter().enumerate() {
|
||||
for (idx, (pat, expr)) in mat.arms.iter().enumerate() {
|
||||
match pat {
|
||||
Pattern::Literal(lit) => {
|
||||
match lit {
|
||||
|
@ -244,16 +241,16 @@ impl<'a> Compiler<'a> {
|
|||
writeln!(self.output, "(if (result i32) (then")?;
|
||||
self.gen_expr(expr, parent_gctx, ctx)?;
|
||||
|
||||
if idx == arms.len() - 1 {
|
||||
if idx == mat.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))?;
|
||||
writeln!(self.output, "{}", ")".repeat(mat.arms.len() * 2))?;
|
||||
}
|
||||
Expr::Block(b) => {
|
||||
ExprData::Block(b) => {
|
||||
let mut ctx = ctx.clone();
|
||||
for (_i, stmt) in b.0.iter().enumerate() {
|
||||
match stmt {
|
||||
|
@ -262,20 +259,18 @@ impl<'a> Compiler<'a> {
|
|||
}
|
||||
Statement::Expr(expr) => {
|
||||
self.gen_expr(expr, parent_gctx, &ctx)?;
|
||||
if !expr.infer(&ctx).unwrap().is_empty() {
|
||||
if !expr.ty.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()));
|
||||
ctx.register(name, expr.ty.clone());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Expr::Call(func, args) => {
|
||||
ExprData::Call(func, args) => {
|
||||
expr.infer(ctx)?;
|
||||
for expr in args {
|
||||
self.gen_expr(expr, parent_gctx, ctx)?;
|
||||
|
@ -316,16 +311,15 @@ impl GenContext {
|
|||
}
|
||||
|
||||
fn get_locals(expr: &Expr, alloc: &mut Allocator, ctx: &mut Context, gctx: &mut GenContext) {
|
||||
match expr {
|
||||
Expr::Block(b) => {
|
||||
match &expr.data {
|
||||
ExprData::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);
|
||||
ctx.register(name, expr.ty.clone());
|
||||
gctx.exprs.push((alloc.alloc_ident(name), expr.clone()));
|
||||
}
|
||||
Statement::TailExpr(expr) => get_locals(expr, alloc, ctx, gctx),
|
||||
|
@ -333,18 +327,17 @@ fn get_locals(expr: &Expr, alloc: &mut Allocator, ctx: &mut Context, gctx: &mut
|
|||
Statement::Func(Func {
|
||||
name, ret, params, ..
|
||||
}) => {
|
||||
ctx.funcs
|
||||
.insert(name.clone(), (params.clone(), ret.clone()));
|
||||
ctx.register(name, Type::Function(params.clone(), Box::new(ret.clone())))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Expr::Unary(_, expr) => get_locals(expr, alloc, ctx, gctx),
|
||||
Expr::Binary(_, a, b) => {
|
||||
ExprData::Unary(_, expr) => get_locals(expr, alloc, ctx, gctx),
|
||||
ExprData::Binary(_, a, b) => {
|
||||
get_locals(a, alloc, ctx, gctx);
|
||||
get_locals(b, alloc, ctx, gctx);
|
||||
}
|
||||
Expr::Literal(Literal::String(s)) => {
|
||||
ExprData::Literal(Literal::String(s)) => {
|
||||
let offset = gctx
|
||||
.strings
|
||||
.last()
|
||||
|
@ -352,18 +345,18 @@ fn get_locals(expr: &Expr, alloc: &mut Allocator, ctx: &mut Context, gctx: &mut
|
|||
.unwrap_or(8);
|
||||
gctx.strings.push((s.clone(), offset));
|
||||
}
|
||||
Expr::Match(pat, arms) => {
|
||||
get_locals(pat, alloc, ctx, gctx);
|
||||
for (_, arm) in arms {
|
||||
ExprData::Match(mat) => {
|
||||
get_locals(&mat.expr, alloc, ctx, gctx);
|
||||
for (_, arm) in &mat.arms {
|
||||
get_locals(arm, alloc, ctx, gctx);
|
||||
}
|
||||
}
|
||||
Expr::Call(_, exprs) => {
|
||||
ExprData::Call(_, exprs) => {
|
||||
for expr in exprs {
|
||||
get_locals(expr, alloc, ctx, gctx);
|
||||
}
|
||||
}
|
||||
Expr::Variable(..) | Expr::Literal(..) => (),
|
||||
ExprData::Variable(..) | ExprData::Literal(..) => (),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
35
src/data.rs
35
src/data.rs
|
@ -20,7 +20,7 @@ pub enum BinaryOp {
|
|||
// Set,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum PrefixOp {
|
||||
Minus,
|
||||
LogicNot,
|
||||
|
@ -57,16 +57,36 @@ pub struct Func {
|
|||
pub struct Block(pub Vec<Statement>);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Expr {
|
||||
pub enum ExprData {
|
||||
Literal(Literal),
|
||||
Variable(String),
|
||||
Binary(BinaryOp, Box<Expr>, Box<Expr>),
|
||||
Unary(PrefixOp, Box<Expr>),
|
||||
Match(Box<Expr>, Vec<(Pattern, Expr)>),
|
||||
Match(Match),
|
||||
Call(String, Vec<Expr>),
|
||||
Block(Block),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Expr {
|
||||
pub span: (usize, usize),
|
||||
pub ty: Type,
|
||||
pub data: ExprData,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Match {
|
||||
pub expr: Box<Expr>,
|
||||
pub arms: Vec<(Pattern, Expr)>,
|
||||
pub span: (usize, usize),
|
||||
pub kind: MatchKind,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum MatchKind {
|
||||
Match, If,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Pattern {
|
||||
Wildcard,
|
||||
|
@ -92,7 +112,7 @@ pub enum Type {
|
|||
// Struct(HashMap<String, Type>),
|
||||
// Enum(HashMap<String, Option<Type>>),
|
||||
// Newtype(HashMap<String, Type>),
|
||||
// Function(Vec<Type10 >, Box<Type>),
|
||||
Function(Vec<(String, Type)>, Box<Type>),
|
||||
Tuple(Vec<Type>),
|
||||
}
|
||||
|
||||
|
@ -166,6 +186,13 @@ impl Type {
|
|||
self == &Type::Tuple(vec![])
|
||||
}
|
||||
|
||||
pub fn is_callable(&self) -> bool {
|
||||
match self {
|
||||
Type::Function(..) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: use actual types
|
||||
pub fn string(&self) -> String {
|
||||
match self {
|
||||
|
|
89
src/error.rs
89
src/error.rs
|
@ -1,13 +1,16 @@
|
|||
use miette::{Diagnostic, NamedSource, SourceSpan};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::data::{Type, Pattern, BinaryOp, PrefixOp};
|
||||
|
||||
#[derive(Debug, Error, Diagnostic)]
|
||||
#[error("some kind of error")]
|
||||
pub enum Error {
|
||||
SyntaxError(SyntaxError),
|
||||
TypeError(TypeError),
|
||||
TypeErrorOld(String),
|
||||
ReferenceError(String),
|
||||
ReferenceError(ReferenceError),
|
||||
ReferenceErrorOld(String),
|
||||
IoError(std::io::Error),
|
||||
OtherError(String),
|
||||
}
|
||||
|
@ -51,25 +54,69 @@ pub struct SyntaxError {
|
|||
#[error("type error!")]
|
||||
#[diagnostic()]
|
||||
pub enum TypeError {
|
||||
#[diagnostic(code(error::types::unary_bad_input))]
|
||||
Unary {
|
||||
#[help]
|
||||
help: String,
|
||||
|
||||
#[label("i do not know what this is")]
|
||||
#[label("this is a {expr_ty:?}")]
|
||||
expr: SourceSpan,
|
||||
|
||||
expr_ty: Type,
|
||||
|
||||
#[label("i do not know what this is")]
|
||||
operator: SourceSpan,
|
||||
// TODO: unary operators besides only prefix
|
||||
operator: PrefixOp,
|
||||
|
||||
#[label("you can't use {operator:?} on this type")]
|
||||
operator_label: SourceSpan,
|
||||
},
|
||||
#[diagnostic(code(error::types::binary_bad_inputs))]
|
||||
Binary {
|
||||
#[help]
|
||||
help: String,
|
||||
|
||||
#[label("i do not know what this is")]
|
||||
expr: SourceSpan,
|
||||
#[label("this is a {lhs_ty:?}")]
|
||||
lhs: SourceSpan,
|
||||
|
||||
#[label("i do not know what this is")]
|
||||
operator: SourceSpan,
|
||||
#[label("and this is a {rhs_ty:?}")]
|
||||
rhs: SourceSpan,
|
||||
|
||||
lhs_ty: Type,
|
||||
rhs_ty: Type,
|
||||
operator: BinaryOp,
|
||||
|
||||
#[label("you can't use {operator:?} on these types")]
|
||||
operator_label: SourceSpan,
|
||||
},
|
||||
#[diagnostic(code(error::types::match_different_types))]
|
||||
MatchPattern {
|
||||
#[help]
|
||||
help: String,
|
||||
|
||||
#[label("you are comparing a {expr_label:?}")]
|
||||
expr_span: SourceSpan,
|
||||
|
||||
#[label("but this pattern matches a {pattern_label:?}")]
|
||||
pattern_span: SourceSpan,
|
||||
|
||||
expr_label: Type,
|
||||
pattern_label: Pattern,
|
||||
|
||||
#[label("in this match")]
|
||||
matcher: SourceSpan,
|
||||
},
|
||||
#[diagnostic(code(error::types::if_not_bool))]
|
||||
IfPattern {
|
||||
#[help]
|
||||
help: String,
|
||||
|
||||
#[label("this is a {expr_label:?}")]
|
||||
expr_span: SourceSpan,
|
||||
expr_label: Type,
|
||||
|
||||
#[label("in this if")]
|
||||
// funny name
|
||||
iffer: SourceSpan,
|
||||
},
|
||||
UnknownType {
|
||||
#[help]
|
||||
|
@ -80,6 +127,30 @@ pub enum TypeError {
|
|||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Error, Diagnostic)]
|
||||
#[error("")]
|
||||
#[diagnostic()]
|
||||
pub struct ReferenceWrapper {
|
||||
#[source_code]
|
||||
pub src: NamedSource,
|
||||
|
||||
#[related]
|
||||
pub reference: Vec<ReferenceError>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error, Diagnostic)]
|
||||
#[error("reference error!")]
|
||||
#[diagnostic()]
|
||||
pub struct ReferenceError {
|
||||
#[label("this {kind} doesn't exist")]
|
||||
pub unknown: SourceSpan,
|
||||
|
||||
pub kind: String,
|
||||
|
||||
#[help]
|
||||
pub help: String,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn syn(pos: (usize, usize), help: impl Into<String>) -> Error {
|
||||
Error::SyntaxError(SyntaxError {
|
||||
|
@ -88,7 +159,7 @@ impl Error {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn ty(what: &'static str) -> Error {
|
||||
pub fn ty_old(what: &'static str) -> Error {
|
||||
Error::TypeErrorOld(what.to_string())
|
||||
}
|
||||
}
|
||||
|
|
16
src/main.rs
16
src/main.rs
|
@ -9,7 +9,7 @@ use std::{fs::OpenOptions, path::PathBuf};
|
|||
use clap::Parser;
|
||||
use lang::{
|
||||
compiler::Compiler,
|
||||
error::{SyntaxWrapper, TypeWrapper},
|
||||
error::{SyntaxWrapper, TypeWrapper, ReferenceWrapper},
|
||||
generator::OutputFormat,
|
||||
lexer, parser,
|
||||
};
|
||||
|
@ -38,7 +38,7 @@ enum Cli {
|
|||
},
|
||||
// future work:
|
||||
//
|
||||
// /// Start a read-print-eval loop
|
||||
// /// Start a read-eval-print-loop
|
||||
// Repl,
|
||||
//
|
||||
// /// Check/lint the source code without running it
|
||||
|
@ -123,10 +123,11 @@ fn main() {
|
|||
}
|
||||
};
|
||||
|
||||
let ctx = parser::Context::new();
|
||||
let mut parser = parser::Parser::new(tokens);
|
||||
let mut statements = vec![];
|
||||
loop {
|
||||
match parser.parse_statement_option() {
|
||||
match parser.parse_statement_option(&ctx) {
|
||||
Ok(None) => break,
|
||||
Ok(Some(tree)) => statements.push(tree),
|
||||
Err(error) => {
|
||||
|
@ -149,6 +150,15 @@ fn main() {
|
|||
reporter.render_report(&mut s, &ty).unwrap();
|
||||
eprintln!("{}", s);
|
||||
}
|
||||
lang::Error::ReferenceError(r) => {
|
||||
let mut s = String::new();
|
||||
let ty = ReferenceWrapper {
|
||||
src: NamedSource::new(file.to_string_lossy(), source),
|
||||
reference: vec![r],
|
||||
};
|
||||
reporter.render_report(&mut s, &ty).unwrap();
|
||||
eprintln!("{}", s);
|
||||
}
|
||||
_ => eprintln!("error: {:?}", error),
|
||||
}
|
||||
return;
|
||||
|
|
145
src/parser.rs
145
src/parser.rs
|
@ -1,6 +1,6 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use crate::data::{BinaryOp, Block, Expr, Func, Literal, Pattern, PrefixOp, Statement, Type};
|
||||
use crate::data::{BinaryOp, Block, Expr, ExprData, Func, Literal, Pattern, PrefixOp, Statement, Type, Match, MatchKind};
|
||||
use crate::lexer::{Symbol, Token, TokenContent};
|
||||
use crate::Error;
|
||||
|
||||
|
@ -9,10 +9,30 @@ pub struct Parser {
|
|||
pos: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug)]
|
||||
pub struct Context {
|
||||
pub locals: HashMap<String, Type>,
|
||||
pub funcs: HashMap<String, (Vec<(String, Type)>, Type)>,
|
||||
data: HashMap<String, Type>,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn new() -> Context {
|
||||
Context {
|
||||
data: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register(&mut self, name: &str, ty: Type) {
|
||||
self.data.insert(name.to_string(), ty);
|
||||
}
|
||||
|
||||
pub fn lookup(&self, name: &str) -> Option<&Type> {
|
||||
self.data.get(name)
|
||||
}
|
||||
|
||||
// TODO: implement this more cheaply?
|
||||
pub fn clone(&self) -> Context {
|
||||
Context { data: self.data.clone() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
|
@ -61,14 +81,14 @@ impl Parser {
|
|||
result
|
||||
}
|
||||
|
||||
pub fn parse_statement_option(&mut self) -> Result<Option<Statement>, Error> {
|
||||
pub fn parse_statement_option(&mut self, ctx: &Context) -> Result<Option<Statement>, Error> {
|
||||
match self.peek_tok() {
|
||||
Some(_) => Some(self.parse_statement()).transpose(),
|
||||
Some(_) => Some(self.parse_statement(ctx)).transpose(),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_statement(&mut self) -> Result<Statement, Error> {
|
||||
fn parse_statement(&mut self, ctx: &Context) -> Result<Statement, Error> {
|
||||
let Some(tok) = self.peek_tok() else {
|
||||
return Err(self.err_eof("statement"));
|
||||
};
|
||||
|
@ -81,7 +101,7 @@ impl Parser {
|
|||
self.parse_type()?;
|
||||
}
|
||||
self.eat(TokenContent::Symbol(Symbol::Set))?;
|
||||
let expr = self.parse_expr(0)?;
|
||||
let expr = self.parse_expr(&ctx, 0)?;
|
||||
self.eat(TokenContent::Symbol(Symbol::Semicolon))?;
|
||||
Statement::Let(name, expr)
|
||||
}
|
||||
|
@ -118,7 +138,7 @@ impl Parser {
|
|||
Type::empty()
|
||||
};
|
||||
self.eat(TokenContent::OpenBrace)?;
|
||||
let block = self.parse_block()?;
|
||||
let block = self.parse_block(&ctx)?;
|
||||
self.eat(TokenContent::CloseBrace)?;
|
||||
Statement::Func(Func {
|
||||
name,
|
||||
|
@ -129,7 +149,7 @@ impl Parser {
|
|||
})
|
||||
}
|
||||
_ => {
|
||||
let expr = self.parse_expr(0)?;
|
||||
let expr = self.parse_expr(&ctx, 0)?;
|
||||
if self
|
||||
.peek_tok()
|
||||
.is_some_and(|tk| tk.token == TokenContent::Symbol(Symbol::Semicolon))
|
||||
|
@ -195,7 +215,8 @@ impl Parser {
|
|||
result
|
||||
}
|
||||
|
||||
fn parse_block(&mut self) -> Result<Block, Error> {
|
||||
fn parse_block(&mut self, ctx: &Context) -> Result<Block, Error> {
|
||||
let mut ctx = ctx.clone();
|
||||
let mut statements = vec![];
|
||||
loop {
|
||||
if self
|
||||
|
@ -204,13 +225,18 @@ impl Parser {
|
|||
{
|
||||
break;
|
||||
}
|
||||
match self.parse_statement()? {
|
||||
stmt @ Statement::TailExpr(..) => {
|
||||
let stmt = self.parse_statement(&ctx)?;
|
||||
match &stmt {
|
||||
Statement::TailExpr(..) => {
|
||||
statements.push(stmt);
|
||||
break;
|
||||
}
|
||||
stmt => statements.push(stmt),
|
||||
Statement::Let(name, expr) => {
|
||||
ctx.register(name, expr.ty.clone());
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
statements.push(stmt);
|
||||
match self.peek_tok() {
|
||||
Some(Token {
|
||||
token: TokenContent::CloseBrace,
|
||||
|
@ -222,7 +248,7 @@ impl Parser {
|
|||
Ok(Block(statements))
|
||||
}
|
||||
|
||||
fn parse_expr(&mut self, binding: u8) -> Result<Expr, Error> {
|
||||
fn parse_expr(&mut self, ctx: &Context, binding: u8) -> Result<Expr, Error> {
|
||||
let Some(tok) = self.next_tok() else {
|
||||
return Err(self.err_eof("expression"));
|
||||
};
|
||||
|
@ -230,9 +256,17 @@ impl Parser {
|
|||
let mut expr = match &tok.token {
|
||||
TokenContent::Number { radix: _, text } => {
|
||||
if text.contains('.') {
|
||||
Expr::Literal(Literal::Float(text.parse().unwrap()))
|
||||
Expr {
|
||||
span: tok.span,
|
||||
ty: Type::Float,
|
||||
data: ExprData::Literal(Literal::Float(text.parse().unwrap())),
|
||||
}
|
||||
} else {
|
||||
Expr::Literal(Literal::Integer(text.parse().unwrap()))
|
||||
Expr {
|
||||
span: tok.span,
|
||||
ty: Type::Integer,
|
||||
data: ExprData::Literal(Literal::Integer(text.parse().unwrap())),
|
||||
}
|
||||
}
|
||||
}
|
||||
TokenContent::Ident(ident) => {
|
||||
|
@ -243,25 +277,41 @@ impl Parser {
|
|||
.peek_tok()
|
||||
.is_some_and(|tok| tok.token == TokenContent::CloseParan)
|
||||
{
|
||||
params.push(self.parse_expr(0)?);
|
||||
params.push(self.parse_expr(ctx, 0)?);
|
||||
if self.eat(TokenContent::Symbol(Symbol::Comma)).is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.eat(TokenContent::CloseParan)?;
|
||||
Expr::Call(ident, params)
|
||||
Expr::new(ExprData::Call(ident, params), tok.span, ctx)?
|
||||
} else {
|
||||
Expr::Variable(ident)
|
||||
Expr::new(ExprData::Variable(ident), tok.span, ctx)?
|
||||
}
|
||||
}
|
||||
TokenContent::False => Expr::Literal(Literal::Boolean(false)),
|
||||
TokenContent::True => Expr::Literal(Literal::Boolean(true)),
|
||||
TokenContent::String(s) => Expr::Literal(Literal::String(s.to_string())),
|
||||
TokenContent::Char(ch) => Expr::Literal(Literal::Char(*ch)),
|
||||
TokenContent::False => Expr {
|
||||
span: tok.span,
|
||||
ty: Type::Boolean,
|
||||
data: ExprData::Literal(Literal::Boolean(false))
|
||||
},
|
||||
TokenContent::True => Expr {
|
||||
span: tok.span,
|
||||
ty: Type::Boolean,
|
||||
data: ExprData::Literal(Literal::Boolean(true))
|
||||
},
|
||||
TokenContent::String(s) => Expr {
|
||||
span: tok.span,
|
||||
ty: Type::String,
|
||||
data: ExprData::Literal(Literal::String(s.to_string()))
|
||||
},
|
||||
TokenContent::Char(ch) => Expr {
|
||||
span: tok.span,
|
||||
ty: Type::Char,
|
||||
data: ExprData::Literal(Literal::Char(*ch))
|
||||
},
|
||||
TokenContent::If => {
|
||||
let cond = self.parse_expr(0)?;
|
||||
let cond = self.parse_expr(ctx, 0)?;
|
||||
self.eat(TokenContent::OpenBrace)?;
|
||||
let block = self.parse_block()?;
|
||||
let block = self.parse_block(ctx)?;
|
||||
self.eat(TokenContent::CloseBrace)?;
|
||||
let otherwise = if self
|
||||
.peek_tok()
|
||||
|
@ -274,14 +324,14 @@ impl Parser {
|
|||
..
|
||||
}) => {
|
||||
self.eat(TokenContent::OpenBrace)?;
|
||||
let b = Some(self.parse_block()?);
|
||||
let b = Some(self.parse_block(ctx)?);
|
||||
self.eat(TokenContent::CloseBrace)?;
|
||||
b
|
||||
}
|
||||
Some(Token {
|
||||
token: TokenContent::If,
|
||||
..
|
||||
}) => Some(Block(vec![Statement::TailExpr(self.parse_expr(0)?)])),
|
||||
}) => Some(Block(vec![Statement::TailExpr(self.parse_expr(ctx, 0)?)])),
|
||||
Some(tk) => {
|
||||
return Err(
|
||||
self.err_syn(tk.span, "this should be followed by an if or block")
|
||||
|
@ -292,14 +342,20 @@ impl Parser {
|
|||
} else {
|
||||
None
|
||||
};
|
||||
let mut map = vec![(Pattern::Literal(Literal::Boolean(true)), Expr::Block(block))];
|
||||
let mut map = vec![(Pattern::Literal(Literal::Boolean(true)), Expr::new(ExprData::Block(block), (0, 1), ctx)?)];
|
||||
if let Some(otherwise) = otherwise {
|
||||
map.push((
|
||||
Pattern::Literal(Literal::Boolean(false)),
|
||||
Expr::Block(otherwise),
|
||||
Expr::new(ExprData::Block(otherwise), (0, 1), ctx)?,
|
||||
));
|
||||
}
|
||||
Expr::Match(Box::new(cond), map)
|
||||
let mat = Match {
|
||||
expr: Box::new(cond),
|
||||
arms: map,
|
||||
span: tok.span,
|
||||
kind: MatchKind::If,
|
||||
};
|
||||
Expr::new(ExprData::Match(mat), tok.span, ctx)?
|
||||
}
|
||||
TokenContent::Symbol(_) => {
|
||||
let Some(op) = PrefixOp::from_token(&tok.token) else {
|
||||
|
@ -308,17 +364,17 @@ impl Parser {
|
|||
"this should be changed into a valid operator or removed outright",
|
||||
));
|
||||
};
|
||||
let expr = self.parse_expr(1)?;
|
||||
Expr::Unary(op, Box::new(expr))
|
||||
let expr = self.parse_expr(ctx, 1)?;
|
||||
Expr::new(ExprData::Unary(op, Box::new(expr)), tok.span, ctx)?
|
||||
}
|
||||
TokenContent::Match => {
|
||||
let expr = self.parse_expr(0)?;
|
||||
let expr = self.parse_expr(ctx, 0)?;
|
||||
let mut arms = vec![];
|
||||
self.eat(TokenContent::OpenBrace)?;
|
||||
loop {
|
||||
let pat = self.parse_pattern()?;
|
||||
self.eat(TokenContent::Symbol(Symbol::FatArrow))?;
|
||||
let expr = self.parse_expr(0)?;
|
||||
let expr = self.parse_expr(ctx, 0)?;
|
||||
arms.push((pat, expr));
|
||||
|
||||
if self.eat(TokenContent::Symbol(Symbol::Comma)).is_err() {
|
||||
|
@ -334,15 +390,21 @@ impl Parser {
|
|||
}
|
||||
}
|
||||
self.eat(TokenContent::CloseBrace)?;
|
||||
Expr::Match(Box::new(expr), arms)
|
||||
let mat = Match {
|
||||
expr: Box::new(expr),
|
||||
arms,
|
||||
span: tok.span,
|
||||
kind: MatchKind::Match,
|
||||
};
|
||||
Expr::new(ExprData::Match(mat), tok.span, ctx)?
|
||||
}
|
||||
TokenContent::OpenBrace => {
|
||||
let b = Expr::Block(self.parse_block()?);
|
||||
let b = ExprData::Block(self.parse_block(ctx)?);
|
||||
self.eat(TokenContent::CloseBrace)?;
|
||||
b
|
||||
todo!()
|
||||
}
|
||||
TokenContent::OpenParan => {
|
||||
let expr = self.parse_expr(0)?;
|
||||
let expr = self.parse_expr(ctx, 0)?;
|
||||
self.eat(TokenContent::CloseParan)?;
|
||||
expr
|
||||
}
|
||||
|
@ -356,9 +418,10 @@ impl Parser {
|
|||
if bind_left < binding {
|
||||
break;
|
||||
}
|
||||
let span = next.span;
|
||||
self.next_tok();
|
||||
let rhs = self.parse_expr(bind_right)?;
|
||||
expr = Expr::Binary(op, Box::new(expr), Box::new(rhs));
|
||||
let rhs = self.parse_expr(ctx, bind_right)?;
|
||||
expr = Expr::new(ExprData::Binary(op, Box::new(expr), Box::new(rhs)), span, ctx)?;
|
||||
}
|
||||
Ok(expr)
|
||||
}
|
||||
|
|
137
src/types.rs
137
src/types.rs
|
@ -2,55 +2,108 @@
|
|||
// fn infer();
|
||||
// }
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{
|
||||
data::{BinaryOp, Block, Expr, Literal, Pattern, PrefixOp, Statement, Type},
|
||||
data::{BinaryOp, Block, Expr, ExprData, Literal, Pattern, PrefixOp, Statement, Type, MatchKind},
|
||||
parser::Context,
|
||||
Error,
|
||||
Error, error::{TypeError, ReferenceError},
|
||||
};
|
||||
|
||||
impl Expr {
|
||||
pub fn new(data: ExprData, span: (usize, usize), ctx: &Context) -> Result<Expr, Error> {
|
||||
Ok(Expr {
|
||||
span,
|
||||
ty: data.infer(ctx, span)?,
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
pub fn infer(&self, ctx: &Context) -> Result<Type, Error> {
|
||||
self.data.infer(ctx, self.span)
|
||||
}
|
||||
}
|
||||
|
||||
impl ExprData {
|
||||
pub fn infer(&self, ctx: &Context, span: (usize, usize)) -> Result<Type, Error> {
|
||||
match self {
|
||||
Self::Literal(lit) => lit.infer(),
|
||||
Self::Binary(op, lhs, rhs) => Ok(op.infer(lhs.infer(ctx)?, rhs.infer(ctx)?)?),
|
||||
Self::Unary(op, expr) => Ok(op.infer(expr.infer(ctx)?)?),
|
||||
Self::Variable(name) => match ctx.locals.get(name) {
|
||||
Some(ty) => Ok(ty.clone()),
|
||||
None => Err(Error::ReferenceError(format!(
|
||||
"cannot find variable {name}"
|
||||
))),
|
||||
ExprData::Literal(lit) => lit.infer(),
|
||||
ExprData::Binary(op, lhs, rhs) => {
|
||||
match op.infer(&lhs.ty, &rhs.ty) {
|
||||
Ok(ty) => Ok(ty),
|
||||
Err(_) => return Err(Error::TypeError(TypeError::Binary {
|
||||
help: "try changing some things".into(),
|
||||
lhs: lhs.span.into(),
|
||||
rhs: rhs.span.into(),
|
||||
lhs_ty: lhs.ty.clone(),
|
||||
rhs_ty: rhs.ty.clone(),
|
||||
operator: op.clone(),
|
||||
operator_label: span.into(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
ExprData::Unary(op, expr) => {
|
||||
match op.infer(&expr.ty) {
|
||||
Ok(ty) => Ok(ty),
|
||||
Err(_) => return Err(Error::TypeError(TypeError::Unary {
|
||||
help: "try changing some things".into(),
|
||||
expr: expr.span.into(),
|
||||
expr_ty: expr.ty.clone(),
|
||||
operator: op.clone(),
|
||||
operator_label: span.into(),
|
||||
}))
|
||||
}
|
||||
},
|
||||
Self::Match(item, arms) => {
|
||||
ExprData::Variable(name) => match ctx.lookup(&name) {
|
||||
Some(ty) => Ok(ty.clone()),
|
||||
None => Err(Error::ReferenceError(ReferenceError {
|
||||
unknown: span.into(),
|
||||
kind: "variable".into(),
|
||||
help: "create it, check scope of variables, etc".into(),
|
||||
})),
|
||||
},
|
||||
ExprData::Match(mat) => {
|
||||
let mut match_ty = None;
|
||||
let item_ty = item.infer(ctx)?;
|
||||
for (pat, expr) in arms {
|
||||
for (pat, expr) in &mat.arms {
|
||||
let ty = expr.infer(ctx)?;
|
||||
if !pat.matches_type(&item_ty)? {
|
||||
return Err(Error::ty("cannot compare different type"));
|
||||
if !pat.matches_type(&mat.expr.ty)? {
|
||||
let err = match mat.kind {
|
||||
MatchKind::Match => {
|
||||
TypeError::MatchPattern {
|
||||
help: "these are different types".into(),
|
||||
expr_span: mat.expr.span.into(),
|
||||
pattern_span: (0, 1).into(),
|
||||
expr_label: mat.expr.ty.clone(),
|
||||
pattern_label: pat.clone(),
|
||||
matcher: span.into(),
|
||||
}
|
||||
},
|
||||
MatchKind::If => {
|
||||
TypeError::IfPattern {
|
||||
help: "this is not a boolean".into(),
|
||||
expr_span: mat.expr.span.into(),
|
||||
expr_label: mat.expr.ty.clone(),
|
||||
iffer: mat.span.into(),
|
||||
}
|
||||
},
|
||||
};
|
||||
return Err(Error::TypeError(err));
|
||||
}
|
||||
if match_ty.is_some_and(|mty| mty != ty) {
|
||||
return Err(Error::ty("branch returns different type"));
|
||||
return Err(Error::ty_old("branch returns different type"));
|
||||
}
|
||||
match_ty = Some(ty);
|
||||
}
|
||||
// TODO: exhaustiveness checks
|
||||
let Some(match_ty) = match_ty else {
|
||||
// TODO: infallible types? `enum Nope {}`
|
||||
return Err(Error::ty("match has no branches to infer"));
|
||||
};
|
||||
Ok(match_ty)
|
||||
Ok(match_ty.unwrap_or_else(Type::empty))
|
||||
}
|
||||
Self::Block(block) => block.infer(ctx),
|
||||
Self::Call(name, args) => match ctx.funcs.get(name) {
|
||||
Some((params, ret)) => {
|
||||
ExprData::Block(block) => block.infer(ctx),
|
||||
ExprData::Call(name, args) => match ctx.lookup(&name) {
|
||||
Some(Type::Function(params, ret)) => {
|
||||
if args.len() < params.len() {
|
||||
return Err(Error::ty("missing parameters"));
|
||||
return Err(Error::ty_old("missing parameters"));
|
||||
}
|
||||
|
||||
if args.len() > params.len() {
|
||||
return Err(Error::ty("too many parameters"));
|
||||
return Err(Error::ty_old("too many parameters"));
|
||||
}
|
||||
|
||||
for (arg, (name, ty)) in args.iter().zip(params) {
|
||||
|
@ -60,18 +113,23 @@ impl Expr {
|
|||
}
|
||||
}
|
||||
|
||||
Ok(ret.clone())
|
||||
Ok((**ret).clone())
|
||||
}
|
||||
None => Err(Error::ReferenceError(format!(
|
||||
"cannot find variable {name}"
|
||||
))),
|
||||
Some(bad) => {
|
||||
return Err(Error::TypeErrorOld(format!("{name} is {bad:?} which is not a function you can call")));
|
||||
}
|
||||
None => Err(Error::ReferenceError(ReferenceError {
|
||||
unknown: span.into(),
|
||||
kind: "function".into(),
|
||||
help: "create it, check scope of functions, etc".into(),
|
||||
})),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BinaryOp {
|
||||
pub fn infer(&self, a: Type, b: Type) -> Result<Type, Error> {
|
||||
pub fn infer(&self, a: &Type, b: &Type) -> Result<Type, Error> {
|
||||
use BinaryOp as B;
|
||||
use Type as T;
|
||||
|
||||
|
@ -103,7 +161,7 @@ impl BinaryOp {
|
|||
}
|
||||
|
||||
impl PrefixOp {
|
||||
pub fn infer(&self, a: Type) -> Result<Type, Error> {
|
||||
pub fn infer(&self, a: &Type) -> Result<Type, Error> {
|
||||
use PrefixOp as U;
|
||||
use Type as T;
|
||||
|
||||
|
@ -131,7 +189,7 @@ impl Block {
|
|||
Statement::TailExpr(expr) => ty = expr.infer(&ctx)?,
|
||||
Statement::Let(name, expr) => {
|
||||
let var_ty = expr.infer(&ctx)?;
|
||||
ctx.locals.insert(name.clone(), var_ty);
|
||||
ctx.register(name, var_ty);
|
||||
ty = Type::empty();
|
||||
}
|
||||
_ => (),
|
||||
|
@ -161,12 +219,3 @@ impl Pattern {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn new() -> Context {
|
||||
Context {
|
||||
locals: HashMap::new(),
|
||||
funcs: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
2
test/bad
2
test/bad
|
@ -1,3 +1,3 @@
|
|||
fn main() {
|
||||
let a = 10 +
|
||||
let x = -true;
|
||||
}
|
Loading…
Reference in a new issue