diff --git a/examples/test.kab b/examples/test.kab index a44821d..4692ab2 100644 --- a/examples/test.kab +++ b/examples/test.kab @@ -1,3 +1,3 @@ #!/usr/bin/env kabel -declare p = 5; +declare p = 51241Q12; debug: print using (3 + 5) * 2; diff --git a/examples/try.kab b/examples/try.kab index 761d1ef..b208a54 100644 --- a/examples/try.kab +++ b/examples/try.kab @@ -1,3 +1,111 @@ "string lit"; ("string lit");# comment 12345*(60+80); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"aaaaaa \ No newline at end of file diff --git a/kabel/src/main.rs b/kabel/src/main.rs index f1fe6d6..150b40e 100644 --- a/kabel/src/main.rs +++ b/kabel/src/main.rs @@ -41,7 +41,8 @@ fn main() -> Result<(), Box> { eprintln!("ICE! Please report this to the kabel developers, along with what you were doing: {:?}", e); } KError::UserError(diags) => { - eprintln!("{}", libkabel::diagnostics::emitters::basic::BasicEmitter::emit(diags, text_source)); + //eprintln!("{}", libkabel::diagnostics::emitters::basic::BasicEmitter::emit(diags, text_source)); + eprintln!("{}", libkabel::diagnostics::emitters::pretty::PrettyEmitter::emit(diags, text_source, argv[1].clone())); } } std::process::exit(1); diff --git a/libkabel/src/diagnostics/emitters/basic.rs b/libkabel/src/diagnostics/emitters/basic.rs index 50c4e60..73dddd2 100644 --- a/libkabel/src/diagnostics/emitters/basic.rs +++ b/libkabel/src/diagnostics/emitters/basic.rs @@ -7,7 +7,7 @@ pub struct BasicEmitter; impl Emitter for BasicEmitter { type Output = String; - fn emit(diag: Vec, _source: String) -> Self::Output { + fn emit(diag: Vec, _source: String, _source_name: String) -> Self::Output { let mut output = String::new(); for msg in diag { diff --git a/libkabel/src/diagnostics/emitters/mod.rs b/libkabel/src/diagnostics/emitters/mod.rs index 0643195..a2d056e 100644 --- a/libkabel/src/diagnostics/emitters/mod.rs +++ b/libkabel/src/diagnostics/emitters/mod.rs @@ -3,8 +3,9 @@ use crate::diagnostics::Diagnostic; pub mod basic; #[cfg(feature = "pretty-emitter")] pub mod pretty; +pub(crate) mod util; pub trait Emitter { type Output; - fn emit(diag: Vec, source: String) -> Self::Output; + fn emit(diag: Vec, source: String, source_name: String) -> Self::Output; } \ No newline at end of file diff --git a/libkabel/src/diagnostics/emitters/pretty.rs b/libkabel/src/diagnostics/emitters/pretty.rs index e69de29..14f63d5 100644 --- a/libkabel/src/diagnostics/emitters/pretty.rs +++ b/libkabel/src/diagnostics/emitters/pretty.rs @@ -0,0 +1,110 @@ +use crate::diagnostics::{Diagnostic, DiagnosticType}; +use crate::diagnostics::emitters::Emitter; +use std::fmt::Write; +use colored::{ColoredString, Colorize}; +use crate::diagnostics::emitters::util::{get_line, pos_to_line_col}; + +pub struct PrettyEmitter; + +impl Emitter for PrettyEmitter { + type Output = String; + + fn emit(diag: Vec, source: String, source_name: String) -> Self::Output { + let mut output = String::new(); + + for msg in diag { + + match msg.diag_type { + DiagnosticType::Error => { + write!(output, "{}", "error".bold().red()).unwrap(); + } + DiagnosticType::Warning => { + write!(output, "{}", "warning".bold().yellow()).unwrap(); + } + _ => { continue; } + } + + writeln!(output, ": {}", msg.message.bold()).unwrap(); + + let mut biggest_line_no = 0; + + for span in &msg.spans { + let (s_line, _) = pos_to_line_col(span.span.start, &source); + let (e_line, _) = pos_to_line_col(span.span.start, &source); + if s_line > biggest_line_no { + biggest_line_no = s_line; + } + if e_line > biggest_line_no { + biggest_line_no = e_line; + } + } + + let line_no_padding = biggest_line_no.to_string().len(); + + for labeled_span in &msg.spans { + let (line, start_col) = pos_to_line_col(labeled_span.span.start, &source); + let (_, end_col) = pos_to_line_col(labeled_span.span.end, &source); + + writeln!(output, "{}{} {}:{}:{}", " ".repeat(line_no_padding), "-->".bright_blue().bold(), source_name, line, start_col).unwrap(); + + let line_hdr_padding = line.to_string().len() + 1; + + writeln!(output, "{}{}", " ".repeat(line_hdr_padding), "|".bright_blue().bold()).unwrap(); + writeln!(output, "{}{} {} {}", line.to_string().bright_blue().bold(), " ".repeat(line_no_padding - line.to_string().len()), "|".bright_blue().bold(), get_line(line, &source).unwrap()).unwrap(); + + + + let mut end_char = match labeled_span.span_type { + DiagnosticType::Error => '^'.to_string().bold().red(), + DiagnosticType::Warning => '^'.to_string().bold().yellow(), + DiagnosticType::Help => { + if labeled_span.label.is_some() { + '-'.to_string().bold().bright_blue() + } else { + '~'.to_string().bold().bright_blue() + } + }, + DiagnosticType::SecondaryError => { + if labeled_span.label.is_some() { + '-'.to_string().bold().red() + } else { + '~'.to_string().bold().red() + } + } + }; + + let message = match (labeled_span.span_type, labeled_span.label.clone()) { + (_, None) => ColoredString::from(""), + (DiagnosticType::Error, Some(e)) => e.bold().red(), + (DiagnosticType::Warning, Some(e)) => e.bold().yellow(), + (DiagnosticType::Help, Some(e)) => e.bold().bright_blue(), + (DiagnosticType::SecondaryError, Some(e)) => e.bold().red() + }; + + println!("{} {}", labeled_span.span.start, labeled_span.span.end); + println!("{} {}", end_col, start_col); + + let underline_length = if end_col == start_col { + end_char = "".into(); + 1 + } else if end_col < start_col { + 0 + } else { + end_col - start_col + }; + + let underline = match labeled_span.span_type { + DiagnosticType::Error => "^".repeat(underline_length).to_string().bold().red(), + DiagnosticType::Warning => "^".repeat(underline_length).to_string().bold().yellow(), + DiagnosticType::Help => "~".repeat(underline_length).to_string().bold().bright_blue(), + DiagnosticType::SecondaryError => "~".repeat(underline_length).to_string().bold().red() + }; + + writeln!(output, "{}{} {}{}{} {}", " ".repeat(line_hdr_padding), "|".bright_blue().bold(), " ".repeat(start_col-1), underline, end_char, message).unwrap(); + writeln!(output).unwrap(); + } + } + + output + } +} \ No newline at end of file diff --git a/libkabel/src/diagnostics/emitters/util.rs b/libkabel/src/diagnostics/emitters/util.rs new file mode 100644 index 0000000..cf5726e --- /dev/null +++ b/libkabel/src/diagnostics/emitters/util.rs @@ -0,0 +1,20 @@ +pub fn pos_to_line_col(pos: usize, source: &str) -> (usize, usize) { + let mut line = 0; + let mut col = 1; + for (n, c) in source.chars().enumerate() { + if c == '\n' { + line += 1; + col = 0; + } else { + col += 1; + } + if n == pos { + break; + } + } + (line, col) +} + +pub fn get_line(line: usize, source: &str) -> Option<&str> { + source.lines().nth(line) +} \ No newline at end of file diff --git a/libkabel/src/diagnostics/mod.rs b/libkabel/src/diagnostics/mod.rs index ad90843..23bf121 100644 --- a/libkabel/src/diagnostics/mod.rs +++ b/libkabel/src/diagnostics/mod.rs @@ -6,14 +6,16 @@ pub mod emitters; #[derive(Debug, Clone, PartialEq, Eq)] pub struct Diagnostic { pub diag_type: DiagnosticType, - pub spans: Vec + pub spans: Vec, + pub message: String, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum DiagnosticType { Error, Warning, - Help + Help, + SecondaryError } #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/libkabel/src/diagnostics/span.rs b/libkabel/src/diagnostics/span.rs index 4f36953..6160636 100644 --- a/libkabel/src/diagnostics/span.rs +++ b/libkabel/src/diagnostics/span.rs @@ -1,11 +1,11 @@ #[derive(Debug, Clone, PartialEq, Eq, Copy)] pub struct Span { - pub start: u64, - pub end: u64 + pub start: usize, + pub end: usize } impl Span { - pub fn new(start: u64, end: u64) -> Self { + pub fn new(start: usize, end: usize) -> Self { Self { start, end } } } \ No newline at end of file diff --git a/libkabel/src/lexer/mod.rs b/libkabel/src/lexer/mod.rs index 9a46516..cfb8dee 100644 --- a/libkabel/src/lexer/mod.rs +++ b/libkabel/src/lexer/mod.rs @@ -23,8 +23,8 @@ pub fn lexer(text_source: &str) -> Result, KError> { let mut chars = text_source.chars().peekable(); - let mut pos: u64 = 0; - let mut span_start: u64 = 0; + let mut pos: usize = 0; + let mut span_start: usize = 0; while let Some(c) = chars.next() { pos += 1; @@ -53,21 +53,39 @@ pub fn lexer(text_source: &str) -> Result, KError> { current_token.push(c); } } else { - continue; // we're at the end. we should bring a user error - // because this string was not properly delimited + + return Err(KError::UserError(vec![ + Diagnostic { + diag_type: DiagnosticType::Error, + message: "unterminated string literal".to_string(), + spans: vec![ + SpanWithLabel { + span: Span::new(span_start-1, span_start-1), + span_type: DiagnosticType::SecondaryError, + label: Some("string began here".to_string()) + }, + SpanWithLabel { + span: Span::new(pos, pos), + span_type: DiagnosticType::Error, + label: Some("expected end quote here".to_string()) + } + ] + } + ])); } } State::Numbering => { + current_token.push(c); // If next char isn't numeric, is at end of this number literal if let Some(c_peek) = chars.peek() { - current_token.push(c); if !c_peek.is_ascii_digit() { - let num = match current_token.parse::() { Ok(n) => n, Err(_) => { + println!("{} {}", span_start, pos); return Err(KError::UserError(vec![ Diagnostic { + message: "invalid numeric literal".to_string(), diag_type: DiagnosticType::Error, spans: vec![ SpanWithLabel { @@ -87,7 +105,6 @@ pub fn lexer(text_source: &str) -> Result, KError> { )); state = State::BuildingToken; current_token = String::new(); - span_start = pos + 1; } } }