From ea2d6b70ed06c60dba9ba81cf53883c85fb92068 Mon Sep 17 00:00:00 2001 From: Bernhard Schommer Date: Fri, 8 Jul 2016 10:15:31 +0200 Subject: Added responsefile support for commandline. Commandline can now be passed in a file specifed with @file on the Commandline. The quoting convention is similar to the one used by gcc, etc. Options are separated by whitespaces and options with whitespaecs need to be quoted. Bug 18303 --- driver/Commandline.ml | 8 ++- driver/Driver.ml | 1 + lib/Responsefile.ml | 133 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 lib/Responsefile.ml diff --git a/driver/Commandline.ml b/driver/Commandline.ml index 0a2c8fca..1981776e 100644 --- a/driver/Commandline.ml +++ b/driver/Commandline.ml @@ -16,6 +16,7 @@ (* Parsing of command-line flags and arguments *) open Printf +open Responsefile type pattern = | Exact of string @@ -99,4 +100,9 @@ let parse_array spec argv first last = in parse first let parse_cmdline spec = - parse_array spec Sys.argv 1 (Array.length Sys.argv - 1) + try + let argv = expand_responsefiles Sys.argv in + parse_array spec argv 1 (Array.length argv - 1) + with Arg.Bad s -> + eprintf "%s" s; + exit 2 diff --git a/driver/Driver.ml b/driver/Driver.ml index 7311215d..6d8cf9ac 100644 --- a/driver/Driver.ml +++ b/driver/Driver.ml @@ -334,6 +334,7 @@ General options:\n\ \ -v Print external commands before invoking them\n\ \ -timings Show the time spent in various compiler passes\n\ \ -version Print the version string and exit\n\ +\ @ Read command line options from \n\ Interpreter mode:\n\ \ -interp Execute given .c files using the reference interpreter\n\ \ -quiet Suppress diagnostic messages for the interpreter\n\ diff --git a/lib/Responsefile.ml b/lib/Responsefile.ml new file mode 100644 index 00000000..c10fe302 --- /dev/null +++ b/lib/Responsefile.ml @@ -0,0 +1,133 @@ +(* *********************************************************************) +(* *) +(* The Compcert verified compiler *) +(* *) +(* Xavier Leroy, INRIA Paris-Rocquencourt *) +(* Bernhard Schommer, AbsInt Angewandte Informatik GmbH *) +(* *) +(* Copyright Institut National de Recherche en Informatique et en *) +(* Automatique. All rights reserved. This file is distributed *) +(* under the terms of the GNU General Public License as published by *) +(* the Free Software Foundation, either version 2 of the License, or *) +(* (at your option) any later version. This file is also distributed *) +(* under the terms of the INRIA Non-Commercial License Agreement. *) +(* *) +(* *********************************************************************) + + +let rec singlequote ic buf = + match input_char ic with + | exception End_of_file -> () + | '\'' -> () + | c -> Buffer.add_char buf c; singlequote ic buf + +let doublequote ic buf = + let rec aux buf = + match input_char ic with + | exception End_of_file -> (* Backslash-newline is ignored. *) + () + | '\"' -> + () + | '\\' -> + begin match input_char ic with + | exception End_of_file -> (* tolerance *) + Buffer.add_char buf '\\' + | '\n' -> + aux buf + | ('\\' | '\"') as c -> + Buffer.add_char buf c; aux buf + | c -> + Buffer.add_char buf '\\'; Buffer.add_char buf c; aux buf + end + | c -> + Buffer.add_char buf c; aux buf in + aux buf + +let doublequote_win ic buf = + let rec aux_win buf n = + match input_char ic with + | exception End_of_file -> (* tolerance *) + add_backslashes n + | '\\' -> + aux_win buf (n+1) + | '\"' -> + if n land 1 = 1 then begin + add_backslashes (n/2); Buffer.add_char buf '\"'; + aux_win buf 0 + end else begin + add_backslashes n + end + | '\n' -> + if n >= 1 then add_backslashes (n-1) else Buffer.add_char buf '\n'; + aux_win buf 0 + | c -> + add_backslashes n; Buffer.add_char buf c; aux_win buf 0 + and add_backslashes n = + for _i = 1 to n do Buffer.add_char buf '\\' done in + aux_win buf 0 + +let doublequote = if Sys.win32 then doublequote_win else doublequote + +let is_add_file file = + String.length file > 1 && String.get file 0 = '@' + +let cut_add file = + String.sub file 1 (String.length file - 1) + +let readwords file = + let visited = ref [] in + let rec aux file = + if Sys.file_exists file then begin + if List.mem file !visited then + raise (Arg.Bad "Circular includes in response files"); + visited := file :: !visited; + let ic = open_in_bin file in + let buf = Buffer.create 32 in + let words = ref [] in + let stash inw = + if inw then begin + let word = Buffer.contents buf in + if is_add_file word then + words := (aux (cut_add word))@ !words + else + words := Buffer.contents buf :: !words; + Buffer.clear buf + end in + let rec unquoted inw = + match input_char ic with + | exception End_of_file -> + stash inw + | ' ' | '\t' | '\r' | '\n' -> + stash inw; unquoted false + | '\\' -> + begin match input_char ic with + | exception End_of_file -> (* tolerance; treat like \newline *) + unquoted inw + | '\n' -> + unquoted inw + | c -> + Buffer.add_char buf c; unquoted true + end + | '\'' -> + singlequote ic buf; unquoted true + | '\"' -> + doublequote ic buf; + unquoted true + | c -> + Buffer.add_char buf c; unquoted true in + unquoted false; + close_in ic; + !words + end else [file] in + List.rev (aux file) + +let expand_responsefiles args = + let acc = ref [] in + for i = (Array.length args - 1) downto 0 do + let file = args.(i) in + if is_add_file file then + acc := readwords (cut_add file) @ !acc + else + acc := file::!acc + done; + Array.of_list !acc -- cgit From 1ad10395dc17a4257d26e8a854cb98e7107ceff5 Mon Sep 17 00:00:00 2001 From: Bernhard Schommer Date: Mon, 11 Jul 2016 10:48:59 +0200 Subject: Added function to write responsefiles. The arguments are written in the responsefile separated by whitespace. If the argument itself contains a whitespace it is quoted. Bug 18308 --- lib/Responsefile.mli | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 lib/Responsefile.mli diff --git a/lib/Responsefile.mli b/lib/Responsefile.mli new file mode 100644 index 00000000..2522b7c5 --- /dev/null +++ b/lib/Responsefile.mli @@ -0,0 +1,24 @@ +(* *********************************************************************) +(* *) +(* The Compcert verified compiler *) +(* *) +(* Xavier Leroy, INRIA Paris-Rocquencourt *) +(* Bernhard Schommer, AbsInt Angewandte Informatik GmbH *) +(* *) +(* Copyright Institut National de Recherche en Informatique et en *) +(* Automatique. All rights reserved. This file is distributed *) +(* under the terms of the GNU General Public License as published by *) +(* the Free Software Foundation, either version 2 of the License, or *) +(* (at your option) any later version. This file is also distributed *) +(* under the terms of the INRIA Non-Commercial License Agreement. *) +(* *) +(* *********************************************************************) + + +val expand_responsefiles: string array -> string array + (** Expand responsefile arguments contained in the array and return the full + set of arguments. *) + +val write_responsefile: out_channel -> string array -> unit + (** Write the arguments on the out_channel. All arguments that contain + whitespaces are quoted. *) -- cgit From a6bde8ba057ff057e311781fd91b4a9ab441731c Mon Sep 17 00:00:00 2001 From: Bernhard Schommer Date: Mon, 11 Jul 2016 12:21:47 +0200 Subject: Really added the function. Bug 18308 --- lib/Responsefile.ml | 16 ++++++++++++++++ lib/Responsefile.mli | 6 +++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/Responsefile.ml b/lib/Responsefile.ml index c10fe302..6dd1bc93 100644 --- a/lib/Responsefile.ml +++ b/lib/Responsefile.ml @@ -131,3 +131,19 @@ let expand_responsefiles args = acc := file::!acc done; Array.of_list !acc + +let write_responsefile oc args start = + let whitespace = Str.regexp "[ \t\r\n]" in + let quote arg = + if Str.string_match whitespace arg 0 then + Filename.quote arg (* We need to quote arguments containing whitespaces *) + else + arg in + let first = ref true in + let sep oc = if !first then + first := false + else + output_string oc " " in + for i = start to (Array.length args -1) do + Printf.fprintf oc "%t%s" sep (quote args.(i)) + done diff --git a/lib/Responsefile.mli b/lib/Responsefile.mli index 2522b7c5..95c74bda 100644 --- a/lib/Responsefile.mli +++ b/lib/Responsefile.mli @@ -19,6 +19,6 @@ val expand_responsefiles: string array -> string array (** Expand responsefile arguments contained in the array and return the full set of arguments. *) -val write_responsefile: out_channel -> string array -> unit - (** Write the arguments on the out_channel. All arguments that contain - whitespaces are quoted. *) +val write_responsefile: out_channel -> string array -> int -> unit + (** Write the arguments starting at the given index as repsonsefile on the given + out_channel. All arguments that contain whitespaces are quoted. *) -- cgit From efa462bd1655c6b2c8f064e214762650092257e8 Mon Sep 17 00:00:00 2001 From: Bernhard Schommer Date: Tue, 12 Jul 2016 13:18:42 +0200 Subject: Added heuristic for passing arg via responsefiles. Since gnu make and other tools under windows seem to have a limit of around 8000 bytes per command line the arguments should be passed via responsefiles instead. Bug 18308 --- driver/Assembler.ml | 9 +++++++-- driver/Driveraux.ml | 23 +++++++++++++++++++++-- driver/Driveraux.mli | 8 +++++++- driver/Frontend.ml | 9 +++++++-- driver/Linker.ml | 13 +++++++++---- lib/Responsefile.ml | 16 ---------------- lib/Responsefile.mli | 4 ---- 7 files changed, 51 insertions(+), 31 deletions(-) diff --git a/driver/Assembler.ml b/driver/Assembler.ml index 52fb17d8..d6cb65ea 100644 --- a/driver/Assembler.ml +++ b/driver/Assembler.ml @@ -18,12 +18,17 @@ open Driveraux (* From asm to object file *) let assemble ifile ofile = - let cmd = List.concat [ - Configuration.asm; + let cmd,opts = match Configuration.asm with + | name::opts -> name,opts + | [] -> assert false (* Should be catched in Configuration *) in + let opts = List.concat [ + opts; ["-o"; ofile]; List.rev !assembler_options; [ifile] ] in + let opts = responsefile opts (fun a -> if gnu_system then ["@"^a] else ["@"^a]) in + let cmd = cmd::opts in let exc = command cmd in if exc <> 0 then begin safe_remove ofile; diff --git a/driver/Driveraux.ml b/driver/Driveraux.ml index 3fe22fac..8ebf261d 100644 --- a/driver/Driveraux.ml +++ b/driver/Driveraux.ml @@ -14,12 +14,33 @@ open Printf open Clflags +(* Is this a gnu based tool chain *) +let gnu_system = Configuration.system <> "diab" + (* Invocation of external tools *) let rec waitpid_no_intr pid = try Unix.waitpid [] pid with Unix.Unix_error (Unix.EINTR, _, _) -> waitpid_no_intr pid +let responsefile args resp_arg = + try + if Sys.win32 && (String.length (String.concat "" args) > 7000) then + let file,oc = Filename.open_temp_file "compcert" "" in + let whitespace = Str.regexp "[ \t\r\n]" in + let quote arg = + if Str.string_match whitespace arg 0 then + Filename.quote arg (* We need to quote arguments containing whitespaces *) + else + arg in + List.iter (fun a -> Printf.fprintf oc "%s " (quote a)) args; + close_out oc; + resp_arg file + else + args + with Sys_error _ -> + args + let command ?stdout args = if !option_v then begin eprintf "+ %s" (String.concat " " args); @@ -94,8 +115,6 @@ let print_error oc msg = List.iter print_one_error msg; output_char oc '\n' -let gnu_system = Configuration.system <> "diab" - (* Command-line parsing *) let explode_comma_option s = match Str.split (Str.regexp ",") s with diff --git a/driver/Driveraux.mli b/driver/Driveraux.mli index 60efcc80..54df4336 100644 --- a/driver/Driveraux.mli +++ b/driver/Driveraux.mli @@ -12,7 +12,13 @@ (* *********************************************************************) -val command: ?stdout:string -> string list -> int +val responsefile: string list -> (string -> string list) -> string list + (** [responsefiles args resp_arg] Test whether [args] should be passed + via responsefile and writes them into a file. [resp_arg] generates + the new argument constructed from the responsefile. If no + responsefile is written the arguments are returned unchanged. *) + +val command: ?stdout:string -> string list -> int (** Execute the command with the given arguments and an optional file for the stdout. Returns the exit code. *) diff --git a/driver/Frontend.ml b/driver/Frontend.ml index 043d4e5a..41b09b58 100644 --- a/driver/Frontend.ml +++ b/driver/Frontend.ml @@ -24,8 +24,11 @@ open Printf let preprocess ifile ofile = let output = if ofile = "-" then None else Some ofile in - let cmd = List.concat [ - Configuration.prepro; + let cmd,opts = match Configuration.prepro with + | name::opts -> name,opts + | [] -> assert false (* Should be catched in Configuration *) in + let opts = List.concat [ + opts; ["-D__COMPCERT__"]; (if !Clflags.use_standard_headers then ["-I" ^ Filename.concat !Clflags.stdlib_path "include" ] @@ -33,6 +36,8 @@ let preprocess ifile ofile = List.rev !prepro_options; [ifile] ] in + let opts = responsefile opts (fun a -> if gnu_system then ["@"^a] else ["@"^a]) in + let cmd = cmd::opts in let exc = command ?stdout:output cmd in if exc <> 0 then begin if ofile <> "-" then safe_remove ofile; diff --git a/driver/Linker.ml b/driver/Linker.ml index 2f767023..14c9bcb3 100644 --- a/driver/Linker.ml +++ b/driver/Linker.ml @@ -19,14 +19,19 @@ open Driveraux (* Linking *) let linker exe_name files = - let cmd = List.concat [ - Configuration.linker; + let cmd,opts = match Configuration.linker with + | name::opts -> name,opts + | [] -> assert false (* Should be catched in Configuration *) in + let opts = List.concat [ + opts; ["-o"; exe_name]; files; (if Configuration.has_runtime_lib - then ["-L" ^ !stdlib_path; "-lcompcert"] - else []) + then ["-L" ^ !stdlib_path; "-lcompcert"] + else []) ] in + let opts = responsefile opts (fun a -> if gnu_system then ["@"^a] else ["-@"^a]) in + let cmd = cmd::opts in let exc = command cmd in if exc <> 0 then begin command_error "linker" exc; diff --git a/lib/Responsefile.ml b/lib/Responsefile.ml index 6dd1bc93..c10fe302 100644 --- a/lib/Responsefile.ml +++ b/lib/Responsefile.ml @@ -131,19 +131,3 @@ let expand_responsefiles args = acc := file::!acc done; Array.of_list !acc - -let write_responsefile oc args start = - let whitespace = Str.regexp "[ \t\r\n]" in - let quote arg = - if Str.string_match whitespace arg 0 then - Filename.quote arg (* We need to quote arguments containing whitespaces *) - else - arg in - let first = ref true in - let sep oc = if !first then - first := false - else - output_string oc " " in - for i = start to (Array.length args -1) do - Printf.fprintf oc "%t%s" sep (quote args.(i)) - done diff --git a/lib/Responsefile.mli b/lib/Responsefile.mli index 95c74bda..b55dac16 100644 --- a/lib/Responsefile.mli +++ b/lib/Responsefile.mli @@ -18,7 +18,3 @@ val expand_responsefiles: string array -> string array (** Expand responsefile arguments contained in the array and return the full set of arguments. *) - -val write_responsefile: out_channel -> string array -> int -> unit - (** Write the arguments starting at the given index as repsonsefile on the given - out_channel. All arguments that contain whitespaces are quoted. *) -- cgit From 2129fe8f2e19c4dd91955e5300e76d924e0a3e6d Mon Sep 17 00:00:00 2001 From: Bernhard Schommer Date: Tue, 19 Jul 2016 09:44:26 +0200 Subject: Merged responfile function into command. Command now decides whether to use a responsefile or call the external command directly. Bug 18004 --- driver/Assembler.ml | 9 ++------- driver/Driveraux.ml | 49 +++++++++++++++++++++++++++---------------------- driver/Driveraux.mli | 6 ------ driver/Frontend.ml | 9 ++------- driver/Linker.ml | 9 ++------- 5 files changed, 33 insertions(+), 49 deletions(-) diff --git a/driver/Assembler.ml b/driver/Assembler.ml index d6cb65ea..52fb17d8 100644 --- a/driver/Assembler.ml +++ b/driver/Assembler.ml @@ -18,17 +18,12 @@ open Driveraux (* From asm to object file *) let assemble ifile ofile = - let cmd,opts = match Configuration.asm with - | name::opts -> name,opts - | [] -> assert false (* Should be catched in Configuration *) in - let opts = List.concat [ - opts; + let cmd = List.concat [ + Configuration.asm; ["-o"; ofile]; List.rev !assembler_options; [ifile] ] in - let opts = responsefile opts (fun a -> if gnu_system then ["@"^a] else ["@"^a]) in - let cmd = cmd::opts in let exc = command cmd in if exc <> 0 then begin safe_remove ofile; diff --git a/driver/Driveraux.ml b/driver/Driveraux.ml index 8ebf261d..2c03c65c 100644 --- a/driver/Driveraux.ml +++ b/driver/Driveraux.ml @@ -17,31 +17,17 @@ open Clflags (* Is this a gnu based tool chain *) let gnu_system = Configuration.system <> "diab" +(* Sage removale of files *) +let safe_remove file = + try Sys.remove file with Sys_error _ -> () + (* Invocation of external tools *) let rec waitpid_no_intr pid = try Unix.waitpid [] pid with Unix.Unix_error (Unix.EINTR, _, _) -> waitpid_no_intr pid -let responsefile args resp_arg = - try - if Sys.win32 && (String.length (String.concat "" args) > 7000) then - let file,oc = Filename.open_temp_file "compcert" "" in - let whitespace = Str.regexp "[ \t\r\n]" in - let quote arg = - if Str.string_match whitespace arg 0 then - Filename.quote arg (* We need to quote arguments containing whitespaces *) - else - arg in - List.iter (fun a -> Printf.fprintf oc "%s " (quote a)) args; - close_out oc; - resp_arg file - else - args - with Sys_error _ -> - args - -let command ?stdout args = +let command stdout args = if !option_v then begin eprintf "+ %s" (String.concat " " args); begin match stdout with @@ -72,12 +58,31 @@ let command ?stdout args = argv.(0) fn (Unix.error_message err) param; -1 +let quote arg = + let whitespace = Str.regexp "[ \t\r\n]" in + if Str.string_match whitespace arg 0 then + Filename.quote arg (* We need to quote arguments containing whitespaces *) + else + arg + +let command ?stdout args = + if Sys.win32 && List.fold_left (fun len arg -> len + String.length arg + 1) 0 args > 7000 then + let file,oc = Filename.open_temp_file "compcert" "" in + let cmd,args = match args with + | cmd::args -> cmd,args + | [] -> assert false (* Should never happen *) in + List.iter (fun a -> Printf.fprintf oc "%s " (quote a)) args; + close_out oc; + let arg = if gnu_system then "@"^file else "-@"^file in + let ret = command stdout [cmd;arg] in + safe_remove file; + ret + else + command stdout args + let command_error n exc = eprintf "Error: %s command failed with exit code %d (use -v to see invocation)\n" n exc -let safe_remove file = - try Sys.remove file with Sys_error _ -> () - (* Determine names for output files. We use -o option if specified and if this is the final destination file (not a dump file). diff --git a/driver/Driveraux.mli b/driver/Driveraux.mli index 54df4336..e6bac6e3 100644 --- a/driver/Driveraux.mli +++ b/driver/Driveraux.mli @@ -12,12 +12,6 @@ (* *********************************************************************) -val responsefile: string list -> (string -> string list) -> string list - (** [responsefiles args resp_arg] Test whether [args] should be passed - via responsefile and writes them into a file. [resp_arg] generates - the new argument constructed from the responsefile. If no - responsefile is written the arguments are returned unchanged. *) - val command: ?stdout:string -> string list -> int (** Execute the command with the given arguments and an optional file for the stdout. Returns the exit code. *) diff --git a/driver/Frontend.ml b/driver/Frontend.ml index 41b09b58..043d4e5a 100644 --- a/driver/Frontend.ml +++ b/driver/Frontend.ml @@ -24,11 +24,8 @@ open Printf let preprocess ifile ofile = let output = if ofile = "-" then None else Some ofile in - let cmd,opts = match Configuration.prepro with - | name::opts -> name,opts - | [] -> assert false (* Should be catched in Configuration *) in - let opts = List.concat [ - opts; + let cmd = List.concat [ + Configuration.prepro; ["-D__COMPCERT__"]; (if !Clflags.use_standard_headers then ["-I" ^ Filename.concat !Clflags.stdlib_path "include" ] @@ -36,8 +33,6 @@ let preprocess ifile ofile = List.rev !prepro_options; [ifile] ] in - let opts = responsefile opts (fun a -> if gnu_system then ["@"^a] else ["@"^a]) in - let cmd = cmd::opts in let exc = command ?stdout:output cmd in if exc <> 0 then begin if ofile <> "-" then safe_remove ofile; diff --git a/driver/Linker.ml b/driver/Linker.ml index 14c9bcb3..305c5603 100644 --- a/driver/Linker.ml +++ b/driver/Linker.ml @@ -19,19 +19,14 @@ open Driveraux (* Linking *) let linker exe_name files = - let cmd,opts = match Configuration.linker with - | name::opts -> name,opts - | [] -> assert false (* Should be catched in Configuration *) in - let opts = List.concat [ - opts; + let cmd = List.concat [ + Configuration.linker; ["-o"; exe_name]; files; (if Configuration.has_runtime_lib then ["-L" ^ !stdlib_path; "-lcompcert"] else []) ] in - let opts = responsefile opts (fun a -> if gnu_system then ["@"^a] else ["-@"^a]) in - let cmd = cmd::opts in let exc = command cmd in if exc <> 0 then begin command_error "linker" exc; -- cgit From f22c32dcda0e8b546e85e8ad95d0ad655e365d38 Mon Sep 17 00:00:00 2001 From: Bernhard Schommer Date: Wed, 20 Jul 2016 13:20:19 +0200 Subject: Added simplified reader and printer for gnu @files The functions expandargv and writeargv resemble the functions from the libiberity that are used by the gnu tools. Additionaly a new configuration is added in order to determine which kind of response files are supported for calls to other tools. Bug 18308 --- .gitignore | 1 + Makefile | 3 +- Makefile.extr | 3 +- configure | 6 +++ driver/Commandline.ml | 2 +- driver/Configuration.ml | 10 ++++ driver/Configuration.mli | 7 +++ driver/Driveraux.ml | 21 +++++--- lib/Responsefile.ml | 133 ----------------------------------------------- lib/Responsefile.mli | 2 +- lib/Responsefile.mll | 97 ++++++++++++++++++++++++++++++++++ 11 files changed, 140 insertions(+), 145 deletions(-) delete mode 100644 lib/Responsefile.ml create mode 100644 lib/Responsefile.mll diff --git a/.gitignore b/.gitignore index 9a85f487..02379a3b 100644 --- a/.gitignore +++ b/.gitignore @@ -52,6 +52,7 @@ cparser/tests/generated/*.err backend/CMparser.automaton lib/Readconfig.ml lib/Tokenize.ml +lib/Responsefile.ml driver/Version.ml # Documentation doc/coq2html diff --git a/Makefile b/Makefile index 47f61dbe..39460702 100644 --- a/Makefile +++ b/Makefile @@ -207,7 +207,8 @@ compcert.ini: Makefile.config echo "has_standard_headers=$(HAS_STANDARD_HEADERS)"; \ echo "asm_supports_cfi=$(ASM_SUPPORTS_CFI)"; \ echo "struct_passing_style=$(STRUCT_PASSING)"; \ - echo "struct_return_style=$(STRUCT_RETURN)";) \ + echo "struct_return_style=$(STRUCT_RETURN)"; \ + echo "response_file_style=$(RESPONSEFILE)";) \ > compcert.ini driver/Version.ml: VERSION diff --git a/Makefile.extr b/Makefile.extr index 51dbd767..8fdc9ffe 100644 --- a/Makefile.extr +++ b/Makefile.extr @@ -63,7 +63,8 @@ MODORDER=tools/modorder .depend.extr PARSERS=backend/CMparser.mly cparser/pre_parser.mly LEXERS=backend/CMlexer.mll cparser/Lexer.mll \ - lib/Tokenize.mll lib/Readconfig.mll + lib/Tokenize.mll lib/Readconfig.mll \ + lib/Responsefile.mll LIBS=str.cmxa unix.cmxa $(MENHIR_LIBS) LIBS_BYTE=$(patsubst %.cmxa,%.cma,$(patsubst %.cmx,%.cmo,$(LIBS))) diff --git a/configure b/configure index 28683ade..718f9b83 100755 --- a/configure +++ b/configure @@ -20,6 +20,7 @@ target='' has_runtime_lib=true has_standard_headers=true clightgen=false +responsefile="gnu" usage='Usage: ./configure [options] target @@ -118,6 +119,7 @@ case "$target" in asm_supports_cfi=false clinker="${toolprefix}dcc" libmath="-lm" + responsefile="unsupported" ;; *) system="linux" @@ -386,6 +388,7 @@ HAS_RUNTIME_LIB=$has_runtime_lib HAS_STANDARD_HEADERS=$has_standard_headers ASM_SUPPORTS_CFI=$asm_supports_cfi CLIGHTGEN=$clightgen +RESPONSEFILE=$responsefile EOF else cat >> Makefile.config <<'EOF' @@ -469,6 +472,9 @@ ASM_SUPPORTS_CFI=false # Turn on/off compilation of clightgen CLIGHTGEN=false +# Whether the other tools support responsefiles in gnu syntax +RESPONSEFILE="none" + EOF fi diff --git a/driver/Commandline.ml b/driver/Commandline.ml index 1981776e..7e683680 100644 --- a/driver/Commandline.ml +++ b/driver/Commandline.ml @@ -101,7 +101,7 @@ let parse_array spec argv first last = let parse_cmdline spec = try - let argv = expand_responsefiles Sys.argv in + let argv = expandargv Sys.argv in parse_array spec argv 1 (Array.length argv - 1) with Arg.Bad s -> eprintf "%s" s; diff --git a/driver/Configuration.ml b/driver/Configuration.ml index e1a02573..be581e14 100644 --- a/driver/Configuration.ml +++ b/driver/Configuration.ml @@ -171,3 +171,13 @@ let struct_return_style = | "int1-8" -> SR_int1to8 | "ref" -> SR_ref | v -> bad_config "struct_return_style" [v] + +type response_file_style = + | Gnu (* responsefiles in gnu compatible syntax *) + | Unsupported (* responsefiles are not supported *) + +let response_file_style = + match get_config_string "response_file_style" with + | "unsupported" -> Unsupported + | "gnu" -> Gnu + | v -> bad_config "response_file_style" [v] diff --git a/driver/Configuration.mli b/driver/Configuration.mli index dde9d6fd..1092bf6d 100644 --- a/driver/Configuration.mli +++ b/driver/Configuration.mli @@ -63,3 +63,10 @@ val struct_passing_style: struct_passing_style val struct_return_style: struct_return_style (** Calling conventions to use for returning structs and unions as first-class values *) + +type response_file_style = + | Gnu (* responsefiles in gnu compatible syntax *) + | Unsupported (* responsefiles are not supported *) + +val response_file_style: response_file_style + (** Style of supported responsefiles *) diff --git a/driver/Driveraux.ml b/driver/Driveraux.ml index 2c03c65c..6bd48344 100644 --- a/driver/Driveraux.ml +++ b/driver/Driveraux.ml @@ -58,20 +58,25 @@ let command stdout args = argv.(0) fn (Unix.error_message err) param; -1 -let quote arg = - let whitespace = Str.regexp "[ \t\r\n]" in - if Str.string_match whitespace arg 0 then - Filename.quote arg (* We need to quote arguments containing whitespaces *) - else - arg +(* This function reimplements quoting of writeargv from libiberty *) +let gnu_quote arg = + let len = String.length arg in + let buf = Buffer.create len in + String.iter (fun c -> match c with + | ' ' | '\t' | '\r' | '\n' | '\\' | '\'' | '"' -> + Buffer.add_char buf '\\' + | _ -> (); + Buffer.add_char buf c) arg; + Buffer.contents buf let command ?stdout args = - if Sys.win32 && List.fold_left (fun len arg -> len + String.length arg + 1) 0 args > 7000 then + let resp = Sys.win32 && Configuration.response_file_style <> Configuration.Unsupported in + if resp && List.fold_left (fun len arg -> len + String.length arg + 1) 0 args > 7000 then let file,oc = Filename.open_temp_file "compcert" "" in let cmd,args = match args with | cmd::args -> cmd,args | [] -> assert false (* Should never happen *) in - List.iter (fun a -> Printf.fprintf oc "%s " (quote a)) args; + List.iter (fun a -> Printf.fprintf oc "%s " (gnu_quote a)) args; close_out oc; let arg = if gnu_system then "@"^file else "-@"^file in let ret = command stdout [cmd;arg] in diff --git a/lib/Responsefile.ml b/lib/Responsefile.ml deleted file mode 100644 index c10fe302..00000000 --- a/lib/Responsefile.ml +++ /dev/null @@ -1,133 +0,0 @@ -(* *********************************************************************) -(* *) -(* The Compcert verified compiler *) -(* *) -(* Xavier Leroy, INRIA Paris-Rocquencourt *) -(* Bernhard Schommer, AbsInt Angewandte Informatik GmbH *) -(* *) -(* Copyright Institut National de Recherche en Informatique et en *) -(* Automatique. All rights reserved. This file is distributed *) -(* under the terms of the GNU General Public License as published by *) -(* the Free Software Foundation, either version 2 of the License, or *) -(* (at your option) any later version. This file is also distributed *) -(* under the terms of the INRIA Non-Commercial License Agreement. *) -(* *) -(* *********************************************************************) - - -let rec singlequote ic buf = - match input_char ic with - | exception End_of_file -> () - | '\'' -> () - | c -> Buffer.add_char buf c; singlequote ic buf - -let doublequote ic buf = - let rec aux buf = - match input_char ic with - | exception End_of_file -> (* Backslash-newline is ignored. *) - () - | '\"' -> - () - | '\\' -> - begin match input_char ic with - | exception End_of_file -> (* tolerance *) - Buffer.add_char buf '\\' - | '\n' -> - aux buf - | ('\\' | '\"') as c -> - Buffer.add_char buf c; aux buf - | c -> - Buffer.add_char buf '\\'; Buffer.add_char buf c; aux buf - end - | c -> - Buffer.add_char buf c; aux buf in - aux buf - -let doublequote_win ic buf = - let rec aux_win buf n = - match input_char ic with - | exception End_of_file -> (* tolerance *) - add_backslashes n - | '\\' -> - aux_win buf (n+1) - | '\"' -> - if n land 1 = 1 then begin - add_backslashes (n/2); Buffer.add_char buf '\"'; - aux_win buf 0 - end else begin - add_backslashes n - end - | '\n' -> - if n >= 1 then add_backslashes (n-1) else Buffer.add_char buf '\n'; - aux_win buf 0 - | c -> - add_backslashes n; Buffer.add_char buf c; aux_win buf 0 - and add_backslashes n = - for _i = 1 to n do Buffer.add_char buf '\\' done in - aux_win buf 0 - -let doublequote = if Sys.win32 then doublequote_win else doublequote - -let is_add_file file = - String.length file > 1 && String.get file 0 = '@' - -let cut_add file = - String.sub file 1 (String.length file - 1) - -let readwords file = - let visited = ref [] in - let rec aux file = - if Sys.file_exists file then begin - if List.mem file !visited then - raise (Arg.Bad "Circular includes in response files"); - visited := file :: !visited; - let ic = open_in_bin file in - let buf = Buffer.create 32 in - let words = ref [] in - let stash inw = - if inw then begin - let word = Buffer.contents buf in - if is_add_file word then - words := (aux (cut_add word))@ !words - else - words := Buffer.contents buf :: !words; - Buffer.clear buf - end in - let rec unquoted inw = - match input_char ic with - | exception End_of_file -> - stash inw - | ' ' | '\t' | '\r' | '\n' -> - stash inw; unquoted false - | '\\' -> - begin match input_char ic with - | exception End_of_file -> (* tolerance; treat like \newline *) - unquoted inw - | '\n' -> - unquoted inw - | c -> - Buffer.add_char buf c; unquoted true - end - | '\'' -> - singlequote ic buf; unquoted true - | '\"' -> - doublequote ic buf; - unquoted true - | c -> - Buffer.add_char buf c; unquoted true in - unquoted false; - close_in ic; - !words - end else [file] in - List.rev (aux file) - -let expand_responsefiles args = - let acc = ref [] in - for i = (Array.length args - 1) downto 0 do - let file = args.(i) in - if is_add_file file then - acc := readwords (cut_add file) @ !acc - else - acc := file::!acc - done; - Array.of_list !acc diff --git a/lib/Responsefile.mli b/lib/Responsefile.mli index b55dac16..ec82c32e 100644 --- a/lib/Responsefile.mli +++ b/lib/Responsefile.mli @@ -15,6 +15,6 @@ (* *********************************************************************) -val expand_responsefiles: string array -> string array +val expandargv: string array -> string array (** Expand responsefile arguments contained in the array and return the full set of arguments. *) diff --git a/lib/Responsefile.mll b/lib/Responsefile.mll new file mode 100644 index 00000000..bb29fd75 --- /dev/null +++ b/lib/Responsefile.mll @@ -0,0 +1,97 @@ +(* *********************************************************************) +(* *) +(* The Compcert verified compiler *) +(* *) +(* Xavier Leroy, INRIA Paris-Rocquencourt *) +(* Bernhard Schommer, AbsInt Angewandte Informatik GmbH *) +(* *) +(* Copyright Institut National de Recherche en Informatique et en *) +(* Automatique. All rights reserved. This file is distributed *) +(* under the terms of the GNU General Public License as published by *) +(* the Free Software Foundation, either version 2 of the License, or *) +(* (at your option) any later version. This file is also distributed *) +(* under the terms of the INRIA Non-Commercial License Agreement. *) +(* *) +(* *********************************************************************) + + +(* Parsing response files with various quoting styles *) + +{ +(* To accumulate the characters in a word or quoted string *) +let buf = Buffer.create 32 + +(* Add the current contents of buf to the list of words seen so far, + taking care not to add empty strings unless warranted (e.g. quoted) *) +let stash inword words = + if inword then begin + let w = Buffer.contents buf in + Buffer.clear buf; + w :: words + end else + words + +} + +let whitespace = [' ' '\t' '\012' '\r' '\n'] + +let backslashes_even = "\\\\"* (* an even number of backslashes *) +let backslashes_odd = "\\\\"* '\\' (* an odd number of backslashes *) + +(* GNU-style quoting *) +(* "Options in file are separated by whitespace. A whitespace + character may be included in an option by surrounding the entire + option in either single or double quotes. Any character (including + a backslash) may be included by prefixing the character to be + included with a backslash. The file may itself contain additional + @file options; any such options will be processed recursively." *) + +rule gnu_unquoted inword words = parse + | eof { List.rev (stash inword words) } + | whitespace+ { gnu_unquoted false (stash inword words) lexbuf } + | '\'' { gnu_single_quote lexbuf; gnu_unquoted true words lexbuf } + | '\"' { gnu_double_quote lexbuf; gnu_unquoted true words lexbuf } + | "" { gnu_one_char lexbuf; gnu_unquoted true words lexbuf } + +and gnu_one_char = parse + | '\\' (_ as c) { Buffer.add_char buf c } + | _ as c { Buffer.add_char buf c } + +and gnu_single_quote = parse + | eof { () (* tolerance *) } + | '\'' { () } + | "" { gnu_one_char lexbuf; gnu_single_quote lexbuf } + +and gnu_double_quote = parse + | eof { () (* tolerance *) } + | '\"' { () } + | "" { gnu_one_char lexbuf; gnu_double_quote lexbuf } + +{ + +let re_responsefile = Str.regexp "@\\(.*\\)$" + +exception Error of string + +let expandargv argv = + let rec expand_arg seen arg k = + if not (Str.string_match re_responsefile arg 0) then + arg :: k + else begin + let filename = Str.matched_group 1 arg in + if List.mem filename seen then + raise (Error ("cycle in response files: " ^ filename)); + let ic = open_in filename in + let words = gnu_unquoted false [] (Lexing.from_channel ic) in + close_in ic; + expand_args (filename :: seen) words k + end + and expand_args seen args k = + match args with + | [] -> k + | a1 :: al -> expand_args seen al (expand_arg seen a1 k) + in + let args = Array.to_list argv in + Array.of_list (List.rev (expand_args [] args [])) + +} -- cgit From 0a38e7727f3c38742704907e0c4dc60da6b99743 Mon Sep 17 00:00:00 2001 From: Bernhard Schommer Date: Thu, 21 Jul 2016 14:39:00 +0200 Subject: Added support for quoting for diab backend. The diab data compiler has different quoting conventions compared to the gnu tools. Bug 18308. --- configure | 2 +- driver/Configuration.ml | 2 ++ driver/Configuration.mli | 1 + driver/Driveraux.ml | 18 +++++++++++++++++- 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/configure b/configure index 718f9b83..13927592 100755 --- a/configure +++ b/configure @@ -119,7 +119,7 @@ case "$target" in asm_supports_cfi=false clinker="${toolprefix}dcc" libmath="-lm" - responsefile="unsupported" + responsefile="diab" ;; *) system="linux" diff --git a/driver/Configuration.ml b/driver/Configuration.ml index be581e14..0a2b3eec 100644 --- a/driver/Configuration.ml +++ b/driver/Configuration.ml @@ -174,10 +174,12 @@ let struct_return_style = type response_file_style = | Gnu (* responsefiles in gnu compatible syntax *) + | Diab (* responsefiles in diab compatible syntax *) | Unsupported (* responsefiles are not supported *) let response_file_style = match get_config_string "response_file_style" with | "unsupported" -> Unsupported | "gnu" -> Gnu + | "diab" -> Diab | v -> bad_config "response_file_style" [v] diff --git a/driver/Configuration.mli b/driver/Configuration.mli index 1092bf6d..7087c3c2 100644 --- a/driver/Configuration.mli +++ b/driver/Configuration.mli @@ -66,6 +66,7 @@ val struct_return_style: struct_return_style type response_file_style = | Gnu (* responsefiles in gnu compatible syntax *) + | Diab (* responsefiles in diab compatible syntax *) | Unsupported (* responsefiles are not supported *) val response_file_style: response_file_style diff --git a/driver/Driveraux.ml b/driver/Driveraux.ml index 6bd48344..1ee39e8e 100644 --- a/driver/Driveraux.ml +++ b/driver/Driveraux.ml @@ -69,14 +69,30 @@ let gnu_quote arg = Buffer.add_char buf c) arg; Buffer.contents buf +let re_whitespace = Str.regexp ".*[ \t\n\r]" + +let diab_quote arg = + let buf = Buffer.create ((String.length arg) + 8) in + let doublequote = Str.string_match re_whitespace arg 0 in + if doublequote then Buffer.add_char buf '"'; + String.iter (fun c -> + if c = '"' then Buffer.add_char buf '\\'; + Buffer.add_char buf c) arg; + if doublequote then Buffer.add_char buf '"'; + Buffer.contents buf + let command ?stdout args = let resp = Sys.win32 && Configuration.response_file_style <> Configuration.Unsupported in if resp && List.fold_left (fun len arg -> len + String.length arg + 1) 0 args > 7000 then + let quote = match Configuration.response_file_style with + | Configuration.Unsupported -> assert false + | Configuration.Gnu -> gnu_quote + | Configuration.Diab -> diab_quote in let file,oc = Filename.open_temp_file "compcert" "" in let cmd,args = match args with | cmd::args -> cmd,args | [] -> assert false (* Should never happen *) in - List.iter (fun a -> Printf.fprintf oc "%s " (gnu_quote a)) args; + List.iter (fun a -> Printf.fprintf oc "%s " (quote a)) args; close_out oc; let arg = if gnu_system then "@"^file else "-@"^file in let ret = command stdout [cmd;arg] in -- cgit From 951c37603e2a807b116f91d7390bd6e641d8092b Mon Sep 17 00:00:00 2001 From: Bernhard Schommer Date: Thu, 21 Jul 2016 14:45:44 +0200 Subject: Corrected diab quoting. Bug 18308 --- driver/Driveraux.ml | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/driver/Driveraux.ml b/driver/Driveraux.ml index 1ee39e8e..a773b37c 100644 --- a/driver/Driveraux.ml +++ b/driver/Driveraux.ml @@ -69,17 +69,20 @@ let gnu_quote arg = Buffer.add_char buf c) arg; Buffer.contents buf -let re_whitespace = Str.regexp ".*[ \t\n\r]" +let re_quote = Str.regexp ".*[ \t\n\r\"]" let diab_quote arg = let buf = Buffer.create ((String.length arg) + 8) in - let doublequote = Str.string_match re_whitespace arg 0 in - if doublequote then Buffer.add_char buf '"'; - String.iter (fun c -> - if c = '"' then Buffer.add_char buf '\\'; - Buffer.add_char buf c) arg; - if doublequote then Buffer.add_char buf '"'; - Buffer.contents buf + let doublequote = Str.string_match re_quote arg 0 in + if doublequote then begin + Buffer.add_char buf '"'; + String.iter (fun c -> + if c = '"' then Buffer.add_char buf '\\'; + Buffer.add_char buf c) arg; + if doublequote then Buffer.add_char buf '"'; + Buffer.contents buf + end else + arg let command ?stdout args = let resp = Sys.win32 && Configuration.response_file_style <> Configuration.Unsupported in -- cgit From eb2844b87fa0e176bd65466d7ab7d16666344406 Mon Sep 17 00:00:00 2001 From: Bernhard Schommer Date: Wed, 10 Aug 2016 13:31:25 +0200 Subject: Added missing begin end around quoting. Bug 18308. --- driver/Driveraux.ml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/driver/Driveraux.ml b/driver/Driveraux.ml index a773b37c..de1326ce 100644 --- a/driver/Driveraux.ml +++ b/driver/Driveraux.ml @@ -62,10 +62,10 @@ let command stdout args = let gnu_quote arg = let len = String.length arg in let buf = Buffer.create len in - String.iter (fun c -> match c with + String.iter (fun c -> begin match c with | ' ' | '\t' | '\r' | '\n' | '\\' | '\'' | '"' -> Buffer.add_char buf '\\' - | _ -> (); + | _ -> () end; Buffer.add_char buf c) arg; Buffer.contents buf -- cgit From 5309f16159e4decd81330622dcdd6eb4b25819a1 Mon Sep 17 00:00:00 2001 From: Bernhard Schommer Date: Tue, 16 Aug 2016 10:35:17 +0200 Subject: Moved quoting functions in Responsefile Also corrected some typos and corrected exception handling for expandargv. Bug 18308 --- driver/Commandline.ml | 2 +- driver/Driveraux.ml | 36 +++++------------------------------- driver/Linker.ml | 4 ++-- lib/Responsefile.mli | 11 +++++++++++ lib/Responsefile.mll | 27 +++++++++++++++++++++++++-- 5 files changed, 44 insertions(+), 36 deletions(-) diff --git a/driver/Commandline.ml b/driver/Commandline.ml index 7e683680..d125736a 100644 --- a/driver/Commandline.ml +++ b/driver/Commandline.ml @@ -103,6 +103,6 @@ let parse_cmdline spec = try let argv = expandargv Sys.argv in parse_array spec argv 1 (Array.length argv - 1) - with Arg.Bad s -> + with Responsefile.Error s -> eprintf "%s" s; exit 2 diff --git a/driver/Driveraux.ml b/driver/Driveraux.ml index de1326ce..33cd9215 100644 --- a/driver/Driveraux.ml +++ b/driver/Driveraux.ml @@ -17,7 +17,7 @@ open Clflags (* Is this a gnu based tool chain *) let gnu_system = Configuration.system <> "diab" -(* Sage removale of files *) +(* Safe removal of files *) let safe_remove file = try Sys.remove file with Sys_error _ -> () @@ -58,46 +58,20 @@ let command stdout args = argv.(0) fn (Unix.error_message err) param; -1 -(* This function reimplements quoting of writeargv from libiberty *) -let gnu_quote arg = - let len = String.length arg in - let buf = Buffer.create len in - String.iter (fun c -> begin match c with - | ' ' | '\t' | '\r' | '\n' | '\\' | '\'' | '"' -> - Buffer.add_char buf '\\' - | _ -> () end; - Buffer.add_char buf c) arg; - Buffer.contents buf - -let re_quote = Str.regexp ".*[ \t\n\r\"]" - -let diab_quote arg = - let buf = Buffer.create ((String.length arg) + 8) in - let doublequote = Str.string_match re_quote arg 0 in - if doublequote then begin - Buffer.add_char buf '"'; - String.iter (fun c -> - if c = '"' then Buffer.add_char buf '\\'; - Buffer.add_char buf c) arg; - if doublequote then Buffer.add_char buf '"'; - Buffer.contents buf - end else - arg - let command ?stdout args = let resp = Sys.win32 && Configuration.response_file_style <> Configuration.Unsupported in if resp && List.fold_left (fun len arg -> len + String.length arg + 1) 0 args > 7000 then - let quote = match Configuration.response_file_style with + let quote,prefix = match Configuration.response_file_style with | Configuration.Unsupported -> assert false - | Configuration.Gnu -> gnu_quote - | Configuration.Diab -> diab_quote in + | Configuration.Gnu -> Responsefile.gnu_quote,"@" + | Configuration.Diab -> Responsefile.diab_quote,"-@" in let file,oc = Filename.open_temp_file "compcert" "" in let cmd,args = match args with | cmd::args -> cmd,args | [] -> assert false (* Should never happen *) in List.iter (fun a -> Printf.fprintf oc "%s " (quote a)) args; close_out oc; - let arg = if gnu_system then "@"^file else "-@"^file in + let arg = prefix^file in let ret = command stdout [cmd;arg] in safe_remove file; ret diff --git a/driver/Linker.ml b/driver/Linker.ml index 305c5603..2f767023 100644 --- a/driver/Linker.ml +++ b/driver/Linker.ml @@ -24,8 +24,8 @@ let linker exe_name files = ["-o"; exe_name]; files; (if Configuration.has_runtime_lib - then ["-L" ^ !stdlib_path; "-lcompcert"] - else []) + then ["-L" ^ !stdlib_path; "-lcompcert"] + else []) ] in let exc = command cmd in if exc <> 0 then begin diff --git a/lib/Responsefile.mli b/lib/Responsefile.mli index ec82c32e..ada5a15d 100644 --- a/lib/Responsefile.mli +++ b/lib/Responsefile.mli @@ -18,3 +18,14 @@ val expandargv: string array -> string array (** Expand responsefile arguments contained in the array and return the full set of arguments. *) + +exception Error of string + (** Raised by [expandargv] in case of an error *) + +val gnu_quote : string -> string + (** [gnu_quote arg] returns [arg] quoted compatible with the gnu tool chain + quoting conventions. *) + +val diab_quote : string -> string + (** [diab_quote arg] returns [arg] quoted compatible with the diab tool chain + quoting conventions. *) diff --git a/lib/Responsefile.mll b/lib/Responsefile.mll index bb29fd75..35a2dbdb 100644 --- a/lib/Responsefile.mll +++ b/lib/Responsefile.mll @@ -15,8 +15,6 @@ (* *********************************************************************) -(* Parsing response files with various quoting styles *) - { (* To accumulate the characters in a word or quoted string *) let buf = Buffer.create 32 @@ -94,4 +92,29 @@ let expandargv argv = let args = Array.to_list argv in Array.of_list (List.rev (expand_args [] args [])) +(* This function reimplements quoting of writeargv from libiberty *) +let gnu_quote arg = + let len = String.length arg in + let buf = Buffer.create len in + String.iter (fun c -> begin match c with + | ' ' | '\t' | '\r' | '\n' | '\\' | '\'' | '"' -> + Buffer.add_char buf '\\' + | _ -> () end; + Buffer.add_char buf c) arg; + Buffer.contents buf + +let re_quote = Str.regexp ".*[ \t\n\r\"]" + +let diab_quote arg = + let buf = Buffer.create ((String.length arg) + 8) in + let doublequote = Str.string_match re_quote arg 0 in + if doublequote then begin + Buffer.add_char buf '"'; + String.iter (fun c -> + if c = '"' then Buffer.add_char buf '\\'; + Buffer.add_char buf c) arg; + if doublequote then Buffer.add_char buf '"'; + Buffer.contents buf + end else + arg } -- cgit