check-expect.lua revision 1.2 1 #! /usr/bin/lua
2 -- $NetBSD: check-expect.lua,v 1.2 2022/01/29 00:52:53 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 if mk_line:match("^#%s*expect%-reset$") then
86 prev_expect_line = 0
87 end
88
89 ---@param text string
90 for offset, text in mk_line:gmatch("#%s*expect([+%-]%d+):%s*(.*)") do
91 local location = ("%s:%d"):format(mk_fname, mk_lineno + tonumber(offset))
92
93 local found = false
94 if by_location[location] ~= nil then
95 for i, message in ipairs(by_location[location]) do
96 if message ~= "" and message:find(text, 1, true) then
97 by_location[location][i] = ""
98 found = true
99 break
100 end
101 end
102 end
103
104 if not found then
105 print_error("error: %s:%d: %s must contain '%s'",
106 mk_fname, mk_lineno, exp_fname, text)
107 end
108 end
109 end
110 end
111
112 for _, fname in ipairs(arg) do
113 check_mk(fname)
114 end
115 os.exit(not had_errors)
116