cleanup code and cargo fmt

This commit is contained in:
tezlm 2023-10-04 15:10:53 -07:00
parent 8f06f8a502
commit 2392087c4b
Signed by: tezlm
GPG key ID: 649733FCD94AFBBA
12 changed files with 353 additions and 193 deletions

36
print3.wat Normal file
View file

@ -0,0 +1,36 @@
;; testing offsets and such
(module
;; import from wasi
;; fn fd_write(fd, *iovs, iovs_len, nwritten) -> bytes_written
(import "wasi_snapshot_preview1" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32)))
;; create memory (size = 1 page = 64KiB)
(memory $foobar 1)
;; export memory - it's required, but we don't use it so the size is set to 0
(export "memory" (memory 0))
;; write string to memory (offset = 8 bytes)
(data (i32.const 20) "Hello, world!\n")
(func $main (export "_start")
;; iov.iov_base - pointer to string (offset = 0 bytes)
;; the string's offset is 8 bytes in memory
(i32.store (i32.const 12) (i32.const 20))
;; iov.iov_len - length of the hello world string (offset = 4 bytes)
;; the string's length is 14 bytes
(i32.store (i32.const 16) (i32.const 24))
(i32.const 1)
(call $fd_write
;; (i32.const 1) ;; fd: stdout = 1
(i32.const 12) ;; data: pointer to memory - this is the first memory we create (index 0)
(i32.const 1) ;; data_len: there's 1 string
(i32.const 2468) ;; nwritten: i don't care about this, write it wherever
)
drop ;; drop number of bytes written
)
)

View file

