From b8e18f5257881f3e85430206ba2404e7db651fed Mon Sep 17 00:00:00 2001 From: tezlm Date: Sun, 22 Oct 2023 13:20:04 -0700 Subject: [PATCH] cleanup and better types/messages --- doc/design.md | 159 ++++++++++++++++++++++++++++++++++++++++++++++ doc/lang.md | 10 +++ example/_reflect | 78 +++++++++++++++++++++++ example/fizzbuzz | 11 ++++ example/hello | 3 + example/http | 20 ++++++ example/readme.md | 1 + example/reflect | 44 +++++++++++++ example/types | 1 + print.wasm | Bin 0 -> 143 bytes src/compiler.rs | 39 ++++++------ src/data.rs | 11 +++- src/error.rs | 14 ++++ src/main.rs | 94 +++++++++++++-------------- src/parser.rs | 36 +++++------ src/types.rs | 77 ++++++++++++++++------ test/t1 | 2 +- test/t2 | 2 +- test/t5.broken | 17 ++--- 19 files changed, 495 insertions(+), 124 deletions(-) create mode 100644 doc/design.md create mode 100644 doc/lang.md create mode 100644 example/_reflect create mode 100644 example/fizzbuzz create mode 100644 example/hello create mode 100644 example/http create mode 100644 example/readme.md create mode 100644 example/reflect create mode 100644 example/types create mode 100644 print.wasm diff --git a/doc/design.md b/doc/design.md new file mode 100644 index 0000000..f0c58d8 --- /dev/null +++ b/doc/design.md @@ -0,0 +1,159 @@ +# design choices + +- no macros, try to keep parsing fast. instead, have constant time expressions +- static typing in a scripting language, but may need to be able to make ad-hoc structs +- push work to the definition instead of making callers handle stuff (ie `Drop` will exist, `defer` won't) + +## typing? + +```rust +// rust style +struct Foo { + param: T, +} + +// zig style +fn Foo(T: type) -> type { + struct { + param: T + } +} + +// how would traits work with zig style stuff? + +// rust style +fn length(vec: Vec) -> u32 { } + +// zig style +fn length(vec: ???) -> u32 { ??? } + +fn main() { + let a: Foo = Foo { + foo: 10, + }; + + let b: Foo(i32) = Foo(i32) { + + }; +} +``` + +# packages? + +```typescript +// rust-style "use" +// can be used to bring anything in scope, not only packages +use foobar; +use foobar::{a, b, c}; + +// typescripty imports +// strings make it easier to import +import "foobar" as name; +import "foobar" as { a, b, c }; +import "https://foobar.tld/path/to/file" as name; +// import "foobar"; // bad? + +// use with urls? +use "https://foobar.tld/path/to/file" as name; +use "https://foobar.tld/path/to/file"::Client; + +// imports as functions +// feels strange if import is a function but export/pub is a keyword +// but it also is more extensible import(something, ...argv) +let foobar = import("foobar"); +let { a, b, c } = import("foobar"); +``` + +# context? + +```rust +trait Entry { + fn is_dir(self) -> bool; + fn is_file(self) -> bool; + fn is_symlink(self) -> bool; + fn name(self); + fn create(self); + fn open(self); + fn remove(self); + fn rename(self); + fn truncate(self, size: u64); + fn exists(self); + fn metadata(self) -> Metadata; +} + +trait File is Read + Write + Seek + Entry {} + +struct Metadata { + ino: u64, + len: u64, + modified: u64, + permissions: Permissions, +} + +trait Filesystem { + fn entry(self, path: Path) -> Entry; + + fn open(self, path: Path, options: OpenOptions) -> File { + self.entry(path).open(options) + } + + fn read(self, path: Path) -> T { + // bikeshedding go brrr + self.open(path, .Read).read(); + // "infer struct type" + foo(path, .{}); + foo(path, :{}); + // maybe ditch {} and go for []? + // but then what would be for arrays? + foo(path, [foo: bar]); + foo(path, [foo = bar]); + foo(path, StructType { foo: bar }); + foo(path, StructType { foo = bar }); + foo(path, StructType [ foo: bar ]); + foo(path, StructType [ foo = bar ]); + + self.open(path, :Read).read() + } + + fn write(self, path: Path, data: View<[u8]>) -> Result<(), Error> { + self.open(path, :Write).write(data) + } +} + +// vars, cli args, current directory/file, etc +struct Environment { ... } + +trait Network { + fn listen_tcp(self, addr: SocketAddr) -> Result; + fn connect_tcp(self, addr: SocketAddr) -> Result; + fn listen_tls(self, addr: SocketAddr) -> Result; + fn connect_tls(self, addr: SocketAddr) -> Result; + fn bind_udp(self, addr: SocketAddr) -> Result; +} + +// all context is passed into +struct Context { + fs: Filesystem, + env: Environment, + os: Os, + net: Network, + log: Log, +} + +// fn main({ log }: Context) { +fn main(ctx: Context) { + // do things with ctx here... + ctx.log.info("hello world!"); + + let http = HttpClient::new(ctx.net); + http.get("https://url.tld/path/to/something").send()?.json()?; + http.post("https://url.tld/path/to/something").body(something)?; +} + +// problem: i'd need to pass ctx to *every* function that wants to log? +fn another() { + // maybe this? + import { log } from "context"; + log.debug("something"); +} +``` diff --git a/doc/lang.md b/doc/lang.md new file mode 100644 index 0000000..dd0016d --- /dev/null +++ b/doc/lang.md @@ -0,0 +1,10 @@ +# language + +``` +let name = 1234; +let name: i32 = 1234; + +fn foo(param: type) -> type { + +} +``` diff --git a/example/_reflect b/example/_reflect new file mode 100644 index 0000000..4770e50 --- /dev/null +++ b/example/_reflect @@ -0,0 +1,78 @@ +// this doesn't work, not sure what to do + +struct Foobar { + is_foo: bool, + number: uint, + name: string, +} + +enum Value { + Null, + Bool(bool), + Integer(int), + Float(float), + String(String), + Array(Vec), + Object(Map), +} + +fn serialize_ungeneric(f: Foobar, w: Write) { + w.write(f"{\"is_foo\":"); + w.write(f.bool.to_string()); + w.write(f",\"number\":"); + w.write(f.number.to_string()); + w.write(f",\"name\":"); + w.write(f.name); + w.write(f"}"); +} + +fn deserialize_ungeneric(v: Value) -> Foobar { + let r = r.into(); + let Value::Object(obj) = v else { + panic("wanted an object"); + }; + Foobar { + is_foo: obj.map.get("is_foo").unwrap().as_bool().unwrap(), + number: obj.map.get("number").unwrap().as_int().unwrap(), + name: obj.map.get("name").unwrap().as_str().unwrap(), + } +} + +fn serialize(data: T, w: Write) { + match std::const::typeof::() { + std::const::Type::Int(i) => w.write(i.to_string()), + std::const::Type::Uint(u) => w.write(u.to_string()), + std::const::Type::Float(f) => w.write(f.to_string()), + std::const::Type::String(s) => w.write(f"\"{s}\""), + std::const::Type::Boolean(b) => w.write(b.to_string()), + std::const::Type::Vec => { + w.write("["); + for (idx, item) in data.enumerate() { + serialize(item, w); + if idx < fields.len() - 1 { + w.write(","); + } + } + w.write("]"); + } + std::const::Type::Struct(fields) => { + w.write("{"); + for (idx, field) in fields.enumerate() { + let f = std::const::get_field(data, field); + w.write("\"{name}\": "); + serialize(f, w); + if idx < fields.len() - 1 { + w.write(","); + } + } + w.write("}"); + } + _ => todo(), + } +} + +fn deserialize(v: Value) -> T { + match (std::const::typeof::(), v) { + (std::const::Type::Int, Value::Int(d) => d + } +} diff --git a/example/fizzbuzz b/example/fizzbuzz new file mode 100644 index 0000000..ed3dbe2 --- /dev/null +++ b/example/fizzbuzz @@ -0,0 +1,11 @@ +fn main(ctx: Context) { + let log = ctx.log.info; + for i in 1...100 { + match (i % 3, i % 5) { + (0, 0) => log("fizzbuzz"), + (0, _) => log("fizz"), + (-, 0) => log("buzz"), + (_, _) => log("{}", i), + } + } +} diff --git a/example/hello b/example/hello new file mode 100644 index 0000000..c4791c2 --- /dev/null +++ b/example/hello @@ -0,0 +1,3 @@ +fn main(ctx: Context) { + ctx.log.info("hello world"); +} diff --git a/example/http b/example/http new file mode 100644 index 0000000..4d84177 --- /dev/null +++ b/example/http @@ -0,0 +1,20 @@ +use std::http::{HttpClient, EventSource}; +use std::json; + +fn main(ctx: Context) -> Result<(), Error> { + let http = HttpClient::new(ctx.net); + + let request = http.post("https://example.com/generate") + .header("content-type", "application/json") + .body(json::to_bytes(.{ + hello: "world", + })) + .send()?; + + let events = EventSource::new(request); + for event in events { + ctx.log.info("got event: {}", event.data); + } + + Ok(()) +} diff --git a/example/readme.md b/example/readme.md new file mode 100644 index 0000000..5b78283 --- /dev/null +++ b/example/readme.md @@ -0,0 +1 @@ +none of these code works - it's mostly to work out how the language will work and feel diff --git a/example/reflect b/example/reflect new file mode 100644 index 0000000..4a3d232 --- /dev/null +++ b/example/reflect @@ -0,0 +1,44 @@ +// this version uses types as values +// every version i tried to make feels oddly hacky, unsure what to do + +fn serialize(data: Any, w: Write) { + match std::const::type_info(std::const::type_of(data)) { + .int => w.write(data.to_string()), + .float => w.write(data.to_string()), + .bool => w.write(data.to_string()), + .enum => todo(), + .struct(s) => { + w.write("{"); + for field in s.fields { + let d = std::const::get_struct_field(s, field.name); + w.write(f"\"{field.name}\":{d}"); + } + w.write("}"); + } + _ => todo(), + } +} + +fn deserialize(into: type, json: Value) -> into { + match (std::const::type_info(into), json) { + (.int, .int(i)) => i, + (.float, .float(f)) => f, + (.bool, .bool(b)) => b, + (.struct(s), .map(m)) => { + let s = std::mem::uninit(into); + for field in s.fields { + s.set_field(field.name, m.get(field.name).expect("missing field!")).expect("wrong type!"); + } + unsafe { s.assume_init() } + }, + (_, _) => todo(), + // (_, _) => panic("expected {into} but got {json.type_name()}"), + } +} + +// in std +mod const { + pub const fn type_of(data: Any) -> type; + pub const fn type_info(ty: type) -> TypeInfo; + pub const fn get_struct_field(data: struct, name: String) -> /* type of data.name */; +} diff --git a/example/types b/example/types new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/example/types @@ -0,0 +1 @@ + diff --git a/print.wasm b/print.wasm new file mode 100644 index 0000000000000000000000000000000000000000..b880b86ca0062c683befdca06b838bc2fc6790b9 GIT binary patch literal 143 zcmWlOu?oU400rMmYb~u5q2eGSimQu@dp7?f4K!k*H6;lKr~Zr|YQ5z+4%!a{z?Epy zX@*k+h%>iQ`%d|n9{tk>H9GU|OuXigRz=qZ10;f6Bm|eW$Obc5_YugH584H=IB*XM incoCtDyzRZH|4ipV^@|N&a Compiler<'a> { &mut alloc, &mut ctx, &mut gctx, - ); + )?; for stmt in stmts { match stmt { @@ -117,11 +117,11 @@ impl<'a> Compiler<'a> { let mut alloc = Allocator::new(); let mut gctx = GenContext::new(); - get_locals(&expr, &mut alloc, &mut ctx, &mut gctx); + 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() { + let ty = match expr.infer(&ctx)? { Type::Integer => "i32", Type::Boolean => "i32", Type::Float => "f64", @@ -259,12 +259,12 @@ impl<'a> Compiler<'a> { } Statement::Expr(expr) => { self.gen_expr(expr, parent_gctx, &ctx)?; - if !expr.ty.is_empty() { + if !expr.infer(&ctx)?.is_empty() { writeln!(self.output, "drop")?; } } - Statement::Let(name, expr) => { - ctx.register(name, expr.ty.clone()); + Statement::Let(l) => { + ctx.register(&l.name, l.infer(&ctx)?); } _ => {} } @@ -310,20 +310,20 @@ impl GenContext { } } -fn get_locals(expr: &Expr, alloc: &mut Allocator, ctx: &mut Context, gctx: &mut GenContext) { +fn get_locals(expr: &Expr, alloc: &mut Allocator, ctx: &mut Context, gctx: &mut GenContext) -> Result<(), Error> { match &expr.data { ExprData::Block(b) => { for stmt in &b.0 { match stmt { - Statement::Let(name, expr) => { + Statement::Let(l) => { // FIXME: block scoped variables (name collisions) - get_locals(expr, alloc, ctx, gctx); + get_locals(&l.init, alloc, ctx, gctx)?; - ctx.register(name, expr.ty.clone()); - gctx.exprs.push((alloc.alloc_ident(name), expr.clone())); + ctx.register(&l.name, l.infer(&ctx)?); + gctx.exprs.push((alloc.alloc_ident(&l.name), l.init.clone())); } - Statement::TailExpr(expr) => get_locals(expr, alloc, ctx, gctx), - Statement::Expr(expr) => get_locals(expr, alloc, ctx, gctx), + Statement::TailExpr(expr) => get_locals(expr, alloc, ctx, gctx)?, + Statement::Expr(expr) => get_locals(expr, alloc, ctx, gctx)?, Statement::Func(Func { name, ret, params, .. }) => { @@ -332,10 +332,10 @@ fn get_locals(expr: &Expr, alloc: &mut Allocator, ctx: &mut Context, gctx: &mut } } } - ExprData::Unary(_, expr) => get_locals(expr, alloc, ctx, gctx), + 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); + get_locals(a, alloc, ctx, gctx)?; + get_locals(b, alloc, ctx, gctx)?; } ExprData::Literal(Literal::String(s)) => { let offset = gctx @@ -346,18 +346,19 @@ fn get_locals(expr: &Expr, alloc: &mut Allocator, ctx: &mut Context, gctx: &mut gctx.strings.push((s.clone(), offset)); } ExprData::Match(mat) => { - get_locals(&mat.expr, alloc, ctx, gctx); + get_locals(&mat.expr, alloc, ctx, gctx)?; for (_, arm) in &mat.arms { - get_locals(arm, alloc, ctx, gctx); + get_locals(arm, alloc, ctx, gctx)?; } } ExprData::Call(_, exprs) => { for expr in exprs { - get_locals(expr, alloc, ctx, gctx); + get_locals(expr, alloc, ctx, gctx)?; } } ExprData::Variable(..) | ExprData::Literal(..) => (), } + Ok(()) } // fn get_strings(expr: &Expr, gctx: &mut GenContext) { diff --git a/src/data.rs b/src/data.rs index 4ec67b1..c43120e 100644 --- a/src/data.rs +++ b/src/data.rs @@ -34,7 +34,7 @@ pub enum SuffixOp { #[derive(Debug, Clone)] pub enum Statement { - Let(String, Expr), + Let(Let), // Type(String, Type), Expr(Expr), TailExpr(Expr), @@ -44,6 +44,13 @@ pub enum Statement { // Type, } +#[derive(Debug, Clone)] +pub struct Let { + pub name: String, + pub ty: Option<(Type, (usize, usize))>, + pub init: Expr, +} + #[derive(Debug, Clone)] pub struct Func { pub name: String, @@ -70,7 +77,7 @@ pub enum ExprData { #[derive(Debug, Clone)] pub struct Expr { pub span: (usize, usize), - pub ty: Type, + // pub ty: Type, pub data: ExprData, } diff --git a/src/error.rs b/src/error.rs index 876a238..025d77f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -118,6 +118,7 @@ pub enum TypeError { // funny name iffer: SourceSpan, }, + #[diagnostic(code(error::types::unknown_type))] UnknownType { #[help] help: String, @@ -125,6 +126,19 @@ pub enum TypeError { #[label("i do not know what this type is")] unknown: SourceSpan, }, + #[diagnostic(code(error::types::mismatched_type))] + MismatchedType { + #[label("you said this is a {claimed_ty:?}")] + claimed_span: SourceSpan, + claimed_ty: Type, + + #[label("but it is a {got_ty:?}")] + got_span: SourceSpan, + got_ty: Type, + + #[help] + help: String, + }, } #[derive(Debug, Error, Diagnostic)] diff --git a/src/main.rs b/src/main.rs index 6a2a210..19b14b5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -67,9 +67,7 @@ enum OutputType { Wasm, } -fn main() { - let args = Cli::parse(); - +fn print_error(file: &std::path::Path, source: String, error: lang::Error) { let theme = miette::GraphicalTheme { characters: miette::ThemeCharacters { hbar: '─', @@ -97,6 +95,41 @@ fn main() { ..Default::default() }; let reporter = miette::GraphicalReportHandler::new().with_theme(theme); + + match error { + lang::Error::SyntaxError(syn) => { + let mut s = String::new(); + let syn = SyntaxWrapper { + src: NamedSource::new(file.to_string_lossy(), source), + syn: vec![syn], + }; + reporter.render_report(&mut s, &syn).unwrap(); + eprintln!("{}", s); + } + lang::Error::TypeError(ty) => { + let mut s = String::new(); + let ty = TypeWrapper { + src: NamedSource::new(file.to_string_lossy(), source), + ty: vec![ty], + }; + 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), + } +} + +fn main() { + let args = Cli::parse(); match &args { Cli::Run { file } | Cli::Compile { file, .. } => { @@ -106,19 +139,7 @@ fn main() { let tokens = match lexer.collect::, _>>() { Ok(tokens) => tokens, Err(error) => { - match error { - lang::Error::SyntaxError(syn) => { - let mut s = String::new(); - let syn = SyntaxWrapper { - src: NamedSource::new(file.to_string_lossy(), source), - syn: vec![syn], - }; - - reporter.render_report(&mut s, &syn).unwrap(); - eprintln!("{}", s); - } - _ => eprintln!("error: {:?}", error), - } + print_error(file, source, error); return; } }; @@ -131,36 +152,7 @@ fn main() { Ok(None) => break, Ok(Some(tree)) => statements.push(tree), Err(error) => { - match error { - lang::Error::SyntaxError(syn) => { - let mut s = String::new(); - let syn = SyntaxWrapper { - src: NamedSource::new(file.to_string_lossy(), source), - syn: vec![syn], - }; - reporter.render_report(&mut s, &syn).unwrap(); - eprintln!("{}", s); - } - lang::Error::TypeError(ty) => { - let mut s = String::new(); - let ty = TypeWrapper { - src: NamedSource::new(file.to_string_lossy(), source), - ty: vec![ty], - }; - 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), - } + print_error(file, source, error); return; } } @@ -171,8 +163,9 @@ fn main() { let mut wat = vec![]; let mut gen = Compiler::new(&mut wat, OutputFormat::WatVerbose); - if let Err(err) = gen.write_module(&statements) { - panic!("{:?}", err); + if let Err(error) = gen.write_module(&statements) { + print_error(file, source, error); + return; } let wat = String::from_utf8(wat).unwrap(); @@ -214,8 +207,9 @@ fn main() { OutputType::Wat => { let mut gen = Compiler::new(&mut output, OutputFormat::WatVerbose); - if let Err(err) = gen.write_module(&statements) { - panic!("{:?}", err); + if let Err(error) = gen.write_module(&statements) { + print_error(&file, source, error); + return; } } OutputType::Wasm => { diff --git a/src/parser.rs b/src/parser.rs index cb15961..ffd4d31 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -9,7 +9,7 @@ pub struct Parser { pos: usize, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Context { data: HashMap, } @@ -30,9 +30,9 @@ impl Context { } // TODO: implement this more cheaply? - pub fn clone(&self) -> Context { - Context { data: self.data.clone() } - } + // pub fn clone(&self) -> Context { + // Context { data: self.data.clone() } + // } } impl Parser { @@ -96,14 +96,16 @@ impl Parser { TokenContent::Let => { self.eat(TokenContent::Let)?; let name = self.parse_ident()?; - if self.eat(TokenContent::Symbol(Symbol::Colon)).is_ok() { - // TODO: types in variables - self.parse_type()?; - } + let name_span = self.tokens.get(self.pos - 1).unwrap().span; + let ty = if self.eat(TokenContent::Symbol(Symbol::Colon)).is_ok() { + Some((self.parse_type()?, name_span)) + } else { + None + }; self.eat(TokenContent::Symbol(Symbol::Set))?; - let expr = self.parse_expr(&ctx, 0)?; + let init = self.parse_expr(ctx, 0)?; self.eat(TokenContent::Symbol(Symbol::Semicolon))?; - Statement::Let(name, expr) + Statement::Let(crate::data::Let { name, ty, init }) } TokenContent::Pub | TokenContent::Fn => { // TODO: public things that aren't functions @@ -138,7 +140,7 @@ impl Parser { Type::empty() }; self.eat(TokenContent::OpenBrace)?; - let block = self.parse_block(&ctx)?; + let block = self.parse_block(ctx)?; self.eat(TokenContent::CloseBrace)?; Statement::Func(Func { name, @@ -149,7 +151,7 @@ impl Parser { }) } _ => { - let expr = self.parse_expr(&ctx, 0)?; + let expr = self.parse_expr(ctx, 0)?; if self .peek_tok() .is_some_and(|tk| tk.token == TokenContent::Symbol(Symbol::Semicolon)) @@ -231,8 +233,8 @@ impl Parser { statements.push(stmt); break; } - Statement::Let(name, expr) => { - ctx.register(name, expr.ty.clone()); + Statement::Let(l) => { + ctx.register(&l.name, l.infer(&ctx)?); } _ => (), } @@ -258,13 +260,11 @@ impl Parser { if text.contains('.') { Expr { span: tok.span, - ty: Type::Float, data: ExprData::Literal(Literal::Float(text.parse().unwrap())), } } else { Expr { span: tok.span, - ty: Type::Integer, data: ExprData::Literal(Literal::Integer(text.parse().unwrap())), } } @@ -290,22 +290,18 @@ impl Parser { } 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 => { diff --git a/src/types.rs b/src/types.rs index edf909d..817174c 100644 --- a/src/types.rs +++ b/src/types.rs @@ -3,16 +3,15 @@ // } use crate::{ - data::{BinaryOp, Block, Expr, ExprData, Literal, Pattern, PrefixOp, Statement, Type, MatchKind}, + data::{BinaryOp, Block, Expr, ExprData, Literal, Pattern, PrefixOp, Statement, Type, MatchKind, Let}, parser::Context, Error, error::{TypeError, ReferenceError}, }; impl Expr { - pub fn new(data: ExprData, span: (usize, usize), ctx: &Context) -> Result { + pub fn new(data: ExprData, span: (usize, usize), _ctx: &Context) -> Result { Ok(Expr { span, - ty: data.infer(ctx, span)?, data }) } @@ -20,6 +19,25 @@ impl Expr { pub fn infer(&self, ctx: &Context) -> Result { self.data.infer(ctx, self.span) } + + pub fn full_span(&self) -> (usize, usize) { + match &self.data { + ExprData::Binary(_, a, b) => { + let a_span = a.full_span(); + let b_span = b.full_span(); + let start = a_span.0; + let end = b_span.0 + b_span.1; + (start, end - start) + } + ExprData::Unary(_, a) => { + let a_span = a.full_span(); + let start = self.span.0; + let end = a_span.0 + a_span.1; + (start, end - start) + } + _ => self.span, + } + } } impl ExprData { @@ -27,32 +45,35 @@ impl ExprData { match self { ExprData::Literal(lit) => lit.infer(), ExprData::Binary(op, lhs, rhs) => { - match op.infer(&lhs.ty, &rhs.ty) { + let lhty = lhs.infer(ctx)?; + let rhty = rhs.infer(ctx)?; + match op.infer(&lhty, &rhty) { Ok(ty) => Ok(ty), - Err(_) => return Err(Error::TypeError(TypeError::Binary { + Err(_) => 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(), + lhs_ty: lhty, + rhs_ty: rhty, operator: op.clone(), operator_label: span.into(), })) } } ExprData::Unary(op, expr) => { - match op.infer(&expr.ty) { + let ty = expr.infer(ctx)?; + match op.infer(&ty) { Ok(ty) => Ok(ty), - Err(_) => return Err(Error::TypeError(TypeError::Unary { + Err(_) => Err(Error::TypeError(TypeError::Unary { help: "try changing some things".into(), expr: expr.span.into(), - expr_ty: expr.ty.clone(), + expr_ty: ty, operator: op.clone(), operator_label: span.into(), })) } }, - ExprData::Variable(name) => match ctx.lookup(&name) { + ExprData::Variable(name) => match ctx.lookup(name) { Some(ty) => Ok(ty.clone()), None => Err(Error::ReferenceError(ReferenceError { unknown: span.into(), @@ -62,16 +83,17 @@ impl ExprData { }, ExprData::Match(mat) => { let mut match_ty = None; + let expr_ty = mat.expr.infer(ctx)?; for (pat, expr) in &mat.arms { let ty = expr.infer(ctx)?; - if !pat.matches_type(&mat.expr.ty)? { + if !pat.matches_type(&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(), + expr_label: expr_ty.clone(), pattern_label: pat.clone(), matcher: span.into(), } @@ -80,7 +102,7 @@ impl ExprData { TypeError::IfPattern { help: "this is not a boolean".into(), expr_span: mat.expr.span.into(), - expr_label: mat.expr.ty.clone(), + expr_label: expr_ty.clone(), iffer: mat.span.into(), } }, @@ -96,7 +118,7 @@ impl ExprData { Ok(match_ty.unwrap_or_else(Type::empty)) } ExprData::Block(block) => block.infer(ctx), - ExprData::Call(name, args) => match ctx.lookup(&name) { + ExprData::Call(name, args) => match ctx.lookup(name) { Some(Type::Function(params, ret)) => { if args.len() < params.len() { return Err(Error::ty_old("missing parameters")); @@ -116,7 +138,7 @@ impl ExprData { Ok((**ret).clone()) } Some(bad) => { - return Err(Error::TypeErrorOld(format!("{name} is {bad:?} which is not a function you can call"))); + Err(Error::TypeErrorOld(format!("{name} is {bad:?} which is not a function you can call"))) } None => Err(Error::ReferenceError(ReferenceError { unknown: span.into(), @@ -187,11 +209,7 @@ impl Block { for statement in &self.0 { match statement { Statement::TailExpr(expr) => ty = expr.infer(&ctx)?, - Statement::Let(name, expr) => { - let var_ty = expr.infer(&ctx)?; - ctx.register(name, var_ty); - ty = Type::empty(); - } + Statement::Let(l) => ctx.register(&l.name, l.infer(&ctx)?), _ => (), } } @@ -199,6 +217,23 @@ impl Block { } } +impl Let { + pub fn infer(&self, ctx: &Context) -> Result { + let var_ty = self.init.infer(&ctx)?; + if self.ty.as_ref().is_some_and(|ty| ty.0 != var_ty) { + Err(Error::TypeError(TypeError::MismatchedType { + claimed_span: self.ty.as_ref().unwrap().1.into(), + claimed_ty: self.ty.as_ref().unwrap().0.clone(), + got_span: self.init.full_span().into(), + got_ty: var_ty, + help: "try changing some things".into(), + })) + } else { + Ok(var_ty) + } + } +} + impl Literal { fn infer(&self) -> Result { match self { diff --git a/test/t1 b/test/t1 index 748315b..98788ce 100644 --- a/test/t1 +++ b/test/t1 @@ -1,6 +1,6 @@ fn main() -> i32 { let foo = 8; - let bar = foo * -3; + let bar: bool = foo * -3; match foo + bar < 10 { true => 123, false => 456, diff --git a/test/t2 b/test/t2 index a02c143..14e8c9b 100644 --- a/test/t2 +++ b/test/t2 @@ -1,5 +1,5 @@ fn main() -> i32 { - two() * three() + two() * three() * four() } fn two() -> i32 { diff --git a/test/t5.broken b/test/t5.broken index 9970e32..8ac6fca 100644 --- a/test/t5.broken +++ b/test/t5.broken @@ -1,10 +1,7 @@ -let mut lexer = lexer::Lexer::new(r#" - fn main() { - let x = "example string!\n"; - let y = "another str.\n"; - print(x); - print(y); - print("Hello, world!"); - } -"#.into()); - +fn main() { + let x = "example string!\n"; + let y = "another str.\n"; + print(x); + print(y); + print("Hello, world!"); +}