12e28c523Srillig#! /usr/bin/lua 2*9e211f35Srillig-- $NetBSD: check-msgs.lua,v 1.22 2024/03/01 17:22:55 rillig Exp $ 32e28c523Srillig 42e28c523Srillig--[[ 52e28c523Srillig 639a12fe1Srilligusage: lua ./check-msgs.lua *.c *.y 72e28c523Srillig 8cf4174e4SrilligCheck that the message text in the comments of the C source code matches the 9cf4174e4Srilligactual user-visible message text in err.c. 102e28c523Srillig 112e28c523Srillig]] 122e28c523Srillig 132e28c523Srillig 1468e4158dSrilliglocal function load_messages() 1568e4158dSrillig local msgs = {} ---@type table<string>string 162e28c523Srillig 1768e4158dSrillig local f = assert(io.open("err.c")) 182e28c523Srillig for line in f:lines() do 19bc8fe445Srillig local msg, id = line:match("%s*\"(.+)\",%s*// (Q?%d+)$") 202e28c523Srillig if msg ~= nil then 2168e4158dSrillig msgs[id] = msg 222e28c523Srillig end 232e28c523Srillig end 242e28c523Srillig 252e28c523Srillig f:close() 262e28c523Srillig 272e28c523Srillig return msgs 282e28c523Srilligend 292e28c523Srillig 302e28c523Srillig 314d5c51ccSrilliglocal had_errors = false 324d5c51ccSrillig---@param fmt string 334d5c51ccSrilligfunction print_error(fmt, ...) 344d5c51ccSrillig print(fmt:format(...)) 354d5c51ccSrillig had_errors = true 364d5c51ccSrilligend 374d5c51ccSrillig 384d5c51ccSrillig 394d5c51ccSrilliglocal function check_message(fname, lineno, id, comment, msgs) 402e28c523Srillig local msg = msgs[id] 412e28c523Srillig 422e28c523Srillig if msg == nil then 4368e4158dSrillig print_error("%s:%d: id=%s not found", fname, lineno, id) 442e28c523Srillig return 452e28c523Srillig end 462e28c523Srillig 4790c29d84Srillig msg = msg:gsub("/%*", "**") 4890c29d84Srillig msg = msg:gsub("%*/", "**") 4990c29d84Srillig msg = msg:gsub("\\(.)", "%1") 50cf4174e4Srillig 512e28c523Srillig if comment == msg then 522e28c523Srillig return 532e28c523Srillig end 542e28c523Srillig 55d4b0f2feSrillig local prefix = comment:match("^(.-)%s*%.%.%.$") 56d4b0f2feSrillig if prefix ~= nil and msg:find(prefix, 1, 1) == 1 then 572e28c523Srillig return 582e28c523Srillig end 592e28c523Srillig 6068e4158dSrillig print_error("%s:%d: id=%-3s msg=%-40s comment=%s", 612e28c523Srillig fname, lineno, id, msg, comment) 622e28c523Srilligend 632e28c523Srillig 64715e58e7Srilliglocal message_prefix = { 65715e58e7Srillig error = "", 66715e58e7Srillig error_at = "", 67715e58e7Srillig warning = "", 68715e58e7Srillig warning_at = "", 69715e58e7Srillig query_message = "Q", 70715e58e7Srillig c99ism = "", 71715e58e7Srillig c11ism = "", 7229b0195fSrillig c23ism = "", 73715e58e7Srillig gnuism = "", 7441c1949fSrillig} 752e28c523Srillig 764d5c51ccSrilliglocal function check_file(fname, msgs) 772e28c523Srillig local f = assert(io.open(fname, "r")) 782e28c523Srillig local lineno = 0 792e28c523Srillig local prev = "" 802e28c523Srillig for line in f:lines() do 812e28c523Srillig lineno = lineno + 1 822e28c523Srillig 8341c1949fSrillig local func, id = line:match("^%s+([%w_]+)%((%d+)[),]") 84715e58e7Srillig local prefix = message_prefix[func] 85715e58e7Srillig if prefix then 86715e58e7Srillig id = prefix .. id 87d4b0f2feSrillig local comment = prev:match("^%s+/%* (.+) %*/$") 882e28c523Srillig if comment ~= nil then 894d5c51ccSrillig check_message(fname, lineno, id, comment, msgs) 90f78ee5c0Srillig else 9168e4158dSrillig print_error("%s:%d: missing comment for %s: /* %s */", 92f78ee5c0Srillig fname, lineno, id, msgs[id]) 932e28c523Srillig end 942e28c523Srillig end 952e28c523Srillig 962e28c523Srillig prev = line 972e28c523Srillig end 982e28c523Srillig 992e28c523Srillig f:close() 1002e28c523Srilligend 1012e28c523Srillig 1022e28c523Srillig 103f963c8e4Srilliglocal function file_contains(filename, text) 104f963c8e4Srillig local f = assert(io.open(filename, "r")) 105a75e3514Srillig local found = f:read("a"):find(text, 1, true) 106f963c8e4Srillig f:close() 107a75e3514Srillig return found 108f963c8e4Srilligend 109f963c8e4Srillig 1104d5c51ccSrillig 111a8a62f37Srillig-- Ensure that each test file for a particular message mentions the full text 112a8a62f37Srillig-- of that message and the message ID. 113f963c8e4Srilliglocal function check_test_files(msgs) 114f963c8e4Srillig local testdir = "../../../tests/usr.bin/xlint/lint1" 115a8a62f37Srillig local cmd = ("cd '%s' && printf '%%s\\n' msg_[0-9][0-9][0-9]*.c"):format(testdir) 116a8a62f37Srillig local filenames = assert(io.popen(cmd)) 117a8a62f37Srillig for filename in filenames:lines() do 11868e4158dSrillig local msgid = filename:match("^msg_(%d%d%d)") 119a8a62f37Srillig if msgs[msgid] then 120a8a62f37Srillig local unescaped_msg = msgs[msgid]:gsub("\\(.)", "%1") 121*9e211f35Srillig local expected_text = ("Test for message: %s [%s]"):format(unescaped_msg, msgid) 122a8a62f37Srillig local fullname = ("%s/%s"):format(testdir, filename) 123a8a62f37Srillig if not file_contains(fullname, expected_text) then 124*9e211f35Srillig print_error("%s must contain: // %s", fullname, expected_text) 125f963c8e4Srillig end 126f963c8e4Srillig end 127f963c8e4Srillig end 128a8a62f37Srillig filenames:close() 129a8a62f37Srilligend 1302e28c523Srillig 131e442b1ffSrilliglocal function check_yacc_file(filename) 132e442b1ffSrillig local decl = {} 1334a0dff37Srillig local decl_list = {} 1344a0dff37Srillig local decl_list_index = 1 135e442b1ffSrillig local f = assert(io.open(filename, "r")) 136e442b1ffSrillig local lineno = 0 137e442b1ffSrillig for line in f:lines() do 138e442b1ffSrillig lineno = lineno + 1 139e442b1ffSrillig local type = line:match("^%%type%s+<[%w_]+>%s+(%S+)$") or 140e442b1ffSrillig line:match("^/%* No type for ([%w_]+)%. %*/$") 141e442b1ffSrillig if type then 1424a0dff37Srillig if decl[type] then 1434a0dff37Srillig print_error("%s:%d: duplicate type declaration for rule %q", 1444a0dff37Srillig filename, lineno, type) 1454a0dff37Srillig end 146e442b1ffSrillig decl[type] = lineno 1474a0dff37Srillig table.insert(decl_list, { lineno = lineno, rule = type }) 148e442b1ffSrillig end 149e442b1ffSrillig local rule = line:match("^([%w_]+):") 150e442b1ffSrillig if rule then 151e442b1ffSrillig if decl[rule] then 152e442b1ffSrillig decl[rule] = nil 153e442b1ffSrillig else 154e442b1ffSrillig print_error("%s:%d: missing type declaration for rule %q", 155e442b1ffSrillig filename, lineno, rule) 156e442b1ffSrillig end 1574a0dff37Srillig if decl_list_index > 0 then 1584a0dff37Srillig local expected = decl_list[decl_list_index] 1594a0dff37Srillig if expected.rule == rule then 1604a0dff37Srillig decl_list_index = decl_list_index + 1 1614a0dff37Srillig else 1624a0dff37Srillig print_error("%s:%d: expecting rule %q (from line %d), got %q", 1634a0dff37Srillig filename, lineno, expected.rule, expected.lineno, rule) 1644a0dff37Srillig decl_list_index = 0 1654a0dff37Srillig end 1664a0dff37Srillig end 167e442b1ffSrillig end 168e442b1ffSrillig end 169e442b1ffSrillig for rule, decl_lineno in pairs(decl) do 170e442b1ffSrillig print_error("%s:%d: missing rule %q", filename, decl_lineno, rule) 171e442b1ffSrillig end 172e442b1ffSrillig f:close() 173e442b1ffSrilligend 174e442b1ffSrillig 1752e28c523Srilliglocal function main(arg) 17668e4158dSrillig local msgs = load_messages() 1772e28c523Srillig for _, fname in ipairs(arg) do 1784d5c51ccSrillig check_file(fname, msgs) 179e442b1ffSrillig if fname:match("%.y$") then 180e442b1ffSrillig check_yacc_file(fname) 181e442b1ffSrillig end 1822e28c523Srillig end 1834d5c51ccSrillig check_test_files(msgs) 1842e28c523Srilligend 1852e28c523Srillig 1864d5c51ccSrilligmain(arg) 1874d5c51ccSrilligos.exit(not had_errors) 188