188 lines
5.7 KiB
V
188 lines
5.7 KiB
V
// Copyright (c) 2019-2022 Alexander Medvednikov. All rights reserved.
|
|
// Use of this source code is governed by an MIT license
|
|
// that can be found in the LICENSE file.
|
|
[has_globals]
|
|
module util
|
|
|
|
import os
|
|
import strings
|
|
import term
|
|
import v.token
|
|
import v.mathutil as mu
|
|
|
|
// The filepath:line:col: format is the default C compiler error output format.
|
|
// It allows editors and IDE's like emacs to quickly find the errors in the
|
|
// output and jump to their source with a keyboard shortcut.
|
|
// Note: using only the filename may lead to inability of IDE/editors
|
|
// to find the source file, when the IDE has a different working folder than
|
|
// v itself.
|
|
// error_context_before - how many lines of source context to print before the pointer line
|
|
// error_context_after - ^^^ same, but after
|
|
const (
|
|
error_context_before = 2
|
|
error_context_after = 2
|
|
)
|
|
|
|
// emanager.support_color - should the error and other messages
|
|
// have ANSI terminal escape color codes in them.
|
|
// By default, v tries to autodetect, if the terminal supports colors.
|
|
// Use -color and -nocolor options to override the detection decision.
|
|
pub const (
|
|
emanager = new_error_manager()
|
|
)
|
|
|
|
pub struct EManager {
|
|
mut:
|
|
support_color bool
|
|
}
|
|
|
|
pub fn new_error_manager() &EManager {
|
|
return &EManager{
|
|
support_color: term.can_show_color_on_stderr() && term.can_show_color_on_stdout()
|
|
}
|
|
}
|
|
|
|
pub fn (e &EManager) set_support_color(b bool) {
|
|
unsafe {
|
|
mut me := e
|
|
me.support_color = b
|
|
}
|
|
}
|
|
|
|
pub fn bold(msg string) string {
|
|
if !util.emanager.support_color {
|
|
return msg
|
|
}
|
|
return term.bold(msg)
|
|
}
|
|
|
|
fn color(kind string, msg string) string {
|
|
if !util.emanager.support_color {
|
|
return msg
|
|
}
|
|
if kind.contains('error') {
|
|
return term.red(msg)
|
|
}
|
|
if kind.contains('notice') {
|
|
return term.yellow(msg)
|
|
}
|
|
return term.magenta(msg)
|
|
}
|
|
|
|
const normalised_workdir = os.wd_at_startup.replace('\\', '/') + '/'
|
|
|
|
// formatted_error - `kind` may be 'error' or 'warn'
|
|
pub fn formatted_error(kind string, omsg string, filepath string, pos token.Pos) string {
|
|
emsg := omsg.replace('main.', '')
|
|
mut path := filepath
|
|
verror_paths_override := os.getenv('VERROR_PATHS')
|
|
if verror_paths_override == 'absolute' {
|
|
path = os.real_path(path)
|
|
} else {
|
|
// always use `/` in the error paths, to ensure the compiler output does not vary in the tests:
|
|
path = path.replace('\\', '/')
|
|
if path.starts_with(util.normalised_workdir) {
|
|
// Get a relative path to the compiler's workdir, when possible:
|
|
path = path.replace_once(util.normalised_workdir, '')
|
|
}
|
|
}
|
|
//
|
|
position := '$path:${pos.line_nr + 1}:${mu.max(1, pos.col + 1)}:'
|
|
scontext := source_file_context(kind, filepath, pos).join('\n')
|
|
final_position := bold(position)
|
|
final_kind := bold(color(kind, kind))
|
|
final_msg := emsg
|
|
final_context := if scontext.len > 0 { '\n$scontext' } else { '' }
|
|
//
|
|
return '$final_position $final_kind $final_msg$final_context'.trim_space()
|
|
}
|
|
|
|
[heap]
|
|
struct LinesCache {
|
|
mut:
|
|
lines map[string][]string
|
|
}
|
|
|
|
__global lines_cache = &LinesCache{}
|
|
|
|
pub fn cached_file2sourcelines(path string) []string {
|
|
if res := lines_cache.lines[path] {
|
|
return res
|
|
}
|
|
source := read_file(path) or { '' }
|
|
res := set_source_for_path(path, source)
|
|
return res
|
|
}
|
|
|
|
// set_source_for_path should be called for every file, over which you want to use util.formatted_error
|
|
pub fn set_source_for_path(path string, source string) []string {
|
|
lines := source.split_into_lines()
|
|
lines_cache.lines[path] = lines
|
|
return lines
|
|
}
|
|
|
|
pub fn source_file_context(kind string, filepath string, pos token.Pos) []string {
|
|
mut clines := []string{}
|
|
source_lines := unsafe { cached_file2sourcelines(filepath) }
|
|
if source_lines.len == 0 {
|
|
return clines
|
|
}
|
|
bline := mu.max(0, pos.line_nr - util.error_context_before)
|
|
aline := mu.max(0, mu.min(source_lines.len - 1, pos.line_nr + util.error_context_after))
|
|
tab_spaces := ' '
|
|
for iline := bline; iline <= aline; iline++ {
|
|
sline := source_lines[iline]
|
|
start_column := mu.max(0, mu.min(pos.col, sline.len))
|
|
end_column := mu.max(0, mu.min(pos.col + mu.max(0, pos.len), sline.len))
|
|
cline := if iline == pos.line_nr {
|
|
sline[..start_column] + color(kind, sline[start_column..end_column]) +
|
|
sline[end_column..]
|
|
} else {
|
|
sline
|
|
}
|
|
clines << '${iline + 1:5d} | ' + cline.replace('\t', tab_spaces)
|
|
//
|
|
if iline == pos.line_nr {
|
|
// The pointerline should have the same spaces/tabs as the offending
|
|
// line, so that it prints the ^ character exactly on the *same spot*
|
|
// where it is needed. That is the reason we can not just
|
|
// use strings.repeat(` `, col) to form it.
|
|
mut pointerline_builder := strings.new_builder(sline.len)
|
|
for i := 0; i < start_column; {
|
|
if sline[i].is_space() {
|
|
pointerline_builder.write_u8(sline[i])
|
|
i++
|
|
} else {
|
|
char_len := utf8_char_len(sline[i])
|
|
spaces := ' '.repeat(utf8_str_visible_length(sline[i..i + char_len]))
|
|
pointerline_builder.write_string(spaces)
|
|
i += char_len
|
|
}
|
|
}
|
|
underline_len := utf8_str_visible_length(sline[start_column..end_column])
|
|
underline := if underline_len > 1 { '~'.repeat(underline_len) } else { '^' }
|
|
pointerline_builder.write_string(bold(color(kind, underline)))
|
|
clines << ' | ' + pointerline_builder.str().replace('\t', tab_spaces)
|
|
}
|
|
}
|
|
return clines
|
|
}
|
|
|
|
[noreturn]
|
|
pub fn verror(kind string, s string) {
|
|
final_kind := bold(color(kind, kind))
|
|
eprintln('$final_kind: $s')
|
|
exit(1)
|
|
}
|
|
|
|
pub fn vlines_escape_path(path string, ccompiler string) string {
|
|
is_cc_tcc := ccompiler.contains('tcc')
|
|
if is_cc_tcc {
|
|
// tcc currently has a bug, causing all #line files,
|
|
// to be prefixed with the *same folder as the .tmp.c file*
|
|
// this ../../ escaping, is a temporary workaround for that
|
|
return '../../../../../..' + cescaped_path(os.real_path(path))
|
|
}
|
|
return cescaped_path(os.real_path(path))
|
|
}
|