cleanup code and cargo fmt
This commit is contained in:
parent
8f06f8a502
commit
2392087c4b
12 changed files with 353 additions and 193 deletions
36
print3.wat
Normal file
36
print3.wat
Normal 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
|
||||||
|
)
|
||||||
|
)
|
|
@ -17,7 +17,7 @@ use crate::Error;
|
||||||
|
|
||||||
/// pipeline to convert parsed tree -> wat/wasm
|
/// pipeline to convert parsed tree -> wat/wasm
|
||||||
pub struct Compiler<'a> {
|
pub struct Compiler<'a> {
|
||||||
output: Box<&'a mut dyn std::io::Write>,
|
output: &'a mut dyn std::io::Write,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Allocator {
|
struct Allocator {
|
||||||
|
@ -44,10 +44,8 @@ struct Allocator {
|
||||||
// "#;
|
// "#;
|
||||||
|
|
||||||
impl<'a> Compiler<'a> {
|
impl<'a> Compiler<'a> {
|
||||||
pub fn new(output: Box<&mut dyn Write>, _output_format: OutputFormat) -> Compiler {
|
pub fn new(output: &mut dyn Write, _output_format: OutputFormat) -> Compiler {
|
||||||
Compiler {
|
Compiler { output }
|
||||||
output,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_module(&mut self, stmts: &[Statement]) -> Result<(), Error> {
|
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 gctx = GenContext::new();
|
||||||
let mut alloc = Allocator::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);
|
// get_strings(&Expr::Block(crate::data::Block(stmts.to_vec())), &mut gctx);
|
||||||
// if !gctx.strings.is_empty() {
|
// if !gctx.strings.is_empty() {
|
||||||
// writeln!(self.output, "(memory 1)")?;
|
// writeln!(self.output, "(memory 1)")?;
|
||||||
|
@ -72,7 +75,7 @@ impl<'a> Compiler<'a> {
|
||||||
for stmt in stmts {
|
for stmt in stmts {
|
||||||
match stmt {
|
match stmt {
|
||||||
Statement::Func(func) => self.write_func(&gctx, &ctx, func)?,
|
Statement::Func(func) => self.write_func(&gctx, &ctx, func)?,
|
||||||
Statement::Let(..) | Statement::TailExpr(..) | Statement::Expr (..) => {
|
Statement::Let(..) | Statement::TailExpr(..) | Statement::Expr(..) => {
|
||||||
return Err(Error::OtherError("incorrect top level statement".into()))
|
return Err(Error::OtherError("incorrect top level statement".into()))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -81,7 +84,12 @@ impl<'a> Compiler<'a> {
|
||||||
Ok(())
|
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)?;
|
write!(self.output, "(func ${}", func.name)?;
|
||||||
|
|
||||||
if func.name == "main" {
|
if func.name == "main" {
|
||||||
|
@ -139,12 +147,19 @@ impl<'a> Compiler<'a> {
|
||||||
Ok(())
|
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 {
|
match expr {
|
||||||
Expr::Literal(lit) => match lit {
|
Expr::Literal(lit) => match lit {
|
||||||
Literal::Integer(int) => writeln!(self.output, "i32.const {int}")?,
|
Literal::Integer(int) => writeln!(self.output, "i32.const {int}")?,
|
||||||
Literal::Float(f) => writeln!(self.output, "f64.const {f}")?,
|
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())?,
|
// Literal::String(s) => writeln!(self.output, "i32.const {}", parent_gctx.strings.iter().find_map(|(s2, off)| (s == s2).then_some(off)).unwrap())?,
|
||||||
_ => todo!(),
|
_ => todo!(),
|
||||||
},
|
},
|
||||||
|
@ -210,8 +225,12 @@ impl<'a> Compiler<'a> {
|
||||||
match pat {
|
match pat {
|
||||||
Pattern::Literal(lit) => {
|
Pattern::Literal(lit) => {
|
||||||
match lit {
|
match lit {
|
||||||
Literal::Integer(int) => writeln!(self.output, "i32.const {}", int)?,
|
Literal::Integer(int) => {
|
||||||
Literal::Boolean(b) => writeln!(self.output, "i32.const {}", if *b { 1 } else { 0 })?,
|
writeln!(self.output, "i32.const {}", int)?
|
||||||
|
}
|
||||||
|
Literal::Boolean(b) => {
|
||||||
|
writeln!(self.output, "i32.const {}", if *b { 1 } else { 0 })?
|
||||||
|
}
|
||||||
_ => todo!(),
|
_ => todo!(),
|
||||||
}
|
}
|
||||||
writeln!(self.output, "local.get $match")?;
|
writeln!(self.output, "local.get $match")?;
|
||||||
|
@ -271,9 +290,7 @@ impl<'a> Compiler<'a> {
|
||||||
// FIXME: not sure what i was thinking
|
// FIXME: not sure what i was thinking
|
||||||
impl Allocator {
|
impl Allocator {
|
||||||
fn new() -> Allocator {
|
fn new() -> Allocator {
|
||||||
Allocator {
|
Allocator { count: 0 }
|
||||||
count: 0,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn alloc_ident(&mut self, name: &str) -> String {
|
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::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 { name, ret, params, .. }) => {
|
Statement::Func(Func {
|
||||||
ctx.funcs.insert(name.clone(), (params.clone(), ret.clone()));
|
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);
|
get_locals(b, alloc, ctx, gctx);
|
||||||
}
|
}
|
||||||
Expr::Literal(Literal::String(s)) => {
|
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));
|
gctx.strings.push((s.clone(), offset));
|
||||||
}
|
}
|
||||||
Expr::Match(pat, arms) => {
|
Expr::Match(pat, arms) => {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// TODO: TypedStatement and TypedExpression?
|
// TODO: TypedStatement and TypedExpression?
|
||||||
|
|
||||||
use crate::lexer::{TokenContent, Symbol};
|
use crate::lexer::{Symbol, TokenContent};
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -175,7 +175,7 @@ impl Type {
|
||||||
Self::Tuple(v) => {
|
Self::Tuple(v) => {
|
||||||
let s: Vec<_> = v.iter().map(Type::string).collect();
|
let s: Vec<_> = v.iter().map(Type::string).collect();
|
||||||
format!("({})", s.join(", "))
|
format!("({})", s.join(", "))
|
||||||
},
|
}
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use miette::{Diagnostic, SourceSpan, NamedSource};
|
use miette::{Diagnostic, NamedSource, SourceSpan};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Debug, Error, Diagnostic)]
|
#[derive(Debug, Error, Diagnostic)]
|
||||||
|
@ -40,7 +40,6 @@ pub struct TypeWrapper {
|
||||||
pub struct SyntaxError {
|
pub struct SyntaxError {
|
||||||
// #[source_code]
|
// #[source_code]
|
||||||
// pub src: NamedSource,
|
// pub src: NamedSource,
|
||||||
|
|
||||||
#[help]
|
#[help]
|
||||||
pub help: String,
|
pub help: String,
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ use crate::Error;
|
||||||
|
|
||||||
/// helper pipeline to convert wat/wasm -> actual text/binary
|
/// helper pipeline to convert wat/wasm -> actual text/binary
|
||||||
pub struct Generator<'a> {
|
pub struct Generator<'a> {
|
||||||
output: Box<&'a mut dyn std::io::Write>,
|
output: &'a mut dyn std::io::Write,
|
||||||
format: OutputFormat,
|
format: OutputFormat,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,11 +24,8 @@ impl OutputFormat {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Generator<'_> {
|
impl Generator<'_> {
|
||||||
pub fn new(output: Box<&mut dyn std::io::Write>, format: OutputFormat) -> Generator {
|
pub fn new(output: &mut dyn std::io::Write, format: OutputFormat) -> Generator {
|
||||||
Generator {
|
Generator { output, format }
|
||||||
output,
|
|
||||||
format,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_module(&mut self) -> Result<(), Error> {
|
pub fn write_module(&mut self) -> Result<(), Error> {
|
||||||
|
|
59
src/lexer.rs
59
src/lexer.rs
|
@ -4,8 +4,6 @@ use crate::Error;
|
||||||
pub struct Lexer {
|
pub struct Lexer {
|
||||||
input: Vec<char>,
|
input: Vec<char>,
|
||||||
pos: usize,
|
pos: usize,
|
||||||
name: String,
|
|
||||||
src: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
|
@ -51,12 +49,10 @@ pub enum Symbol {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Lexer {
|
impl Lexer {
|
||||||
pub fn new(input: &str, name: &str) -> Lexer {
|
pub fn new(input: &str) -> Lexer {
|
||||||
Lexer {
|
Lexer {
|
||||||
input: input.chars().collect(),
|
input: input.chars().collect(),
|
||||||
pos: 0,
|
pos: 0,
|
||||||
src: input.to_string(),
|
|
||||||
name: name.to_string(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +69,10 @@ impl Lexer {
|
||||||
.get(self.pos)
|
.get(self.pos)
|
||||||
.is_some_and(|c| c.is_ascii_alphanumeric())
|
.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
|
token
|
||||||
}
|
}
|
||||||
|
@ -81,7 +80,10 @@ impl Lexer {
|
||||||
self.pos += 1;
|
self.pos += 1;
|
||||||
let ch = self.lex_char()?;
|
let ch = self.lex_char()?;
|
||||||
if !self.input.get(self.pos).is_some_and(|c| *c == '\'') {
|
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;
|
self.pos += 1;
|
||||||
TokenContent::Char(ch)
|
TokenContent::Char(ch)
|
||||||
|
@ -111,7 +113,10 @@ impl Lexer {
|
||||||
}
|
}
|
||||||
_ => self.lex_op()?,
|
_ => 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> {
|
fn lex_number(&mut self) -> Result<TokenContent, Error> {
|
||||||
|
@ -144,7 +149,10 @@ impl Lexer {
|
||||||
buffer.push(ch);
|
buffer.push(ch);
|
||||||
self.pos += 1;
|
self.pos += 1;
|
||||||
} else if ch.is_ascii_digit() {
|
} 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 {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -182,10 +190,12 @@ impl Lexer {
|
||||||
let ch = match self.input.get(self.pos) {
|
let ch = match self.input.get(self.pos) {
|
||||||
Some('\\') => {
|
Some('\\') => {
|
||||||
self.pos += 1;
|
self.pos += 1;
|
||||||
let ch = self
|
let ch = self.input.get(self.pos).ok_or_else(|| {
|
||||||
.input
|
Error::syn(
|
||||||
.get(self.pos)
|
(self.pos, 1),
|
||||||
.ok_or_else(|| Error::syn((self.pos, 1), "there should be an character here (which will be escaped)"))?;
|
"there should be an character here (which will be escaped)",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
match ch {
|
match ch {
|
||||||
'n' => '\n',
|
'n' => '\n',
|
||||||
't' => '\t',
|
't' => '\t',
|
||||||
|
@ -193,11 +203,23 @@ impl Lexer {
|
||||||
'"' => '\"',
|
'"' => '\"',
|
||||||
// 'x' => '\x',
|
// 'x' => '\x',
|
||||||
// 'u' => '\u',
|
// '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,
|
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;
|
self.pos += 1;
|
||||||
Ok(ch)
|
Ok(ch)
|
||||||
|
@ -360,7 +382,12 @@ impl Lexer {
|
||||||
',' => Symbol::Comma,
|
',' => Symbol::Comma,
|
||||||
';' => Symbol::Semicolon,
|
';' => Symbol::Semicolon,
|
||||||
'?' => Symbol::Question,
|
'?' => 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;
|
self.pos += 1;
|
||||||
Ok(TokenContent::Symbol(symbol))
|
Ok(TokenContent::Symbol(symbol))
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
#![allow(dead_code, clippy::single_match, clippy::only_used_in_recursion)]
|
#![allow(dead_code, clippy::single_match, clippy::only_used_in_recursion)]
|
||||||
|
|
||||||
|
pub mod compiler;
|
||||||
pub mod data;
|
pub mod data;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod compiler;
|
|
||||||
pub mod generator;
|
pub mod generator;
|
||||||
pub mod lexer;
|
pub mod lexer;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
|
|
83
src/main.rs
83
src/main.rs
|
@ -4,10 +4,15 @@ a second time when generating (so the types are known), there should be
|
||||||
a better way
|
a better way
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::{path::PathBuf, fs::OpenOptions};
|
use std::{fs::OpenOptions, path::PathBuf};
|
||||||
|
|
||||||
use clap::Parser;
|
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;
|
use miette::NamedSource;
|
||||||
|
|
||||||
#[derive(Debug, clap::Parser)]
|
#[derive(Debug, clap::Parser)]
|
||||||
|
@ -31,7 +36,6 @@ enum Cli {
|
||||||
/// The file containing the code you want to compile
|
/// The file containing the code you want to compile
|
||||||
file: PathBuf,
|
file: PathBuf,
|
||||||
},
|
},
|
||||||
|
|
||||||
// future work:
|
// future work:
|
||||||
//
|
//
|
||||||
// /// Start a read-print-eval loop
|
// /// Start a read-print-eval loop
|
||||||
|
@ -59,7 +63,8 @@ enum Cli {
|
||||||
|
|
||||||
#[derive(Debug, Clone, clap::ValueEnum)]
|
#[derive(Debug, Clone, clap::ValueEnum)]
|
||||||
enum OutputType {
|
enum OutputType {
|
||||||
Wat, Wasm,
|
Wat,
|
||||||
|
Wasm,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
@ -93,10 +98,10 @@ fn main() {
|
||||||
};
|
};
|
||||||
let reporter = miette::GraphicalReportHandler::new().with_theme(theme);
|
let reporter = miette::GraphicalReportHandler::new().with_theme(theme);
|
||||||
|
|
||||||
match args {
|
match &args {
|
||||||
Cli::Run { file } => {
|
Cli::Run { file } | Cli::Compile { file, .. } => {
|
||||||
let source = std::fs::read_to_string(&file).expect("no source!");
|
let source = std::fs::read_to_string(file).expect("no source!");
|
||||||
let lexer = lexer::Lexer::new(&source, file.to_str().unwrap());
|
let lexer = lexer::Lexer::new(&source);
|
||||||
|
|
||||||
let tokens = match lexer.collect::<Result<Vec<_>, _>>() {
|
let tokens = match lexer.collect::<Result<Vec<_>, _>>() {
|
||||||
Ok(tokens) => tokens,
|
Ok(tokens) => tokens,
|
||||||
|
@ -105,7 +110,7 @@ fn main() {
|
||||||
lang::Error::SyntaxError(syn) => {
|
lang::Error::SyntaxError(syn) => {
|
||||||
let mut s = String::new();
|
let mut s = String::new();
|
||||||
let syn = SyntaxWrapper {
|
let syn = SyntaxWrapper {
|
||||||
src: NamedSource::new(file.to_string_lossy().to_string(), source),
|
src: NamedSource::new(file.to_string_lossy(), source),
|
||||||
syn: vec![syn],
|
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![];
|
let mut statements = vec![];
|
||||||
loop {
|
loop {
|
||||||
match parser.next() {
|
match parser.parse_statement_option() {
|
||||||
Ok(None) => break,
|
Ok(None) => break,
|
||||||
Ok(Some(tree)) => statements.push(tree),
|
Ok(Some(tree)) => statements.push(tree),
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
|
@ -129,7 +134,7 @@ fn main() {
|
||||||
lang::Error::SyntaxError(syn) => {
|
lang::Error::SyntaxError(syn) => {
|
||||||
let mut s = String::new();
|
let mut s = String::new();
|
||||||
let syn = SyntaxWrapper {
|
let syn = SyntaxWrapper {
|
||||||
src: NamedSource::new(file.to_string_lossy().to_string(), source),
|
src: NamedSource::new(file.to_string_lossy(), source),
|
||||||
syn: vec![syn],
|
syn: vec![syn],
|
||||||
};
|
};
|
||||||
reporter.render_report(&mut s, &syn).unwrap();
|
reporter.render_report(&mut s, &syn).unwrap();
|
||||||
|
@ -138,7 +143,7 @@ fn main() {
|
||||||
lang::Error::TypeError(ty) => {
|
lang::Error::TypeError(ty) => {
|
||||||
let mut s = String::new();
|
let mut s = String::new();
|
||||||
let ty = TypeWrapper {
|
let ty = TypeWrapper {
|
||||||
src: NamedSource::new(file.to_string_lossy().to_string(), source),
|
src: NamedSource::new(file.to_string_lossy(), source),
|
||||||
ty: vec![ty],
|
ty: vec![ty],
|
||||||
};
|
};
|
||||||
reporter.render_report(&mut s, &ty).unwrap();
|
reporter.render_report(&mut s, &ty).unwrap();
|
||||||
|
@ -151,8 +156,10 @@ fn main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
match args {
|
||||||
|
Cli::Run { .. } => {
|
||||||
let mut wat = vec![];
|
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) {
|
if let Err(err) = gen.write_module(&statements) {
|
||||||
panic!("{:?}", err);
|
panic!("{:?}", err);
|
||||||
|
@ -164,34 +171,16 @@ fn main() {
|
||||||
let linker = wasmtime::Linker::new(&engine);
|
let linker = wasmtime::Linker::new(&engine);
|
||||||
let mut store = wasmtime::Store::new(&engine, 4);
|
let mut store = wasmtime::Store::new(&engine, 4);
|
||||||
let instance = linker.instantiate(&mut store, &module).unwrap();
|
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());
|
dbg!(start.call(&mut store, ()).unwrap());
|
||||||
}
|
}
|
||||||
Cli::Compile { file, format, output } => {
|
Cli::Compile {
|
||||||
let source = std::fs::read_to_string(&file).expect("no source!");
|
format,
|
||||||
let lexer = lexer::Lexer::new(&source, file.to_str().unwrap());
|
output,
|
||||||
|
file,
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let format = format.unwrap_or_else(|| {
|
let format = format.unwrap_or_else(|| {
|
||||||
if file.extension().is_some_and(|ext| ext == "wasm") {
|
if file.extension().is_some_and(|ext| ext == "wasm") {
|
||||||
OutputType::Wasm
|
OutputType::Wasm
|
||||||
|
@ -201,20 +190,26 @@ fn main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut output: Box<dyn std::io::Write> = match output {
|
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()),
|
None => Box::new(std::io::stdout()),
|
||||||
};
|
};
|
||||||
|
|
||||||
match format {
|
match format {
|
||||||
OutputType::Wat => {
|
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) {
|
if let Err(err) = gen.write_module(&statements) {
|
||||||
panic!("{:?}", err);
|
panic!("{:?}", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
OutputType::Wasm => {
|
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) {
|
if let Err(err) = gen.write_module(&statements) {
|
||||||
panic!("{:?}", err);
|
panic!("{:?}", err);
|
||||||
|
@ -223,4 +218,6 @@ fn main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
112
src/parser.rs
112
src/parser.rs
|
@ -1,14 +1,12 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::data::{BinaryOp, Block, Expr, Literal, Pattern, PrefixOp, Statement, Type, Func};
|
use crate::data::{BinaryOp, Block, Expr, Func, Literal, Pattern, PrefixOp, Statement, Type};
|
||||||
use crate::lexer::{TokenContent, Symbol, Token};
|
use crate::lexer::{Symbol, Token, TokenContent};
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
pub struct Parser {
|
pub struct Parser {
|
||||||
tokens: Vec<Token>,
|
tokens: Vec<Token>,
|
||||||
pos: usize,
|
pos: usize,
|
||||||
src: String,
|
|
||||||
name: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -18,13 +16,8 @@ pub struct Context {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parser {
|
impl Parser {
|
||||||
pub fn new(tokens: Vec<Token>, src: String, name: String) -> Parser {
|
pub fn new(tokens: Vec<Token>) -> Parser {
|
||||||
Parser {
|
Parser { tokens, pos: 0 }
|
||||||
src,
|
|
||||||
name,
|
|
||||||
tokens,
|
|
||||||
pos: 0,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn err_syn(&self, pos: (usize, usize), help: &str) -> Error {
|
fn err_syn(&self, pos: (usize, usize), help: &str) -> Error {
|
||||||
|
@ -32,7 +25,10 @@ impl Parser {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn err_eof(&self, wanted: &str) -> Error {
|
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 {
|
fn err_ty_unknown(&self, pos: (usize, usize), help: &str) -> Error {
|
||||||
|
@ -65,7 +61,7 @@ impl Parser {
|
||||||
result
|
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() {
|
match self.peek_tok() {
|
||||||
Some(_) => Some(self.parse_statement()).transpose(),
|
Some(_) => Some(self.parse_statement()).transpose(),
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
|
@ -80,7 +76,7 @@ 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 let Ok(_) = self.eat(TokenContent::Symbol(Symbol::Colon)) {
|
if self.eat(TokenContent::Symbol(Symbol::Colon)).is_ok() {
|
||||||
// TODO: types in variables
|
// TODO: types in variables
|
||||||
self.parse_type()?;
|
self.parse_type()?;
|
||||||
}
|
}
|
||||||
|
@ -101,7 +97,10 @@ impl Parser {
|
||||||
let name = self.parse_ident()?;
|
let name = self.parse_ident()?;
|
||||||
self.eat(TokenContent::OpenParan)?;
|
self.eat(TokenContent::OpenParan)?;
|
||||||
let mut params = vec![];
|
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()?;
|
let name = self.parse_ident()?;
|
||||||
self.eat(TokenContent::Symbol(Symbol::Colon))?;
|
self.eat(TokenContent::Symbol(Symbol::Colon))?;
|
||||||
let ty = self.parse_type()?;
|
let ty = self.parse_type()?;
|
||||||
|
@ -121,11 +120,20 @@ impl Parser {
|
||||||
self.eat(TokenContent::OpenBrace)?;
|
self.eat(TokenContent::OpenBrace)?;
|
||||||
let block = self.parse_block()?;
|
let block = self.parse_block()?;
|
||||||
self.eat(TokenContent::CloseBrace)?;
|
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)?;
|
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))?;
|
self.eat(TokenContent::Symbol(Symbol::Semicolon))?;
|
||||||
Statement::Expr(expr)
|
Statement::Expr(expr)
|
||||||
} else {
|
} else {
|
||||||
|
@ -145,11 +153,19 @@ impl Parser {
|
||||||
"i32" => Type::Integer,
|
"i32" => Type::Integer,
|
||||||
"f64" => Type::Float,
|
"f64" => Type::Float,
|
||||||
"bool" => Type::Boolean,
|
"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 => {
|
TokenContent::OpenParan => {
|
||||||
let mut tys = vec![];
|
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()?);
|
tys.push(self.parse_type()?);
|
||||||
|
|
||||||
if self.eat(TokenContent::Symbol(Symbol::Comma)).is_err() {
|
if self.eat(TokenContent::Symbol(Symbol::Comma)).is_err() {
|
||||||
|
@ -158,7 +174,7 @@ impl Parser {
|
||||||
}
|
}
|
||||||
self.eat(TokenContent::CloseParan)?;
|
self.eat(TokenContent::CloseParan)?;
|
||||||
Type::Tuple(tys)
|
Type::Tuple(tys)
|
||||||
},
|
}
|
||||||
_ => return Err(self.err_syn(tok.span, "this should be a pattern")),
|
_ => return Err(self.err_syn(tok.span, "this should be a pattern")),
|
||||||
};
|
};
|
||||||
Ok(ty)
|
Ok(ty)
|
||||||
|
@ -166,8 +182,13 @@ impl Parser {
|
||||||
|
|
||||||
fn parse_ident(&mut self) -> Result<String, Error> {
|
fn parse_ident(&mut self) -> Result<String, Error> {
|
||||||
let result = match self.peek_tok() {
|
let result = match self.peek_tok() {
|
||||||
Some(Token { token: TokenContent::Ident(ident), .. }) => Ok(ident.to_string()),
|
Some(Token {
|
||||||
Some(t) => return Err(self.err_syn(t.span, &format!("expected ident, got {:?}", t.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")),
|
None => return Err(self.err_eof("ident")),
|
||||||
};
|
};
|
||||||
self.pos += 1;
|
self.pos += 1;
|
||||||
|
@ -177,7 +198,10 @@ impl Parser {
|
||||||
fn parse_block(&mut self) -> Result<Block, Error> {
|
fn parse_block(&mut self) -> Result<Block, Error> {
|
||||||
let mut statements = vec![];
|
let mut statements = vec![];
|
||||||
loop {
|
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;
|
break;
|
||||||
}
|
}
|
||||||
match self.parse_statement()? {
|
match self.parse_statement()? {
|
||||||
|
@ -188,7 +212,10 @@ impl Parser {
|
||||||
stmt => statements.push(stmt),
|
stmt => statements.push(stmt),
|
||||||
}
|
}
|
||||||
match self.peek_tok() {
|
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();
|
let ident = ident.clone();
|
||||||
if self.eat(TokenContent::OpenParan).is_ok() {
|
if self.eat(TokenContent::OpenParan).is_ok() {
|
||||||
let mut params = vec![];
|
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)?);
|
params.push(self.parse_expr(0)?);
|
||||||
if self.eat(TokenContent::Symbol(Symbol::Comma)).is_err() {
|
if self.eat(TokenContent::Symbol(Symbol::Comma)).is_err() {
|
||||||
break;
|
break;
|
||||||
|
@ -233,17 +263,30 @@ impl Parser {
|
||||||
self.eat(TokenContent::OpenBrace)?;
|
self.eat(TokenContent::OpenBrace)?;
|
||||||
let block = self.parse_block()?;
|
let block = self.parse_block()?;
|
||||||
self.eat(TokenContent::CloseBrace)?;
|
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();
|
self.next_tok();
|
||||||
match self.peek_tok() {
|
match self.peek_tok() {
|
||||||
Some(Token { token: TokenContent::OpenBrace, .. }) => {
|
Some(Token {
|
||||||
|
token: TokenContent::OpenBrace,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
self.eat(TokenContent::OpenBrace)?;
|
self.eat(TokenContent::OpenBrace)?;
|
||||||
let b = Some(self.parse_block()?);
|
let b = Some(self.parse_block()?);
|
||||||
self.eat(TokenContent::CloseBrace)?;
|
self.eat(TokenContent::CloseBrace)?;
|
||||||
b
|
b
|
||||||
}
|
}
|
||||||
Some(Token { token: TokenContent::If, .. }) => Some(Block(vec![Statement::TailExpr(self.parse_expr(0)?)])),
|
Some(Token {
|
||||||
Some(tk) => return Err(self.err_syn(tk.span, "this should be followed by an if or block")),
|
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")),
|
None => return Err(self.err_eof("if or block")),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -260,7 +303,10 @@ impl Parser {
|
||||||
}
|
}
|
||||||
TokenContent::Symbol(_) => {
|
TokenContent::Symbol(_) => {
|
||||||
let Some(op) = PrefixOp::from_token(&tok.token) else {
|
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)?;
|
let expr = self.parse_expr(1)?;
|
||||||
Expr::Unary(op, Box::new(expr))
|
Expr::Unary(op, Box::new(expr))
|
||||||
|
@ -279,7 +325,11 @@ impl Parser {
|
||||||
break;
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
2
test/bad
2
test/bad
|
@ -1,3 +1,3 @@
|
||||||
fn main() {
|
fn main() {
|
||||||
let a = 10 * true;
|
let a = 10 +
|
||||||
}
|
}
|
|
@ -1,14 +1,44 @@
|
||||||
use lang::{lexer::{Lexer, TokenContent, Symbol}, Error};
|
use lang::{
|
||||||
|
lexer::{Lexer, Symbol, Token, TokenContent},
|
||||||
|
Error,
|
||||||
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_foo() {
|
fn test_foo() {
|
||||||
let tokens: Result<Vec<_>, Error> = Lexer::new("1 * 5 / 3").collect();
|
let tokens: Result<Vec<_>, Error> = Lexer::new("1 * 5 / 3").collect();
|
||||||
let tokens = tokens.expect("should parse");
|
let tokens = tokens.expect("should parse");
|
||||||
assert_eq!(tokens, vec![
|
assert_eq!(
|
||||||
TokenContent::Number { radix: 10, text: "1".into() },
|
tokens,
|
||||||
TokenContent::Symbol(Symbol::Star),
|
vec![
|
||||||
TokenContent::Number { radix: 10, text: "5".into() },
|
Token {
|
||||||
TokenContent::Symbol(Symbol::Slash),
|
span: (0, 1),
|
||||||
TokenContent::Number { radix: 10, text: "3".into() },
|
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()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue