Commit Diff


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<String> = env::args().collect();
 
     if args.len() < 3 {
         eprintln!("usage: ol <command> <file>");
-        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<String> = 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)
+    }
 }