commit faf55781ee6fb6fdcf94f474767adb31a8b78081 from: Murilo Ijanc date: Sat Mar 28 14:15:00 2026 UTC Add type checking and contextual error messages Compiler infers types and validates type annotations at compile time (e.g. let x: int = "hello" is an error). Span gains line_col() for mapping byte offsets to line:column. Errors now render with source context showing the exact location and underline. commit - 11a12b204a216d8d8e25fdac1103c8beef3027e3 commit + faf55781ee6fb6fdcf94f474767adb31a8b78081 blob - aa69daf3d651955eac5e92fe1316dc2a7d997967 blob + a9c854949dc2e7fee21727040a19261ab0f262a7 --- src/error.rs +++ src/error.rs @@ -35,6 +35,42 @@ impl OlangError { span, } } + + pub fn render( + &self, + source: &str, + filename: &str, + ) -> String { + let (line, col) = + self.span.line_col(source); + let line_text = + source.lines().nth(line - 1).unwrap_or(""); + let underline_len = if self.span.end + > self.span.start + { + self.span.end - self.span.start + } else { + 1 + }; + + let line_num = format!("{}", line); + let padding = " ".repeat(line_num.len()); + + format!( + "error: {}\n \ + --> {}:{}:{}\n\ + {padding} |\n\ + {line_num} | {line_text}\n\ + {padding} | {}{} {}", + self.message, + filename, + line, + col, + " ".repeat(col - 1), + "^".repeat(underline_len), + self.message, + ) + } } impl fmt::Display for OlangError { blob - fa4dce2b2edbef541e42a742ef2aeeaff7999c20 blob + f05d168da63de62d6a140f7a61955207b6c1340c --- src/main.rs +++ src/main.rs @@ -26,12 +26,16 @@ use std::env; use std::fs; use std::process; +use error::OlangError; + fn main() { let args: Vec = env::args().collect(); if args.len() < 3 { eprintln!("usage: ol "); - eprintln!("commands: run, tokenize, parse, compile"); + eprintln!( + "commands: run, tokenize, parse, compile" + ); process::exit(1); } @@ -41,16 +45,18 @@ fn main() { let source = match fs::read_to_string(filename) { Ok(s) => s, Err(e) => { - eprintln!("error reading '{filename}': {e}"); + eprintln!( + "error reading '{filename}': {e}" + ); process::exit(1); } }; match command.as_str() { - "run" => cmd_run(&source), - "tokenize" => cmd_tokenize(&source), - "parse" => cmd_parse(&source), - "compile" => cmd_compile(&source), + "run" => cmd_run(&source, filename), + "tokenize" => cmd_tokenize(&source, filename), + "parse" => cmd_parse(&source, filename), + "compile" => cmd_compile(&source, filename), _ => { eprintln!("unknown command: {command}"); process::exit(1); @@ -58,62 +64,86 @@ fn main() { } } -fn cmd_run(source: &str) { +fn die( + e: &OlangError, + source: &str, + filename: &str, +) -> ! { + eprintln!("{}", e.render(source, filename)); + process::exit(1); +} + +fn cmd_run(source: &str, filename: &str) { let mut lexer = lexer::Lexer::new(source); let tokens = match lexer.tokenize() { Ok(t) => t, - Err(e) => { eprintln!("{e}"); process::exit(1); } + Err(e) => die(&e, source, filename), }; + let mut parser = parser::Parser::new(tokens); let program = match parser.parse_program() { Ok(p) => p, - Err(e) => { eprintln!("{e}"); process::exit(1); } + Err(e) => die(&e, source, filename), }; + let comp = compiler::Compiler::new(); - let chunk = match comp.compile(&program) { - Ok(c) => c, - Err(e) => { eprintln!("{e}"); process::exit(1); } + let result = match comp.compile(&program) { + Ok(r) => r, + Err(e) => die(&e, source, filename), }; - let mut machine = vm::VM::new(chunk); + + let mut machine = vm::VM::new( + result.functions, + result.main_index, + ); if let Err(e) = machine.run() { eprintln!("runtime error: {e}"); process::exit(1); } } -fn cmd_compile(source: &str) { +fn cmd_compile(source: &str, filename: &str) { let mut lexer = lexer::Lexer::new(source); let tokens = match lexer.tokenize() { Ok(t) => t, - Err(e) => { eprintln!("{e}"); process::exit(1); } + Err(e) => die(&e, source, filename), }; + let mut parser = parser::Parser::new(tokens); let program = match parser.parse_program() { Ok(p) => p, - Err(e) => { eprintln!("{e}"); process::exit(1); } + Err(e) => die(&e, source, filename), }; + let comp = compiler::Compiler::new(); match comp.compile(&program) { - Ok(chunk) => chunk.disassemble("main"), - Err(e) => { eprintln!("{e}"); process::exit(1); } + Ok(result) => { + for chunk in &result.functions { + chunk.disassemble(&chunk.name); + println!(); + } + } + Err(e) => die(&e, source, filename), } } -fn cmd_parse(source: &str) { +fn cmd_parse(source: &str, filename: &str) { let mut lexer = lexer::Lexer::new(source); let tokens = match lexer.tokenize() { Ok(t) => t, - Err(e) => { eprintln!("{e}"); process::exit(1); } + Err(e) => die(&e, source, filename), }; + let mut parser = parser::Parser::new(tokens); match parser.parse_program() { Ok(program) => println!("{:#?}", program), - Err(e) => { eprintln!("{e}"); process::exit(1); } + Err(e) => die(&e, source, filename), } } -fn cmd_tokenize(source: &str) { +fn cmd_tokenize(source: &str, filename: &str) { let mut lexer = lexer::Lexer::new(source); + match lexer.tokenize() { Ok(tokens) => { let parts: Vec = tokens @@ -122,6 +152,6 @@ fn cmd_tokenize(source: &str) { .collect(); println!("{}", parts.join(" ")); } - Err(e) => { eprintln!("{e}"); process::exit(1); } + Err(e) => die(&e, source, filename), } } blob - 5a454a528486b4073ba49646dfbb0384bb8001f4 blob + f22795feb03848eb60c5260becd0e6239170139f --- src/span.rs +++ src/span.rs @@ -25,4 +25,24 @@ impl Span { pub fn new(start: usize, end: usize) -> Self { Self { start, end } } + + pub fn line_col( + &self, + source: &str, + ) -> (usize, usize) { + let mut line = 1; + let mut col = 1; + for (i, ch) in source.char_indices() { + if i >= self.start { + break; + } + if ch == '\n' { + line += 1; + col = 1; + } else { + col += 1; + } + } + (line, col) + } }