1#! /usr/bin/lua 2-- $NetBSD: check-msgs.lua,v 1.22 2024/03/01 17:22:55 rillig Exp $ 3 4--[[ 5 6usage: lua ./check-msgs.lua *.c *.y 7 8Check that the message text in the comments of the C source code matches the 9actual user-visible message text in err.c. 10 11]] 12 13 14local 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*// (Q?%d+)$") 20 if msg ~= nil then 21 msgs[id] = msg 22 end 23 end 24 25 f:close() 26 27 return msgs 28end 29 30 31local had_errors = false 32---@param fmt string 33function print_error(fmt, ...) 34 print(fmt:format(...)) 35 had_errors = true 36end 37 38 39local 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) 62end 63 64local 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 76local 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() 100end 101 102 103local 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 108end 109 110 111-- Ensure that each test file for a particular message mentions the full text 112-- of that message and the message ID. 113local 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 = ("Test for message: %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() 129end 130 131local function check_yacc_file(filename) 132 local decl = {} 133 local decl_list = {} 134 local decl_list_index = 1 135 local f = assert(io.open(filename, "r")) 136 local lineno = 0 137 for line in f:lines() do 138 lineno = lineno + 1 139 local type = line:match("^%%type%s+<[%w_]+>%s+(%S+)$") or 140 line:match("^/%* No type for ([%w_]+)%. %*/$") 141 if type then 142 if decl[type] then 143 print_error("%s:%d: duplicate type declaration for rule %q", 144 filename, lineno, type) 145 end 146 decl[type] = lineno 147 table.insert(decl_list, { lineno = lineno, rule = type }) 148 end 149 local rule = line:match("^([%w_]+):") 150 if rule then 151 if decl[rule] then 152 decl[rule] = nil 153 else 154 print_error("%s:%d: missing type declaration for rule %q", 155 filename, lineno, rule) 156 end 157 if decl_list_index > 0 then 158 local expected = decl_list[decl_list_index] 159 if expected.rule == rule then 160 decl_list_index = decl_list_index + 1 161 else 162 print_error("%s:%d: expecting rule %q (from line %d), got %q", 163 filename, lineno, expected.rule, expected.lineno, rule) 164 decl_list_index = 0 165 end 166 end 167 end 168 end 169 for rule, decl_lineno in pairs(decl) do 170 print_error("%s:%d: missing rule %q", filename, decl_lineno, rule) 171 end 172 f:close() 173end 174 175local function main(arg) 176 local msgs = load_messages() 177 for _, fname in ipairs(arg) do 178 check_file(fname, msgs) 179 if fname:match("%.y$") then 180 check_yacc_file(fname) 181 end 182 end 183 check_test_files(msgs) 184end 185 186main(arg) 187os.exit(not had_errors) 188