cleanup and better types/messages
This commit is contained in:
parent
298e69611f
commit
b8e18f5257
19 changed files with 495 additions and 124 deletions
159
doc/design.md
Normal file
159
doc/design.md
Normal 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
10
doc/lang.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
# language
|
||||
|
||||
```
|
||||
let name = 1234;
|
||||
let name: i32 = 1234;
|
||||
|
||||
fn foo(param: type) -> type {
|
||||
|
||||
}
|
||||
```
|
78
example/_reflect
Normal file
78
example/_reflect
Normal 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
11
example/fizzbuzz
Normal 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
3
example/hello
Normal file
|
@ -0,0 +1,3 @@
|
|||
fn main(ctx: Context) {
|
||||
ctx.log.info("hello world");
|
||||
}
|
20
example/http
Normal file
20
example/http
Normal 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
1
example/readme.md
Normal 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
44
example/reflect
Normal 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
1
example/types
Normal file
|
@ -0,0 +1 @@
|
|||
|
BIN
print.wasm
Normal file
BIN
print.wasm
Normal file
Binary file not shown.
|
@ -63,7 +63,7 @@ impl<'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) {
|
||||
|
|
11
src/data.rs
11
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,
|
||||
}
|
||||
|
||||
|
|
14
src/error.rs
14
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)]
|
||||
|
|
94
src/main.rs
94
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: '─',
|
||||
|
@ -98,6 +96,41 @@ fn main() {
|
|||
};
|
||||
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, .. } => {
|
||||
let source = std::fs::read_to_string(file).expect("no source!");
|
||||
|
@ -106,19 +139,7 @@ fn main() {
|
|||
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),
|
||||
}
|
||||
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 => {
|
||||
|
|
|
@ -9,7 +9,7 @@ pub struct Parser {
|
|||
pos: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Context {
|
||||
data: HashMap<String, Type>,
|
||||
}
|
||||
|
@ -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 => {
|
||||
|
|
77
src/types.rs
77
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<Expr, Error> {
|
||||
pub fn new(data: ExprData, span: (usize, usize), _ctx: &Context) -> Result<Expr, Error> {
|
||||
Ok(Expr {
|
||||
span,
|
||||
ty: data.infer(ctx, span)?,
|
||||
data
|
||||
})
|
||||
}
|
||||
|
@ -20,6 +19,25 @@ impl Expr {
|
|||
pub fn infer(&self, ctx: &Context) -> Result<Type, Error> {
|
||||
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<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 {
|
||||
fn infer(&self) -> Result<Type, Error> {
|
||||
match self {
|
||||
|
|
2
test/t1
2
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,
|
||||
|
|
2
test/t2
2
test/t2
|
@ -1,5 +1,5 @@
|
|||
fn main() -> i32 {
|
||||
two() * three()
|
||||
two() * three() * four()
|
||||
}
|
||||
|
||||
fn two() -> i32 {
|
||||
|
|
|
@ -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!");
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue