Refactor Compilation error to contain a Token (#535)

Use a `Token` to represent the error context, instead of a mess of
fields. Remove write_message_context in favor of `Token::write_context`.
This commit is contained in:
Casey Rodarmor 2019-11-13 19:32:50 -08:00 committed by GitHub
parent 7046443b39
commit 598f1c3200
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 219 additions and 259 deletions

View File

@ -34,7 +34,7 @@ check:
cargo check
watch +COMMAND='test':
cargo watch --clear --exec build --exec "{{COMMAND}}"
cargo watch --clear --exec "{{COMMAND}}"
man:
cargo build --features help4help2man

View File

@ -40,13 +40,17 @@ impl<'a: 'b, 'b> AssignmentResolver<'a, 'b> {
self.evaluated.insert(name);
} else {
let message = format!("attempted to resolve unknown assignment `{}`", name);
return Err(CompilationError {
let token = Token {
src: "",
offset: 0,
line: 0,
column: 0,
width: 0,
length: 0,
kind: TokenKind::Unspecified,
};
return Err(CompilationError {
kind: Internal { message },
token,
});
}
Ok(())

View File

@ -35,10 +35,7 @@ pub(crate) use unicode_width::UnicodeWidthChar;
pub(crate) use crate::{config_error, keyword, search_error, setting};
// functions
pub(crate) use crate::{
default::default, empty::empty, load_dotenv::load_dotenv, output::output,
write_message_context::write_message_context,
};
pub(crate) use crate::{default::default, empty::empty, load_dotenv::load_dotenv, output::output};
// traits
pub(crate) use crate::{

View File

@ -1,13 +1,9 @@
use crate::common::*;
#[derive(Debug, PartialEq)]
pub(crate) struct CompilationError<'a> {
pub(crate) src: &'a str,
pub(crate) offset: usize,
pub(crate) line: usize,
pub(crate) column: usize,
pub(crate) width: usize,
pub(crate) kind: CompilationErrorKind<'a>,
pub(crate) struct CompilationError<'src> {
pub(crate) token: Token<'src>,
pub(crate) kind: CompilationErrorKind<'src>,
}
impl Error for CompilationError<'_> {}
@ -25,7 +21,7 @@ impl Display for CompilationError<'_> {
f,
"Alias `{}` defined on line {} shadows recipe `{}` defined on line {}",
alias,
self.line.ordinal(),
self.token.line.ordinal(),
alias,
recipe_line.ordinal(),
)?;
@ -90,7 +86,7 @@ impl Display for CompilationError<'_> {
"Alias `{}` first defined on line {} is redefined on line {}",
alias,
first.ordinal(),
self.line.ordinal(),
self.token.line.ordinal(),
)?;
}
DuplicateDependency { recipe, dependency } => {
@ -106,7 +102,7 @@ impl Display for CompilationError<'_> {
"Recipe `{}` first defined on line {} is redefined on line {}",
recipe,
first.ordinal(),
self.line.ordinal()
self.token.line.ordinal()
)?;
}
DuplicateSet { setting, first } => {
@ -115,7 +111,7 @@ impl Display for CompilationError<'_> {
"Setting `{}` first set on line {} is redefined on line {}",
setting,
first.ordinal(),
self.line.ordinal(),
self.token.line.ordinal(),
)?;
}
DependencyHasParameters { recipe, dependency } => {
@ -223,14 +219,6 @@ impl Display for CompilationError<'_> {
write!(f, "{}", message.suffix())?;
write_message_context(
f,
Color::fmt(f).error(),
self.src,
self.offset,
self.line,
self.column,
self.width,
)
self.token.write_context(f, Color::fmt(f).error())
}
}

View File

