cleanup and better types/messages

This commit is contained in:
tezlm 2023-10-22 13:20:04 -07:00
parent 298e69611f
commit b8e18f5257
Signed by: tezlm
GPG key ID: 649733FCD94AFBBA
19 changed files with 495 additions and 124 deletions

159
doc/design.md Normal file
View file

@ -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<T> {
param: T,
}
// zig style
fn Foo(T: type) -> type {
struct {
param: T
}
}
// how would traits work with zig style stuff?
// rust style
fn length<T>(vec: Vec<T>) -> u32 { }
// zig style
fn length(vec: ???) -> u32 { ??? }
fn main() {
let a: Foo<i32> = 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<T>(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<TcpServer, Error>;
fn connect_tcp(self, addr: SocketAddr) -> Result<TcpClient, Error>;
fn listen_tls(self, addr: SocketAddr) -> Result<TlsServer, Error>;
fn connect_tls(self, addr: SocketAddr) -> Result<TlsClient, Error>;
fn bind_udp(self, addr: SocketAddr) -> Result<UdpSocket, Error>;
}
// 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");
}
```

10
doc/lang.md Normal file
View file

@ -0,0 +1,10 @@
# language
```
let name = 1234;
let name: i32 = 1234;
fn foo(param: type) -> type {
}
```

78
example/_reflect Normal file
View file

@ -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<Value>),
Object(Map<String, Value>),
}
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<T>(data: T, w: Write) {
match std::const::typeof::<T>() {
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<T>(v: Value) -> T {
match (std::const::typeof::<T>(), v) {
(std::const::Type::Int, Value::Int(d) => d
}
}

11
example/fizzbuzz Normal file
View file

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

3
example/hello Normal file
View file

@ -0,0 +1,3 @@
fn main(ctx: Context) {
ctx.log.info("hello world");
}

20
example/http Normal file
View file

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

1
example/readme.md Normal file
View file

@ -0,0 +1 @@
none of these code works - it's mostly to work out how the language will work and feel

44
example/reflect Normal file
View file

@ -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 */;
}

1
example/types Normal file
View file

@ -0,0 +1 @@

BIN
print.wasm Normal file

Binary file not shown.

View file

@ -63,7 +63,7 @@ impl<'a> Compiler<'a> {
&mut alloc, &mut alloc,
&mut ctx, &mut ctx,
&mut gctx, &mut gctx,
); )?;
for stmt in stmts { for stmt in stmts {
match stmt { match stmt {
@ -117,11 +117,11 @@ impl<'a> Compiler<'a> {
let mut alloc = Allocator::new(); let mut alloc = Allocator::new();
let mut gctx = GenContext::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; let exprs = gctx.exprs;
for (name, expr) in &exprs { for (name, expr) in &exprs {
let ty = match expr.infer(&ctx).unwrap() { let ty = match expr.infer(&ctx)? {
Type::Integer => "i32", Type::Integer => "i32",
Type::Boolean => "i32", Type::Boolean => "i32",
Type::Float => "f64", Type::Float => "f64",
@ -259,12 +259,12 @@ impl<'a> Compiler<'a> {
} }
Statement::Expr(expr) => { Statement::Expr(expr) => {
self.gen_expr(expr, parent_gctx, &ctx)?; self.gen_expr(expr, parent_gctx, &ctx)?;
if !expr.ty.is_empty() { if !expr.infer(&ctx)?.is_empty() {
writeln!(self.output, "drop")?; writeln!(self.output, "drop")?;
} }
} }
Statement::Let(name, expr) => { Statement::Let(l) => {
ctx.register(name, expr.ty.clone()); 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 { match &expr.data {
ExprData::Block(b) => { ExprData::Block(b) => {
for stmt in &b.0 { for stmt in &b.0 {
match stmt { match stmt {
Statement::Let(name, expr) => { Statement::Let(l) => {
// FIXME: block scoped variables (name collisions) // 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()); ctx.register(&l.name, l.infer(&ctx)?);
gctx.exprs.push((alloc.alloc_ident(name), expr.clone())); gctx.exprs.push((alloc.alloc_ident(&l.name), l.init.clone()));
} }
Statement::TailExpr(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::Expr(expr) => get_locals(expr, alloc, ctx, gctx)?,
Statement::Func(Func { Statement::Func(Func {
name, ret, params, .. 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) => { ExprData::Binary(_, a, b) => {
get_locals(a, alloc, ctx, gctx); get_locals(a, alloc, ctx, gctx)?;
get_locals(b, alloc, ctx, gctx); get_locals(b, alloc, ctx, gctx)?;
} }
ExprData::Literal(Literal::String(s)) => { ExprData::Literal(Literal::String(s)) => {
let offset = gctx 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)); gctx.strings.push((s.clone(), offset));
} }
ExprData::Match(mat) => { ExprData::Match(mat) => {
get_locals(&mat.expr, alloc, ctx, gctx); get_locals(&mat.expr, alloc, ctx, gctx)?;
for (_, arm) in &mat.arms { for (_, arm) in &mat.arms {
get_locals(arm, alloc, ctx, gctx); get_locals(arm, alloc, ctx, gctx)?;
} }
} }
ExprData::Call(_, exprs) => { ExprData::Call(_, exprs) => {
for expr in exprs { for expr in exprs {
get_locals(expr, alloc, ctx, gctx); get_locals(expr, alloc, ctx, gctx)?;
} }
} }
ExprData::Variable(..) | ExprData::Literal(..) => (), ExprData::Variable(..) | ExprData::Literal(..) => (),
} }
Ok(())
} }
// fn get_strings(expr: &Expr, gctx: &mut GenContext) { // fn get_strings(expr: &Expr, gctx: &mut GenContext) {

View file

@ -34,7 +34,7 @@ pub enum SuffixOp {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Statement { pub enum Statement {
Let(String, Expr), Let(Let),
// Type(String, Type), // Type(String, Type),
Expr(Expr), Expr(Expr),
TailExpr(Expr), TailExpr(Expr),
@ -44,6 +44,13 @@ pub enum Statement {
// Type, // Type,
} }
#[derive(Debug, Clone)]
pub struct Let {
pub name: String,
pub ty: Option<(Type, (usize, usize))>,
pub init: Expr,
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Func { pub struct Func {
pub name: String, pub name: String,
@ -70,7 +77,7 @@ pub enum ExprData {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Expr { pub struct Expr {
pub span: (usize, usize), pub span: (usize, usize),
pub ty: Type, // pub ty: Type,
pub data: ExprData, pub data: ExprData,
} }

View file

@ -118,6 +118,7 @@ pub enum TypeError {
// funny name // funny name
iffer: SourceSpan, iffer: SourceSpan,
}, },
#[diagnostic(code(error::types::unknown_type))]
UnknownType { UnknownType {
#[help] #[help]
help: String, help: String,
@ -125,6 +126,19 @@ pub enum TypeError {
#[label("i do not know what this type is")] #[label("i do not know what this type is")]
unknown: SourceSpan, 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)] #[derive(Debug, Error, Diagnostic)]

View file

@ -67,9 +67,7 @@ enum OutputType {
Wasm, Wasm,
} }
fn main() { fn print_error(file: &std::path::Path, source: String, error: lang::Error) {
let args = Cli::parse();
let theme = miette::GraphicalTheme { let theme = miette::GraphicalTheme {
characters: miette::ThemeCharacters { characters: miette::ThemeCharacters {
hbar: '', hbar: '',
@ -98,39 +96,6 @@ fn main() {
}; };
let reporter = miette::GraphicalReportHandler::new().with_theme(theme); let reporter = miette::GraphicalReportHandler::new().with_theme(theme);
match &args {
Cli::Run { file } | Cli::Compile { 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) => {
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),
}
return;
}
};
let ctx = parser::Context::new();
let mut parser = parser::Parser::new(tokens);
let mut statements = vec![];
loop {
match parser.parse_statement_option(&ctx) {
Ok(None) => break,
Ok(Some(tree)) => statements.push(tree),
Err(error) => {
match error { match error {
lang::Error::SyntaxError(syn) => { lang::Error::SyntaxError(syn) => {
let mut s = String::new(); let mut s = String::new();
@ -161,6 +126,33 @@ fn main() {
} }
_ => eprintln!("error: {:?}", error), _ => eprintln!("error: {:?}", error),
} }
}
fn main() {
let args = Cli::parse();
match &args {
Cli::Run { file } | Cli::Compile { 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) => {
print_error(file, source, error);
return;
}
};
let ctx = parser::Context::new();
let mut parser = parser::Parser::new(tokens);
let mut statements = vec![];
loop {
match parser.parse_statement_option(&ctx) {
Ok(None) => break,
Ok(Some(tree)) => statements.push(tree),
Err(error) => {
print_error(file, source, error);
return; return;
} }
} }
@ -171,8 +163,9 @@ fn main() {
let mut wat = vec![]; let mut wat = vec![];
let mut gen = Compiler::new(&mut wat, OutputFormat::WatVerbose); let mut gen = Compiler::new(&mut wat, OutputFormat::WatVerbose);
if let Err(err) = gen.write_module(&statements) { if let Err(error) = gen.write_module(&statements) {
panic!("{:?}", err); print_error(file, source, error);
return;
} }
let wat = String::from_utf8(wat).unwrap(); let wat = String::from_utf8(wat).unwrap();
@ -214,8 +207,9 @@ fn main() {
OutputType::Wat => { OutputType::Wat => {
let mut gen = Compiler::new(&mut output, OutputFormat::WatVerbose); let mut gen = Compiler::new(&mut output, OutputFormat::WatVerbose);
if let Err(err) = gen.write_module(&statements) { if let Err(error) = gen.write_module(&statements) {
panic!("{:?}", err); print_error(&file, source, error);
return;
} }
} }
OutputType::Wasm => { OutputType::Wasm => {

View file

@ -9,7 +9,7 @@ pub struct Parser {
pos: usize, pos: usize,
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct Context { pub struct Context {
data: HashMap<String, Type>, data: HashMap<String, Type>,
} }
@ -30,9 +30,9 @@ impl Context {
} }
// TODO: implement this more cheaply? // TODO: implement this more cheaply?
pub fn clone(&self) -> Context { // pub fn clone(&self) -> Context {
Context { data: self.data.clone() } // Context { data: self.data.clone() }
} // }
} }
impl Parser { impl Parser {
@ -96,14 +96,16 @@ impl Parser {
TokenContent::Let => { TokenContent::Let => {
self.eat(TokenContent::Let)?; self.eat(TokenContent::Let)?;
let name = self.parse_ident()?; let name = self.parse_ident()?;
if self.eat(TokenContent::Symbol(Symbol::Colon)).is_ok() { let name_span = self.tokens.get(self.pos - 1).unwrap().span;
// TODO: types in variables let ty = if self.eat(TokenContent::Symbol(Symbol::Colon)).is_ok() {
self.parse_type()?; Some((self.parse_type()?, name_span))
} } else {
None
};
self.eat(TokenContent::Symbol(Symbol::Set))?; 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))?; self.eat(TokenContent::Symbol(Symbol::Semicolon))?;
Statement::Let(name, expr) Statement::Let(crate::data::Let { name, ty, init })
} }
TokenContent::Pub | TokenContent::Fn => { TokenContent::Pub | TokenContent::Fn => {
// TODO: public things that aren't functions // TODO: public things that aren't functions
@ -138,7 +140,7 @@ impl Parser {
Type::empty() Type::empty()
}; };
self.eat(TokenContent::OpenBrace)?; self.eat(TokenContent::OpenBrace)?;
let block = self.parse_block(&ctx)?; let block = self.parse_block(ctx)?;
self.eat(TokenContent::CloseBrace)?; self.eat(TokenContent::CloseBrace)?;
Statement::Func(Func { Statement::Func(Func {
name, name,
@ -149,7 +151,7 @@ impl Parser {
}) })
} }
_ => { _ => {
let expr = self.parse_expr(&ctx, 0)?; let expr = self.parse_expr(ctx, 0)?;
if self if self
.peek_tok() .peek_tok()
.is_some_and(|tk| tk.token == TokenContent::Symbol(Symbol::Semicolon)) .is_some_and(|tk| tk.token == TokenContent::Symbol(Symbol::Semicolon))
@ -231,8 +233,8 @@ impl Parser {
statements.push(stmt); statements.push(stmt);
break; break;
} }
Statement::Let(name, expr) => { Statement::Let(l) => {
ctx.register(name, expr.ty.clone()); ctx.register(&l.name, l.infer(&ctx)?);
} }
_ => (), _ => (),
} }
@ -258,13 +260,11 @@ impl Parser {
if text.contains('.') { if text.contains('.') {
Expr { Expr {
span: tok.span, span: tok.span,
ty: Type::Float,
data: ExprData::Literal(Literal::Float(text.parse().unwrap())), data: ExprData::Literal(Literal::Float(text.parse().unwrap())),
} }
} else { } else {
Expr { Expr {
span: tok.span, span: tok.span,
ty: Type::Integer,
data: ExprData::Literal(Literal::Integer(text.parse().unwrap())), data: ExprData::Literal(Literal::Integer(text.parse().unwrap())),
} }
} }
@ -290,22 +290,18 @@ impl Parser {
} }
TokenContent::False => Expr { TokenContent::False => Expr {
span: tok.span, span: tok.span,
ty: Type::Boolean,
data: ExprData::Literal(Literal::Boolean(false)) data: ExprData::Literal(Literal::Boolean(false))
}, },
TokenContent::True => Expr { TokenContent::True => Expr {
span: tok.span, span: tok.span,
ty: Type::Boolean,
data: ExprData::Literal(Literal::Boolean(true)) data: ExprData::Literal(Literal::Boolean(true))
}, },
TokenContent::String(s) => Expr { TokenContent::String(s) => Expr {
span: tok.span, span: tok.span,
ty: Type::String,
data: ExprData::Literal(Literal::String(s.to_string())) data: ExprData::Literal(Literal::String(s.to_string()))
}, },
TokenContent::Char(ch) => Expr { TokenContent::Char(ch) => Expr {
span: tok.span, span: tok.span,
ty: Type::Char,
data: ExprData::Literal(Literal::Char(*ch)) data: ExprData::Literal(Literal::Char(*ch))
}, },
TokenContent::If => { TokenContent::If => {

View file

@ -3,16 +3,15 @@
// } // }
use crate::{ 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, parser::Context,
Error, error::{TypeError, ReferenceError}, Error, error::{TypeError, ReferenceError},
}; };
impl Expr { impl Expr {
pub fn new(data: ExprData, span: (usize, usize), ctx: &Context) -> Result<Expr, Error> { pub fn new(data: ExprData, span: (usize, usize), _ctx: &Context) -> Result<Expr, Error> {
Ok(Expr { Ok(Expr {
span, span,
ty: data.infer(ctx, span)?,
data data
}) })
} }
@ -20,6 +19,25 @@ impl Expr {
pub fn infer(&self, ctx: &Context) -> Result<Type, Error> { pub fn infer(&self, ctx: &Context) -> Result<Type, Error> {
self.data.infer(ctx, self.span) 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 { impl ExprData {
@ -27,32 +45,35 @@ impl ExprData {
match self { match self {
ExprData::Literal(lit) => lit.infer(), ExprData::Literal(lit) => lit.infer(),
ExprData::Binary(op, lhs, rhs) => { 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), Ok(ty) => Ok(ty),
Err(_) => return Err(Error::TypeError(TypeError::Binary { Err(_) => Err(Error::TypeError(TypeError::Binary {
help: "try changing some things".into(), help: "try changing some things".into(),
lhs: lhs.span.into(), lhs: lhs.span.into(),
rhs: rhs.span.into(), rhs: rhs.span.into(),
lhs_ty: lhs.ty.clone(), lhs_ty: lhty,
rhs_ty: rhs.ty.clone(), rhs_ty: rhty,
operator: op.clone(), operator: op.clone(),
operator_label: span.into(), operator_label: span.into(),
})) }))
} }
} }
ExprData::Unary(op, expr) => { ExprData::Unary(op, expr) => {
match op.infer(&expr.ty) { let ty = expr.infer(ctx)?;
match op.infer(&ty) {
Ok(ty) => Ok(ty), Ok(ty) => Ok(ty),
Err(_) => return Err(Error::TypeError(TypeError::Unary { Err(_) => Err(Error::TypeError(TypeError::Unary {
help: "try changing some things".into(), help: "try changing some things".into(),
expr: expr.span.into(), expr: expr.span.into(),
expr_ty: expr.ty.clone(), expr_ty: ty,
operator: op.clone(), operator: op.clone(),
operator_label: span.into(), operator_label: span.into(),
})) }))
} }
}, },
ExprData::Variable(name) => match ctx.lookup(&name) { ExprData::Variable(name) => match ctx.lookup(name) {
Some(ty) => Ok(ty.clone()), Some(ty) => Ok(ty.clone()),
None => Err(Error::ReferenceError(ReferenceError { None => Err(Error::ReferenceError(ReferenceError {
unknown: span.into(), unknown: span.into(),
@ -62,16 +83,17 @@ impl ExprData {
}, },
ExprData::Match(mat) => { ExprData::Match(mat) => {
let mut match_ty = None; let mut match_ty = None;
let expr_ty = mat.expr.infer(ctx)?;
for (pat, expr) in &mat.arms { for (pat, expr) in &mat.arms {
let ty = expr.infer(ctx)?; let ty = expr.infer(ctx)?;
if !pat.matches_type(&mat.expr.ty)? { if !pat.matches_type(&expr_ty)? {
let err = match mat.kind { let err = match mat.kind {
MatchKind::Match => { MatchKind::Match => {
TypeError::MatchPattern { TypeError::MatchPattern {
help: "these are different types".into(), help: "these are different types".into(),
expr_span: mat.expr.span.into(), expr_span: mat.expr.span.into(),
pattern_span: (0, 1).into(), pattern_span: (0, 1).into(),
expr_label: mat.expr.ty.clone(), expr_label: expr_ty.clone(),
pattern_label: pat.clone(), pattern_label: pat.clone(),
matcher: span.into(), matcher: span.into(),
} }
@ -80,7 +102,7 @@ impl ExprData {
TypeError::IfPattern { TypeError::IfPattern {
help: "this is not a boolean".into(), help: "this is not a boolean".into(),
expr_span: mat.expr.span.into(), expr_span: mat.expr.span.into(),
expr_label: mat.expr.ty.clone(), expr_label: expr_ty.clone(),
iffer: mat.span.into(), iffer: mat.span.into(),
} }
}, },
@ -96,7 +118,7 @@ impl ExprData {
Ok(match_ty.unwrap_or_else(Type::empty)) Ok(match_ty.unwrap_or_else(Type::empty))
} }
ExprData::Block(block) => block.infer(ctx), 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)) => { Some(Type::Function(params, ret)) => {
if args.len() < params.len() { if args.len() < params.len() {
return Err(Error::ty_old("missing parameters")); return Err(Error::ty_old("missing parameters"));
@ -116,7 +138,7 @@ impl ExprData {
Ok((**ret).clone()) Ok((**ret).clone())
} }
Some(bad) => { 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 { None => Err(Error::ReferenceError(ReferenceError {
unknown: span.into(), unknown: span.into(),
@ -187,11 +209,7 @@ impl Block {
for statement in &self.0 { for statement in &self.0 {
match statement { match statement {
Statement::TailExpr(expr) => ty = expr.infer(&ctx)?, Statement::TailExpr(expr) => ty = expr.infer(&ctx)?,
Statement::Let(name, expr) => { Statement::Let(l) => ctx.register(&l.name, l.infer(&ctx)?),
let var_ty = expr.infer(&ctx)?;
ctx.register(name, var_ty);
ty = Type::empty();
}
_ => (), _ => (),
} }
} }
@ -199,6 +217,23 @@ impl Block {
} }
} }
impl Let {
pub fn infer(&self, ctx: &Context) -> Result<Type, Error> {
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 { impl Literal {
fn infer(&self) -> Result<Type, Error> { fn infer(&self) -> Result<Type, Error> {
match self { match self {

View file

@ -1,6 +1,6 @@
fn main() -> i32 { fn main() -> i32 {
let foo = 8; let foo = 8;
let bar = foo * -3; let bar: bool = foo * -3;
match foo + bar < 10 { match foo + bar < 10 {
true => 123, true => 123,
false => 456, false => 456,

View file

@ -1,5 +1,5 @@
fn main() -> i32 { fn main() -> i32 {
two() * three() two() * three() * four()
} }
fn two() -> i32 { fn two() -> i32 {

View file

@ -1,10 +1,7 @@
let mut lexer = lexer::Lexer::new(r#" fn main() {
fn main() {
let x = "example string!\n"; let x = "example string!\n";
let y = "another str.\n"; let y = "another str.\n";
print(x); print(x);
print(y); print(y);
print("Hello, world!"); print("Hello, world!");
} }
"#.into());