From d5665997e0cf39c59d71328c15c570a5c331e449 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sat, 7 Sep 2019 13:44:41 +0300 Subject: [PATCH] compiler: @FILE, @LINE, @FN, @COLUMN --- compiler/comptime.v | 2 +- compiler/fn.v | 4 ++-- compiler/parser.v | 34 ++++++++++++++++++++++++++-------- compiler/scanner.v | 18 +++++++++++++++++- 4 files changed, 46 insertions(+), 12 deletions(-) diff --git a/compiler/comptime.v b/compiler/comptime.v index aebeb7103e..5239d85b91 100644 --- a/compiler/comptime.v +++ b/compiler/comptime.v @@ -115,7 +115,7 @@ fn (p mut Parser) comp_time() { os.rm('.vwebtmpl.v') } pp.is_vweb = true - pp.cur_fn = p.cur_fn // give access too all variables in current function + pp.set_current_fn( p.cur_fn ) // give access too all variables in current function pp.parse(.main) tmpl_fn_body := p.cgen.lines.slice(pos + 2, p.cgen.lines.len).join('\n').clone() end_pos := tmpl_fn_body.last_index('Builder_str( sb )') + 19 // TODO diff --git a/compiler/fn.v b/compiler/fn.v index 2e30c36172..f85b44eded 100644 --- a/compiler/fn.v +++ b/compiler/fn.v @@ -284,7 +284,7 @@ fn (p mut Parser) fn_decl() { '' } if !p.is_vweb { - p.cur_fn = f + p.set_current_fn( f ) } // Generate `User_register()` instead of `register()` // Internally it's still stored as "register" in type User @@ -492,7 +492,7 @@ _thread_so = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)&reload_so, 0, 0, 0); return } p.check_unused_variables() - p.cur_fn = EmptyFn + p.set_current_fn( EmptyFn ) p.returns = false if !is_generic { p.genln('}') diff --git a/compiler/parser.v b/compiler/parser.v index d058519beb..8892a4d42a 100644 --- a/compiler/parser.v +++ b/compiler/parser.v @@ -119,6 +119,11 @@ fn (v mut V) new_parser(path string) Parser { return p } +fn (p mut Parser) set_current_fn(f &Fn) { + p.cur_fn = f + p.scanner.fn_name = '${f.mod}.${f.name}' +} + fn (p mut Parser) next() { p.prev_tok2 = p.prev_tok p.prev_tok = p.tok @@ -262,7 +267,7 @@ fn (p mut Parser) parse(pass Pass) { case Token.eof: p.log('end of parse()') if p.is_script && !p.pref.is_test { - p.cur_fn = MainFn + p.set_current_fn( MainFn ) p.check_unused_variables() } if false && !p.first_pass() && p.fileis('main.v') { @@ -281,12 +286,12 @@ fn (p mut Parser) parse(pass Pass) { // we need to set it to save and find variables if p.first_pass() { if p.cur_fn.name == '' { - p.cur_fn = MainFn + p.set_current_fn( MainFn ) } return } if p.cur_fn.name == '' { - p.cur_fn = MainFn + p.set_current_fn( MainFn ) if p.pref.is_repl { p.cur_fn.clear_vars() } @@ -2532,16 +2537,28 @@ fn (p mut Parser) string_expr() { // Custom format? ${t.hour:02d} custom := p.tok == .colon if custom { - format += '%' + mut cformat := '' p.next() if p.tok == .dot { - format += '.' + cformat += '.' p.next() } - format += p.lit// 02 + if p.tok == .minus { // support for left aligned formatting + cformat += '-' + p.next() + } + cformat += p.lit// 02 p.next() - format += p.lit// f - // println('custom str F=$format') + fspec := p.lit // f + cformat += fspec + if fspec == 's' { + //println('custom str F=$cformat | format_specifier: "$fspec" | typ: $typ ') + if typ != 'string' { + p.error('only v strings can be formatted with a :${cformat} format, but you have given "${val}", which has type ${typ}.') + } + args = args.all_before_last('${val}.len, ${val}.str') + '${val}.str' + } + format += '%$cformat' p.next() } else { @@ -2563,6 +2580,7 @@ fn (p mut Parser) string_expr() { } format += f } + //println('interpolation format is: |${format}| args are: |${args}| ') } if complex_inter { p.fgen('}') diff --git a/compiler/scanner.v b/compiler/scanner.v index f96917eac4..c771f269a2 100644 --- a/compiler/scanner.v +++ b/compiler/scanner.v @@ -24,6 +24,7 @@ mut: fmt_indent int fmt_line_empty bool prev_tok Token + fn_name string // needed for @FN } fn new_scanner(file_path string) &Scanner { @@ -394,6 +395,17 @@ fn (s mut Scanner) scan() ScanRes { case `@`: s.pos++ name := s.ident_name() + // @FN => will be substituted with the name of the current V function + // @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). + // This allows things like this: + // println( 'file: ' + @FILE + ' | line: ' + @LINE + ' | fn: ' + @FN) + // ... which is useful while debugging/tracing + if name == 'FN' { return scan_res(.str, s.fn_name) } + if name == 'FILE' { return scan_res(.str, os.realpath(s.file_path)) } + if name == 'LINE' { return scan_res(.str, (s.line_nr+1).str()) } + if name == 'COLUMN' { return scan_res(.str, (s.current_column()).str()) } if !is_key(name) { s.error('@ must be used before keywords (e.g. `@type string`)') } @@ -580,9 +592,13 @@ fn (s &Scanner) find_current_line_start_position() int { return linestart } +fn (s &Scanner) current_column() int { + return s.pos - s.find_current_line_start_position() +} + fn (s &Scanner) error(msg string) { fullpath := os.realpath( s.file_path ) - column := s.pos - s.find_current_line_start_position() + column := s.current_column() // 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