check-msgs.lua revision 1.19 1 #! /usr/bin/lua
2 -- $NetBSD: check-msgs.lua,v 1.19 2023/07/10 11:46:14 rillig Exp $
3
4 --[[
5
6 usage: lua ./check-msgs.lua *.c *.y
7
8 Check that the message text in the comments of the C source code matches the
9 actual user-visible message text in err.c.
10
11 ]]
12
13
14 local function load_messages()
15 local msgs = {} ---@type table<string>string
16
17 local f = assert(io.open("err.c"))
18 for line in f:lines() do
19 local msg, id = line:match("%s*\"(.+)\",%s*/%*%s*(Q?%d+)%s*%*/$")
20 if msg ~= nil then
21 msgs[id] = msg
22 end
23 end
24
25 f:close()
26
27 return msgs
28 end
29
30
31 local had_errors = false
32 ---@param fmt string
33 function print_error(fmt, ...)
34 print(fmt:format(...))
35 had_errors = true
36 end
37
38
39 local function check_message(fname, lineno, id, comment, msgs)
40 local msg = msgs[id]
41
42 if msg == nil then
43 print_error("%s:%d: id=%s not found", fname, lineno, id)
44 return
45 end
46
47 msg = msg:gsub("/%*", "**")
48 msg = msg:gsub("%*/", "**")
49 msg = msg:gsub("\\(.)", "%1")
50
51 if comment == msg then
52 return
53 end
54
55 local prefix = comment:match("^(.-)%s*%.%.%.$")
56 if prefix ~= nil and msg:find(prefix, 1, 1) == 1 then
57 return
58 end
59
60 print_error("%s:%d: id=%-3s msg=%-40s comment=%s",
61 fname, lineno, id, msg, comment)
62 end
63
64 local message_prefix = {
65 error = "",
66 error_at = "",
67 warning = "",
68 warning_at = "",
69 query_message = "Q",
70 c99ism = "",
71 c11ism = "",
72 c23ism = "",
73 gnuism = "",
74 }
75
76 local function check_file(fname, msgs)
77 local f = assert(io.open(fname, "r"))
78 local lineno = 0
79 local prev = ""
80 for line in f:lines() do
81 lineno = lineno + 1
82
83 local func, id = line:match("^%s+([%w_]+)%((%d+)[),]")
84 local prefix = message_prefix[func]
85 if prefix then
86 id = prefix .. id
87 local comment = prev:match("^%s+/%* (.+) %*/$")
88 if comment ~= nil then
89 check_message(fname, lineno, id, comment, msgs)
90 else
91 print_error("%s:%d: missing comment for %s: /* %s */",
92 fname, lineno, id, msgs[id])
93 end
94 end
95
96 prev = line
97 end
98
99 f:close()
100 end
101
102
103 local function file_contains(filename, text)
104 local f = assert(io.open(filename, "r"))
105 local found = f:read("a"):find(text, 1, true)
106 f:close()
107 return found
108 end
109
110
111 -- Ensure that each test file for a particular message mentions the full text
112 -- of that message and the message ID.
113 local function check_test_files(msgs)
114 local testdir = "../../../tests/usr.bin/xlint/lint1"
115 local cmd = ("cd '%s' && printf '%%s\\n' msg_[0-9][0-9][0-9]*.c"):format(testdir)
116 local filenames = assert(io.popen(cmd))
117 for filename in filenames:lines() do
118 local msgid = filename:match("^msg_(%d%d%d)")
119 if msgs[msgid] then
120 local unescaped_msg = msgs[msgid]:gsub("\\(.)", "%1")
121 local expected_text = ("%s [%s]"):format(unescaped_msg, msgid)
122 local fullname = ("%s/%s"):format(testdir, filename)
123 if not file_contains(fullname, expected_text) then
124 print_error("%s must contain: %s", fullname, expected_text)
125 end
126 end
127 end
128 filenames:close()
129 end
130
131 local function check_yacc_file(filename)
132 local decl = {}
133 local f = assert(io.open(filename, "r"))
134 local lineno = 0
135 for line in f:lines() do
136 lineno = lineno + 1
137 local type = line:match("^%%type%s+<[%w_]+>%s+(%S+)$") or
138 line:match("^/%* No type for ([%w_]+)%. %*/$")
139 if type then
140 decl[type] = lineno
141 end
142 local rule = line:match("^([%w_]+):")
143 if rule then
144 if decl[rule] then
145 decl[rule] = nil
146 else
147 print_error("%s:%d: missing type declaration for rule %q",
148 filename, lineno, rule)
149 end
150 end
151 end
152 for rule, decl_lineno in pairs(decl) do
153 print_error("%s:%d: missing rule %q", filename, decl_lineno, rule)
154 end
155 f:close()
156 end
157
158 local function main(arg)
159 local msgs = load_messages()
160 for _, fname in ipairs(arg) do
161 check_file(fname, msgs)
162 if fname:match("%.y$") then
163 check_yacc_file(fname)
164 end
165 end
166 check_test_files(msgs)
167 end
168
169 main(arg)
170 os.exit(not had_errors)
171