#! /usr/bin/lua -- $NetBSD: check-msgs.lua,v 1.22 2024/03/01 17:22:55 rillig Exp $ --[[ usage: lua ./check-msgs.lua *.c *.y Check that the message text in the comments of the C source code matches the actual user-visible message text in err.c. ]] local function load_messages() local msgs = {} ---@type tablestring local f = assert(io.open("err.c")) for line in f:lines() do local msg, id = line:match("%s*\"(.+)\",%s*// (Q?%d+)$") if msg ~= nil then msgs[id] = msg end end f:close() return msgs end local had_errors = false ---@param fmt string function print_error(fmt, ...) print(fmt:format(...)) had_errors = true end local function check_message(fname, lineno, id, comment, msgs) local msg = msgs[id] if msg == nil then print_error("%s:%d: id=%s not found", fname, lineno, id) return end msg = msg:gsub("/%*", "**") msg = msg:gsub("%*/", "**") msg = msg:gsub("\\(.)", "%1") if comment == msg then return end local prefix = comment:match("^(.-)%s*%.%.%.$") if prefix ~= nil and msg:find(prefix, 1, 1) == 1 then return end print_error("%s:%d: id=%-3s msg=%-40s comment=%s", fname, lineno, id, msg, comment) end local message_prefix = { error = "", error_at = "", warning = "", warning_at = "", query_message = "Q", c99ism = "", c11ism = "", c23ism = "", gnuism = "", } local function check_file(fname, msgs) local f = assert(io.open(fname, "r")) local lineno = 0 local prev = "" for line in f:lines() do lineno = lineno + 1 local func, id = line:match("^%s+([%w_]+)%((%d+)[),]") local prefix = message_prefix[func] if prefix then id = prefix .. id local comment = prev:match("^%s+/%* (.+) %*/$") if comment ~= nil then check_message(fname, lineno, id, comment, msgs) else print_error("%s:%d: missing comment for %s: /* %s */", fname, lineno, id, msgs[id]) end end prev = line end f:close() end local function file_contains(filename, text) local f = assert(io.open(filename, "r")) local found = f:read("a"):find(text, 1, true) f:close() return found end -- Ensure that each test file for a particular message mentions the full text -- of that message and the message ID. local function check_test_files(msgs) local testdir = "../../../tests/usr.bin/xlint/lint1" local cmd = ("cd '%s' && printf '%%s\\n' msg_[0-9][0-9][0-9]*.c"):format(testdir) local filenames = assert(io.popen(cmd)) for filename in filenames:lines() do local msgid = filename:match("^msg_(%d%d%d)") if msgs[msgid] then local unescaped_msg = msgs[msgid]:gsub("\\(.)", "%1") local expected_text = ("Test for message: %s [%s]"):format(unescaped_msg, msgid) local fullname = ("%s/%s"):format(testdir, filename) if not file_contains(fullname, expected_text) then print_error("%s must contain: // %s", fullname, expected_text) end end end filenames:close() end local function check_yacc_file(filename) local decl = {} local decl_list = {} local decl_list_index = 1 local f = assert(io.open(filename, "r")) local lineno = 0 for line in f:lines() do lineno = lineno + 1 local type = line:match("^%%type%s+<[%w_]+>%s+(%S+)$") or line:match("^/%* No type for ([%w_]+)%. %*/$") if type then if decl[type] then print_error("%s:%d: duplicate type declaration for rule %q", filename, lineno, type) end decl[type] = lineno table.insert(decl_list, { lineno = lineno, rule = type }) end local rule = line:match("^([%w_]+):") if rule then if decl[rule] then decl[rule] = nil else print_error("%s:%d: missing type declaration for rule %q", filename, lineno, rule) end if decl_list_index > 0 then local expected = decl_list[decl_list_index] if expected.rule == rule then decl_list_index = decl_list_index + 1 else print_error("%s:%d: expecting rule %q (from line %d), got %q", filename, lineno, expected.rule, expected.lineno, rule) decl_list_index = 0 end end end end for rule, decl_lineno in pairs(decl) do print_error("%s:%d: missing rule %q", filename, decl_lineno, rule) end f:close() end local function main(arg) local msgs = load_messages() for _, fname in ipairs(arg) do check_file(fname, msgs) if fname:match("%.y$") then check_yacc_file(fname) end end check_test_files(msgs) end main(arg) os.exit(not had_errors)