vis

a vi-like editor based on Plan 9's structural regular expressions

git clone https://9o.is/git/vis.git

output.lua

(4567B)


      1 -- Copyright 2022-2025 Mitchell. See LICENSE.
      2 -- LPeg lexer for tool output.
      3 -- If a warning or error is recognized, tags its filename, line, column (if available),
      4 -- and message, and sets the line state to 1 for an error (first bit), and 2 for a warning
      5 -- (second bit).
      6 -- This is similar to Lexilla's errorlist lexer.
      7 
      8 local lexer = lexer
      9 local starts_line = lexer.starts_line
     10 local P, S, R = lpeg.P, lpeg.S, lpeg.R
     11 
     12 local lex = lexer.new(..., {lex_by_line = true})
     13 
     14 -- Tags a pattern as plain text.
     15 local function text(patt) return lex:tag(lexer.DEFAULT, patt) end
     16 
     17 -- Tags a pattern as a filename.
     18 local function filename(patt) return lex:tag('filename', patt) end
     19 
     20 -- Typical line and column number patterns.
     21 local line = text('line ')^-1 * lex:tag('line', lexer.dec_num)
     22 local column = lex:tag('column', lexer.dec_num)
     23 
     24 -- Tags a pattern as an error/warning/etc. message.
     25 local function message(patt) return lex:tag('message', patt) end
     26 
     27 -- Immediately marks the current line as an error.
     28 -- This should only be specified at the end of a rule, or else LPeg may backtrack and mistakenly
     29 -- mark a non-error line.
     30 local function mark_error(_, pos)
     31 	lexer.line_state[lexer.line_from_position(pos)] = 1
     32 	return true
     33 end
     34 
     35 -- Immediately marks the current line as a warning.
     36 -- This should only be specified at the end of a rule, or else LPeg may backtrack and mistakenly
     37 -- mark a non-warning line.
     38 local function mark_warning(_, pos)
     39 	lexer.line_state[lexer.line_from_position(pos)] = 2
     40 	return true
     41 end
     42 
     43 -- filename:line: message (ruby)
     44 -- filename:line:col: message (c, cpp, go, ...)
     45 -- filename: line X: message (bash)
     46 local c_filename = filename((lexer.nonnewline - ':')^1)
     47 local colon = text(':' * P(' ')^-1)
     48 local warning = message(lexer.to_eol('warning: ')) * mark_warning
     49 local note = message(lexer.to_eol('note: ')) -- do not mark
     50 local error = message(lexer.to_eol()) * mark_error
     51 lex:add_rule('common', starts_line(c_filename) * colon * line * colon * (column * colon)^-1 *
     52 	(warning + note + error))
     53 
     54 -- prog: filename:line: message (awk, lua)
     55 -- /usr/bin/prog: filename:line: message
     56 lex:add_rule('prog',
     57 	starts_line(text(lexer.word + '/' * (lexer.any - ':')^1)) * colon * c_filename * colon * line *
     58 		colon * (warning + error))
     59 
     60 -- File "filename", line X (python)
     61 local py_filename = filename((lexer.nonnewline - '"')^1)
     62 lex:add_rule('python',
     63 	starts_line(text('File "'), true) * py_filename * text('", ') * line * mark_error)
     64 
     65 -- filename(line): error: message (d, cuda)
     66 local lparen, rparen = text('('), text(')')
     67 local d_filename = filename((lexer.nonnewline - '(')^1)
     68 local d_error = message(lexer.to_eol(S('Ee') * 'rror')) * mark_error
     69 lex:add_rule('d', starts_line(d_filename) * lparen * line * rparen * colon * d_error)
     70 
     71 -- "filename" line X: message (gnuplot)
     72 local gp_filename = filename((lexer.nonnewline - '"')^1)
     73 lex:add_rule('gnuplot', starts_line(text('"')) * gp_filename * text('" ') * line * colon * error)
     74 
     75 -- at com.path(filename:line) (java)
     76 lex:add_rule('java',
     77 	starts_line(text('at ' * (lexer.nonnewline - '(')^1), true) * lparen * c_filename * colon * line *
     78 		rparen * mark_error)
     79 
     80 -- message in filename on line X (php)
     81 lex:add_rule('php', starts_line(message((lexer.nonnewline - ' in ')^1)) * text(' in ') *
     82 	filename((lexer.nonnewline - ' on ')^1) * text(' on ') * line * mark_error)
     83 
     84 -- filename(line, col): message (vb, csharp, fsharp, ...)
     85 lex:add_rule('vb',
     86 	starts_line(filename((lexer.nonnewline - '(')^1)) * lparen * line * text(', ') * column * rparen *
     87 		colon * error)
     88 
     89 -- message at filename line X (perl)
     90 lex:add_rule('perl', starts_line(message((lexer.nonnewline - ' at ')^1)) * text(' at ') *
     91 	filename((lexer.nonnewline - ' line ')^1) * text(' line ') * line * mark_error)
     92 
     93 -- CMake Error at filename:line: (cmake)
     94 lex:add_rule('cmake',
     95 	starts_line(text('CMake Error at ')) * c_filename * colon * line * colon * mark_error)
     96 
     97 -- CSI sequences, including colors.
     98 local csi = P('\x1B[')
     99 local non_csi_seq = text((lexer.nonnewline - csi)^1)
    100 local colors = {'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'}
    101 local csi_color = P(false)
    102 for i, name in ipairs(colors) do
    103 	local bold, color = '1;', tostring(30 + i - 1)
    104 	csi_color = csi_color +
    105 		(lex:tag('csi', csi * bold * color * 'm') * lex:tag('csi.' .. name .. '.bright', non_csi_seq)) +
    106 		(lex:tag('csi', csi * color * 'm') * lex:tag('csi.' .. name, non_csi_seq))
    107 end
    108 local csi_seq = #csi * (csi_color + lex:tag('csi', csi * (lexer.nonnewline - R('@~'))^0 * R('@~')))
    109 
    110 lex:add_rule('any_line', (non_csi_seq + csi_seq)^1)
    111 
    112 return lex