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