@ -17,7 +17,7 @@ use crate::Error;
/// pipeline to convert parsed tree -> wat/wasm
pub struct Compiler<'a> {
output: Box<&'a mut dyn std::io::Write>,
output: &'a mut dyn std::io::Write,
}
struct Allocator {
@ -44,10 +44,8 @@ struct Allocator {
// "#;
impl<'a> Compiler<'a> {
pub fn new(output: Box<&mut dyn Write>, _output_format: OutputFormat) -> Compiler {
Compiler {
output,
}
pub fn new(output: &mut dyn Write, _output_format: OutputFormat) -> Compiler {
Compiler { output }
}
pub fn write_module(&mut self, stmts: &[Statement]) -> Result<(), Error> {
@ -60,7 +58,12 @@ impl<'a> Compiler<'a> {
let mut gctx = GenContext::new();
let mut alloc = Allocator::new();
get_locals(&Expr::Block(crate::data::Block(stmts.to_vec())), &mut alloc, &mut ctx, &mut gctx);
get_locals(
&Expr::Block(crate::data::Block(stmts.to_vec())),
&mut alloc,
&mut ctx,
&mut gctx,
);
// get_strings(&Expr::Block(crate::data::Block(stmts.to_vec())), &mut gctx);
// if !gctx.strings.is_empty() {
// writeln!(self.output, "(memory 1)")?;
@ -81,7 +84,12 @@ impl<'a> Compiler<'a> {
Ok(())
}
fn write_func(&mut self, parent_gctx: &GenContext, ctx: &Context, func: &Func) -> Result<(), Error> {
fn write_func(
&mut self,
parent_gctx: &GenContext,
ctx: &Context,
func: &Func,
) -> Result<(), Error> {
write!(self.output, "(func ${}", func.name)?;
if func.name == "main" {
@ -139,12 +147,19 @@ impl<'a> Compiler<'a> {
Ok(())
}
fn gen_expr(&mut self, expr: &Expr, parent_gctx: &GenContext, ctx: &Context) -> Result<(), Error> {
fn gen_expr(
&mut self,
expr: &Expr,
parent_gctx: &GenContext,
ctx: &Context,
) -> Result<(), Error> {
match expr {
Expr::Literal(lit) => match lit {
Literal::Integer(int) => writeln!(self.output, "i32.const {int}")?,
Literal::Float(f) => writeln!(self.output, "f64.const {f}")?,
Literal::Boolean(b) => writeln!(self.output, "i32.const {}", if *b { 1 } else { 0 })?,
Literal::Boolean(b) => {
writeln!(self.output, "i32.const {}", if *b { 1 } else { 0 })?
}
// Literal::String(s) => writeln!(self.output, "i32.const {}", parent_gctx.strings.iter().find_map(|(s2, off)| (s == s2).then_some(off)).unwrap())?,
_ => todo!(),
},
@ -210,8 +225,12 @@ impl<'a> Compiler<'a> {
match pat {
Pattern::Literal(lit) => {
match lit {
Literal::Integer(int) => writeln!(self.output, "i32.const {}", int)?,
Literal::Boolean(b) => writeln!(self.output, "i32.const {}", if *b { 1 } else { 0 })?,
Literal::Integer(int) => {
writeln!(self.output, "i32.const {}", int)?
}
Literal::Boolean(b) => {
writeln!(self.output, "i32.const {}", if *b { 1 } else { 0 })?
}
_ => todo!(),
}
writeln!(self.output, "local.get $match")?;
@ -271,9 +290,7 @@ impl<'a> Compiler<'a> {
// FIXME: not sure what i was thinking
impl Allocator {
fn new() -> Allocator {
Allocator {
count: 0,
}
Allocator { count: 0 }
}
fn alloc_ident(&mut self, name: &str) -> String {
@ -313,8 +330,11 @@ fn get_locals(expr: &Expr, alloc: &mut Allocator, ctx: &mut Context, gctx: &mut
}
Statement::TailExpr(expr) => get_locals(expr, alloc, ctx, gctx),
Statement::Expr(expr) => get_locals(expr, alloc, ctx, gctx),
Statement::Func(Func { name, ret, params, .. }) => {
ctx.funcs.insert(name.clone(), (params.clone(), ret.clone()));
Statement::Func(Func {
name, ret, params, ..
}) => {
ctx.funcs
.insert(name.clone(), (params.clone(), ret.clone()));
}
}
}
@ -325,7 +345,11 @@ fn get_locals(expr: &Expr, alloc: &mut Allocator, ctx: &mut Context, gctx: &mut
get_locals(b, alloc, ctx, gctx);
}
Expr::Literal(Literal::String(s)) => {
let offset = gctx.strings.last().map(|(s, off)| off + s.len()).unwrap_or(8);
let offset = gctx
.strings
.last()
.map(|(s, off)| off + s.len())
.unwrap_or(8);
gctx.strings.push((s.clone(), offset));
}
Expr::Match(pat, arms) => {

View file

@ -1,6 +1,6 @@
// TODO: TypedStatement and TypedExpression?
use crate::lexer::{TokenContent, Symbol};
use crate::lexer::{Symbol, TokenContent};
#[rustfmt::skip]
#[derive(Debug, Clone)]
@ -175,7 +175,7 @@ impl Type {
Self::Tuple(v) => {
let s: Vec<_> = v.iter().map(Type::string).collect();
format!("({})", s.join(", "))
},
}
_ => unimplemented!(),
}
}

View file

@ -1,4 +1,4 @@
use miette::{Diagnostic, SourceSpan, NamedSource};
use miette::{Diagnostic, NamedSource, SourceSpan};
use thiserror::Error;
#[derive(Debug, Error, Diagnostic)]
@ -40,7 +40,6 @@ pub struct TypeWrapper {
pub struct SyntaxError {
// #[source_code]
// pub src: NamedSource,
#[help]
pub help: String,

View file

@ -3,7 +3,7 @@ use crate::Error;
/// helper pipeline to convert wat/wasm -> actual text/binary
pub struct Generator<'a> {
output: Box<&'a mut dyn std::io::Write>,
output: &'a mut dyn std::io::Write,
format: OutputFormat,
}
@ -24,11 +24,8 @@ impl OutputFormat {
}
impl Generator<'_> {
pub fn new(output: Box<&mut dyn std::io::Write>, format: OutputFormat) -> Generator {
Generator {
output,
format,
}
pub fn new(output: &mut dyn std::io::Write, format: OutputFormat) -> Generator {
Generator { output, format }
}
pub fn write_module(&mut self) -> Result<(), Error> {

View file

@ -4,8 +4,6 @@ use crate::Error;
pub struct Lexer {
input: Vec<char>,
pos: usize,
name: String,
src: String,
}
#[rustfmt::skip]
@ -51,12 +49,10 @@ pub enum Symbol {
}
impl Lexer {
pub fn new(input: &str, name: &str) -> Lexer {
pub fn new(input: &str) -> Lexer {
Lexer {
input: input.chars().collect(),
pos: 0,
src: input.to_string(),
name: name.to_string(),
}
}
@ -73,7 +69,10 @@ impl Lexer {
.get(self.pos)
.is_some_and(|c| c.is_ascii_alphanumeric())
{
return Err(Error::syn((self.pos, 1), "there shouldn't be extra characters after the number"));
return Err(Error::syn(
(self.pos, 1),
"there shouldn't be extra characters after the number",
));
}
token
}
@ -81,7 +80,10 @@ impl Lexer {
self.pos += 1;
let ch = self.lex_char()?;
if !self.input.get(self.pos).is_some_and(|c| *c == '\'') {
return Err(Error::syn((start, self.pos - start), "close your character with a '"));
return Err(Error::syn(
(start, self.pos - start),
"close your character with a '",
));
}
self.pos += 1;
TokenContent::Char(ch)
@ -111,7 +113,10 @@ impl Lexer {
}
_ => self.lex_op()?,
};
Ok(Some(Token { token: tok, span: (start, self.pos - start) }))
Ok(Some(Token {
token: tok,
span: (start, self.pos - start),
}))
}
fn lex_number(&mut self) -> Result<TokenContent, Error> {
@ -144,7 +149,10 @@ impl Lexer {
buffer.push(ch);
self.pos += 1;
} else if ch.is_ascii_digit() {
return Err(Error::syn((self.pos, 1), "you seem to have used the wrong base for this number"));
return Err(Error::syn(
(self.pos, 1),
"you seem to have used the wrong base for this number",
));
} else {
break;
}
@ -182,10 +190,12 @@ impl Lexer {
let ch = match self.input.get(self.pos) {
Some('\\') => {
self.pos += 1;
let ch = self
.input
.get(self.pos)
.ok_or_else(|| Error::syn((self.pos, 1), "there should be an character here (which will be escaped)"))?;
let ch = self.input.get(self.pos).ok_or_else(|| {
Error::syn(
(self.pos, 1),
"there should be an character here (which will be escaped)",
)
})?;
match ch {
'n' => '\n',
't' => '\t',
@ -193,11 +203,23 @@ impl Lexer {
'"' => '\"',
// 'x' => '\x',
// 'u' => '\u',
_ => return Err(Error::syn((self.pos - 1, 2), &format!("i only know how to escape \\n, \\t, \\\\, or \\\", not \\{ch}"))),
_ => {
return Err(Error::syn(
(self.pos - 1, 2),
format!(
"i only know how to escape \\n, \\t, \\\\, or \\\", not \\{ch}"
),
))
}
}
}
Some(ch) => *ch,
None => return Err(Error::syn((self.pos, 1), "there should be a character here, not an eof")),
None => {
return Err(Error::syn(
(self.pos, 1),
"there should be a character here, not an eof",
))
}
};
self.pos += 1;
Ok(ch)
@ -360,7 +382,12 @@ impl Lexer {
',' => Symbol::Comma,
';' => Symbol::Semicolon,
'?' => Symbol::Question,
_ => return Err(Error::syn((self.pos, self.pos), "unexpected character here")),
_ => {
return Err(Error::syn(
(self.pos, self.pos),
"unexpected character here",
))
}
};
self.pos += 1;
Ok(TokenContent::Symbol(symbol))

View file

@ -1,8 +1,8 @@
#![allow(dead_code, clippy::single_match, clippy::only_used_in_recursion)]
pub mod compiler;
pub mod data;
pub mod error;
pub mod compiler;
pub mod generator;
pub mod lexer;
pub mod parser;

View file

@ -4,10 +4,15 @@ a second time when generating (so the types are known), there should be
a better way
*/
use std::{path::PathBuf, fs::OpenOptions};
use std::{fs::OpenOptions, path::PathBuf};
use clap::Parser;
use lang::{compiler::Compiler, generator::OutputFormat, lexer, parser, error::{SyntaxWrapper, TypeWrapper}};
use lang::{
compiler::Compiler,
error::{SyntaxWrapper, TypeWrapper},
generator::OutputFormat,
lexer, parser,
};
use miette::NamedSource;
#[derive(Debug, clap::Parser)]
@ -31,7 +36,6 @@ enum Cli {
/// The file containing the code you want to compile
file: PathBuf,
},
// future work:
//
// /// Start a read-print-eval loop
@ -59,7 +63,8 @@ enum Cli {
#[derive(Debug, Clone, clap::ValueEnum)]
enum OutputType {
Wat, Wasm,
Wat,
Wasm,
}
fn main() {
@ -93,10 +98,10 @@ fn main() {
};
let reporter = miette::GraphicalReportHandler::new().with_theme(theme);
match args {
Cli::Run { file } => {
let source = std::fs::read_to_string(&file).expect("no source!");
let lexer = lexer::Lexer::new(&source, file.to_str().unwrap());
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,
@ -105,7 +110,7 @@ fn main() {
lang::Error::SyntaxError(syn) => {
let mut s = String::new();
let syn = SyntaxWrapper {
src: NamedSource::new(file.to_string_lossy().to_string(), source),
src: NamedSource::new(file.to_string_lossy(), source),
syn: vec![syn],
};
@ -118,10 +123,10 @@ fn main() {
}
};
let mut parser = parser::Parser::new(tokens, source.clone(), file.to_string_lossy().to_string());
let mut parser = parser::Parser::new(tokens);
let mut statements = vec![];
loop {
match parser.next() {
match parser.parse_statement_option() {
Ok(None) => break,
Ok(Some(tree)) => statements.push(tree),
Err(error) => {
@ -129,7 +134,7 @@ fn main() {
lang::Error::SyntaxError(syn) => {
let mut s = String::new();
let syn = SyntaxWrapper {
src: NamedSource::new(file.to_string_lossy().to_string(), source),
src: NamedSource::new(file.to_string_lossy(), source),
syn: vec![syn],
};
reporter.render_report(&mut s, &syn).unwrap();
@ -138,7 +143,7 @@ fn main() {
lang::Error::TypeError(ty) => {
let mut s = String::new();
let ty = TypeWrapper {
src: NamedSource::new(file.to_string_lossy().to_string(), source),
src: NamedSource::new(file.to_string_lossy(), source),
ty: vec![ty],
};
reporter.render_report(&mut s, &ty).unwrap();
@ -151,8 +156,10 @@ fn main() {
}
}
match args {
Cli::Run { .. } => {
let mut wat = vec![];
let mut gen = Compiler::new(Box::new(&mut wat), OutputFormat::WatVerbose);
let mut gen = Compiler::new(&mut wat, OutputFormat::WatVerbose);
if let Err(err) = gen.write_module(&statements) {
panic!("{:?}", err);
@ -164,34 +171,16 @@ fn main() {
let linker = wasmtime::Linker::new(&engine);
let mut store = wasmtime::Store::new(&engine, 4);
let instance = linker.instantiate(&mut store, &module).unwrap();
let start = instance.get_typed_func::<(), i32>(&mut store, "_start").unwrap();
let start = instance
.get_typed_func::<(), i32>(&mut store, "_start")
.unwrap();
dbg!(start.call(&mut store, ()).unwrap());
}
Cli::Compile { file, format, output } => {
let source = std::fs::read_to_string(&file).expect("no source!");
let lexer = lexer::Lexer::new(&source, file.to_str().unwrap());
let tokens = match lexer.collect::<Result<Vec<_>, _>>() {
Ok(tokens) => tokens,
Err(error) => {
eprintln!("error: {:?}", error);
return
}
};
let mut parser = parser::Parser::new(tokens, source.clone(), file.to_string_lossy().to_string());
let mut statements = vec![];
loop {
match parser.next() {
Ok(None) => break,
Ok(Some(tree)) => statements.push(tree),
Err(error) => {
eprintln!("error: {:?}", error);
return;
}
}
}
Cli::Compile {
format,
output,
file,
} => {
let format = format.unwrap_or_else(|| {
if file.extension().is_some_and(|ext| ext == "wasm") {
OutputType::Wasm
@ -201,20 +190,26 @@ fn main() {
});
let mut output: Box<dyn std::io::Write> = match output {
Some(path) => Box::new(OpenOptions::new().create(true).write(true).open(path).expect("failed to open file")),
Some(path) => Box::new(
OpenOptions::new()
.create(true)
.write(true)
.open(path)
.expect("failed to open file"),
),
None => Box::new(std::io::stdout()),
};
match format {
OutputType::Wat => {
let mut gen = Compiler::new(Box::new(&mut output), OutputFormat::WatVerbose);
let mut gen = Compiler::new(&mut output, OutputFormat::WatVerbose);
if let Err(err) = gen.write_module(&statements) {
panic!("{:?}", err);
}
}
OutputType::Wasm => {
let mut gen = Compiler::new(Box::new(&mut output), OutputFormat::Wasm);
let mut gen = Compiler::new(&mut output, OutputFormat::Wasm);
if let Err(err) = gen.write_module(&statements) {
panic!("{:?}", err);
@ -224,3 +219,5 @@ fn main() {
}
}
}
}
}

View file

@ -1,14 +1,12 @@
use std::collections::HashMap;
use crate::data::{BinaryOp, Block, Expr, Literal, Pattern, PrefixOp, Statement, Type, Func};
use crate::lexer::{TokenContent, Symbol, Token};
use crate::data::{BinaryOp, Block, Expr, Func, Literal, Pattern, PrefixOp, Statement, Type};
use crate::lexer::{Symbol, Token, TokenContent};
use crate::Error;
pub struct Parser {
tokens: Vec<Token>,
pos: usize,
src: String,
name: String,
}
#[derive(Debug, Clone)]
@ -18,13 +16,8 @@ pub struct Context {
}
impl Parser {
pub fn new(tokens: Vec<Token>, src: String, name: String) -> Parser {
Parser {
src,
name,
tokens,
pos: 0,
}
pub fn new(tokens: Vec<Token>) -> Parser {
Parser { tokens, pos: 0 }
}
fn err_syn(&self, pos: (usize, usize), help: &str) -> Error {
@ -32,7 +25,10 @@ impl Parser {
}
fn err_eof(&self, wanted: &str) -> Error {
self.err_syn(self.tokens.last().map(|tok| tok.span).unwrap_or((0, 0)), &format!("wanted {}, got eof", wanted))
self.err_syn(
self.tokens.last().map(|tok| tok.span).unwrap_or((0, 0)),
&format!("wanted {}, got eof", wanted),
)
}
fn err_ty_unknown(&self, pos: (usize, usize), help: &str) -> Error {
@ -65,7 +61,7 @@ impl Parser {
result
}
pub fn next(&mut self) -> Result<Option<Statement>, Error> {
pub fn parse_statement_option(&mut self) -> Result<Option<Statement>, Error> {
match self.peek_tok() {
Some(_) => Some(self.parse_statement()).transpose(),
None => Ok(None),
@ -80,7 +76,7 @@ impl Parser {
TokenContent::Let => {
self.eat(TokenContent::Let)?;
let name = self.parse_ident()?;
if let Ok(_) = self.eat(TokenContent::Symbol(Symbol::Colon)) {
if self.eat(TokenContent::Symbol(Symbol::Colon)).is_ok() {
// TODO: types in variables
self.parse_type()?;
}
@ -101,7 +97,10 @@ impl Parser {
let name = self.parse_ident()?;
self.eat(TokenContent::OpenParan)?;
let mut params = vec![];
while !self.peek_tok().is_some_and(|tok| tok.token == TokenContent::CloseParan) {
while !self
.peek_tok()
.is_some_and(|tok| tok.token == TokenContent::CloseParan)
{
let name = self.parse_ident()?;
self.eat(TokenContent::Symbol(Symbol::Colon))?;
let ty = self.parse_type()?;
@ -121,11 +120,20 @@ impl Parser {
self.eat(TokenContent::OpenBrace)?;
let block = self.parse_block()?;
self.eat(TokenContent::CloseBrace)?;
Statement::Func(Func { name, params, ret, block, public })
Statement::Func(Func {
name,
params,
ret,
block,
public,
})
}
_ => {
let expr = self.parse_expr(0)?;
if self.peek_tok().is_some_and(|tk| tk.token == TokenContent::Symbol(Symbol::Semicolon)) {
if self
.peek_tok()
.is_some_and(|tk| tk.token == TokenContent::Symbol(Symbol::Semicolon))
{
self.eat(TokenContent::Symbol(Symbol::Semicolon))?;
Statement::Expr(expr)
} else {
@ -145,11 +153,19 @@ impl Parser {
"i32" => Type::Integer,
"f64" => Type::Float,
"bool" => Type::Boolean,
_ => return Err(self.err_ty_unknown(tok.span, "use a type that i know (currently only i32 and bool)")),
_ => {
return Err(self.err_ty_unknown(
tok.span,
"use a type that i know (currently only i32 and bool)",
))
}
},
TokenContent::OpenParan => {
let mut tys = vec![];
while !self.peek_tok().is_some_and(|tok| tok.token == TokenContent::CloseParan) {
while !self
.peek_tok()
.is_some_and(|tok| tok.token == TokenContent::CloseParan)
{
tys.push(self.parse_type()?);
if self.eat(TokenContent::Symbol(Symbol::Comma)).is_err() {
@ -158,7 +174,7 @@ impl Parser {
}
self.eat(TokenContent::CloseParan)?;
Type::Tuple(tys)
},
}
_ => return Err(self.err_syn(tok.span, "this should be a pattern")),
};
Ok(ty)
@ -166,8 +182,13 @@ impl Parser {
fn parse_ident(&mut self) -> Result<String, Error> {
let result = match self.peek_tok() {
Some(Token { token: TokenContent::Ident(ident), .. }) => Ok(ident.to_string()),
Some(t) => return Err(self.err_syn(t.span, &format!("expected ident, got {:?}", t.token))),
Some(Token {
token: TokenContent::Ident(ident),
..
}) => Ok(ident.to_string()),
Some(t) => {
return Err(self.err_syn(t.span, &format!("expected ident, got {:?}", t.token)))
}
None => return Err(self.err_eof("ident")),
};
self.pos += 1;
@ -177,7 +198,10 @@ impl Parser {
fn parse_block(&mut self) -> Result<Block, Error> {
let mut statements = vec![];
loop {
if self.peek_tok().is_some_and(|tok| tok.token == TokenContent::CloseBrace) {
if self
.peek_tok()
.is_some_and(|tok| tok.token == TokenContent::CloseBrace)
{
break;
}
match self.parse_statement()? {
@ -188,7 +212,10 @@ impl Parser {
stmt => statements.push(stmt),
}
match self.peek_tok() {
Some(Token { token: TokenContent::CloseBrace, .. }) => break,
Some(Token {
token: TokenContent::CloseBrace,
..
}) => break,
_ => (),
};
}
@ -212,7 +239,10 @@ impl Parser {
let ident = ident.clone();
if self.eat(TokenContent::OpenParan).is_ok() {
let mut params = vec![];
while !self.peek_tok().is_some_and(|tok| tok.token == TokenContent::CloseParan) {
while !self
.peek_tok()
.is_some_and(|tok| tok.token == TokenContent::CloseParan)
{
params.push(self.parse_expr(0)?);
if self.eat(TokenContent::Symbol(Symbol::Comma)).is_err() {
break;
@ -233,17 +263,30 @@ impl Parser {
self.eat(TokenContent::OpenBrace)?;
let block = self.parse_block()?;
self.eat(TokenContent::CloseBrace)?;
let otherwise = if self.peek_tok().is_some_and(|t| t.token == TokenContent::Else) {
let otherwise = if self
.peek_tok()
.is_some_and(|t| t.token == TokenContent::Else)
{
self.next_tok();
match self.peek_tok() {
Some(Token { token: TokenContent::OpenBrace, .. }) => {
Some(Token {
token: TokenContent::OpenBrace,
..
}) => {
self.eat(TokenContent::OpenBrace)?;
let b = Some(self.parse_block()?);
self.eat(TokenContent::CloseBrace)?;
b
}
Some(Token { token: TokenContent::If, .. }) => Some(Block(vec![Statement::TailExpr(self.parse_expr(0)?)])),
Some(tk) => return Err(self.err_syn(tk.span, "this should be followed by an if or block")),
Some(Token {
token: TokenContent::If,
..
}) => Some(Block(vec![Statement::TailExpr(self.parse_expr(0)?)])),
Some(tk) => {
return Err(
self.err_syn(tk.span, "this should be followed by an if or block")
)
}
None => return Err(self.err_eof("if or block")),
}
} else {
@ -260,7 +303,10 @@ impl Parser {
}
TokenContent::Symbol(_) => {
let Some(op) = PrefixOp::from_token(&tok.token) else {
return Err(self.err_syn(tok.span, "this should be changed into a valid operator or removed outright"));
return Err(self.err_syn(
tok.span,
"this should be changed into a valid operator or removed outright",
));
};
let expr = self.parse_expr(1)?;
Expr::Unary(op, Box::new(expr))
@ -279,7 +325,11 @@ impl Parser {
break;
}
if self.peek_tok().is_none() || self.peek_tok().is_some_and(|t| t.token == TokenContent::CloseBrace) {
if self.peek_tok().is_none()
|| self
.peek_tok()
.is_some_and(|t| t.token == TokenContent::CloseBrace)
{
break;
}
}

View file

@ -1,3 +1,3 @@
fn main() {
let a = 10 * true;
let a = 10 +
}

View file

@ -1,14 +1,44 @@
use lang::{lexer::{Lexer, TokenContent, Symbol}, Error};
use lang::{
lexer::{Lexer, Symbol, Token, TokenContent},
Error,
};
#[test]
fn test_foo() {
let tokens: Result<Vec<_>, Error> = Lexer::new("1 * 5 / 3").collect();
let tokens = tokens.expect("should parse");
assert_eq!(tokens, vec![
TokenContent::Number { radix: 10, text: "1".into() },
TokenContent::Symbol(Symbol::Star),
TokenContent::Number { radix: 10, text: "5".into() },
TokenContent::Symbol(Symbol::Slash),
TokenContent::Number { radix: 10, text: "3".into() },
])
assert_eq!(
tokens,
vec![
Token {
span: (0, 1),
token: TokenContent::Number {
radix: 10,
text: "1".into()
},
},
Token {
span: (1, 1),
token: TokenContent::Symbol(Symbol::Star),
},
Token {
span: (2, 1),
token: TokenContent::Number {
radix: 10,
text: "5".into()
},
},
Token {
span: (3, 1),
token: TokenContent::Symbol(Symbol::Slash),
},
Token {
span: (4, 1),
token: TokenContent::Number {
radix: 10,
text: "3".into()
},
},
]
)
}