diff --git a/Cargo.lock b/Cargo.lock index 85a9fa4..d7cc1f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -124,9 +124,6 @@ name = "bumpalo" version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" -dependencies = [ - "serde", -] [[package]] name = "byteorder" @@ -200,7 +197,6 @@ version = "0.1.0" dependencies = [ "anyhow", "axum", - "bumpalo", "chrono", "dotenv", "log", diff --git a/Cargo.toml b/Cargo.toml index 7aca3cb..e570c6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,5 +20,4 @@ tracing-subscriber = "0.3.20" anyhow = "1.0" tower-livereload = "0.9.6" log = "0.4.28" -bumpalo = { version = "3.19", features = ["collections", "boxed", "serde"] } walkdir = "2.5.0" diff --git a/README.md b/README.md index f603844..36b9fab 100644 --- a/README.md +++ b/README.md @@ -100,9 +100,9 @@ And I cherry picked things I like, mostly from rust and python. ``` let a = 42 ``` -* declares a variable of type i64 +* declares a variable of type i64 (signed 64 bit integer) -or explictly as u32 +or explictly as u32 (unsigned 32 bit integer) ``` let a:u32 = 42 ``` diff --git a/src/ast_compiler.rs b/src/ast_compiler.rs index b277030..eebb613 100644 --- a/src/ast_compiler.rs +++ b/src/ast_compiler.rs @@ -1,11 +1,7 @@ -use crate::tokens::TokenType::{ - Bang, Bool, Char, Colon, Date, Eol, Equal, F32, F64, False, FloatingPoint, Fn, Greater, - GreaterEqual, GreaterGreater, I32, I64, Identifier, If, Indent, Integer, LeftBracket, - LeftParen, Less, LessEqual, LessLess, Let, ListType, MapType, Minus, Object, Plus, Print, - RightBracket, RightParen, SingleRightArrow, Slash, Star, StringType, True, U32, U64, -}; +use crate::tokens::TokenType::{Bang, Bool, Colon, Date, Eol, Equal, F32, F64, False, FloatingPoint, Fn, Greater, GreaterEqual, GreaterGreater, I32, I64, Identifier, If, Indent, Integer, LeftBracket, LeftParen, Less, LessEqual, LessLess, Let, ListType, MapType, Minus, Object, Plus, Print, RightBracket, RightParen, SignedInteger, SingleRightArrow, Slash, Star, StringType, True, U32, U64, UnsignedInteger, Char}; use crate::tokens::{Token, TokenType}; use crate::value::Value; +use anyhow::anyhow; use log::debug; use std::collections::HashMap; @@ -15,7 +11,7 @@ pub fn compile(tokens: Vec) -> anyhow::Result> { } #[derive(Debug, Clone)] -pub(crate) struct Function { +pub struct Function { pub(crate) name: Token, pub(crate) parameters: Vec, pub(crate) return_type: TokenType, @@ -53,7 +49,7 @@ impl AstCompiler { self.compile(expected_indent) } - fn compile(&mut self, expected_indent: usize) -> anyhow::Result>{ + fn compile(&mut self, expected_indent: usize) -> anyhow::Result> { if !self.had_error { let mut statements = vec![]; while !self.is_at_end() { @@ -201,11 +197,13 @@ impl AstCompiler { let var_type = match calculate_type(declared_type, inferred_type) { Ok(var_type) => var_type, Err(e) => { - println!("error at line {}", name_token.line); self.had_error = true; - return Err(e); + return Err(anyhow!("error at line {}: {}", name_token.line, e)); } }; + // match var_type{ + // U32 => U32() + // } self.vars.push(Expression::Variable { name: name_token.lexeme.to_string(), var_type, @@ -246,7 +244,7 @@ impl AstCompiler { } fn or(&mut self) -> anyhow::Result { - let mut expr = self.and()?; + let expr = self.and()?; self.binary(vec![TokenType::LogicalOr], expr) } @@ -281,7 +279,7 @@ impl AstCompiler { } fn bitshift(&mut self) -> anyhow::Result { - let mut expr = self.term()?; + let expr = self.term()?; self.binary(vec![GreaterGreater, LessLess], expr) } @@ -361,6 +359,12 @@ impl AstCompiler { literaltype: StringType, value: Value::String(self.previous().lexeme.to_string()), } + } else if self.match_token(vec![Char]) { + Expression::Literal { + line: self.peek().line, + literaltype: Char, + value: Value::Char(self.previous().lexeme.chars().next().unwrap()), + } } else if self.match_token(vec![LeftParen]) { let expr = self.expression()?; self.consume(RightParen, "Expect ')' after expression.")?; @@ -418,7 +422,6 @@ impl AstCompiler { } fn function_call(&mut self, name: String) -> anyhow::Result { - println!("function call {}", name); let function_name = self.functions.get(&name).unwrap().name.lexeme.clone(); let function = self.functions.get(&function_name).unwrap().clone(); @@ -509,20 +512,20 @@ fn calculate_type( declared_type: Option, inferred_type: TokenType, ) -> anyhow::Result { - println!( - "declared type {:?} inferred type: {:?}", - declared_type, inferred_type - ); Ok(if let Some(declared_type) = declared_type { if declared_type != inferred_type { match (declared_type, inferred_type) { - (I32, I64) => I32, + (I32, I64) => I32, //need this? + (I32, Integer) => I32, (U32, U64) => U32, + (U32, Integer) => U32, (F32, F64) => F32, + (F32, FloatingPoint) => F32, (F64, I64) => F64, + (F64, FloatingPoint) => F64, (U64, I64) => U64, (U64, I32) => U64, - (StringType, _) => StringType, // meh, this all needs rigorous testing + (StringType, _) => StringType, // meh, this all needs rigorous testing. Update: this is in progress _ => { return Err(anyhow::anyhow!( "Incompatible types. Expected {}, found {}", @@ -697,7 +700,16 @@ impl Expression { Self::Grouping { expression, .. } => expression.infer_type(), Self::Literal { literaltype, .. } => literaltype.clone(), Self::List { literaltype, .. } => literaltype.clone(), - Self::Unary { right, .. } => right.infer_type(), + Self::Unary { + right, operator, .. + } => { + let literal_type = right.infer_type(); + if literal_type == Integer && operator.token_type == Minus { + SignedInteger + } else { + UnsignedInteger + } + } Self::Variable { var_type, .. } => var_type.clone(), Self::FunctionCall { return_type, .. } => return_type.clone(), } diff --git a/src/bytecode_compiler.rs b/src/bytecode_compiler.rs index 104751b..065b29e 100644 --- a/src/bytecode_compiler.rs +++ b/src/bytecode_compiler.rs @@ -9,7 +9,6 @@ use crate::vm::{ OP_GREATER_EQUAL, OP_LESS, OP_LESS_EQUAL, OP_MULTIPLY, OP_NEGATE, OP_NOT, OP_OR, OP_PRINT, OP_RETURN, OP_SHL, OP_SHR, OP_SUBTRACT, }; -use anyhow::anyhow; use std::collections::HashMap; pub fn compile( @@ -47,7 +46,7 @@ pub(crate) fn compile_name( struct Compiler { chunk: Chunk, - had_error: bool, + _had_error: bool, current_line: usize, vars: HashMap, } @@ -56,7 +55,7 @@ impl Compiler { fn new(name: &str) -> Self { Self { chunk: Chunk::new(name), - had_error: false, + _had_error: false, current_line: 0, vars: HashMap::new(), } @@ -142,7 +141,6 @@ impl Compiler { } else { name.clone() }; - println!("call {}", name); let name_index = self .chunk .find_constant(&name) diff --git a/src/compiler_tests.rs b/src/compiler_tests.rs new file mode 100644 index 0000000..e5d660a --- /dev/null +++ b/src/compiler_tests.rs @@ -0,0 +1,94 @@ +#[cfg(test)] +mod tests { + use crate::compile; + use crate::scanner::scan; + + #[test] + fn literal_int() { + assert!(compile("1").is_ok()); + } + + #[test] + fn literal_float() { + assert!(compile("2.1").is_ok()); + } + + #[test] + fn literal_float_scientific() { + assert!(compile("2.1e5").is_ok()); + } + + #[test] + fn literal_string() { + assert!(compile(r#""a""#).is_ok()); + } + + #[test] + fn literal_list() { + assert!(compile(r#"["abc","def"]"#).is_ok()); + } + + #[test] + fn let_infer_type() { + assert!(compile(r#"let a=1"#).is_ok()); + } + + #[test] + fn let_u32() { + assert!(compile(r#"let a:u32=1"#).is_ok()); + } + + #[test] + fn let_char() { + assert!(scan(r#"let a:char='a'"#).is_ok()); + } + + #[test] + fn let_u32_invalid_value_negative() { + let r = compile("let a:u32=-1"); + assert!(r.is_err()); + if let Err(e) = &r { + assert_eq!( + e.to_string(), + "error at line 1: Incompatible types. Expected u32, found i32/64" + ); + } + } + + #[test] + fn let_u64_invalid_value_negative() { + let r = compile("let a:u64=-1"); + assert!(r.is_err()); + if let Err(e) = &r { + assert_eq!( + e.to_string(), + "error at line 1: Incompatible types. Expected u64, found i32/64" + ); + } + } + + #[test] + fn let_u64_invalid_value_string() { + let r = compile(r#"let a:u64="not ok""#); + assert!(r.is_err()); + if let Err(e) = &r { + assert_eq!( + e.to_string(), + "error at line 1: Incompatible types. Expected u64, found string" + ); + } + } + + #[test] + fn call_fn_with_args_returns_value() { + assert!( + compile( + r#" +fn hello(name: string) -> string: + "Hello " + name +hello("world")"# + ) + .is_ok() + ); + } +} diff --git a/src/lib.rs b/src/lib.rs index fd2c69b..dafe60b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,20 @@ -pub mod scanner; +use std::collections::HashMap; +use crate::scanner::scan; + pub mod ast_compiler; pub mod bytecode_compiler; -pub mod vm; pub mod chunk; mod keywords; +pub mod scanner; +mod compiler_tests; mod tokens; mod value; +pub mod vm; + +pub fn compile(src: &str) -> anyhow::Result { + let tokens = scan(src)?; + let mut registry = HashMap::new(); + let ast= ast_compiler::compile(tokens)?; + let bytecode = bytecode_compiler::compile("", &ast, &mut registry)?; + Ok(bytecode) +} diff --git a/src/main.rs b/src/main.rs index d57e27c..64a6aba 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,7 +24,7 @@ async fn main() -> anyhow::Result<()> { if path.is_file() && path.ends_with("web.crud") { print!("compiling {:?}: ", path); let source = fs::read_to_string(path)?; - let tokens = scan(&source); + let tokens = scan(&source)?; match ast_compiler::compile(tokens) { Ok(statements) => { let path = path @@ -53,7 +53,10 @@ async fn main() -> anyhow::Result<()> { registry: registry.clone(), }); println!("adding {}", path); - app = app.route(&format!("/{}",path.replace("/web", "")), get(handle_get).with_state(state.clone())); + app = app.route( + &format!("/{}", path.replace("/web", "")), + get(handle_get).with_state(state.clone()), + ); } let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?; println!("listening on {}", listener.local_addr()?); @@ -77,33 +80,3 @@ async fn handle_get(State(state): State>) -> Result, )) } -// - -#[cfg(test)] -mod tests { - use super::*; - - #[tokio::test] - async fn test_compile() -> anyhow::Result<()> { - let tokens = scan( - r#" -fn hello(name: string) -> string: - "Hello "+name -hello("sander")"#, - ); - let mut registry = HashMap::new(); - match ast_compiler::compile(tokens) { - Ok(statements) => { - println!("{:?}", statements); - let chunk = compile("", &statements, &mut registry)?; - chunk.disassemble(); - // println!("{}", interpret(&chunk).await?); - } - Err(e) => { - println!("{}", e) - } - } - println!("{:?}", registry); - Ok(()) - } -} diff --git a/src/scanner.rs b/src/scanner.rs index fcbbe05..bcb1c22 100644 --- a/src/scanner.rs +++ b/src/scanner.rs @@ -1,3 +1,4 @@ +use anyhow::anyhow; use crate::tokens::TokenType::{BitXor, FloatingPoint, Integer}; use crate::{ keywords, @@ -7,7 +8,7 @@ use crate::{ }, }; -pub fn scan(source: &str) -> Vec { +pub fn scan(source: &str) -> anyhow::Result> { let scanner = Scanner { chars: source.chars().collect(), current: 0, @@ -20,17 +21,17 @@ pub fn scan(source: &str) -> Vec { } impl Scanner { - fn scan(mut self) -> Vec { + fn scan(mut self) -> anyhow::Result> { while !self.is_at_end() { self.start = self.current; - self.scan_token(); + self.scan_token()?; } self.add_token(TokenType::Eol); self.add_token(TokenType::Eof); - self.tokens + Ok(self.tokens) } - fn scan_token(&mut self) { + fn scan_token(&mut self) -> anyhow::Result<()>{ let c = self.advance(); if self.new_line && (c == ' ' || c == '\t') { self.add_token(TokenType::Indent); @@ -106,7 +107,8 @@ impl Scanner { self.add_token(TokenType::Slash); } } - '"' => self.string(), + '\'' => self.char()?, + '"' => self.string()?, '\r' | '\t' | ' ' => {} '\n' => { self.line += 1; @@ -136,11 +138,12 @@ impl Scanner { } else if is_alpha(c) { self.identifier(); } else { - println!("Unexpected identifier at line {}", self.line); + return Err(anyhow!("Unexpected identifier at line {}", self.line)); } } } } + Ok(()) } fn identifier(&mut self) { @@ -154,7 +157,7 @@ impl Scanner { } fn number(&mut self) { - while is_digit(self.peek()) { + while is_digit(self.peek() ) { self.advance(); } let mut has_dot = false; @@ -163,14 +166,35 @@ impl Scanner { self.advance(); } - while is_digit(self.peek()) { + while is_digit_or_scientific(self.peek()) { self.advance(); } let value: String = self.chars[self.start..self.current].iter().collect(); self.add_token_with_value(if has_dot { FloatingPoint } else { Integer }, value); } - fn string(&mut self) { + fn char(&mut self) -> anyhow::Result<()>{ + while self.peek() != '\'' && !self.is_at_end() { + self.advance(); + } + + if self.is_at_end() { + return Err(anyhow!("Unterminated char at {}", self.line)) + } + + self.advance(); + + let value: String = self.chars[self.start + 1..self.current - 1] + .iter() + .collect(); + if value.len() != 1 { + return Err(anyhow!("Illegal char length for {} at line {}", value, self.line)) + } + self.add_token_with_value(TokenType::Char, value); + Ok(()) + } + + fn string(&mut self) -> anyhow::Result<()>{ while self.peek() != '"' && !self.is_at_end() { if self.peek() == '\n' { self.line += 1; @@ -179,7 +203,7 @@ impl Scanner { } if self.is_at_end() { - println!("Unterminated string at {}", self.line) + return Err(anyhow!("Unterminated string at {}", self.line)) } self.advance(); @@ -188,6 +212,7 @@ impl Scanner { .iter() .collect(); self.add_token_with_value(TokenType::StringType, value); + Ok(()) } fn peek(&self) -> char { @@ -244,6 +269,10 @@ fn is_digit(c: char) -> bool { c >= '0' && c <= '9' } +fn is_digit_or_scientific(c: char) -> bool { + is_digit(c) || c=='e' || c=='E' +} + fn is_alphanumeric(c: char) -> bool { is_alpha(c) || is_digit(c) } diff --git a/src/tokens.rs b/src/tokens.rs index a484d71..a7a04af 100644 --- a/src/tokens.rs +++ b/src/tokens.rs @@ -17,11 +17,6 @@ impl Token { } } -#[derive(Debug)] -enum Value { - None, -} - #[derive(Debug, PartialEq, Clone, Copy, Hash)] pub enum TokenType { Bang, @@ -55,7 +50,9 @@ pub enum TokenType { Identifier, If, Indent, - Integer, //undetermined integer type + Integer, + SignedInteger, + UnsignedInteger, LeftBrace, LeftBracket, LeftParen, @@ -69,7 +66,7 @@ pub enum TokenType { LogicalOr, Minus, Not, - FloatingPoint, //undetermined float type + FloatingPoint, Object, Plus, Print, @@ -157,6 +154,8 @@ impl fmt::Display for TokenType { TokenType::True => write!(f, "true"), TokenType::Void => write!(f, "()"), TokenType::While => write!(f, "while"), + TokenType::SignedInteger => write!(f, "i32/64"), + TokenType::UnsignedInteger => write!(f, "u32/64"), } } } diff --git a/src/value.rs b/src/value.rs index 9c35942..fd3f3a7 100644 --- a/src/value.rs +++ b/src/value.rs @@ -143,7 +143,7 @@ impl Display for Value { &Value::Enum => write!(f, "enum"), &Value::Struct(v) => write!(f, "{}", v), &Value::List(v) => write!(f, "{:?}", v), - &Value::Map(v) => write!(f, "map"), + &Value::Map(_) => write!(f, "map"), &Value::Error(v) => write!(f, "{}", v), &Value::Void => write!(f, "()"), } @@ -295,7 +295,7 @@ impl Not for &Value { type Output = anyhow::Result; fn not(self) -> Self::Output { - match (self) { + match self { Value::Bool(b) => Ok(Value::Bool(!b)), Value::I32(i32) => Ok(Value::I32(!i32)), Value::I64(i64) => Ok(Value::I64(!i64)), diff --git a/src/vm.rs b/src/vm.rs index da86ec2..1729169 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1,7 +1,6 @@ use crate::chunk::Chunk; use crate::value::Value; use anyhow::anyhow; -use bumpalo::Bump; use std::collections::HashMap; use tracing::debug; @@ -175,10 +174,6 @@ impl <'a> Vm<'a> { chunk.constants[index].to_string() //string?? } - fn reset_stack(&mut self) { - self.stack.clear(); - } - fn push(&mut self, value: Value) { self.stack.push(value); }