1*b51d2245Srillig-- $NetBSD: t_options.lua,v 1.8 2024/12/12 05:33:47 rillig Exp $ 2a9183f83Srillig-- 3a9183f83Srillig-- Copyright (c) 2023 The NetBSD Foundation, Inc. 4a9183f83Srillig-- All rights reserved. 5a9183f83Srillig-- 6a9183f83Srillig-- Redistribution and use in source and binary forms, with or without 7a9183f83Srillig-- modification, are permitted provided that the following conditions 8a9183f83Srillig-- are met: 9a9183f83Srillig-- 1. Redistributions of source code must retain the above copyright 10a9183f83Srillig-- notice, this list of conditions and the following disclaimer. 11a9183f83Srillig-- 2. Redistributions in binary form must reproduce the above copyright 12a9183f83Srillig-- notice, this list of conditions and the following disclaimer in the 13a9183f83Srillig-- documentation and/or other materials provided with the distribution. 14a9183f83Srillig-- 15a9183f83Srillig-- THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 16a9183f83Srillig-- ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 17a9183f83Srillig-- TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 18a9183f83Srillig-- PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 19a9183f83Srillig-- BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20a9183f83Srillig-- CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21a9183f83Srillig-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22a9183f83Srillig-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23a9183f83Srillig-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24a9183f83Srillig-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25a9183f83Srillig-- POSSIBILITY OF SUCH DAMAGE. 26a9183f83Srillig 27*b51d2245Srillig-- usage: [INDENT=...] lua t_options.lua <file.c>... 280df62e5bSrillig-- 29*b51d2245Srillig-- Run indent on several inputs with different command line options, verifying 30*b51d2245Srillig-- that the actual output equals the expected output. 31a9183f83Srillig-- 32a9183f83Srillig-- The test files contain the input to be formatted, the formatting options 33a9183f83Srillig-- and the output, all as close together as possible. The test files use the 34a9183f83Srillig-- following directives: 35a9183f83Srillig-- 360df62e5bSrillig-- //indent input 37a9183f83Srillig-- Specifies the input to be formatted. 38a9183f83Srillig-- //indent run [options] 39a9183f83Srillig-- Runs indent on the input, using the given options. 400df62e5bSrillig-- //indent end 41a9183f83Srillig-- Finishes an '//indent input' or '//indent run' section. 42a9183f83Srillig-- //indent run-equals-input [options] 43*b51d2245Srillig-- Runs indent on the input, expecting that the output is the 44*b51d2245Srillig-- same as the input. 45a9183f83Srillig-- //indent run-equals-prev-output [options] 46a9183f83Srillig-- Runs indent on the input, expecting the same output as from 47a9183f83Srillig-- the previous run. 48a9183f83Srillig-- 49a9183f83Srillig-- All text outside input..end or run..end directives is not passed to indent. 50a9183f83Srillig-- 51a9183f83Srillig-- Inside the input..end or run..end sections, comments that start with '$' 52a9183f83Srillig-- are filtered out, they can be used for remarks near the affected code. 53a9183f83Srillig-- 54a9183f83Srillig-- The actual output from running indent is written to stdout, the expected 55*b51d2245Srillig-- test output is written to 'expected.out', ready to be compared using diff. 56a9183f83Srillig 57a9183f83Srilliglocal warned = false 58a9183f83Srillig 59a9183f83Srilliglocal filename = "" 60a9183f83Srilliglocal lineno = 0 61a9183f83Srillig 62a9183f83Srilliglocal prev_empty_lines = 0 -- the finished "block" of empty lines 63a9183f83Srilliglocal curr_empty_lines = 0 -- the ongoing "block" of empty lines 64a9183f83Srilliglocal max_empty_lines = 0 -- between sections 65a9183f83Srilliglocal seen_input_section = false-- the first input section is not checked 66a9183f83Srillig 67a9183f83Srilliglocal section = "" -- "", "input" or "run" 68a9183f83Srilliglocal section_excl_comm = "" -- without dollar comments 69a9183f83Srilliglocal section_incl_comm = "" -- with dollar comments 70a9183f83Srillig 71a9183f83Srilliglocal input_excl_comm = "" -- the input text for indent 72a9183f83Srilliglocal input_incl_comm = "" -- used for duplicate checks 73a9183f83Srilliglocal unused_input_lineno = 0 74a9183f83Srillig 75a9183f83Srilliglocal output_excl_comm = "" -- expected output 76a9183f83Srilliglocal output_incl_comm = "" -- used for duplicate checks 77a9183f83Srilliglocal output_lineno = 0 78a9183f83Srillig 79a9183f83Srilliglocal expected_out = assert(io.open("expected.out", "w")) 80a9183f83Srillig 81*b51d2245Srilliglocal function err(ln, msg) 82a9183f83Srillig io.stderr:write(("%s:%d: error: %s\n"):format(filename, ln, msg)) 83a9183f83Srillig os.exit(false) 84a9183f83Srilligend 85a9183f83Srillig 86a9183f83Srilliglocal function warn(ln, msg) 87a9183f83Srillig io.stderr:write(("%s:%d: warning: %s\n"):format(filename, ln, msg)) 88a9183f83Srillig warned = true 89a9183f83Srilligend 90a9183f83Srillig 91a9183f83Srilliglocal function init_file(fname) 92a9183f83Srillig filename = fname 93a9183f83Srillig lineno = 0 94a9183f83Srillig 95a9183f83Srillig prev_empty_lines = 0 96a9183f83Srillig curr_empty_lines = 0 97a9183f83Srillig max_empty_lines = 0 98a9183f83Srillig seen_input_section = false 99a9183f83Srillig 100a9183f83Srillig section = "" 101a9183f83Srillig section_excl_comm = "" 102a9183f83Srillig section_incl_comm = "" 103a9183f83Srillig 104a9183f83Srillig input_excl_comm = "" 105a9183f83Srillig input_incl_comm = "" 106a9183f83Srillig unused_input_lineno = 0 107a9183f83Srillig 108a9183f83Srillig output_excl_comm = "" 109a9183f83Srillig output_incl_comm = "" 110a9183f83Srillig output_lineno = 0 111a9183f83Srilligend 112a9183f83Srillig 113a9183f83Srilliglocal function check_empty_lines_block(n) 114a9183f83Srillig if max_empty_lines ~= n and seen_input_section then 115a9183f83Srillig warn(lineno, ("expecting %d empty %s, got %d") 116*b51d2245Srillig :format(n, n ~= 1 and "lines" or "line", max_empty_lines)) 117a9183f83Srillig end 118a9183f83Srilligend 119a9183f83Srillig 120a9183f83Srilliglocal function check_unused_input() 121a9183f83Srillig if unused_input_lineno ~= 0 then 122a9183f83Srillig warn(unused_input_lineno, "input is not used") 123a9183f83Srillig end 124a9183f83Srilligend 125a9183f83Srillig 126a9183f83Srilliglocal function run_indent(inp, args) 127a9183f83Srillig local indent = os.getenv("INDENT") or "indent" 128ca907427Srillig local cmd = indent .. " " .. args .. " 2>t_options.err" 129a9183f83Srillig 1300df62e5bSrillig local indent_in = assert(io.popen(cmd, "w")) 131a9183f83Srillig indent_in:write(inp) 1320df62e5bSrillig local ok, kind, info = indent_in:close() 133a9183f83Srillig if not ok then 13495820b53Srillig print("// " .. kind .. " " .. info) 135a9183f83Srillig end 136ca907427Srillig for line in io.lines("t_options.err") do 13795820b53Srillig print("// " .. line) 138ca907427Srillig end 139a9183f83Srilligend 140a9183f83Srillig 141*b51d2245Srilliglocal function handle_line_outside_section(line) 142a9183f83Srillig if line == "" then 143a9183f83Srillig curr_empty_lines = curr_empty_lines + 1 144*b51d2245Srillig return 145*b51d2245Srillig end 146a9183f83Srillig if curr_empty_lines > max_empty_lines then 147a9183f83Srillig max_empty_lines = curr_empty_lines 148a9183f83Srillig end 149a9183f83Srillig if curr_empty_lines > 0 then 150a9183f83Srillig if prev_empty_lines > 1 then 151a9183f83Srillig warn(lineno - curr_empty_lines - 1, 152a9183f83Srillig prev_empty_lines .. " empty lines a few " 153a9183f83Srillig .. "lines above, should be only 1") 154a9183f83Srillig end 155a9183f83Srillig prev_empty_lines = curr_empty_lines 156a9183f83Srillig end 157a9183f83Srillig curr_empty_lines = 0 158a9183f83Srilligend 159a9183f83Srillig 16089b72942Srilliglocal function handle_indent_input() 161a9183f83Srillig if prev_empty_lines ~= 2 and seen_input_section then 162a9183f83Srillig warn(lineno, "input section needs 2 empty lines " 163a9183f83Srillig .. "above, not " .. prev_empty_lines) 164a9183f83Srillig end 165a9183f83Srillig check_empty_lines_block(2) 166a9183f83Srillig check_unused_input() 167a9183f83Srillig section = "input" 168a9183f83Srillig section_excl_comm = "" 169a9183f83Srillig section_incl_comm = "" 170a9183f83Srillig unused_input_lineno = lineno 171a9183f83Srillig seen_input_section = true 172a9183f83Srillig output_excl_comm = "" 173a9183f83Srillig output_incl_comm = "" 174a9183f83Srillig output_lineno = 0 17589b72942Srilligend 176a9183f83Srillig 17789b72942Srilliglocal function handle_indent_run(args) 178a9183f83Srillig if section ~= "" then 179a9183f83Srillig warn(lineno, "unfinished section '" .. section .. "'") 180a9183f83Srillig end 181a9183f83Srillig check_empty_lines_block(1) 182a9183f83Srillig if prev_empty_lines ~= 1 then 183a9183f83Srillig warn(lineno, "run section needs 1 empty line above, " 184a9183f83Srillig .. "not " .. prev_empty_lines) 185a9183f83Srillig end 186a9183f83Srillig section = "run" 187a9183f83Srillig output_lineno = lineno 188a9183f83Srillig section_excl_comm = "" 189a9183f83Srillig section_incl_comm = "" 190a9183f83Srillig 191a9183f83Srillig run_indent(input_excl_comm, args) 192a9183f83Srillig unused_input_lineno = 0 19389b72942Srilligend 194a9183f83Srillig 19589b72942Srilliglocal function handle_indent_run_equals_input(args) 196a9183f83Srillig check_empty_lines_block(1) 197a9183f83Srillig run_indent(input_excl_comm, args) 198a9183f83Srillig expected_out:write(input_excl_comm) 199a9183f83Srillig unused_input_lineno = 0 200a9183f83Srillig max_empty_lines = 0 201eead4095Srillig output_incl_comm = "" 202eead4095Srillig output_excl_comm = "" 20389b72942Srilligend 204a9183f83Srillig 20589b72942Srilliglocal function handle_indent_run_equals_prev_output(args) 206eead4095Srillig if output_incl_comm == "" then 207eead4095Srillig warn(lineno, 208eead4095Srillig "no previous output; use run-equals-input instead") 209eead4095Srillig end 210a9183f83Srillig check_empty_lines_block(1) 211a9183f83Srillig run_indent(input_excl_comm, args) 212a9183f83Srillig expected_out:write(output_excl_comm) 213a9183f83Srillig max_empty_lines = 0 21489b72942Srilligend 215a9183f83Srillig 21689b72942Srilliglocal function handle_indent_end_input() 217a9183f83Srillig if section_incl_comm == input_incl_comm then 218a9183f83Srillig warn(lineno, "duplicate input; remove this section") 219a9183f83Srillig end 220a9183f83Srillig 221a9183f83Srillig input_excl_comm = section_excl_comm 222a9183f83Srillig input_incl_comm = section_incl_comm 223a9183f83Srillig section = "" 224a9183f83Srillig max_empty_lines = 0 22589b72942Srilligend 226a9183f83Srillig 22789b72942Srilliglocal function handle_indent_end_run() 228a9183f83Srillig if section_incl_comm == input_incl_comm then 229a9183f83Srillig warn(output_lineno, 230a9183f83Srillig "output == input; use run-equals-input") 231a9183f83Srillig end 232a9183f83Srillig if section_incl_comm == output_incl_comm then 233a9183f83Srillig warn(output_lineno, 234a9183f83Srillig "duplicate output; use run-equals-prev-output") 235a9183f83Srillig end 236a9183f83Srillig 237a9183f83Srillig output_excl_comm = section_excl_comm 238a9183f83Srillig output_incl_comm = section_incl_comm 239a9183f83Srillig section = "" 240a9183f83Srillig max_empty_lines = 0 24189b72942Srilligend 242a9183f83Srillig 24389b72942Srilliglocal function handle_indent_directive(line, command, args) 24489b72942Srillig print(line) 24589b72942Srillig expected_out:write(line .. "\n") 24689b72942Srillig 2470df62e5bSrillig if command == "input" and args ~= "" then 2480df62e5bSrillig warn(lineno, "'//indent input' does not take arguments") 2490df62e5bSrillig elseif command == "input" then 25089b72942Srillig handle_indent_input() 25189b72942Srillig elseif command == "run" then 25289b72942Srillig handle_indent_run(args) 25389b72942Srillig elseif command == "run-equals-input" then 25489b72942Srillig handle_indent_run_equals_input(args) 25589b72942Srillig elseif command == "run-equals-prev-output" then 25689b72942Srillig handle_indent_run_equals_prev_output(args) 25789b72942Srillig elseif command == "end" and args ~= "" then 25889b72942Srillig warn(lineno, "'//indent end' does not take arguments") 25989b72942Srillig elseif command == "end" and section == "input" then 26089b72942Srillig handle_indent_end_input() 26189b72942Srillig elseif command == "end" and section == "run" then 26289b72942Srillig handle_indent_end_run() 263a9183f83Srillig elseif command == "end" then 264a9183f83Srillig warn(lineno, "misplaced '//indent end'") 265a9183f83Srillig else 266*b51d2245Srillig err(lineno, "invalid line '" .. line .. "'") 267a9183f83Srillig end 268a9183f83Srillig 269a9183f83Srillig prev_empty_lines = 0 270a9183f83Srillig curr_empty_lines = 0 271a9183f83Srilligend 272a9183f83Srillig 273a9183f83Srilliglocal function handle_line(line) 274a9183f83Srillig if section == "" then 275*b51d2245Srillig handle_line_outside_section(line) 276a9183f83Srillig end 277a9183f83Srillig 278a9183f83Srillig -- Hide comments starting with dollar from indent; they are used for 279a9183f83Srillig -- marking bugs and adding other remarks directly in the input or 280a9183f83Srillig -- output sections. 281a9183f83Srillig if line:match("^%s*/[*]%s*[$].*[*]/$") 282a9183f83Srillig or line:match("^%s*//%s*[$]") then 283a9183f83Srillig if section ~= "" then 284a9183f83Srillig section_incl_comm = section_incl_comm .. line .. "\n" 285a9183f83Srillig end 286a9183f83Srillig return 287a9183f83Srillig end 288a9183f83Srillig 289a9183f83Srillig local cmd, args = line:match("^//indent%s+([^%s]+)%s*(.*)$") 290a9183f83Srillig if cmd then 291a9183f83Srillig handle_indent_directive(line, cmd, args) 292a9183f83Srillig return 293a9183f83Srillig end 294a9183f83Srillig 295a9183f83Srillig if section == "input" or section == "run" then 296a9183f83Srillig section_excl_comm = section_excl_comm .. line .. "\n" 297a9183f83Srillig section_incl_comm = section_incl_comm .. line .. "\n" 298a9183f83Srillig end 299a9183f83Srillig 300a9183f83Srillig if section == "run" then 301a9183f83Srillig expected_out:write(line .. "\n") 302a9183f83Srillig end 303a9183f83Srillig 304a9183f83Srillig if section == "" 305a9183f83Srillig and line ~= "" 306a9183f83Srillig and line:sub(1, 1) ~= "/" 307a9183f83Srillig and line:sub(1, 2) ~= " *" then 308a9183f83Srillig warn(lineno, "non-comment line outside 'input' or 'run' " 309a9183f83Srillig .. "section") 310a9183f83Srillig end 311a9183f83Srilligend 312a9183f83Srillig 313a9183f83Srilliglocal function handle_file(fname) 314a9183f83Srillig init_file(fname) 315a9183f83Srillig local f = assert(io.open(fname)) 316a9183f83Srillig for line in f:lines() do 317a9183f83Srillig lineno = lineno + 1 318a9183f83Srillig handle_line(line) 319a9183f83Srillig end 320a9183f83Srillig f:close() 321a9183f83Srilligend 322a9183f83Srillig 323a9183f83Srilliglocal function main() 324a9183f83Srillig for _, arg in ipairs(arg) do 325a9183f83Srillig handle_file(arg) 326a9183f83Srillig end 327a9183f83Srillig if section ~= "" then 328*b51d2245Srillig err(lineno, "still in section '" .. section .. "'") 329a9183f83Srillig end 330a9183f83Srillig check_unused_input() 331a9183f83Srillig expected_out:close() 332a9183f83Srillig os.exit(not warned) 333a9183f83Srilligend 334a9183f83Srillig 335a9183f83Srilligmain() 336