1 1.1 rillig #! /usr/bin/lua 2 1.22 rillig -- $NetBSD: check-msgs.lua,v 1.22 2024/03/01 17:22:55 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.21 rillig local msg, id = line:match("%s*\"(.+)\",%s*// (Q?%d+)$") 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.22 rillig local expected_text = ("Test for message: %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.22 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.20 rillig local decl_list = {} 134 1.20 rillig local decl_list_index = 1 135 1.19 rillig local f = assert(io.open(filename, "r")) 136 1.19 rillig local lineno = 0 137 1.19 rillig for line in f:lines() do 138 1.19 rillig lineno = lineno + 1 139 1.19 rillig local type = line:match("^%%type%s+<[%w_]+>%s+(%S+)$") or 140 1.19 rillig line:match("^/%* No type for ([%w_]+)%. %*/$") 141 1.19 rillig if type then 142 1.20 rillig if decl[type] then 143 1.20 rillig print_error("%s:%d: duplicate type declaration for rule %q", 144 1.20 rillig filename, lineno, type) 145 1.20 rillig end 146 1.19 rillig decl[type] = lineno 147 1.20 rillig table.insert(decl_list, { lineno = lineno, rule = type }) 148 1.19 rillig end 149 1.19 rillig local rule = line:match("^([%w_]+):") 150 1.19 rillig if rule then 151 1.19 rillig if decl[rule] then 152 1.19 rillig decl[rule] = nil 153 1.19 rillig else 154 1.19 rillig print_error("%s:%d: missing type declaration for rule %q", 155 1.19 rillig filename, lineno, rule) 156 1.19 rillig end 157 1.20 rillig if decl_list_index > 0 then 158 1.20 rillig local expected = decl_list[decl_list_index] 159 1.20 rillig if expected.rule == rule then 160 1.20 rillig decl_list_index = decl_list_index + 1 161 1.20 rillig else 162 1.20 rillig print_error("%s:%d: expecting rule %q (from line %d), got %q", 163 1.20 rillig filename, lineno, expected.rule, expected.lineno, rule) 164 1.20 rillig decl_list_index = 0 165 1.20 rillig end 166 1.20 rillig end 167 1.19 rillig end 168 1.19 rillig end 169 1.19 rillig for rule, decl_lineno in pairs(decl) do 170 1.19 rillig print_error("%s:%d: missing rule %q", filename, decl_lineno, rule) 171 1.19 rillig end 172 1.19 rillig f:close() 173 1.19 rillig end 174 1.19 rillig 175 1.1 rillig local function main(arg) 176 1.16 rillig local msgs = load_messages() 177 1.1 rillig for _, fname in ipairs(arg) do 178 1.11 rillig check_file(fname, msgs) 179 1.19 rillig if fname:match("%.y$") then 180 1.19 rillig check_yacc_file(fname) 181 1.19 rillig end 182 1.1 rillig end 183 1.11 rillig check_test_files(msgs) 184 1.1 rillig end 185 1.1 rillig 186 1.11 rillig main(arg) 187 1.11 rillig os.exit(not had_errors) 188