diff --git a/.gitignore b/.gitignore index 0aeca07..cdd06ef 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ /out* /TODO /vscode +/fuzz +/perf.data* diff --git a/src/main.rs b/src/main.rs index ec843ca..3ca1b16 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,8 +15,8 @@ use tokenizer::ZernError; fn compile_file(args: Args) -> Result<(), ZernError> { let source = match fs::read_to_string(&args.path) { Ok(x) => x, - Err(_) => { - eprintln!("\x1b[91mERROR\x1b[0m: failed to open {}", args.path); + Err(e) => { + eprintln!("\x1b[91mERROR\x1b[0m: failed to open {}: {e}", args.path); process::exit(1); } }; diff --git a/src/parser.rs b/src/parser.rs index c6a046d..51caad8 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -154,6 +154,7 @@ pub struct Parser { tokens: Vec, current: usize, is_inside_function: bool, + depth: usize, } impl Parser { @@ -162,6 +163,7 @@ impl Parser { tokens, current: 0, is_inside_function: false, + depth: 0, } } @@ -446,7 +448,14 @@ impl Parser { } fn expression(&mut self) -> Result { - self.or_and() + self.depth += 1; + if self.depth > 200 { + return error!(self.previous().loc, "maximum expression depth reached"); + } + + let out = self.or_and(); + self.depth -= 1; + out } fn or_and(&mut self) -> Result { diff --git a/src/symbol_table.rs b/src/symbol_table.rs index 163919d..82a4187 100644 --- a/src/symbol_table.rs +++ b/src/symbol_table.rs @@ -5,31 +5,35 @@ use crate::{ tokenizer::{ZernError, error}, }; -pub type Type = String; - pub struct StructField { pub offset: usize, - pub field_type: Type, + pub field_type: String, +} + +#[derive(Clone)] +pub enum FnParams { + Normal(Vec), + Variadic, } #[derive(Clone)] pub struct FnType { - pub return_type: Type, - pub params: Option>, + pub return_type: String, + pub params: FnParams, } impl FnType { fn new(return_type: &str, params: Vec<&str>) -> FnType { FnType { return_type: return_type.to_string(), - params: Some(params.iter().map(|x| x.to_string()).collect()), + params: FnParams::Normal(params.iter().map(|x| x.to_string()).collect()), } } fn new_variadic(return_type: &str) -> FnType { FnType { return_type: return_type.to_string(), - params: None, + params: FnParams::Variadic, } } } @@ -74,9 +78,15 @@ impl SymbolTable { return error!(name.loc, format!("tried to redefine '{}'", name.lexeme)); } let mut value = if value.lexeme.starts_with("0x") { - u64::from_str_radix(&value.lexeme[2..], 16).unwrap() + match u64::from_str_radix(&value.lexeme[2..], 16) { + Ok(v) => v, + Err(_) => return error!(value.loc, "failed to parse hex numeric constant"), + } } else { - value.lexeme.parse().unwrap() + match value.lexeme.parse() { + Ok(v) => v, + Err(_) => return error!(value.loc, "failed to parse numeric constant"), + } } as i64; if *neg { value = -value; @@ -110,7 +120,7 @@ impl SymbolTable { name.lexeme.clone(), FnType { return_type, - params: Some( + params: FnParams::Normal( params.iter().map(|x| x.var_type.lexeme.clone()).collect(), ), }, @@ -119,7 +129,7 @@ impl SymbolTable { name.lexeme.clone(), FnType { return_type, - params: None, + params: FnParams::Variadic, }, ), }; diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 2acd6af..a080dbd 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -245,6 +245,9 @@ impl Tokenizer { return error!(self.loc, "unterminated char literal"); } _ = self.match_char('\\'); // if its an escape sequence skip \ and read one more + if self.eof() { + return error!(self.loc, "unterminated char literal"); + } self.advance(); if !self.match_char('\'') { return error!(self.loc, "expected ' after char literal"); @@ -257,6 +260,12 @@ impl Tokenizer { while !self.eof() { if self.peek() == '\\' { self.advance(); + if self.eof() { + return error!( + self.loc, + format!("unterminated string, started at {}", start_loc) + ); + } } else if self.peek() == '"' { break; } else if self.peek() == '\n' { @@ -340,7 +349,10 @@ impl Tokenizer { fn scan_number(&mut self) -> Result<(), ZernError> { let mut is_float = false; - if self.match_char('x') { + if self.source[self.current - 1] == '0' && self.match_char('x') { + if !self.peek().is_ascii_hexdigit() { + return error!(self.loc, "expected a digit after '0x'"); + } while self.peek().is_ascii_hexdigit() { self.advance(); } diff --git a/src/typechecker.rs b/src/typechecker.rs index 668394d..d667b8d 100644 --- a/src/typechecker.rs +++ b/src/typechecker.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use crate::{ parser::{Expr, ExprKind, Params, Stmt}, - symbol_table::{SymbolTable, Type}, + symbol_table::{FnParams, SymbolTable}, tokenizer::{TokenType, ZernError, error}, }; @@ -36,7 +36,7 @@ macro_rules! expect_types { static BUILTIN_TYPES: [&str; 8] = ["void", "u8", "i64", "f64", "str", "bool", "ptr", "any"]; pub struct Env { - scopes: Vec>, + scopes: Vec>, } impl Env { @@ -59,7 +59,7 @@ impl Env { self.scopes.last_mut().unwrap().insert(name, var_type); } - fn get_var_type(&self, name: &str) -> Option<&Type> { + fn get_var_type(&self, name: &str) -> Option<&String> { for scope in self.scopes.iter().rev() { if let Some(var) = scope.get(name) { return Some(var); @@ -365,7 +365,7 @@ impl<'a> TypeChecker<'a> { Ok(()) } - pub fn typecheck_expr(&mut self, env: &mut Env, expr: &Expr) -> Result { + pub fn typecheck_expr(&mut self, env: &mut Env, expr: &Expr) -> Result { let expr_type = match &expr.kind { ExprKind::Binary { left, op, right } => { let left_type = self.typecheck_expr(env, left)?; @@ -464,24 +464,31 @@ impl<'a> TypeChecker<'a> { if let ExprKind::Variable(callee_name) = &callee.kind { if let Some(fn_type) = self.symbol_table.functions.get(&callee_name.lexeme) { // its a function (defined/builtin/extern) - if let Some(params) = fn_type.params.clone() { - if params.len() != args.len() { - return error!( - &paren.loc, - format!( - "expected {} arguments, got {}", - params.len(), - args.len() - ) - ); + match &fn_type.params { + FnParams::Normal(params) => { + if params.len() != args.len() { + return error!( + &paren.loc, + format!( + "expected {} arguments, got {}", + params.len(), + args.len() + ) + ); + } + for (i, arg) in args.iter().enumerate() { + expect_type!( + self.typecheck_expr(env, arg)?, + params[i], + paren.loc + ); + } } - for (i, arg) in args.iter().enumerate() { - expect_type!(self.typecheck_expr(env, arg)?, params[i], paren.loc); - } - } else { - // its a variadic function, cant check arg types - for arg in args { - self.typecheck_expr(env, arg)?; + FnParams::Variadic => { + // cant check arg types + for arg in args { + self.typecheck_expr(env, arg)?; + } } } Ok(fn_type.return_type.clone()) @@ -587,35 +594,38 @@ impl<'a> TypeChecker<'a> { } }; - if let Some(params) = &func_type.params { - if params.len() != args.len() + 1 { - return error!( - method.loc, - format!( - "expected {} arguments, got {}", - params.len() - 1, - args.len() - ) - ); + match &func_type.params { + FnParams::Normal(params) => { + if params.len() != args.len() + 1 { + return error!( + method.loc, + format!( + "expected {} arguments, got {}", + params.len() - 1, + args.len() + ) + ); + } + if params[0] != receiver_type { + return error!( + method.loc, + format!( + "first parameter of the method must be of type {}", + receiver_type + ) + ); + } + for (i, arg) in args.iter().enumerate() { + expect_type!(self.typecheck_expr(env, arg)?, params[i + 1], method.loc); + } + Ok(func_type.return_type.clone()) } - if params[0] != receiver_type { - return error!( - method.loc, - format!( - "first parameter of the method must be of type {}", - receiver_type - ) - ); + FnParams::Variadic => { + for arg in args { + self.typecheck_expr(env, arg)?; + } + Ok(func_type.return_type.clone()) } - for (i, arg) in args.iter().enumerate() { - expect_type!(self.typecheck_expr(env, arg)?, params[i + 1], method.loc); - } - Ok(func_type.return_type.clone()) - } else { - for arg in args { - self.typecheck_expr(env, arg)?; - } - Ok(func_type.return_type.clone()) } } }?;