Home | History | Annotate | Line # | Download | only in lint1
      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