@ -11,15 +11,15 @@ use TokenKind::*;
/// regex-based lexer, which was slower and generally godawful. However,
/// this should not be taken as a slight against regular expressions,
/// the lexer was just idiosyncratically bad.
pub(crate) struct Lexer<'a> {
pub(crate) struct Lexer<'src> {
/// Source text
src: &'a str,
src: &'src str,
/// Char iterator
chars: Chars<'a>,
chars: Chars<'src>,
/// Tokens
tokens: Vec<Token<'a>>,
tokens: Vec<Token<'src>>,
/// State stack
state: Vec<State<'a>>,
state: Vec<State<'src>>,
/// Current token start
token_start: Position,
/// Current token end
@ -28,14 +28,14 @@ pub(crate) struct Lexer<'a> {
next: Option<char>,
}
impl<'a> Lexer<'a> {
impl<'src> Lexer<'src> {
/// Lex `text`
pub(crate) fn lex(src: &str) -> CompilationResult<Vec<Token>> {
Lexer::new(src).tokenize()
}
/// Create a new Lexer to lex `text`
fn new(src: &'a str) -> Lexer<'a> {
fn new(src: &'src str) -> Lexer<'src> {
let mut chars = src.chars();
let next = chars.next();
@ -58,7 +58,7 @@ impl<'a> Lexer<'a> {
/// Advance over the chracter in `self.next`, updating
/// `self.token_end` accordingly.
fn advance(&mut self) -> CompilationResult<'a, ()> {
fn advance(&mut self) -> CompilationResult<'src, ()> {
match self.next {
Some(c) => {
let len_utf8 = c.len_utf8();
@ -84,7 +84,7 @@ impl<'a> Lexer<'a> {
}
/// Lexeme of in-progress token
fn lexeme(&self) -> &'a str {
fn lexeme(&self) -> &'src str {
&self.src[self.token_start.offset..self.token_end.offset]
}
@ -104,7 +104,7 @@ impl<'a> Lexer<'a> {
}
/// Un-lexed text
fn rest(&self) -> &'a str {
fn rest(&self) -> &'src str {
&self.src[self.token_end.offset..]
}
@ -124,7 +124,7 @@ impl<'a> Lexer<'a> {
}
/// Get current state
fn state(&self) -> CompilationResult<'a, State<'a>> {
fn state(&self) -> CompilationResult<'src, State<'src>> {
if self.state.is_empty() {
Err(self.internal_error("Lexer state stack empty"))
} else {
@ -133,7 +133,7 @@ impl<'a> Lexer<'a> {
}
/// Pop current state from stack
fn pop_state(&mut self) -> CompilationResult<'a, ()> {
fn pop_state(&mut self) -> CompilationResult<'src, ()> {
if self.state.pop().is_none() {
Err(self.internal_error("Lexer attempted to pop in start state"))
} else {
@ -158,26 +158,31 @@ impl<'a> Lexer<'a> {
}
/// Create an internal error with `message`
fn internal_error(&self, message: impl Into<String>) -> CompilationError<'a> {
// Use `self.token_end` as the location of the error
CompilationError {
fn internal_error(&self, message: impl Into<String>) -> CompilationError<'src> {
let token = Token {
src: self.src,
offset: self.token_end.offset,
line: self.token_end.line,
column: self.token_end.column,
width: 0,
length: 0,
kind: Unspecified,
};
// Use `self.token_end` as the location of the error
CompilationError {
kind: CompilationErrorKind::Internal {
message: message.into(),
},
token,
}
}
/// Create a compilation error with `kind`
fn error(&self, kind: CompilationErrorKind<'a>) -> CompilationError<'a> {
fn error(&self, kind: CompilationErrorKind<'src>) -> CompilationError<'src> {
// Use the in-progress token span as the location of the error.
// The width of the error site to highlight depends on the kind of error:
let width = match kind {
let length = match kind {
// highlight ' or "
UnterminatedString => 1,
// highlight `
@ -186,26 +191,24 @@ impl<'a> Lexer<'a> {
_ => self.lexeme().len(),
};
CompilationError {
let token = Token {
kind: Unspecified,
src: self.src,
offset: self.token_start.offset,
line: self.token_start.line,
column: self.token_start.column,
width,
kind,
}
length,
};
CompilationError { token, kind }
}
fn unterminated_interpolation_error(
&self,
interpolation_start: Position,
) -> CompilationError<'a> {
interpolation_start: Token<'src>,
) -> CompilationError<'src> {
CompilationError {
src: self.src,
offset: interpolation_start.offset,
line: interpolation_start.line,
column: interpolation_start.column,
width: 2,
token: interpolation_start,
kind: UnterminatedInterpolation,
}
}
@ -251,7 +254,7 @@ impl<'a> Lexer<'a> {
}
/// Consume the text and produce a series of tokens
fn tokenize(mut self) -> CompilationResult<'a, Vec<Token<'a>>> {
fn tokenize(mut self) -> CompilationResult<'src, Vec<Token<'src>>> {
loop {
if self.token_start.column == 0 {
self.lex_line_start()?;
@ -287,7 +290,7 @@ impl<'a> Lexer<'a> {
}
/// Handle blank lines and indentation
fn lex_line_start(&mut self) -> CompilationResult<'a, ()> {
fn lex_line_start(&mut self) -> CompilationResult<'src, ()> {
let nonblank_index = self
.rest()
.char_indices()
@ -384,7 +387,7 @@ impl<'a> Lexer<'a> {
}
/// Lex token beginning with `start` in normal state
fn lex_normal(&mut self, start: char) -> CompilationResult<'a, ()> {
fn lex_normal(&mut self, start: char) -> CompilationResult<'src, ()> {
match start {
'@' => self.lex_single(At),
'[' => self.lex_single(BracketL),
@ -418,9 +421,9 @@ impl<'a> Lexer<'a> {
/// Lex token beginning with `start` in interpolation state
fn lex_interpolation(
&mut self,
interpolation_start: Position,
interpolation_start: Token<'src>,
start: char,
) -> CompilationResult<'a, ()> {
) -> CompilationResult<'src, ()> {
// Check for end of interpolation
if self.rest_starts_with("}}") {
// Pop interpolation state
@ -437,7 +440,7 @@ impl<'a> Lexer<'a> {
}
/// Lex token beginning with `start` in text state
fn lex_text(&mut self) -> CompilationResult<'a, ()> {
fn lex_text(&mut self) -> CompilationResult<'src, ()> {
enum Terminator {
Newline,
NewlineCarriageReturn,
@ -482,30 +485,31 @@ impl<'a> Lexer<'a> {
self.lex_double(Eol)
}
Interpolation => {
self.lex_double(InterpolationStart)?;
self.state.push(State::Interpolation {
interpolation_start: self.token_start,
interpolation_start: self.tokens[self.tokens.len() - 1],
});
self.lex_double(InterpolationStart)
Ok(())
}
EndOfFile => self.pop_state(),
}
}
/// Lex token beginning with `start` in indented state
fn lex_indented(&mut self) -> CompilationResult<'a, ()> {
fn lex_indented(&mut self) -> CompilationResult<'src, ()> {
self.state.push(State::Text);
Ok(())
}
/// Lex a single character token
fn lex_single(&mut self, kind: TokenKind) -> CompilationResult<'a, ()> {
fn lex_single(&mut self, kind: TokenKind) -> CompilationResult<'src, ()> {
self.advance()?;
self.token(kind);
Ok(())
}
/// Lex a double character token
fn lex_double(&mut self, kind: TokenKind) -> CompilationResult<'a, ()> {
fn lex_double(&mut self, kind: TokenKind) -> CompilationResult<'src, ()> {
self.advance()?;
self.advance()?;
self.token(kind);
@ -513,7 +517,7 @@ impl<'a> Lexer<'a> {
}
/// Lex a token starting with ':'
fn lex_colon(&mut self) -> CompilationResult<'a, ()> {
fn lex_colon(&mut self) -> CompilationResult<'src, ()> {
self.advance()?;
if self.next_is('=') {
@ -527,7 +531,7 @@ impl<'a> Lexer<'a> {
}
/// Lex a token starting with '{'
fn lex_brace_l(&mut self) -> CompilationResult<'a, ()> {
fn lex_brace_l(&mut self) -> CompilationResult<'src, ()> {
if !self.rest_starts_with("{{") {
self.advance()?;
@ -538,7 +542,7 @@ impl<'a> Lexer<'a> {
}
/// Lex a token starting with '}'
fn lex_brace_r(&mut self) -> CompilationResult<'a, ()> {
fn lex_brace_r(&mut self) -> CompilationResult<'src, ()> {
if !self.rest_starts_with("}}") {
self.advance()?;
@ -549,7 +553,7 @@ impl<'a> Lexer<'a> {
}
/// Lex a carriage return and line feed
fn lex_cr_lf(&mut self) -> CompilationResult<'a, ()> {
fn lex_cr_lf(&mut self) -> CompilationResult<'src, ()> {
if !self.rest_starts_with("\r\n") {
// advance over \r
self.advance()?;
@ -561,7 +565,7 @@ impl<'a> Lexer<'a> {
}
/// Lex name: [a-zA-Z_][a-zA-Z0-9_]*
fn lex_identifier(&mut self) -> CompilationResult<'a, ()> {
fn lex_identifier(&mut self) -> CompilationResult<'src, ()> {
// advance over initial character
self.advance()?;
@ -579,7 +583,7 @@ impl<'a> Lexer<'a> {
}
/// Lex comment: #[^\r\n]
fn lex_comment(&mut self) -> CompilationResult<'a, ()> {
fn lex_comment(&mut self) -> CompilationResult<'src, ()> {
// advance over #
self.advance()?;
@ -593,7 +597,7 @@ impl<'a> Lexer<'a> {
}
/// Lex backtick: `[^\r\n]*`
fn lex_backtick(&mut self) -> CompilationResult<'a, ()> {
fn lex_backtick(&mut self) -> CompilationResult<'src, ()> {
// advance over initial `
self.advance()?;
@ -612,7 +616,7 @@ impl<'a> Lexer<'a> {
}
/// Lex whitespace: [ \t]+
fn lex_whitespace(&mut self) -> CompilationResult<'a, ()> {
fn lex_whitespace(&mut self) -> CompilationResult<'src, ()> {
while self.next_is_whitespace() {
self.advance()?
}
@ -623,7 +627,7 @@ impl<'a> Lexer<'a> {
}
/// Lex raw string: '[^']*'
fn lex_raw_string(&mut self) -> CompilationResult<'a, ()> {
fn lex_raw_string(&mut self) -> CompilationResult<'src, ()> {
// advance over opening '
self.advance()?;
@ -646,7 +650,7 @@ impl<'a> Lexer<'a> {
}
/// Lex cooked string: "[^"\n\r]*" (also processes escape sequences)
fn lex_cooked_string(&mut self) -> CompilationResult<'a, ()> {
fn lex_cooked_string(&mut self) -> CompilationResult<'src, ()> {
// advance over opening "
self.advance()?;
@ -780,7 +784,7 @@ mod tests {
Dedent | Eof => "",
// Variable lexemes
Text | StringCooked | StringRaw | Identifier | Comment | Backtick => {
Text | StringCooked | StringRaw | Identifier | Comment | Backtick | Unspecified => {
panic!("Token {:?} has no default lexeme", kind)
}
}
@ -808,22 +812,24 @@ mod tests {
offset: usize,
line: usize,
column: usize,
width: usize,
length: usize,
kind: CompilationErrorKind,
) {
let expected = CompilationError {
match Lexer::lex(src) {
Ok(_) => panic!("Lexing succeeded but expected"),
Err(have) => {
let want = CompilationError {
token: Token {
kind: have.token.kind,
src,
offset,
line,
column,
width,
length,
},
kind,
};
match Lexer::lex(src) {
Ok(_) => panic!("Lexing succeeded but expected: {}\n{}", expected, src),
Err(actual) => {
assert_eq!(actual, expected);
assert_eq!(have, want);
}
}
}

View File

@ -87,7 +87,6 @@ mod use_color;
mod variables;
mod verbosity;
mod warning;
mod write_message_context;
pub use crate::run::run;

View File

@ -702,24 +702,26 @@ mod tests {
offset: usize,
line: usize,
column: usize,
width: usize,
length: usize,
kind: CompilationErrorKind,
) {
let expected = CompilationError {
let tokens = Lexer::lex(src).expect("Lexing failed in parse test...");
match Parser::parse(&tokens) {
Ok(_) => panic!("Parsing unexpectedly succeeded"),
Err(have) => {
let want = CompilationError {
token: Token {
kind: have.token.kind,
src,
offset,
line,
column,
width,
length,
},
kind,
};
let tokens = Lexer::lex(src).expect("Lexing failed in parse test...");
match Parser::parse(&tokens) {
Ok(_) => panic!("Parsing succeeded but expected: {}\n{}", expected, src),
Err(actual) => {
assert_eq!(actual, expected);
assert_eq!(have, want);
}
}
}

View File

@ -82,6 +82,17 @@ impl Error for RuntimeError<'_> {
}
}
impl<'a> RuntimeError<'a> {
fn context(&self) -> Option<Token> {
use RuntimeError::*;
match self {
FunctionCall { function, .. } => Some(function.token()),
Backtick { token, .. } => Some(*token),
_ => None,
}
}
}
impl<'a> Display for RuntimeError<'a> {
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
use RuntimeError::*;
@ -94,12 +105,10 @@ impl<'a> Display for RuntimeError<'a> {
let message = color.message();
write!(f, "{}", message.prefix())?;
let mut error_token: Option<Token> = None;
match *self {
match self {
UnknownRecipes {
ref recipes,
ref suggestion,
recipes,
suggestion,
} => {
write!(
f,
@ -111,7 +120,7 @@ impl<'a> Display for RuntimeError<'a> {
write!(f, "\nDid you mean `{}`?", suggestion)?;
}
}
UnknownOverrides { ref overrides } => {
UnknownOverrides { overrides } => {
write!(
f,
"{} {} overridden on the command line but not present in justfile",
@ -121,7 +130,7 @@ impl<'a> Display for RuntimeError<'a> {
}
ArgumentCountMismatch {
recipe,
ref parameters,
parameters,
found,
min,
max,
@ -133,7 +142,7 @@ impl<'a> Display for RuntimeError<'a> {
"Recipe `{}` got {} {} but {}takes {}",
recipe,
found,
Count("argument", found),
Count("argument", *found),
if expected < found { "only " } else { "" },
expected
)?;
@ -143,7 +152,7 @@ impl<'a> Display for RuntimeError<'a> {
"Recipe `{}` got {} {} but takes at least {}",
recipe,
found,
Count("argument", found),
Count("argument", *found),
min
)?;
} else if found > max {
@ -152,7 +161,7 @@ impl<'a> Display for RuntimeError<'a> {
"Recipe `{}` got {} {} but takes at most {}",
recipe,
found,
Count("argument", found),
Count("argument", *found),
max
)?;
}
@ -182,8 +191,8 @@ impl<'a> Display for RuntimeError<'a> {
}
Cygpath {
recipe,
ref output_error,
} => match *output_error {
output_error,
} => match output_error {
OutputError::Code(code) => {
write!(
f,
@ -208,7 +217,7 @@ impl<'a> Display for RuntimeError<'a> {
recipe
)?;
}
OutputError::Io(ref io_error) => {
OutputError::Io(io_error) => {
match io_error.kind() {
io::ErrorKind::NotFound => write!(
f,
@ -225,7 +234,7 @@ impl<'a> Display for RuntimeError<'a> {
_ => write!(f, "Could not run `cygpath` executable:\n{}", io_error),
}?;
}
OutputError::Utf8(ref utf8_error) => {
OutputError::Utf8(utf8_error) => {
write!(
f,
"Cygpath successfully translated recipe `{}` shebang interpreter path, \
@ -234,28 +243,24 @@ impl<'a> Display for RuntimeError<'a> {
)?;
}
},
Dotenv { ref dotenv_error } => {
Dotenv { dotenv_error } => {
writeln!(f, "Failed to load .env: {}", dotenv_error)?;
}
FunctionCall {
ref function,
ref message,
} => {
FunctionCall { function, message } => {
writeln!(
f,
"Call to function `{}` failed: {}",
function.lexeme(),
message
)?;
error_token = Some(function.token());
}
Shebang {
recipe,
ref command,
ref argument,
ref io_error,
command,
argument,
io_error,
} => {
if let Some(ref argument) = *argument {
if let Some(argument) = argument {
write!(
f,
"Recipe `{}` with shebang `#!{} {}` execution error: {}",
@ -295,12 +300,10 @@ impl<'a> Display for RuntimeError<'a> {
recipe, n
)?;
} else {
write!(f, "Recipe `{}` failed for an unknown reason", recipe)?;
}
}
IoError {
recipe,
ref io_error,
} => {
IoError { recipe, io_error } => {
match io_error.kind() {
io::ErrorKind::NotFound => writeln!(
f,
@ -320,32 +323,23 @@ impl<'a> Display for RuntimeError<'a> {
),
}?;
}
TmpdirIoError {
recipe,
ref io_error,
} => writeln!(
TmpdirIoError { recipe, io_error } => writeln!(
f,
"Recipe `{}` could not be run because of an IO error while trying \
to create a temporary directory or write a file to that directory`:{}",
recipe, io_error
)?,
Backtick {
ref token,
ref output_error,
} => match *output_error {
Backtick { output_error, .. } => match output_error {
OutputError::Code(code) => {
writeln!(f, "Backtick failed with exit code {}", code)?;
error_token = Some(*token);
}
OutputError::Signal(signal) => {
writeln!(f, "Backtick was terminated by signal {}", signal)?;
error_token = Some(*token);
}
OutputError::Unknown => {
writeln!(f, "Backtick failed for an unknown reason")?;
error_token = Some(*token);
}
OutputError::Io(ref io_error) => {
OutputError::Io(io_error) => {
match io_error.kind() {
io::ErrorKind::NotFound => write!(
f,
@ -364,15 +358,13 @@ impl<'a> Display for RuntimeError<'a> {
io_error
),
}?;
error_token = Some(*token);
}
OutputError::Utf8(ref utf8_error) => {
OutputError::Utf8(utf8_error) => {
writeln!(
f,
"Backtick succeeded but stdout was not utf8: {}",
utf8_error
)?;
error_token = Some(*token);
}
},
NoRecipes => {
@ -387,10 +379,10 @@ impl<'a> Display for RuntimeError<'a> {
"Recipe `{}` cannot be used as default recipe since it requires at least {} {}.",
recipe,
min_arguments,
Count("argument", min_arguments),
Count("argument", *min_arguments),
)?;
}
Internal { ref message } => {
Internal { message } => {
write!(
f,
"Internal runtime error, this may indicate a bug in just: {} \
@ -402,16 +394,8 @@ impl<'a> Display for RuntimeError<'a> {
write!(f, "{}", message.suffix())?;
if let Some(token) = error_token {
write_message_context(
f,
Color::fmt(f).error(),
token.src,
token.offset,
token.line,
token.column,
token.lexeme().len(),
)?;
if let Some(token) = self.context() {
token.write_context(f, Color::fmt(f).error())?;
}
Ok(())

View File

@ -1,9 +1,9 @@
use crate::common::*;
#[derive(Copy, Clone, PartialEq, Debug)]
pub(crate) enum State<'a> {
pub(crate) enum State<'src> {
Normal,
Indented { indentation: &'a str },
Indented { indentation: &'src str },
Text,
Interpolation { interpolation_start: Position },
Interpolation { interpolation_start: Token<'src> },
}

View File

@ -42,26 +42,28 @@ pub(crate) fn analysis_error(
offset: usize,
line: usize,
column: usize,
width: usize,
length: usize,
kind: CompilationErrorKind,
) {
let expected = CompilationError {
src,
offset,
line,
column,
width,
kind,
};
let tokens = Lexer::lex(src).expect("Lexing failed in parse test...");
let module = Parser::parse(&tokens).expect("Parsing failed in analysis test...");
match Analyzer::analyze(module) {
Ok(_) => panic!("Analysis succeeded but expected: {}\n{}", expected, src),
Err(actual) => {
assert_eq!(actual, expected);
Ok(_) => panic!("Analysis unexpectedly succeeded"),
Err(have) => {
let want = CompilationError {
token: Token {
kind: have.token.kind,
src,
offset,
line,
column,
length,
},
kind,
};
assert_eq!(have, want);
}
}
}

View File

@ -16,13 +16,64 @@ impl<'a> Token<'a> {
}
pub(crate) fn error(&self, kind: CompilationErrorKind<'a>) -> CompilationError<'a> {
CompilationError {
column: self.column,
offset: self.offset,
line: self.line,
src: self.src,
width: self.length,
kind,
CompilationError { token: *self, kind }
}
pub(crate) fn write_context(&self, f: &mut Formatter, color: Color) -> fmt::Result {
let width = if self.length == 0 { 1 } else { self.length };
let line_number = self.line.ordinal();
match self.src.lines().nth(self.line) {
Some(line) => {
let mut i = 0;
let mut space_column = 0;
let mut space_line = String::new();
let mut space_width = 0;
for c in line.chars() {
if c == '\t' {
space_line.push_str(" ");
if i < self.column {
space_column += 4;
}
if i >= self.column && i < self.column + width {
space_width += 4;
}
} else {
if i < self.column {
space_column += UnicodeWidthChar::width(c).unwrap_or(0);
}
if i >= self.column && i < self.column + width {
space_width += UnicodeWidthChar::width(c).unwrap_or(0);
}
space_line.push(c);
}
i += c.len_utf8();
}
let line_number_width = line_number.to_string().len();
writeln!(f, "{0:1$} |", "", line_number_width)?;
writeln!(f, "{} | {}", line_number, space_line)?;
write!(f, "{0:1$} |", "", line_number_width)?;
write!(
f,
" {0:1$}{2}{3:^<4$}{5}",
"",
space_column,
color.prefix(),
"",
space_width,
color.suffix()
)?;
}
None => {
if self.offset != self.src.len() {
write!(
f,
"internal error: Error has invalid line number: {}",
line_number
)?
}
}
}
Ok(())
}
}

View File

@ -24,6 +24,7 @@ pub(crate) enum TokenKind {
StringCooked,
StringRaw,
Text,
Unspecified,
Whitespace,
}
@ -57,6 +58,7 @@ impl Display for TokenKind {
StringRaw => "raw string",
Text => "command text",
Whitespace => "whitespace",
Unspecified => "unspecified",
}
)
}

View File

@ -39,15 +39,7 @@ impl Display for Warning<'_> {
if let Some(token) = self.context() {
writeln!(f)?;
write_message_context(
f,
Color::fmt(f).warning(),
token.src,
token.offset,
token.line,
token.column,
token.lexeme().len(),
)?;
token.write_context(f, Color::fmt(f).warning())?;
}
Ok(())

View File

@ -1,67 +0,0 @@
use crate::common::*;
pub(crate) fn write_message_context(
f: &mut Formatter,
color: Color,
text: &str,
offset: usize,
line: usize,
column: usize,
width: usize,
) -> Result<(), fmt::Error> {
let width = if width == 0 { 1 } else { width };
let line_number = line.ordinal();
match text.lines().nth(line) {
Some(line) => {
let mut i = 0;
let mut space_column = 0;
let mut space_line = String::new();
let mut space_width = 0;
for c in line.chars() {
if c == '\t' {
space_line.push_str(" ");
if i < column {
space_column += 4;
}
if i >= column && i < column + width {
space_width += 4;
}
} else {
if i < column {
space_column += UnicodeWidthChar::width(c).unwrap_or(0);
}
if i >= column && i < column + width {
space_width += UnicodeWidthChar::width(c).unwrap_or(0);
}
space_line.push(c);
}
i += c.len_utf8();
}
let line_number_width = line_number.to_string().len();
writeln!(f, "{0:1$} |", "", line_number_width)?;
writeln!(f, "{} | {}", line_number, space_line)?;
write!(f, "{0:1$} |", "", line_number_width)?;
write!(
f,
" {0:1$}{2}{3:^<4$}{5}",
"",
space_column,
color.prefix(),
"",
space_width,
color.suffix()
)?;
}
None => {
if offset != text.len() {
write!(
f,
"internal error: Error has invalid line number: {}",
line_number
)?
}
}
}
Ok(())
}