check-expect.lua revision 1.1 1 #! /usr/bin/lua
2 -- $NetBSD: check-expect.lua,v 1.1 2022/01/15 12:35:18 rillig Exp $
3
4 --[[
5
6 usage: lua ./check-expect.lua *.mk
7
8 Check that each text from an '# expect: ...' comment in the .mk source files
9 occurs in the corresponding .exp file, in the same order as in the .mk file.
10
11 Check that each text from an '# expect[+-]offset: ...' comment in the .mk
12 source files occurs in the corresponding .exp file and refers back to the
13 correct line in the .mk file.
14
15 ]]
16
17
18 local had_errors = false
19 ---@param fmt string
20 function print_error(fmt, ...)
21 print(fmt:format(...))
22 had_errors = true
23 end
24
25
26 ---@return nil | string[]
27 local 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
39 end
40
41
42 ---@param exp_lines string[]
43 local 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
61 end
62
63
64 local 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 while i < #exp_lines and text ~= exp_lines[i + 1] do
76 i = i + 1
77 end
78 if i < #exp_lines then
79 prev_expect_line = i + 1
80 else
81 print_error("error: %s:%d: '%s:%d+' must contain '%s'",
82 mk_fname, mk_lineno, exp_fname, prev_expect_line + 1, text)
83 end
84 end
85
86 ---@param text string
87 for offset, text in mk_line:gmatch("#%s*expect([+%-]%d+):%s*(.*)") do
88 local location = ("%s:%d"):format(mk_fname, mk_lineno + tonumber(offset))
89
90 local found = false
91 if by_location[location] ~= nil then
92 for i, message in ipairs(by_location[location]) do
93 if message ~= "" and message:find(text, 1, true) then
94 by_location[location][i] = ""
95 found = true
96 break
97 end
98 end
99 end
100
101 if not found then
102 print_error("error: %s:%d: %s must contain '%s'",
103 mk_fname, mk_lineno, exp_fname, text)
104 end
105 end
106 end
107 end
108
109 for _, fname in ipairs(arg) do
110 check_mk(fname)
111 end
112 os.exit(not had_errors)
113