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 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) {
|
||||||
|
|
11
src/data.rs
11
src/data.rs
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
14
src/error.rs
14
src/error.rs
|
@ -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)]
|
||||||
|
|
74
src/main.rs
74
src/main.rs
|
@ -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 => {
|
||||||
|
|
|
@ -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 => {
|
||||||
|
|
77
src/types.rs
77
src/types.rs
|
@ -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 {
|
||||||
|
|
2
test/t1
2
test/t1
|
@ -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,
|
||||||
|
|
2
test/t2
2
test/t2
|
@ -1,5 +1,5 @@
|
||||||
fn main() -> i32 {
|
fn main() -> i32 {
|
||||||
two() * three()
|
two() * three() * four()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn two() -> i32 {
|
fn two() -> i32 {
|
||||||
|
|
|
@ -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());
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue