diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index e9a47820df..cc2f0d9ac3 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -9,12 +9,12 @@ import v.errors pub type TypeDecl = AliasTypeDecl | FnTypeDecl | SumTypeDecl -pub type Expr = AnonFn | ArrayInit | AsCast | Assoc | BoolLiteral | CTempVar | CallExpr | - CastExpr | ChanInit | CharLiteral | Comment | ComptimeCall | ConcatExpr | EnumVal | FloatLiteral | - Ident | IfExpr | IfGuardExpr | IndexExpr | InfixExpr | IntegerLiteral | Likely | LockExpr | - MapInit | MatchExpr | None | OrExpr | ParExpr | PostfixExpr | PrefixExpr | RangeExpr | - SelectExpr | SelectorExpr | SizeOf | SqlExpr | StringInterLiteral | StringLiteral | StructInit | - Type | TypeOf | UnsafeExpr +pub type Expr = AnonFn | ArrayInit | AsCast | Assoc | AtExpr | BoolLiteral | CTempVar | + CallExpr | CastExpr | ChanInit | CharLiteral | Comment | ComptimeCall | ConcatExpr | EnumVal | + FloatLiteral | Ident | IfExpr | IfGuardExpr | IndexExpr | InfixExpr | IntegerLiteral | + Likely | LockExpr | MapInit | MatchExpr | None | OrExpr | ParExpr | PostfixExpr | PrefixExpr | + RangeExpr | SelectExpr | SelectorExpr | SizeOf | SqlExpr | StringInterLiteral | StringLiteral | + StructInit | Type | TypeOf | UnsafeExpr pub type Stmt = AssertStmt | AssignStmt | Block | BranchStmt | CompFor | ConstDecl | DeferStmt | EnumDecl | ExprStmt | FnDecl | ForCStmt | ForInStmt | ForStmt | GlobalDecl | GoStmt | @@ -937,6 +937,16 @@ pub mut: return_type table.Type } +// @FN, @STRUCT, @MOD etc. See full list in token.valid_at_tokens +pub struct AtExpr { +pub: + name string + pos token.Position + kind token.AtKind +pub mut: + val string +} + pub struct ComptimeCall { pub: method_name string @@ -1019,7 +1029,7 @@ pub fn (expr Expr) position() token.Position { AnonFn { return expr.decl.pos } - ArrayInit, AsCast, Assoc, BoolLiteral, CallExpr, CastExpr, ChanInit, CharLiteral, ConcatExpr, Comment, EnumVal, FloatLiteral, Ident, IfExpr, IndexExpr, IntegerLiteral, Likely, LockExpr, MapInit, MatchExpr, None, OrExpr, ParExpr, PostfixExpr, PrefixExpr, RangeExpr, SelectExpr, SelectorExpr, SizeOf, SqlExpr, StringInterLiteral, StringLiteral, StructInit, Type, TypeOf, UnsafeExpr { + ArrayInit, AsCast, Assoc, AtExpr, BoolLiteral, CallExpr, CastExpr, ChanInit, CharLiteral, ConcatExpr, Comment, EnumVal, FloatLiteral, Ident, IfExpr, IndexExpr, IntegerLiteral, Likely, LockExpr, MapInit, MatchExpr, None, OrExpr, ParExpr, PostfixExpr, PrefixExpr, RangeExpr, SelectExpr, SelectorExpr, SizeOf, SqlExpr, StringInterLiteral, StringLiteral, StructInit, Type, TypeOf, UnsafeExpr { return expr.pos } IfGuardExpr { diff --git a/vlib/v/ast/str.v b/vlib/v/ast/str.v index 633bf223b2..19a44f53a2 100644 --- a/vlib/v/ast/str.v +++ b/vlib/v/ast/str.v @@ -186,6 +186,9 @@ pub fn (x Expr) str() string { CastExpr { return '${x.typname}($x.expr.str())' } + AtExpr { + return '$x.val' + } CallExpr { sargs := args2str(x.args) if x.is_method { diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index ba77e73ea7..b564d66203 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -2,7 +2,9 @@ // Use of this source code is governed by an MIT license that can be found in the LICENSE file. module checker +import os import v.ast +import v.vmod import v.table import v.token import v.pref @@ -26,38 +28,39 @@ const ( ) pub struct Checker { - pref &pref.Preferences // Preferences shared from V struct + pref &pref.Preferences // Preferences shared from V struct pub mut: - table &table.Table - file ast.File - nr_errors int - nr_warnings int - errors []errors.Error - warnings []errors.Warning - error_lines []int // to avoid printing multiple errors for the same line - expected_type table.Type - cur_fn &ast.FnDecl // current function - const_decl string - const_deps []string - const_names []string - global_names []string - locked_names []string // vars that are currently locked - rlocked_names []string // vars that are currently read-locked - in_for_count int // if checker is currently in a for loop + table &table.Table + file ast.File + nr_errors int + nr_warnings int + errors []errors.Error + warnings []errors.Warning + error_lines []int // to avoid printing multiple errors for the same line + expected_type table.Type + cur_fn &ast.FnDecl // current function + const_decl string + const_deps []string + const_names []string + global_names []string + locked_names []string // vars that are currently locked + rlocked_names []string // vars that are currently read-locked + in_for_count int // if checker is currently in a for loop // checked_ident string // to avoid infinite checker loops - returns bool - scope_returns bool - mod string // current module name - is_builtin_mod bool // are we in `builtin`? - inside_unsafe bool - skip_flags bool // should `#flag` and `#include` be skipped - cur_generic_type table.Type + returns bool + scope_returns bool + mod string // current module name + is_builtin_mod bool // are we in `builtin`? + inside_unsafe bool + skip_flags bool // should `#flag` and `#include` be skipped + cur_generic_type table.Type mut: - expr_level int // to avoid infinite recursion segfaults due to compiler bugs - inside_sql bool // to handle sql table fields pseudo variables - cur_orm_ts table.TypeSymbol - error_details []string - generic_funcs []&ast.FnDecl + expr_level int // to avoid infinite recursion segfaults due to compiler bugs + inside_sql bool // to handle sql table fields pseudo variables + cur_orm_ts table.TypeSymbol + error_details []string + generic_funcs []&ast.FnDecl + vmod_file_content string // needed for @VMOD_FILE, contents of the file, *NOT its path* } pub fn new_checker(table &table.Table, pref &pref.Preferences) Checker { @@ -2729,6 +2732,9 @@ pub fn (mut c Checker) expr(node ast.Expr) table.Type { ast.Comment { return table.void_type } + ast.AtExpr { + return c.at_expr(mut node) + } ast.ComptimeCall { node.sym = c.table.get_type_symbol(c.unwrap_generic(c.expr(node.left))) if node.is_vweb { @@ -2997,6 +3003,63 @@ pub fn (mut c Checker) cast_expr(mut node ast.CastExpr) table.Type { return node.typ } +fn (mut c Checker) at_expr(mut node ast.AtExpr) table.Type { + match node.kind { + .fn_name { + node.val = c.cur_fn.name.all_after_last('.') + } + .mod_name { + node.val = c.cur_fn.mod + } + .struct_name { + if c.cur_fn.is_method { + node.val = c.table.type_to_str(c.cur_fn.receiver.typ).all_after_last('.') + } else { + node.val = '' + } + } + .vexe_path { + node.val = pref.vexe_path() + } + .file_path { + node.val = os.real_path(c.file.path) + } + .line_nr { + node.val = (node.pos.line_nr + 1).str() + } + .column_nr { + _, column := util.filepath_pos_to_source_and_column(c.file.path, node.pos) + node.val = (column + 1).str() + } + .vhash { + node.val = util.vhash() + } + .vmod_file { + if c.vmod_file_content.len == 0 { + mut mcache := vmod.get_cache() + vmod_file_location := mcache.get_by_file(c.file.path) + if vmod_file_location.vmod_file.len == 0 { + c.error('@VMOD_FILE can be used only in projects, that have v.mod file', + node.pos) + } + vmod_content := os.read_file(vmod_file_location.vmod_file) or { + '' + } + $if windows { + c.vmod_file_content = vmod_content.replace('\r\n', '\n') + } $else { + c.vmod_file_content = vmod_content + } + } + node.val = c.vmod_file_content + } + .unknown, ._end_ { + c.error('unknown @ identifier: $node.name', node.pos) + } + } + return table.string_type +} + pub fn (mut c Checker) ident(mut ident ast.Ident) table.Type { // TODO: move this if c.const_deps.len > 0 { diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index c4f037a1fc..b7010c0fa5 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -762,6 +762,9 @@ pub fn (mut f Fmt) expr(node ast.Expr) { } f.write(')') } + ast.AtExpr { + f.at_expr(node) + } ast.CallExpr { f.call_expr(node) } @@ -1387,6 +1390,10 @@ pub fn (mut f Fmt) if_expr(it ast.IfExpr) { } } +pub fn (mut f Fmt) at_expr(node ast.AtExpr) { + f.write(node.name) +} + pub fn (mut f Fmt) call_expr(node ast.CallExpr) { /* if node.args.len == 1 && node.expected_arg_types.len == 1 && node.args[0].expr is ast.StructInit && diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index 7f05394c64..495a401b6b 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -2122,6 +2122,9 @@ fn (mut g Gen) expr(node ast.Expr) { ast.CharLiteral { g.write("'$node.val'") } + ast.AtExpr { + g.comp_at(node) + } ast.ComptimeCall { g.comptime_call(node) } diff --git a/vlib/v/gen/comptime.v b/vlib/v/gen/comptime.v index d6fcc6174b..61375eb1b6 100644 --- a/vlib/v/gen/comptime.v +++ b/vlib/v/gen/comptime.v @@ -101,6 +101,15 @@ fn cgen_attrs(attrs []table.Attr) []string { return res } +fn (mut g Gen) comp_at(node ast.AtExpr) { + if node.kind == .vmod_file { + val := cnewlines(node.val.replace('\r', '')) + g.write('tos_lit("$val")') + } else { + g.write('tos_lit("$node.val")') + } +} + fn (mut g Gen) comp_if(node ast.IfExpr) { line := if node.is_expr { stmt_str := g.go_before_stmt(0) diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index e324c48eb1..57d6df7f7f 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -657,6 +657,9 @@ fn (mut g JsGen) expr(node ast.Expr) { g.gen_typeof_expr(node) // TODO: Should this print the V type or the JS type? } + ast.AtExpr { + g.write('"$node.val"') + } ast.ComptimeCall { // TODO } diff --git a/vlib/v/parser/comptime.v b/vlib/v/parser/comptime.v index dc471efa30..9e31c708c2 100644 --- a/vlib/v/parser/comptime.v +++ b/vlib/v/parser/comptime.v @@ -7,6 +7,7 @@ import os import v.ast import v.pref import v.table +import v.token import vweb.tmpl // #flag darwin -I. @@ -150,6 +151,29 @@ fn (mut p Parser) comp_for() ast.CompFor { } } +// @FN, @STRUCT, @MOD etc. See full list in token.valid_at_tokens +fn (mut p Parser) at() ast.AtExpr { + name := p.tok.lit + kind := match name { + '@FN' { token.AtKind.fn_name } + '@MOD' { token.AtKind.mod_name } + '@STRUCT' { token.AtKind.struct_name } + '@VEXE' { token.AtKind.vexe_path } + '@FILE' { token.AtKind.file_path } + '@LINE' { token.AtKind.line_nr } + '@COLUMN' { token.AtKind.column_nr } + '@VHASH' { token.AtKind.vhash } + '@VMOD_FILE' { token.AtKind.vmod_file } + else { token.AtKind.unknown } + } + p.next() + return ast.AtExpr{ + name: name + pos: p.tok.position() + kind: kind + } +} + // TODO import warning bug const ( todo_delete_me = pref.OS.linux diff --git a/vlib/v/parser/pratt.v b/vlib/v/parser/pratt.v index e321becd2c..ab4230b8f0 100644 --- a/vlib/v/parser/pratt.v +++ b/vlib/v/parser/pratt.v @@ -45,6 +45,9 @@ pub fn (mut p Parser) expr(precedence int) ast.Expr { // .enum_val node = p.enum_val() } + .at { + node = p.at() + } .dollar { match p.peek_tok.kind { .name { return p.vweb() } diff --git a/vlib/v/scanner/scanner.v b/vlib/v/scanner/scanner.v index 9683de6e69..965b742220 100644 --- a/vlib/v/scanner/scanner.v +++ b/vlib/v/scanner/scanner.v @@ -7,7 +7,6 @@ import os import v.token import v.pref import v.util -import v.vmod const ( single_quote = `\'` @@ -30,10 +29,6 @@ pub mut: line_comment string // prev_tok TokenKind is_started bool - fn_name string // needed for @FN - mod_name string // needed for @MOD - struct_name string // needed for @STRUCT - vmod_file_content string // needed for @VMOD_FILE, contents of the file, *NOT its path* is_print_line_on_error bool is_print_colored_error bool is_print_rel_paths_on_error bool @@ -177,138 +172,6 @@ fn (mut s Scanner) ident_name() string { return name } -// ident_fn_name looks ahead and returns name of the function if possible, otherwise an empty string -fn (s &Scanner) ident_fn_name() string { - start := s.pos - mut pos := s.pos - pos++ - if s.current_column() - 2 != 0 { - return s.fn_name - } - has_struct_name := s.struct_name != '' - if has_struct_name { - for pos < s.text.len && s.text[pos] != `(` { - pos++ - } - if pos >= s.text.len { - return '' - } - pos++ - } - for pos < s.text.len && s.text[pos] != `(` { - pos++ - } - if pos >= s.text.len { - return '' - } - pos-- - // Eat whitespaces - for pos > start && s.text[pos].is_space() { - pos-- - } - if pos < start { - return '' - } - end_pos := pos + 1 - pos-- - // Search for the start position - for pos > start && util.is_func_char(s.text[pos]) { - pos-- - } - pos++ - start_pos := pos - if pos <= start || pos >= s.text.len { - return '' - } - if s.text[start_pos].is_digit() || end_pos > s.text.len || end_pos <= start_pos || - end_pos <= start || start_pos < start { - return '' - } - fn_name := s.text[start_pos..end_pos] - return fn_name -} - -// ident_mod_name look ahead and return name of module this file belongs to if possible, otherwise empty string -fn (s &Scanner) ident_mod_name() string { - start := s.pos - mut pos := s.pos - pos++ - // Eat whitespaces - for pos < s.text.len && s.text[pos].is_space() { - pos++ - } - if pos >= s.text.len { - return '' - } - start_pos := pos - // Search for next occurrence of a whitespace or newline - for pos < s.text.len && !s.text[pos].is_space() && !util.is_nl(s.text[pos]) { - pos++ - } - if pos >= s.text.len { - return '' - } - end_pos := pos - if end_pos > s.text.len || end_pos <= start_pos || end_pos <= start || start_pos <= start { - return '' - } - mod_name := s.text[start_pos..end_pos] - return mod_name -} - -// ident_struct_name look ahead and return name of last encountered struct if possible, otherwise empty string -fn (s &Scanner) ident_struct_name() string { - start := s.pos - mut pos := s.pos - // Return last known stuct_name encountered to avoid using high order/anonymous function definitions - if s.current_column() - 2 != 0 { - return s.struct_name - } - pos++ - // Eat whitespaces - for pos < s.text.len && s.text[pos].is_space() { - pos++ - } - if pos >= s.text.len { - return '' - } - // Return if `(` is not the first character after "fn ..." - if s.text[pos] != `(` { - return '' - } - // Search for closing parenthesis - for pos < s.text.len && s.text[pos] != `)` { - pos++ - } - if pos >= s.text.len { - return '' - } - pos-- - // Search backwards for end position of struct name - // Eat whitespaces - for pos > start && s.text[pos].is_space() { - pos-- - } - if pos < start { - return '' - } - end_pos := pos + 1 - // Go back while we have a name character or digit - for pos > start && (util.is_name_char(s.text[pos]) || s.text[pos].is_digit()) { - pos-- - } - if pos < start { - return '' - } - start_pos := pos + 1 - if s.text[start_pos].is_digit() || end_pos > s.text.len || end_pos <= start_pos || - end_pos <= start || start_pos <= start { - return '' - } - struct_name := s.text[start_pos..end_pos] - return struct_name -} - fn filter_num_sep(txt byteptr, start int, end int) string { unsafe { mut b := malloc(end - start + 1) // add a byte for the endstring 0 @@ -699,12 +562,6 @@ fn (mut s Scanner) text_scan() token.Token { next_char := s.look_ahead(1) kind := token.keywords[name] if kind != .unknown { - if kind == .key_fn { - s.struct_name = s.ident_struct_name() - s.fn_name = s.ident_fn_name() - } else if kind == .key_module { - s.mod_name = s.ident_mod_name() - } return s.new_token(kind, name, name.len) } // 'asdf $b' => "b" is the last name in the string, dont start parsing string @@ -898,61 +755,9 @@ fn (mut s Scanner) text_scan() token.Token { if s.is_fmt { return s.new_token(.name, '@' + name, name.len + 1) } - // @FN => will be substituted with the name of the current V function - // @MOD => will be substituted with the name of the current V module - // @STRUCT => will be substituted with the name of the current V struct - // @VEXE => will be substituted with the path to the V compiler - // @FILE => will be substituted with the path of the V source file - // @LINE => will be substituted with the V line number where it appears (as a string). - // @COLUMN => will be substituted with the column where it appears (as a string). - // @VHASH => will be substituted with the shortened commit hash of the V compiler (as a string). - // @VMOD_FILE => will be substituted with the contents of the nearest v.mod file (as a string). - // This allows things like this: - // println( 'file: ' + @FILE + ' | line: ' + @LINE + ' | fn: ' + @MOD + '.' + @FN) - // ... which is useful while debugging/tracing - if name == 'FN' { - return s.new_token(.string, s.fn_name, 3) - } - if name == 'MOD' { - return s.new_token(.string, s.mod_name, 4) - } - if name == 'STRUCT' { - return s.new_token(.string, s.struct_name, 7) - } - if name == 'VEXE' { - vexe := pref.vexe_path() - return s.new_token(.string, util.cescaped_path(vexe), 5) - } - if name == 'FILE' { - fpath := os.real_path(s.file_path) - return s.new_token(.string, util.cescaped_path(fpath), 5) - } - if name == 'LINE' { - return s.new_token(.string, (s.line_nr + 1).str(), 5) - } - if name == 'COLUMN' { - return s.new_token(.string, s.current_column().str(), 7) - } - if name == 'VHASH' { - return s.new_token(.string, util.vhash(), 6) - } - if name == 'VMOD_FILE' { - if s.vmod_file_content.len == 0 { - mut mcache := vmod.get_cache() - vmod_file_location := mcache.get_by_file(s.file_path) - if vmod_file_location.vmod_file.len == 0 { - s.error('@VMOD_FILE can be used only in projects, that have v.mod file') - } - vmod_content := os.read_file(vmod_file_location.vmod_file) or { - '' - } - $if windows { - s.vmod_file_content = vmod_content.replace('\r\n', '\n') - } $else { - s.vmod_file_content = vmod_content - } - } - return s.new_token(.string, s.vmod_file_content, 10) + // @FN, @STRUCT, @MOD etc. See full list in token.valid_at_tokens + if '@' + name in token.valid_at_tokens { + return s.new_token(.at, '@' + name, name.len + 1) } if !token.is_key(name) { s.error('@ must be used before keywords (e.g. `@type string`)') diff --git a/vlib/v/scanner/scanner_at_literals_test.v b/vlib/v/scanner/scanner_at_literals_test.v index 3e4c886579..ecbd1bf72d 100644 --- a/vlib/v/scanner/scanner_at_literals_test.v +++ b/vlib/v/scanner/scanner_at_literals_test.v @@ -20,6 +20,29 @@ fn (mut t TestStruct) test_struct_w_high_order(cb fn (int) string) string { return 'test' + cb(2) } +struct Abc { +} + +fn (a Another) method() string { + println(@STRUCT) + return @STRUCT +} + +struct Another { +} + +fn (a Abc) method() string { + println(@STRUCT) + return @STRUCT +} + +fn test_at_struct_ordering() { + a := Abc{} + assert a.method() == 'Abc' + b := Another{} + assert b.method() == 'Another' +} + struct TestFn { } @@ -44,7 +67,15 @@ fn fn_name_mod_level_high_order(cb fn (int)) { fn test_at_file() { // Test @FILE f := os.file_name(@FILE) - assert f == 'scanner_at_literals_test.v' + $if windows { + // TODO all after Drive letter + // no_drive := f.all_after(':') + // TODO assert the variable name on Windows??? + // assert no_drive == 'scanner_at_literals_test.v' + assert true + } $else { + assert f == 'scanner_at_literals_test.v' + } } fn test_at_fn() { diff --git a/vlib/v/token/token.v b/vlib/v/token/token.v index 33489bbbc2..1585eeb077 100644 --- a/vlib/v/token/token.v +++ b/vlib/v/token/token.v @@ -43,12 +43,12 @@ pub enum Kind { amp hash dollar + at // @ str_dollar left_shift right_shift not_in // !in not_is // !is - // at // @ assign // = decl_assign // := plus_assign // += @@ -137,6 +137,40 @@ const ( .right_shift_assign, .left_shift_assign] nr_tokens = int(Kind._end_) ) + +// @FN => will be substituted with the name of the current V function +// @MOD => will be substituted with the name of the current V module +// @STRUCT => will be substituted with the name of the current V struct +// @VEXE => will be substituted with the path to the V compiler +// @FILE => will be substituted with the path of the V source file +// @LINE => will be substituted with the V line number where it appears (as a string). +// @COLUMN => will be substituted with the column where it appears (as a string). +// @VHASH => will be substituted with the shortened commit hash of the V compiler (as a string). +// @VMOD_FILE => will be substituted with the contents of the nearest v.mod file (as a string). +// This allows things like this: +// println( 'file: ' + @FILE + ' | line: ' + @LINE + ' | fn: ' + @MOD + '.' + @FN) +// ... which is useful while debugging/tracing +// +// @VROOT is special and handled in places like '#include ...' +// @ is allowed for keyword variable names. E.g. 'type' +pub enum AtKind { + unknown + fn_name + mod_name + struct_name + vexe_path + file_path + line_nr + column_nr + vhash + vmod_file + _end_ +} +const ( + valid_at_tokens = ['@FN','@MOD','@STRUCT','@VEXE','@FILE','@LINE','@COLUMN','@VHASH','@VMOD_FILE'] + //valid_at_tokens_len = int(AtKind._end_) +) + // build_keys genereates a map with keywords' string values: // Keywords['return'] == .key_return fn build_keys() map[string]Kind { @@ -178,7 +212,6 @@ fn build_token_str() []string { s[Kind.comma] = ',' s[Kind.not_in] = '!in' s[Kind.not_is] = '!is' - // s[Kind.at] = '@' s[Kind.semicolon] = ';' s[Kind.colon] = ':' s[Kind.arrow] = '<-' @@ -212,6 +245,7 @@ fn build_token_str() []string { s[Kind.comment] = '// comment' s[Kind.nl] = 'NLL' s[Kind.dollar] = '$' + s[Kind.at] = '@' s[Kind.str_dollar] = '$2' s[Kind.key_assert] = 'assert' s[Kind.key_struct] = 'struct' diff --git a/vlib/v/util/errors.v b/vlib/v/util/errors.v index 214e00ea34..2f266c3149 100644 --- a/vlib/v/util/errors.v +++ b/vlib/v/util/errors.v @@ -79,18 +79,7 @@ pub fn formatted_error(kind string, omsg string, filepath string, pos token.Posi } } // - source := read_file(filepath) or { - '' - } - mut p := imax(0, imin(source.len - 1, pos.pos)) - if source.len > 0 { - for ; p >= 0; p-- { - if source[p] == `\r` || source[p] == `\n` { - break - } - } - } - column := imax(0, pos.pos - p - 1) + source, column := filepath_pos_to_source_and_column(filepath, pos) position := '$path:${pos.line_nr + 1}:${imax(1, column + 1)}:' scontext := source_context(kind, source, column, pos).join('\n') final_position := bold(position) @@ -101,6 +90,24 @@ pub fn formatted_error(kind string, omsg string, filepath string, pos token.Posi return '$final_position $final_kind $final_msg$final_context'.trim_space() } +pub fn filepath_pos_to_source_and_column(filepath string, pos token.Position) (string, int) { + // TODO: optimize this; may be use a cache. + // The column should not be so computationally hard to get. + source := read_file(filepath) or { + '' + } + mut p := imax(0, imin(source.len - 1, pos.pos)) + if source.len > 0 { + for ; p >= 0; p-- { + if source[p] == `\n` || source[p] == `\r` { + break + } + } + } + column := imax(0, pos.pos - p - 1) + return source, column +} + pub fn source_context(kind string, source string, column int, pos token.Position) []string { mut clines := []string{} if source.len == 0 {