1#! /usr/bin/lua 2-- $NetBSD: check-expect.lua,v 1.3 2022/04/15 09:33:20 rillig Exp $ 3 4--[[ 5 6usage: lua ./check-expect.lua *.mk 7 8Check that each text from an '# expect: ...' comment in the .mk source files 9occurs in the corresponding .exp file, in the same order as in the .mk file. 10 11Check that each text from an '# expect[+-]offset: ...' comment in the .mk 12source files occurs in the corresponding .exp file and refers back to the 13correct line in the .mk file. 14 15]] 16 17 18local had_errors = false 19---@param fmt string 20function print_error(fmt, ...) 21 print(fmt:format(...)) 22 had_errors = true 23end 24 25 26---@return nil | string[] 27local function load_lines(fname) 28 local lines = {} 29 30 local f = io.open(fname, "r") 31 if f == nil then return nil end 32 33 for line in f:lines() do 34 table.insert(lines, line) 35 end 36 f:close() 37 38 return lines 39end 40 41 42---@param exp_lines string[] 43local function collect_lineno_diagnostics(exp_lines) 44 ---@type table<string, string[]> 45 local by_location = {} 46 47 for _, line in ipairs(exp_lines) do 48 ---@type string | nil, string, string 49 local l_fname, l_lineno, l_msg = 50 line:match("^make: \"([^\"]+)\" line (%d+): (.*)") 51 if l_fname ~= nil then 52 local location = ("%s:%d"):format(l_fname, l_lineno) 53 if by_location[location] == nil then 54 by_location[location] = {} 55 end 56 table.insert(by_location[location], l_msg) 57 end 58 end 59 60 return by_location 61end 62 63 64local function check_mk(mk_fname) 65 local exp_fname = mk_fname:gsub("%.mk$", ".exp") 66 local mk_lines = load_lines(mk_fname) 67 local exp_lines = load_lines(exp_fname) 68 if exp_lines == nil then return end 69 local by_location = collect_lineno_diagnostics(exp_lines) 70 local prev_expect_line = 0 71 72 for mk_lineno, mk_line in ipairs(mk_lines) do 73 for text in mk_line:gmatch("#%s*expect:%s*(.*)") do 74 local i = prev_expect_line 75 -- As of 2022-04-15, some lines in the .exp files contain trailing 76 -- whitespace. If possible, this should be avoided by rewriting the 77 -- debug logging. When done, the gsub can be removed. 78 -- See deptgt-phony.exp lines 14 and 15. 79 while i < #exp_lines and text ~= exp_lines[i + 1]:gsub("%s*$", "") do 80 i = i + 1 81 end 82 if i < #exp_lines then 83 prev_expect_line = i + 1 84 else 85 print_error("error: %s:%d: '%s:%d+' must contain '%s'", 86 mk_fname, mk_lineno, exp_fname, prev_expect_line + 1, text) 87 end 88 end 89 if mk_line:match("^#%s*expect%-reset$") then 90 prev_expect_line = 0 91 end 92 93 ---@param text string 94 for offset, text in mk_line:gmatch("#%s*expect([+%-]%d+):%s*(.*)") do 95 local location = ("%s:%d"):format(mk_fname, mk_lineno + tonumber(offset)) 96 97 local found = false 98 if by_location[location] ~= nil then 99 for i, message in ipairs(by_location[location]) do 100 if message ~= "" and message:find(text, 1, true) then 101 by_location[location][i] = "" 102 found = true 103 break 104 end 105 end 106 end 107 108 if not found then 109 print_error("error: %s:%d: %s must contain '%s'", 110 mk_fname, mk_lineno, exp_fname, text) 111 end 112 end 113 end 114end 115 116for _, fname in ipairs(arg) do 117 check_mk(fname) 118end 119os.exit(not had_errors) 120