From b0b048c03930b826ab3dbf88b56fd664fca26886 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Fri, 22 May 2020 15:32:31 +0300 Subject: Add script command redirect aliases --- doc/testscript.cli | 26 +-- .../script/lexer+command-line.test.testscript | 36 ++-- libbuild2/build/script/lexer.cxx | 10 +- libbuild2/build/script/lexer.hxx | 6 +- .../script/parser+here-document.test.testscript | 102 ++++++++--- .../script/parser+here-string.test.testscript | 24 ++- .../build/script/parser+pipe-expr.test.testscript | 4 +- .../build/script/parser+redirect.test.testscript | 152 ++++++++++++---- .../build/script/parser+regex.test.testscript | 87 ++++----- libbuild2/build/script/parser.cxx | 6 +- libbuild2/cc/lexer.cxx | 13 +- libbuild2/lexer.hxx | 4 +- .../script/lexer+command-expansion.test.testscript | 102 +++++++++-- libbuild2/script/lexer.cxx | 162 +++++++++++++---- libbuild2/script/lexer.hxx | 53 +++++- libbuild2/script/lexer.test.cxx | 12 +- libbuild2/script/parser.cxx | 68 +++++-- libbuild2/script/parser.hxx | 3 +- libbuild2/script/run.cxx | 13 +- libbuild2/script/script.cxx | 118 ++++++------- libbuild2/script/script.hxx | 21 ++- libbuild2/script/token.cxx | 43 +++-- libbuild2/script/token.hxx | 24 ++- libbuild2/test/script/lexer.cxx | 10 +- libbuild2/test/script/lexer.hxx | 6 +- .../test/script/parser+redirect.test.testscript | 8 +- libbuild2/test/script/parser+regex.test.testscript | 5 +- libbuild2/test/script/parser.cxx | 5 +- tests/test/script/runner/redirect.testscript | 195 ++++++++++++++++++--- 29 files changed, 967 insertions(+), 351 deletions(-) diff --git a/doc/testscript.cli b/doc/testscript.cli index 6b47dad..665446e 100644 --- a/doc/testscript.cli +++ b/doc/testscript.cli @@ -545,7 +545,7 @@ complete picture: \ $* 'World' >'Hello, World!' : command-name -$* 'John' 'Jane' >EOO : command-names +$* 'John' 'Jane' >>EOO : command-names Hello, Jane! Hello, John! EOO @@ -1536,9 +1536,9 @@ stderr: '2'(out-redirect) in-redirect: '<-'|\ '<|'|\ - '<'{':'?'/'?} |\ - '<<'{':'?'/'?} |\ - '<<<' + ('<='|'<<<') |\ + ('<<='|'<<'){':'?'/'?} |\ + ('<<<='|'<'){':'?'/'?} out-redirect: '>-'|\ '>|'|\ @@ -1546,9 +1546,9 @@ out-redirect: '>-'|\ '>=' |\ '>+' |\ '>&' ('1'|'2')|\ - '>'{':'?'/'?}'~'? |\ - '>>'{':'?'/'?}'~'? |\ - '>>>' + ('>?'|'>>>') |\ + ('>>?'|'>>'){':'?'/'?}'~'? |\ + ('>>>?'|'>'){':'?'/'?}'~'? here-document: * @@ -1866,9 +1866,9 @@ $* a1>- \ in-redirect: '<-'|\ '<|'|\ - '<'{':'?'/'?} |\ - '<<'{':'?'/'?} |\ - '<<<' + ('<='|'<<<') |\ + ('<<='|'<<'){':'?'/'?} |\ + ('<<<='|'<'){':'?'/'?} \ The \c{stdin} data can come from a pipe, here-string (\c{<}), here-document @@ -1905,9 +1905,9 @@ out-redirect: '>-'|\ '>=' |\ '>+' |\ '>&' ('1'|'2')|\ - '>'{':'?'/'?}'~'? |\ - '>>'{':'?'/'?}'~'? |\ - '>>>' + ('>?'|'>>>') |\ + ('>>?'|'>>'){':'?'/'?}'~'? |\ + ('>>>?'|'>'){':'?'/'?}'~'? \ The \c{stdout} and \c{stderr} data can go to a pipe (\c{stdout} only), file diff --git a/libbuild2/build/script/lexer+command-line.test.testscript b/libbuild2/build/script/lexer+command-line.test.testscript index 1777583..3eceae8 100644 --- a/libbuild2/build/script/lexer+command-line.test.testscript +++ b/libbuild2/build/script/lexer+command-line.test.testscript @@ -47,24 +47,36 @@ test.arguments = command-line : str : - $* <"cmd b" >>EOO + $* <"cmd <<<=a 1>>>?b" >>EOO 'cmd' - < + <<<= 'a' '1' - > + >>>? 'b' EOO : str-nn : - $* <"cmd <:a 1>:b" >>EOO + $* <"cmd <<<=:a 1>>>?:b" >>EOO 'cmd' - <: + <<<=: 'a' '1' - >: + >>>?: + 'b' + + EOO + + : str-nn-alias + : + $* <"cmd <<<:a 1>>>?:b" >>EOO + 'cmd' + <<<: + 'a' + '1' + >>>?: 'b' EOO @@ -83,26 +95,26 @@ test.arguments = command-line : doc-nn : - $* <"cmd <<:EOI 1>>:EOO" >>EOO + $* <"cmd <<:EOI 1>>?:EOO" >>EOO 'cmd' <<: 'EOI' '1' - >>: + >>?: 'EOO' EOO : file-cmp : - $* <"cmd <<>>out 2>>>err" >>EOO + $* <"cmd <=in >?out 2>?err" >>EOO 'cmd' - <<< + <= 'in' - >>> + >? 'out' '2' - >>> + >? 'err' EOO diff --git a/libbuild2/build/script/lexer.cxx b/libbuild2/build/script/lexer.cxx index de93b6b..716d898 100644 --- a/libbuild2/build/script/lexer.cxx +++ b/libbuild2/build/script/lexer.cxx @@ -13,6 +13,14 @@ namespace build2 { using type = token_type; + build2::script::redirect_aliases lexer::redirect_aliases { + type (type::in_file), + type (type::in_doc), + type (type::in_str), + type (type::out_file_ovr), + type (type::out_file_app), + nullopt}; + void lexer:: mode (build2::lexer_mode m, char ps, @@ -192,7 +200,7 @@ namespace build2 m == lexer_mode::first_token || m == lexer_mode::second_token) { - if (optional t = next_cmd_op (c, sep, m)) + if (optional t = next_cmd_op (c, sep)) return move (*t); } diff --git a/libbuild2/build/script/lexer.hxx b/libbuild2/build/script/lexer.hxx index f755cea..7d919e5 100644 --- a/libbuild2/build/script/lexer.hxx +++ b/libbuild2/build/script/lexer.hxx @@ -48,7 +48,8 @@ namespace build2 const char* escapes = nullptr) : base_lexer (is, name, line, nullptr /* escapes */, - false /* set_mode */) + false /* set_mode */, + redirect_aliases) { mode (m, '\0', escapes); } @@ -62,6 +63,9 @@ namespace build2 virtual token next () override; + public: + static redirect_aliases_type redirect_aliases; + private: token next_line (); diff --git a/libbuild2/build/script/parser+here-document.test.testscript b/libbuild2/build/script/parser+here-document.test.testscript index 8ea6373..f56a5e1 100644 --- a/libbuild2/build/script/parser+here-document.test.testscript +++ b/libbuild2/build/script/parser+here-document.test.testscript @@ -6,49 +6,85 @@ { : missing-newline : + $* <'cmd <<=' 2>>EOE != 0 + buildfile:11:8: error: expected here-document end marker + EOE + + : missing-newline-alias + : $* <'cmd <<' 2>>EOE != 0 buildfile:11:7: error: expected here-document end marker EOE : missing-exit : + $* <'cmd <<= != 0' 2>>EOE != 0 + buildfile:11:9: error: expected here-document end marker + EOE + + : missing-exit-alias + : $* <'cmd << != 0' 2>>EOE != 0 buildfile:11:8: error: expected here-document end marker EOE : missing-empty : + $* <'cmd <<=""' 2>>EOE != 0 + buildfile:11:8: error: expected here-document end marker + EOE + + : missing-empty-alias + : $* <'cmd <<""' 2>>EOE != 0 buildfile:11:7: error: expected here-document end marker EOE : unseparated-expansion : + $* <'cmd <<=FOO$foo' 2>>EOE != 0 + buildfile:11:11: error: here-document end marker must be literal + EOE + + : unseparated-expansion-alias + : $* <'cmd <>EOE != 0 buildfile:11:10: error: here-document end marker must be literal EOE : quoted-single-partial : - $* <"cmd <>EOE != 0 - buildfile:11:7: error: partially-quoted here-document end marker + $* <"cmd <<=F'O'O" 2>>EOE != 0 + buildfile:11:8: error: partially-quoted here-document end marker EOE : quoted-double-partial : - $* <'cmd <<"FO"O' 2>>EOE != 0 - buildfile:11:7: error: partially-quoted here-document end marker + $* <'cmd <<="FO"O' 2>>EOE != 0 + buildfile:11:8: error: partially-quoted here-document end marker EOE : quoted-mixed : - $* <"cmd <<\"FO\"'O'" 2>>EOE != 0 - buildfile:11:7: error: partially-quoted here-document end marker + $* <"cmd <<=\"FO\"'O'" 2>>EOE != 0 + buildfile:11:8: error: partially-quoted here-document end marker EOE : unseparated : $* <>EOO + cmd <<=EOF!=0 + foo + EOF + EOI + cmd <<=EOF != 0 + foo + EOF + EOO + + : unseparated-alias + : + $* <>EOO cmd <>EOO + cmd <<='EOF' + foo + EOF + EOI + cmd <<=EOF + foo + EOF + EOO + + : quoted-single-alias + : + $* <>EOO cmd <<'EOF' foo EOF @@ -73,6 +121,18 @@ : quoted-double : $* <>EOO + cmd <<="EOF" + foo + EOF + EOI + cmd <<=EOF + foo + EOF + EOO + + : quoted-double-alias + : + $* <>EOO cmd <<"EOF" foo EOF @@ -89,13 +149,13 @@ : basic : $* <>EOO - cmd <>EOO - cmd <>EOO - cmd <>EOO x = foo bar - cmd <<"EOF" + cmd <<="EOF" $x EOF EOI - cmd <>EOO x = foo - cmd <<"EOF" + cmd <<="EOF" $x bar $x EOF EOI - cmd <>EOE != 0 - cmd <>EOO -cmd <>EOO -cmd <<"EOF" +cmd <<="EOF" 'single' "double" b'o't"h" ('single' "double") EOF EOI -cmd <>EOO -cmd <"" +cmd <<<="" EOI -cmd <'' +cmd <<<='' EOO : empty-nn : $* <>EOO -cmd <:"" +cmd <<<=:"" EOI -cmd <:'' +cmd <<<=:'' +EOO + +: empty-alias +: +$* <>EOO +cmd <<<"" +EOI +cmd <<<'' +EOO + +: empty-nn-alias +: +$* <>EOO +cmd <<<:"" +EOI +cmd <<<:'' EOO diff --git a/libbuild2/build/script/parser+pipe-expr.test.testscript b/libbuild2/build/script/parser+pipe-expr.test.testscript index 3dd6b1b..a6ca12e 100644 --- a/libbuild2/build/script/parser+pipe-expr.test.testscript +++ b/libbuild2/build/script/parser+pipe-expr.test.testscript @@ -36,7 +36,7 @@ EOO : here-doc : $* <>EOO -cmd1 <>EOO2 && cmd3 <&1 | cmd4 2>>EOE4 >>EOO4 +cmd1 <<=EOI1 | cmd2 >>?EOO2 && cmd3 <<=EOI3 2>&1 | cmd4 2>>?EOE4 >>?EOO4 input one EOI1 @@ -53,7 +53,7 @@ output four EOO4 EOI -cmd1 <>EOO2 && cmd3 <&1 | cmd4 >>EOO4 2>>EOE4 +cmd1 <<=EOI1 | cmd2 >>?EOO2 && cmd3 <<=EOI3 2>&1 | cmd4 >>?EOO4 2>>?EOE4 input one EOI1 diff --git a/libbuild2/build/script/parser+redirect.test.testscript b/libbuild2/build/script/parser+redirect.test.testscript index 641381e..82c04ea 100644 --- a/libbuild2/build/script/parser+redirect.test.testscript +++ b/libbuild2/build/script/parser+redirect.test.testscript @@ -21,9 +21,9 @@ : portable-path : $* <>EOO - cmd /bar 2>/baz + cmd <<<=/foo >>>?/bar 2>>>?/baz EOI - cmd /bar 2>/baz + cmd <<<=/foo >>>?/bar 2>>>?/baz EOO } @@ -33,9 +33,9 @@ : portable-path : $* <>EOO - cmd >/~%foo% 2>/~%bar% + cmd >>>?/~%foo% 2>>>?/~%bar% EOI - cmd >/~%foo% 2>/~%bar% + cmd >>>?/~%foo% 2>>>?/~%bar% EOO } } @@ -49,7 +49,7 @@ : portable-path : $* <>EOO - cmd </EOO_ 2>/EOE_ + cmd <<=/EOI_ >>?/EOO_ 2>>?/EOE_ foo EOI_ bar @@ -57,7 +57,7 @@ baz EOE_ EOI - cmd </EOO_ 2>/EOE_ + cmd <<=/EOI_ >>?/EOO_ 2>>?/EOE_ foo EOI_ bar @@ -72,11 +72,35 @@ : in-out : $* <>EOO - cmd <<:/EOF >>:/EOF + cmd <<=:/EOF >>?:/EOF foo EOF EOI - cmd <<:/EOF >>:/EOF + cmd <<=:/EOF >>?:/EOF + foo + EOF + EOO + + : in-alias-out + : + $* <>EOO + cmd <<:/EOF >>?:/EOF + foo + EOF + EOI + cmd <<:/EOF >>?:/EOF + foo + EOF + EOO + + : out-in-alias + : + $* <>EOO + cmd >>?:/EOF <<:/EOF + foo + EOF + EOI + cmd <<:/EOF >>?:/EOF foo EOF EOO @@ -87,21 +111,21 @@ : modifiers : $* <>EOE != 0 - cmd <<:/EOF >>:EOF + cmd <<=:/EOF >>?:EOF foo EOF EOI - buildfile:11:16: error: different modifiers for shared here-document 'EOF' + buildfile:11:18: error: different modifiers for shared here-document 'EOF' EOE : quoting : $* <>EOE != 0 - cmd <>"EOF" + cmd <<=EOF >>?"EOF" foo EOF EOI - buildfile:11:13: error: different quoting for shared here-document 'EOF' + buildfile:11:15: error: different quoting for shared here-document 'EOF' EOE } } @@ -113,13 +137,13 @@ : portable-path : $* <>EOO - cmd >/~%EOF% 2>/~%EOE% + cmd >>?/~%EOF% 2>>?/~%EOE% foo EOF bar EOE EOI - cmd >/~%EOF% 2>/~%EOE% + cmd >>?/~%EOF% 2>>?/~%EOE% foo EOF bar @@ -132,11 +156,11 @@ : in-out : $* <>EOO - cmd >>~/EOF/ 2>>~/EOF/ + cmd >>?~/EOF/ 2>>?~/EOF/ foo EOF EOI - cmd >>~/EOF/ 2>>~/EOF/ + cmd >>?~/EOF/ 2>>?~/EOF/ foo EOF EOO @@ -147,21 +171,21 @@ : introducers : $* <>EOE != 0 - cmd >>~/EOF/ 2>>~%EOF% + cmd >>?~/EOF/ 2>>?~%EOF% foo EOF EOI - buildfile:11:18: error: different introducers for shared here-document regex 'EOF' + buildfile:11:20: error: different introducers for shared here-document regex 'EOF' EOE : flags : $* <>EOE != 0 - cmd >>~/EOF/ 2>>~/EOF/i + cmd >>?~/EOF/ 2>>?~/EOF/i foo EOF EOI - buildfile:11:18: error: different global flags for shared here-document regex 'EOF' + buildfile:11:20: error: different global flags for shared here-document regex 'EOF' EOE } } @@ -179,23 +203,23 @@ : string : $* <>EOO - cmd >>EOF >bar + cmd >>?EOF >>>?bar foo EOF EOI - cmd >bar + cmd >>>?bar EOO : regex : $* <>EOO - cmd >>FOO >>~/BAR/ + cmd >>?FOO >>?~/BAR/ foo FOO bar BAR EOI - cmd >>~/BAR/ + cmd >>?~/BAR/ bar BAR EOO @@ -215,11 +239,11 @@ : different-modifiers : $* <>EOE != 0 - cmd >>EOF >>/EOF + cmd >>?EOF >>?/EOF foo EOF EOI - buildfile:11:14: error: different modifiers for shared here-document 'EOF' + buildfile:11:16: error: different modifiers for shared here-document 'EOF' EOE } } @@ -260,9 +284,9 @@ : cmp : $* <>EOO - cmd 0<<>>b 2>>>c + cmd 0<=a 1>?b 2>?c EOI - cmd <<>>b 2>>>c + cmd <=a >?b 2>?c EOO : write @@ -276,9 +300,9 @@ : quote : $* <>EOO - cmd 0<<<"a f" 1>="b f" 2>+"c f" + cmd 0<="a f" 1>="b f" 2>+"c f" EOI - cmd <<<'a f' >='b f' 2>+'c f' + cmd <='a f' >='b f' 2>+'c f' EOO : in @@ -287,17 +311,37 @@ : missed : $* <>EOE !=0 - cmd <<< + cmd <= EOI - buildfile:11:8: error: missing stdin file + buildfile:11:7: error: missing stdin file EOE : empty : $* <>EOE !=0 - cmd <<<"" + cmd <="" EOI - buildfile:11:8: error: empty stdin redirect path + buildfile:11:7: error: empty stdin redirect path + EOE + } + + : in-alias + : + { + : missed + : + $* <>EOE !=0 + cmd < + EOI + buildfile:11:6: error: missing stdin file + EOE + + : empty + : + $* <>EOE !=0 + cmd <"" + EOI + buildfile:11:6: error: empty stdin redirect path EOE } @@ -321,6 +365,26 @@ EOE } + : out-alias + : + { + : missed + : + $* <>EOE !=0 + cmd > + EOI + buildfile:11:6: error: missing stdout file + EOE + + : empty + : + $* <>EOE !=0 + cmd >"" + EOI + buildfile:11:6: error: empty stdout redirect path + EOE + } + : err : { @@ -340,6 +404,26 @@ buildfile:11:8: error: empty stderr redirect path EOE } + + : err-alias + : + { + : missed + : + $* <>EOE !=0 + cmd 2> + EOI + buildfile:11:7: error: missing stderr file + EOE + + : empty + : + $* <>EOE !=0 + cmd 2>"" + EOI + buildfile:11:7: error: empty stderr redirect path + EOE + } } : merge diff --git a/libbuild2/build/script/parser+regex.test.testscript b/libbuild2/build/script/parser+regex.test.testscript index 4a47e73..625bfdf 100644 --- a/libbuild2/build/script/parser+regex.test.testscript +++ b/libbuild2/build/script/parser+regex.test.testscript @@ -9,61 +9,61 @@ { : missed : - $* <'cmd >~' 2>>EOE != 0 - buildfile:11:7: error: missing stdout here-string regex + $* <'cmd >>>?~' 2>>EOE != 0 + buildfile:11:10: error: missing stdout here-string regex EOE : no-introducer : - $* <'cmd >~""' 2>>EOE != 0 - buildfile:11:7: error: no introducer character in stdout regex redirect + $* <'cmd >>>?~""' 2>>EOE != 0 + buildfile:11:10: error: no introducer character in stdout regex redirect EOE : no-term-introducer : - $* <'cmd >~/' 2>>EOE != 0 - buildfile:11:7: error: no closing introducer character in stdout regex redirect + $* <'cmd >>>?~/' 2>>EOE != 0 + buildfile:11:10: error: no closing introducer character in stdout regex redirect EOE : portable-path-introducer : - $* <'cmd >/~/foo/' 2>>EOE != 0 - buildfile:11:8: error: portable path modifier and '/' introducer in stdout regex redirect + $* <'cmd >>>?/~/foo/' 2>>EOE != 0 + buildfile:11:11: error: portable path modifier and '/' introducer in stdout regex redirect EOE : empty : - $* <'cmd >~//' 2>>EOE != 0 - buildfile:11:7: error: stdout regex redirect is empty + $* <'cmd >>>?~//' 2>>EOE != 0 + buildfile:11:10: error: stdout regex redirect is empty EOE : no-flags : - $* <'cmd >~/fo*/' >'cmd >~/fo*/' + $* <'cmd >>>?~/fo*/' >'cmd >>>?~/fo*/' : idot : - $* <'cmd >~/fo*/d' >'cmd >~/fo*/d' + $* <'cmd >>>?~/fo*/d' >'cmd >>>?~/fo*/d' : icase : - $* <'cmd >~/fo*/i' >'cmd >~/fo*/i' + $* <'cmd >>>?~/fo*/i' >'cmd >>>?~/fo*/i' : invalid-flags1 : - $* <'cmd >~/foo/z' 2>>EOE != 0 - buildfile:11:7: error: junk at the end of stdout regex redirect + $* <'cmd >>>?~/foo/z' 2>>EOE != 0 + buildfile:11:10: error: junk at the end of stdout regex redirect EOE : invalid-flags2 : - $* <'cmd >~/foo/iz' 2>>EOE != 0 - buildfile:11:7: error: junk at the end of stdout regex redirect + $* <'cmd >>>?~/foo/iz' 2>>EOE != 0 + buildfile:11:10: error: junk at the end of stdout regex redirect EOE : no-newline : - $* <'cmd >:~/fo*/' >'cmd >:~/fo*/' + $* <'cmd >>>?:~/fo*/' >'cmd >>>?:~/fo*/' } : stderr @@ -71,8 +71,8 @@ { : missed : - $* <'cmd 2>~' 2>>EOE != 0 - buildfile:11:8: error: missing stderr here-string regex + $* <'cmd 2>>>?~' 2>>EOE != 0 + buildfile:11:11: error: missing stderr here-string regex EOE : no-introducer @@ -81,15 +81,15 @@ : All we need is to make sure that the proper description is passed to : the parse_regex() function. : - $* <'cmd 2>~""' 2>>EOE != 0 - buildfile:11:8: error: no introducer character in stderr regex redirect + $* <'cmd 2>>>?~""' 2>>EOE != 0 + buildfile:11:11: error: no introducer character in stderr regex redirect EOE } : modifier-last : - $* <'cmd >~/x' 2>>EOE != 0 - buildfile:11:7: error: no closing introducer character in stdout regex redirect + $* <'cmd >>>?~/x' 2>>EOE != 0 + buildfile:11:10: error: no closing introducer character in stdout regex redirect EOE } @@ -101,14 +101,14 @@ { : missed : - $* <'cmd >>~' 2>>EOE != 0 - buildfile:11:8: error: expected here-document regex end marker + $* <'cmd >>?~' 2>>EOE != 0 + buildfile:11:9: error: expected here-document regex end marker EOE : portable-path-introducer : $* <>EOE != 0 - cmd >>/~/EOO/ + cmd >>?/~/EOO/ foo EOO EOI @@ -118,7 +118,7 @@ : unterminated-line-char : $* <>EOE != 0 - cmd >>~/EOO/ + cmd >>?~/EOO/ / EOO EOI @@ -128,7 +128,7 @@ : empty : $* <>EOE != 0 - cmd >>:~/EOO/ + cmd >>?:~/EOO/ EOO EOI buildfile:12:1: error: empty here-document regex @@ -137,7 +137,7 @@ : no-flags : $* <>EOO - cmd 2>>~/EOE/ + cmd 2>>?~/EOE/ foo /? /foo/ @@ -149,7 +149,7 @@ //* EOE EOI - cmd 2>>~/EOE/ + cmd 2>>?~/EOE/ foo /? /foo/ @@ -162,15 +162,18 @@ EOE EOO - : no-newline + : no-newline-str + : + $* <'cmd >>>?:~/fo*/' >'cmd >>>?:~/fo*/' + + : no-newline-doc : - $* <'cmd >:~/fo*/' >'cmd >:~/fo*/' $* <>EOO - cmd 2>>:~/EOE/ + cmd 2>>?:~/EOE/ foo EOE EOI - cmd 2>>:~/EOE/ + cmd 2>>?:~/EOE/ foo EOE EOO @@ -181,11 +184,11 @@ : idot : $* <>EOO - cmd 2>>~/EOE/d + cmd 2>>?~/EOE/d foo EOE EOI - cmd 2>>~/EOE/d + cmd 2>>?~/EOE/d foo EOE EOO @@ -193,11 +196,11 @@ : icase : $* <>EOO - cmd 2>>~/EOE/i + cmd 2>>?~/EOE/i foo EOE EOI - cmd 2>>~/EOE/i + cmd 2>>?~/EOE/i foo EOE EOO @@ -209,14 +212,14 @@ { : missed : - $* <'cmd 2>>~' 2>>EOE != 0 - buildfile:11:9: error: expected here-document regex end marker + $* <'cmd 2>>?~' 2>>EOE != 0 + buildfile:11:10: error: expected here-document regex end marker EOE } : modifier-last : - $* <'cmd >>~:/FOO/' 2>>EOE != 0 + $* <'cmd >>?~:/FOO/' 2>>EOE != 0 buildfile:11:5: error: no closing introducer character in here-document regex end marker EOE } diff --git a/libbuild2/build/script/parser.cxx b/libbuild2/build/script/parser.cxx index 86019ba..60000c2 100644 --- a/libbuild2/build/script/parser.cxx +++ b/libbuild2/build/script/parser.cxx @@ -141,7 +141,7 @@ namespace build2 pair p; if (lt != line_type::cmd_else && lt != line_type::cmd_end) - p = parse_command_expr (t, tt); + p = parse_command_expr (t, tt, lexer::redirect_aliases); if (tt != type::newline) fail (t) << "expected newline instead of " << t; @@ -232,7 +232,9 @@ namespace build2 // assert (!pre_parse_); - pair p (parse_command_expr (t, tt)); + pair p ( + parse_command_expr (t, tt, lexer::redirect_aliases)); + assert (tt == type::newline); parse_here_documents (t, tt, p); diff --git a/libbuild2/cc/lexer.cxx b/libbuild2/cc/lexer.cxx index d57f5eb..d2be3d8 100644 --- a/libbuild2/cc/lexer.cxx +++ b/libbuild2/cc/lexer.cxx @@ -48,8 +48,8 @@ namespace build2 auto lexer:: peek (bool e) -> xchar { - if (unget_) - return ungetc_; + if (ungetn_ != 0) + return ungetb_[ungetn_ - 1]; if (unpeek_) return unpeekc_; @@ -98,11 +98,8 @@ namespace build2 inline auto lexer:: get (bool e) -> xchar { - if (unget_) - { - unget_ = false; - return ungetc_; - } + if (ungetn_ != 0) + return ungetb_[--ungetn_]; else { xchar c (peek (e)); @@ -117,7 +114,7 @@ namespace build2 // Increment the logical line similar to how base will increment the // physical (the column counts are the same). // - if (log_line_ && c == '\n' && !unget_) + if (log_line_ && c == '\n' && ungetn_ == 0) ++*log_line_; base::get (c); diff --git a/libbuild2/lexer.hxx b/libbuild2/lexer.hxx index 8dd58c8..749668e 100644 --- a/libbuild2/lexer.hxx +++ b/libbuild2/lexer.hxx @@ -106,7 +106,7 @@ namespace build2 }; class LIBBUILD2_SYMEXPORT lexer: - public butl::char_scanner + public butl::char_scanner { public: // If escape is not NULL then only escape sequences with characters from @@ -256,7 +256,7 @@ namespace build2 namespace butl // ADL { inline build2::location - get_location (const butl::char_scanner::xchar& c, + get_location (const butl::char_scanner::xchar& c, const void* data) { using namespace build2; diff --git a/libbuild2/script/lexer+command-expansion.test.testscript b/libbuild2/script/lexer+command-expansion.test.testscript index 9422cba..f4d69d2 100644 --- a/libbuild2/script/lexer+command-expansion.test.testscript +++ b/libbuild2/script/lexer+command-expansion.test.testscript @@ -113,17 +113,37 @@ test.arguments = command-expansion { : newline : - $* <:"0>EOO + $* <:"0<<<=a b" >>EOO '0' - < + <<<= 'a b' EOO : no-newline : - $* <:"0<:a b" >>EOO + $* <:"0<<<=:a b" >>EOO '0' - <: + <<<=: + 'a b' + EOO + } + + : in-alias + : + { + : newline + : + $* <:"0<<>EOO + '0' + <<< + 'a b' + EOO + + : no-newline + : + $* <:"0<<<:a b" >>EOO + '0' + <<<: 'a b' EOO } @@ -133,17 +153,17 @@ test.arguments = command-expansion { : newline : - $* <:"1>a b" >>EOO + $* <:"1>>>?a b" >>EOO '1' - > + >>>? 'a b' EOO : no-newline : - $* <:"1>:a b" >>EOO + $* <:"1>>>?:a b" >>EOO '1' - >: + >>>?: 'a b' EOO } @@ -157,6 +177,26 @@ test.arguments = command-expansion { : newline : + $* <:"0<<=E O I" >>EOO + '0' + <<= + 'E O I' + EOO + + : no-newline + : + $* <:"0<<=:E O I" >>EOO + '0' + <<=: + 'E O I' + EOO + } + + : in-alias + : + { + : newline + : $* <:"0<>EOO '0' << @@ -177,17 +217,17 @@ test.arguments = command-expansion { : newline : - $* <:"1>>E O O" >>EOO + $* <:"1>>?E O O" >>EOO '1' - >> + >>? 'E O O' EOO : no-newline : - $* <:"1>>:E O O" >>EOO + $* <:"1>>?:E O O" >>EOO '1' - >>: + >>?: 'E O O' EOO } @@ -198,9 +238,17 @@ test.arguments = command-expansion { : in : - $* <:"0<<>EOO + $* <:"0<=a b" >>EOO '0' - <<< + <= + 'a b' + EOO + + : in-alias + : + $* <:"0>EOO + '0' + < 'a b' EOO @@ -212,6 +260,14 @@ test.arguments = command-expansion 'a b' EOO + : out-alias + : + $* <:"1>a b" >>EOO + '1' + > + 'a b' + EOO + : out-app : $* <:"1>+a b" >>EOO @@ -219,8 +275,26 @@ test.arguments = command-expansion >+ 'a b' EOO + + : out-app-alias + : + $* <:"1>>a b" >>EOO + '1' + >> + 'a b' + EOO } +: no-out-alias +: +$* <:"1>>>a b" >>EOO +'1' +>> +> +'a b' +EOO + + : cleanup : { diff --git a/libbuild2/script/lexer.cxx b/libbuild2/script/lexer.cxx index fff9307..d78e999 100644 --- a/libbuild2/script/lexer.cxx +++ b/libbuild2/script/lexer.cxx @@ -165,7 +165,7 @@ namespace build2 // if (m == lexer_mode::command_expansion) { - if (optional t = next_cmd_op (c, sep, m)) + if (optional t = next_cmd_op (c, sep)) return move (*t); } @@ -176,14 +176,12 @@ namespace build2 } optional lexer:: - next_cmd_op (const xchar& c, bool sep, lexer_mode m) + next_cmd_op (const xchar& c, bool sep) { - auto make_token = [&sep, &m, &c] (type t, string v = string ()) + auto make_token = [&sep, &c] (type t, string v = string ()) { - bool q (m == lexer_mode::here_line_double); - return token (t, move (v), sep, - (q ? quote_type::double_ : quote_type::unquoted), q, + quote_type::unquoted, false, c.line, c.column, token_printer); }; @@ -247,89 +245,183 @@ namespace build2 // case '<': { - type r (type::in_str); + optional r; xchar p (peek ()); - if (p == '|' || p == '-' || p == '<') + if (p == '|' || p == '-' || p == '=' || p == '<') // <| <- <= << { - get (); + xchar c (get ()); switch (p) { - case '|': return make_token (type::in_pass); - case '-': return make_token (type::in_null); - case '<': + case '|': return make_token (type::in_pass); // <| + case '-': return make_token (type::in_null); // <- + case '=': return make_token (type::in_file); // <= + case '<': // << { - r = type::in_doc; p = peek (); - if (p == '<') + if (p == '=' || p == '<') // <<= <<< { - get (); - r = type::in_file; + xchar c (get ()); + + switch (p) + { + case '=': + { + r = type::in_doc; // <<= + break; + } + case '<': + { + p = peek (); + + if (p == '=') + { + get (); + r = type::in_str; // <<<= + } + + if (!r && redirect_aliases.lll) + r = type::in_lll; // <<< + + // We can still end up with the << or < redirect alias, + // if any of them is present. + // + if (!r) + unget (c); + } + + break; + } } + + if (!r && redirect_aliases.ll) + r = type::in_ll; // << + + // We can still end up with the < redirect alias, if it is + // present. + // + if (!r) + unget (c); + break; } } } + if (!r && redirect_aliases.l) + r = type::in_l; // < + + if (!r) + return nullopt; + // Handle modifiers. // const char* mods (nullptr); - switch (r) + + switch (redirect_aliases.resolve (*r)) { case type::in_str: case type::in_doc: mods = ":/"; break; } - return make_token_with_modifiers (r, mods); + token t (make_token_with_modifiers (*r, mods)); + + return t; } // > // case '>': { - type r (type::out_str); + optional r; xchar p (peek ()); - if (p == '|' || p == '-' || p == '!' || p == '&' || - p == '=' || p == '+' || p == '>') + if (p == '|' || p == '-' || p == '!' || p == '&' || // >| >- >! >& + p == '=' || p == '+' || p == '?' || p == '>') // >= >+ >? >> { - get (); + xchar c (get ()); switch (p) { - case '|': return make_token (type::out_pass); - case '-': return make_token (type::out_null); - case '!': return make_token (type::out_trace); - case '&': return make_token (type::out_merge); - case '=': return make_token (type::out_file_ovr); - case '+': return make_token (type::out_file_app); - case '>': + case '|': return make_token (type::out_pass); // >| + case '-': return make_token (type::out_null); // >- + case '!': return make_token (type::out_trace); // >! + case '&': return make_token (type::out_merge); // >& + case '=': return make_token (type::out_file_ovr); // >= + case '+': return make_token (type::out_file_app); // >+ + case '?': return make_token (type::out_file_cmp); // >? + case '>': // >> { - r = type::out_doc; p = peek (); - if (p == '>') + if (p == '?' || p == '>') // >>? >>> { - get (); - r = type::out_file_cmp; + xchar c (get ()); + + switch (p) + { + case '?': + { + r = type::out_doc; // >>? + break; + } + case '>': + { + p = peek (); + + if (p == '?') + { + get (); + r = type::out_str; // >>>? + } + + if (!r && redirect_aliases.ggg) + r = type::out_ggg; // >>> + + // We can still end up with the >> or > redirect alias, + // if any of themis present. + // + if (!r) + unget (c); + } + + break; + } } + + if (!r && redirect_aliases.gg) + r = type::out_gg; // >> + + // We can still end up with the > redirect alias, if it is + // present. + // + if (!r) + unget (c); + break; } } } + if (!r && redirect_aliases.g) + r = type::out_g; // > + + if (!r) + return nullopt; + // Handle modifiers. // const char* mods (nullptr); const char* stop (nullptr); - switch (r) + + switch (redirect_aliases.resolve (*r)) { case type::out_str: case type::out_doc: mods = ":/~"; stop = "~"; break; } - return make_token_with_modifiers (r, mods, stop); + return make_token_with_modifiers (*r, mods, stop); } } diff --git a/libbuild2/script/lexer.hxx b/libbuild2/script/lexer.hxx index c0d617d..bdeba66 100644 --- a/libbuild2/script/lexer.hxx +++ b/libbuild2/script/lexer.hxx @@ -33,21 +33,58 @@ namespace build2 lexer_mode (base_type v): base_type (v) {} }; + // Redirects the <, <<, <<<, >, >>, and >>> aliases resolve to. + // + struct redirect_aliases + { + optional l; // < + optional ll; // << + optional lll; // <<< + optional g; // > + optional gg; // >> + optional ggg; // >>> + + // If the token type is a redirect alias then return the token type + // it resolves to and the passed type otherwise. It's the caller's + // responsibility to make sure that the corresponding alias is present. + // + token_type + resolve (token_type t) const noexcept + { + switch (t) + { + case token_type::in_l: assert (l); return *l; + case token_type::in_ll: assert (ll); return *ll; + case token_type::in_lll: assert (lll); return *lll; + case token_type::out_g: assert (g); return *g; + case token_type::out_gg: assert (gg); return *gg; + case token_type::out_ggg: assert (ggg); return *ggg; + } + + return t; + } + }; + class lexer: public build2::lexer { public: using base_lexer = build2::lexer; using base_mode = build2::lexer_mode; - // Note that neither the name nor escape arguments are copied. + using redirect_aliases_type = script::redirect_aliases; + + // Note that none of the name, redirect aliases, and escape arguments + // are copied. // lexer (istream& is, const path_name& name, lexer_mode m, + const redirect_aliases_type& ra, const char* escapes = nullptr) : base_lexer (is, name, 1 /* line */, nullptr /* escapes */, - false /* set_mode */) + false /* set_mode */), + redirect_aliases (ra) { mode (m, '\0', escapes); } @@ -69,19 +106,23 @@ namespace build2 virtual token next () override; + public: + const redirect_aliases_type& redirect_aliases; + protected: lexer (istream& is, const path_name& name, uint64_t line, const char* escapes, - bool set_mode) - : base_lexer (is, name, line, escapes, set_mode) {} + bool set_mode, + const redirect_aliases_type& ra) + : base_lexer (is, name, line, escapes, set_mode), + redirect_aliases (ra) {} // Return the next token if it is a command operator (|, ||, &&, // redirect, or cleanup) and nullopt otherwise. // optional next_cmd_op (const xchar&, // The token first character (last got char). - bool sep, // The token is separated. - lexer_mode); // The current (potentially "expired") mode. + bool sep); // The token is separated. private: token diff --git a/libbuild2/script/lexer.test.cxx b/libbuild2/script/lexer.test.cxx index 24fe335..b8de241 100644 --- a/libbuild2/script/lexer.test.cxx +++ b/libbuild2/script/lexer.test.cxx @@ -37,7 +37,17 @@ namespace build2 cin.exceptions (istream::failbit | istream::badbit); path_name in (""); - lexer l (cin, in, m); + + using type = token_type; + + redirect_aliases ra {type (type::in_file), + type (type::in_doc), + type (type::in_str), + type (type::out_file_ovr), + type (type::out_file_app), + nullopt}; + + lexer l (cin, in, m, ra); // No use printing eos since we will either get it or loop forever. // diff --git a/libbuild2/script/parser.cxx b/libbuild2/script/parser.cxx index 6c651a6..aa60111 100644 --- a/libbuild2/script/parser.cxx +++ b/libbuild2/script/parser.cxx @@ -97,7 +97,8 @@ namespace build2 } pair parser:: - parse_command_expr (token& t, type& tt) + parse_command_expr (token& t, type& tt, + const redirect_aliases& ra) { // enter: first token of the command line // leave: or unknown token @@ -201,7 +202,7 @@ namespace build2 { assert (r); // Must already be present. - if (r->modifiers.find (':') == string::npos) + if (r->modifiers ().find (':') == string::npos) w += '\n'; r->str = move (w); }; @@ -218,7 +219,7 @@ namespace build2 case 2: what = "stderr regex redirect"; break; } - check_regex_mod (r->modifiers, w, l, what); + check_regex_mod (r->modifiers (), w, l, what); regex_parts rp (parse_regex (w, l, what)); @@ -233,7 +234,7 @@ namespace build2 // Note that the position is synthetic, but that's ok as we don't // expect any diagnostics to refer this line. // - if (r->modifiers.find (':') == string::npos) + if (r->modifiers ().find (':') == string::npos) re.lines.emplace_back (l.line, l.column, string (), false); }; @@ -382,9 +383,24 @@ namespace build2 // Parse the redirect operator. // - auto parse_redirect = - [&c, &expr, &p, &mod, &hd, this] (token& t, const location& l) + // If the token type is the redirect alias then tt must contain the type + // the alias resolves to and the token type otherwise. Note that this + // argument defines the redirect semantics. Also note that the token is + // saved into the redirect to keep the modifiers and the original + // representation. + // + auto parse_redirect = [&c, &expr, &p, &mod, &hd, this] + (token&& t, type tt, const location& l) { + // The redirect alias token type must be resolved. + // + assert (tt != type::in_l && + tt != type::in_ll && + tt != type::in_lll && + tt != type::out_g && + tt != type::out_gg && + tt != type::out_ggg); + // Our semantics is the last redirect seen takes effect. // assert (p == pending::none && mod.empty ()); @@ -415,8 +431,6 @@ namespace build2 c.arguments.pop_back (); } - type tt (t.type); - // Validate/set default file descriptor. // switch (tt) @@ -452,7 +466,9 @@ namespace build2 } } - mod = move (t.value); + // Don't move as we will save the token into the redirect object. + // + mod = t.value; // Handle the none redirect (no data allowed) in the switch construct // if/when the respective syntax is invented. @@ -516,7 +532,7 @@ namespace build2 // Don't move as still may be used for pending here-document end // marker processing. // - r->modifiers = mod; + r->token = move (t); switch (rt) { @@ -640,6 +656,8 @@ namespace build2 for (bool done (false); !done; l = get_location (t)) { + tt = ra.resolve (tt); + switch (tt) { case type::newline: @@ -878,7 +896,7 @@ namespace build2 case type::out_file_ovr: case type::out_file_app: { - parse_redirect (t, l); + parse_redirect (move (t), tt, l); break; } @@ -1053,11 +1071,11 @@ namespace build2 // args = 'x=\"foo bar\"' // cmd $args # cmd x="foo bar" // - istringstream is (s); path_name in (""); lexer lex (is, in, lexer_mode::command_expansion, + ra, "\'\"\\"); // Treat the first "sub-token" as always separated from what @@ -1075,7 +1093,7 @@ namespace build2 for (; t.type != type::eos; t = lex.next ()) { - type tt (t.type); + type tt (ra.resolve (t.type)); l = build2::get_location (t, in); // Re-lexing double-quotes will recognize $, ( inside as @@ -1155,7 +1173,7 @@ namespace build2 case type::out_file_ovr: case type::out_file_app: { - parse_redirect (t, l); + parse_redirect (move (t), tt, l); break; } @@ -1309,8 +1327,17 @@ namespace build2 { command& c (p.first[i->expr].pipe[i->pipe]); - (i->fd == 0 ? c.in : i->fd == 1 ? c.out : c.err) = - redirect (redirect_type::here_doc_ref, *r); + optional& ir (i->fd == 0 ? c.in : + i->fd == 1 ? c.out : + c.err); + + // Must be present since it is referenced by here-doc. + // + assert (ir); + + // Note: preserve the original representation. + // + ir = redirect (redirect_type::here_doc_ref, *r, move (ir->token)); } } @@ -1660,6 +1687,8 @@ namespace build2 build2::parser::lexer_ = l; } + static redirect_aliases no_redirect_aliases; + void parser:: apply_value_attributes (const variable* var, value& lhs, @@ -1671,7 +1700,12 @@ namespace build2 path_ = &name; istringstream is (attributes); - lexer l (is, name, lexer_mode::attributes); + + // Note that the redirect alias information is not used in the + // attributes lexer mode. + // + lexer l (is, name, lexer_mode::attributes, no_redirect_aliases); + set_lexer (&l); token t; diff --git a/libbuild2/script/parser.hxx b/libbuild2/script/parser.hxx index ecd9f5a..d47f88e 100644 --- a/libbuild2/script/parser.hxx +++ b/libbuild2/script/parser.hxx @@ -12,6 +12,7 @@ #include #include +#include // redirect_aliases #include namespace build2 @@ -86,7 +87,7 @@ namespace build2 using here_docs = vector; pair - parse_command_expr (token&, token_type&); + parse_command_expr (token&, token_type&, const redirect_aliases&); command_exit parse_command_exit (token&, token_type&); diff --git a/libbuild2/script/run.cxx b/libbuild2/script/run.cxx index 3474ccb..870a70f 100644 --- a/libbuild2/script/run.cxx +++ b/libbuild2/script/run.cxx @@ -266,7 +266,7 @@ namespace build2 eop = path (op + ".orig"); save (eop, - transform (rd.str, false /* regex */, rd.modifiers, env), + transform (rd.str, false /* regex */, rd.modifiers (), env), ll); env.clean_special (eop); @@ -432,14 +432,14 @@ namespace build2 if (l.regex) // Regex (possibly empty), { r += rl.intro; - r += transform (l.value, true /* regex */, rd.modifiers, env); + r += transform (l.value, true /* regex */, rd.modifiers (), env); r += rl.intro; r += l.flags; } else if (!l.special.empty ()) // Special literal. r += rl.intro; else // Textual literal. - r += transform (l.value, false /* regex */, rd.modifiers, env); + r += transform (l.value, false /* regex */, rd.modifiers (), env); r += l.special; return r; @@ -517,7 +517,7 @@ namespace build2 { string s (transform (l.value, true /* regex */, - rd.modifiers, + rd.modifiers (), env)); c = line_char ( @@ -556,7 +556,8 @@ namespace build2 // rls += line_char (transform (l.value, false /* regex */, - rd.modifiers, env), + rd.modifiers (), + env), pool); } @@ -1091,7 +1092,7 @@ namespace build2 isp = std_path ("stdin"); save (isp, - transform (in.str, false /* regex */, in.modifiers, env), + transform (in.str, false /* regex */, in.modifiers (), env), ll); env.clean_special (isp); diff --git a/libbuild2/script/script.cxx b/libbuild2/script/script.cxx index a93315f..5a2eda3 100644 --- a/libbuild2/script/script.cxx +++ b/libbuild2/script/script.cxx @@ -235,44 +235,56 @@ namespace build2 to_stream_q (o, s.str ()); }; - auto print_redirect = [&o, print_path] (const redirect& r, - const char* prefix) + auto print_redirect = [&o, print_path] (const redirect& r, int fd) { - o << ' ' << prefix; + const redirect& er (r.effective ()); - size_t n (string::traits_type::length (prefix)); - assert (n > 0); + // Print the none redirect (no data allowed) if/when the respective + // syntax is invented. + // + if (er.type == redirect_type::none) + return; + + o << ' '; - char d (prefix[n - 1]); // Redirect direction. + // Print the redirect file descriptor. + // + if (fd == 2) + o << fd; + + // Print the redirect original representation and the modifiers, if + // present. + // + r.token.printer (o, r.token, print_mode::raw); - switch (r.type) + // Print the rest of the redirect (file path, etc). + // + switch (er.type) { - case redirect_type::none: assert (false); break; - case redirect_type::pass: o << '|'; break; - case redirect_type::null: o << '-'; break; - case redirect_type::trace: o << '!'; break; - case redirect_type::merge: o << '&' << r.fd; break; + case redirect_type::none: assert (false); break; + case redirect_type::here_doc_ref: assert (false); break; + + case redirect_type::pass: + case redirect_type::null: + case redirect_type::trace: break; + case redirect_type::merge: o << er.fd; break; + + case redirect_type::file: + { + print_path (er.file.path); + break; + } case redirect_type::here_str_literal: case redirect_type::here_doc_literal: { - bool doc (r.type == redirect_type::here_doc_literal); - - // For here-document add another '>' or '<'. Note that here end - // marker never needs to be quoted. - // - if (doc) - o << d; - - o << r.modifiers; - - if (doc) - o << r.end; + if (er.type == redirect_type::here_doc_literal) + o << er.end; else { - const string& v (r.str); + const string& v (er.str); to_stream_q (o, - r.modifiers.find (':') == string::npos + er.modifiers ().find (':') == string::npos ? string (v, 0, v.size () - 1) // Strip newline. : v); } @@ -283,20 +295,10 @@ namespace build2 case redirect_type::here_str_regex: case redirect_type::here_doc_regex: { - bool doc (r.type == redirect_type::here_doc_regex); - - // For here-document add another '>' or '<'. Note that here end - // marker never needs to be quoted. - // - if (doc) - o << d; - - o << r.modifiers; + const regex_lines& re (er.regex); - const regex_lines& re (r.regex); - - if (doc) - o << re.intro + r.end + re.intro + re.flags; + if (er.type == redirect_type::here_doc_regex) + o << re.intro + er.end + re.intro + re.flags; else { assert (!re.lines.empty ()); // Regex can't be empty. @@ -307,23 +309,6 @@ namespace build2 break; } - - case redirect_type::file: - { - // For stdin or stdout-comparison redirect add '>>' or '<<' (and - // so make it '<<<' or '>>>'). Otherwise add '+' or '=' (and so - // make it '>+' or '>='). - // - if (d == '<' || r.file.mode == redirect_fmode::compare) - o << d << d; - else - o << (r.file.mode == redirect_fmode::append ? '+' : '='); - - print_path (r.file.path); - break; - } - - case redirect_type::here_doc_ref: assert (false); break; } }; @@ -358,7 +343,7 @@ namespace build2 } } - o << (r.modifiers.find (':') == string::npos ? "" : "\n") << r.end; + o << (r.modifiers ().find (':') == string::npos ? "" : "\n") << r.end; }; if ((m & command_to_stream::header) == command_to_stream::header) @@ -377,17 +362,14 @@ namespace build2 // Redirects. // - // Print the none redirect (no data allowed) if/when the respective - // syntax is invened. - // - if (c.in && c.in->effective ().type != redirect_type::none) - print_redirect (c.in->effective (), "<"); + if (c.in) + print_redirect (*c.in, 0); - if (c.out && c.out->effective ().type != redirect_type::none) - print_redirect (c.out->effective (), ">"); + if (c.out) + print_redirect (*c.out, 1); - if (c.err && c.err->effective ().type != redirect_type::none) - print_redirect (c.err->effective (), "2>"); + if (c.err) + print_redirect (*c.err, 2); for (const auto& p: c.cleanups) { @@ -513,7 +495,7 @@ namespace build2 redirect:: redirect (redirect&& r) noexcept : type (r.type), - modifiers (move (r.modifiers)), + token (move (r.token)), end (move (r.end)), end_line (r.end_line), end_column (r.end_column) @@ -593,7 +575,7 @@ namespace build2 redirect:: redirect (const redirect& r) : type (r.type), - modifiers (r.modifiers), + token (r.token), end (r.end), end_line (r.end_line), end_column (r.end_column) diff --git a/libbuild2/script/script.hxx b/libbuild2/script/script.hxx index abb2fd7..22747a8 100644 --- a/libbuild2/script/script.hxx +++ b/libbuild2/script/script.hxx @@ -8,7 +8,7 @@ #include #include -#include // replay_tokens +#include #include namespace build2 @@ -54,7 +54,7 @@ namespace build2 // Note that the exact spacing and partial quoting may not be restored due // to the information loss. // - LIBBUILD2_SYMEXPORT void + void dump (ostream&, const string& ind, const lines&); // Parse object model. @@ -167,7 +167,10 @@ namespace build2 reference_wrapper ref; // Note: no chains. }; - string modifiers; // Redirect modifiers. + // Modifiers and the original representation (potentially an alias). + // + build2::token token; + string end; // Here-document end marker (no regex intro/flags). uint64_t end_line; // Here-document end marker location. uint64_t end_column; @@ -179,8 +182,10 @@ namespace build2 // Create redirect of the reference type. // - redirect (redirect_type t, const redirect& r) - : type (redirect_type::here_doc_ref), ref (r) + redirect (redirect_type t, const redirect& r, build2::token tk) + : type (redirect_type::here_doc_ref), + ref (r), + token (move (tk)) { // There is no support (and need) for reference chains. // @@ -217,6 +222,12 @@ namespace build2 { return type == redirect_type::here_doc_ref ? ref.get () : *this; } + + const string& + modifiers () const noexcept + { + return token.value; + } }; // cleanup diff --git a/libbuild2/script/token.cxx b/libbuild2/script/token.cxx index 6c9de87..1c612a5 100644 --- a/libbuild2/script/token.cxx +++ b/libbuild2/script/token.cxx @@ -20,24 +20,31 @@ namespace build2 switch (t.type) { - case token_type::clean: os << q << '&' << v << q; break; - case token_type::pipe: os << q << '|' << q; break; - - case token_type::in_pass: os << q << "<|" << q; break; - case token_type::in_null: os << q << "<-" << q; break; - case token_type::in_str: os << q << '<' << v << q; break; - case token_type::in_doc: os << q << "<<" << v << q; break; - case token_type::in_file: os << q << "<<<" << q; break; - - case token_type::out_pass: os << q << ">|" << q; break; - case token_type::out_null: os << q << ">-" << q; break; - case token_type::out_trace: os << q << ">!" << q; break; - case token_type::out_merge: os << q << ">&" << q; break; - case token_type::out_str: os << q << '>' << v << q; break; - case token_type::out_doc: os << q << ">>" << v << q; break; - case token_type::out_file_cmp: os << q << ">>>" << v << q; break; - case token_type::out_file_ovr: os << q << ">=" << v << q; break; - case token_type::out_file_app: os << q << ">+" << v << q; break; + case token_type::clean: os << q << '&' << v << q; break; + case token_type::pipe: os << q << '|' << q; break; + + case token_type::in_pass: os << q << "<|" << q; break; + case token_type::in_null: os << q << "<-" << q; break; + case token_type::in_file: os << q << "<=" << q; break; + case token_type::in_doc: os << q << "<<=" << v << q; break; + case token_type::in_str: os << q << "<<<=" << v << q; break; + + case token_type::out_pass: os << q << ">|" << q; break; + case token_type::out_null: os << q << ">-" << q; break; + case token_type::out_trace: os << q << ">!" << q; break; + case token_type::out_merge: os << q << ">&" << q; break; + case token_type::out_file_ovr: os << q << ">=" << q; break; + case token_type::out_file_app: os << q << ">+" << q; break; + case token_type::out_file_cmp: os << q << ">?" << q; break; + case token_type::out_doc: os << q << ">>?" << v << q; break; + case token_type::out_str: os << q << ">>>?" << v << q; break; + + case token_type::in_l: os << q << '<' << v << q; break; + case token_type::in_ll: os << q << "<<" << v << q; break; + case token_type::in_lll: os << q << "<<<" << v << q; break; + case token_type::out_g: os << q << '>' << v << q; break; + case token_type::out_gg: os << q << ">>" << v << q; break; + case token_type::out_ggg: os << q << ">>>" << v << q; break; default: build2::token_printer (os, t, m); } diff --git a/libbuild2/script/token.hxx b/libbuild2/script/token.hxx index a2ccaee..0186bd9 100644 --- a/libbuild2/script/token.hxx +++ b/libbuild2/script/token.hxx @@ -22,23 +22,33 @@ namespace build2 // NOTE: remember to update token_printer()! pipe = base_type::value_next, // | - clean, // &{?!} (modifiers in value) + clean, // &{?!} (modifiers in value) in_pass, // <| in_null, // <- - in_str, // <{:/} (modifiers in value) - in_doc, // <<{:/} (modifiers in value) - in_file, // <<< + in_file, // <= + in_doc, // <<={:/} (modifiers in value) + in_str, // <<<={:/} (modifiers in value) out_pass, // >| out_null, // >- out_trace, // >! out_merge, // >& - out_str, // >{:/~} (modifiers in value) - out_doc, // >>{:/~} (modifiers in value) - out_file_cmp, // >>> out_file_ovr, // >= out_file_app, // >+ + out_file_cmp, // >? + out_doc, // >>?{:/~} (modifiers in value) + out_str, // >>>?{:/~} (modifiers in value) + + // The modifiers are in the token value, if the redirect the alias + // resolves to supports the modifiers. + // + in_l, // < + in_ll, // << + in_lll, // <<< + out_g, // > + out_gg, // >> + out_ggg, // >>> value_next }; diff --git a/libbuild2/test/script/lexer.cxx b/libbuild2/test/script/lexer.cxx index 0e8691c..a94109b 100644 --- a/libbuild2/test/script/lexer.cxx +++ b/libbuild2/test/script/lexer.cxx @@ -15,6 +15,14 @@ namespace build2 { using type = token_type; + build2::script::redirect_aliases lexer::redirect_aliases { + type (type::in_str), + type (type::in_doc), + type (type::in_file), + type (type::out_str), + type (type::out_doc), + type (type::out_file_cmp)}; + void lexer:: mode (base_mode m, char ps, optional esc, uintptr_t data) { @@ -239,7 +247,7 @@ namespace build2 m == lexer_mode::first_token || m == lexer_mode::second_token) { - if (optional t = next_cmd_op (c, sep, m)) + if (optional t = next_cmd_op (c, sep)) return move (*t); } diff --git a/libbuild2/test/script/lexer.hxx b/libbuild2/test/script/lexer.hxx index 4b6c53a..452e794 100644 --- a/libbuild2/test/script/lexer.hxx +++ b/libbuild2/test/script/lexer.hxx @@ -48,7 +48,8 @@ namespace build2 const char* escapes = nullptr) : base_lexer (is, name, 1 /* line */, nullptr /* escapes */, - false /* set_mode */) + false /* set_mode */, + redirect_aliases) { mode (m, '\0', escapes); } @@ -62,6 +63,9 @@ namespace build2 virtual token next () override; + public: + static redirect_aliases_type redirect_aliases; + private: token next_line (); diff --git a/libbuild2/test/script/parser+redirect.test.testscript b/libbuild2/test/script/parser+redirect.test.testscript index 3858808..79530e0 100644 --- a/libbuild2/test/script/parser+redirect.test.testscript +++ b/libbuild2/test/script/parser+redirect.test.testscript @@ -49,7 +49,7 @@ : portable-path : $* <>EOO - cmd </EOO_ 2>/EOE_ + cmd <>/EOO_ 2>>/EOE_ foo EOI_ bar @@ -57,7 +57,7 @@ baz EOE_ EOI - cmd </EOO_ 2>/EOE_ + cmd <>/EOO_ 2>>/EOE_ foo EOI_ bar @@ -113,13 +113,13 @@ : portable-path : $* <>EOO - cmd >/~%EOF% 2>/~%EOE% + cmd >>/~%EOF% 2>>/~%EOE% foo EOF bar EOE EOI - cmd >/~%EOF% 2>/~%EOE% + cmd >>/~%EOF% 2>>/~%EOE% foo EOF bar diff --git a/libbuild2/test/script/parser+regex.test.testscript b/libbuild2/test/script/parser+regex.test.testscript index 8627304..db418b3 100644 --- a/libbuild2/test/script/parser+regex.test.testscript +++ b/libbuild2/test/script/parser+regex.test.testscript @@ -162,9 +162,12 @@ EOE EOO - : no-newline + : no-newline-str : $* <'cmd >:~/fo*/' >'cmd >:~/fo*/' + + : no-newline-doc + : $* <>EOO cmd 2>>:~/EOE/ foo diff --git a/libbuild2/test/script/parser.cxx b/libbuild2/test/script/parser.cxx index 798b743..c206e0a 100644 --- a/libbuild2/test/script/parser.cxx +++ b/libbuild2/test/script/parser.cxx @@ -428,7 +428,7 @@ namespace build2 pair p; if (lt != line_type::cmd_else && lt != line_type::cmd_end) - p = parse_command_expr (t, tt); + p = parse_command_expr (t, tt, lexer::redirect_aliases); // Colon and semicolon are only valid in test command lines and // after 'end' in if-else. Note that we still recognize them @@ -1248,7 +1248,8 @@ namespace build2 // Note: this one is only used during execution. - pair p (parse_command_expr (t, tt)); + pair p ( + parse_command_expr (t, tt, lexer::redirect_aliases)); switch (tt) { diff --git a/tests/test/script/runner/redirect.testscript b/tests/test/script/runner/redirect.testscript index b8fe74d..0fe3aa3 100644 --- a/tests/test/script/runner/redirect.testscript +++ b/tests/test/script/runner/redirect.testscript @@ -80,10 +80,14 @@ psr = ($cxx.target.class != 'windows' ? '/' : '\\') # Path separator in regex. : literal : { - $c <'$* -i 0 foo' && $b : out - $c <'$* -e foo 2>foo' && $b : err - $c <'$* -i 1 foo' && $b : inout + $c <'$* -i 0 <<<=foo' && $b : in + $c <'$* -i 0 >>?foo' && $b : out + $c <'$* -o foo >foo' && $b : out-alias + $c <'$* -e foo 2>>>?foo' && $b : err + $c <'$* -e foo 2>foo' && $b : err-alias + $c <'$* -i 1 <<<=foo >>>?foo' && $b : inout + $c <'$* -i 1 foo' && $b : inout-alias : inout-fail : @@ -101,11 +105,16 @@ psr = ($cxx.target.class != 'windows' ? '/' : '\\') # Path separator in regex. info: test id: 1 EOE - $c <'$* -i 2 foo' && $b : inerr - $c <'$* -i 1 -e bar foo 2>bar' && $b : inout-err - $c <'$* -o "" >""' && $b : empty - $c <'$* -i 1 <:"foo" >:"foo"' && $b : no-newline - $c <'$* -i 1 <:"" >:""' && $b : no-newline-empty + $c <'$* -i 2 <<<=foo 2>>>?foo' && $b : inerr + $c <'$* -i 2 foo' && $b : inerr-alias + $c <'$* -i 1 -e bar <<<=foo 1>>>?foo 2>>>?bar' && $b : inout-err + $c <'$* -i 1 -e bar foo 2>bar' && $b : inout-err-alias + $c <'$* -o "" >>>?""' && $b : empty + $c <'$* -o "" >""' && $b : empty-alias + $c <'$* -i 1 <<<=:"foo" >>>?:"foo"' && $b : no-newline + $c <'$* -i 1 <:"foo" >:"foo"' && $b : no-newline-alias + $c <'$* -i 1 <<<=:"" >>>?:""' && $b : no-newline-empty + $c <'$* -i 1 <:"" >:""' && $b : no-newline-empty-alias : no-newline-fail1 : @@ -132,6 +141,15 @@ psr = ($cxx.target.class != 'windows' ? '/' : '\\') # Path separator in regex. : merge : $c <>?EOE 1>&2 + foo + bar + EOE + EOI + + : merge-alias + : + $c <>EOE 1>&2 foo bar @@ -155,6 +173,10 @@ psr = ($cxx.target.class != 'windows' ? '/' : '\\') # Path separator in regex. { : match : + $c <'$* -o foo >>>?~/Foo?/i' && $b + + : match-alias + : $c <'$* -o foo >~/Foo?/i' && $b : mismatch @@ -209,6 +231,15 @@ psr = ($cxx.target.class != 'windows' ? '/' : '\\') # Path separator in regex. : in : $c <>?EOO + foo + bar + EOO + EOI + + : out-alias + : + $c <>EOO foo bar @@ -227,6 +267,15 @@ psr = ($cxx.target.class != 'windows' ? '/' : '\\') # Path separator in regex. : err : $c <>?EOO + foo + bar + EOO + EOI + + : err-alias + : + $c <>EOO foo bar @@ -236,6 +285,18 @@ psr = ($cxx.target.class != 'windows' ? '/' : '\\') # Path separator in regex. : inout : $c <>?EOO + foo + bar + EOF + foo + bar + EOO + EOI + + : inout-alias + : + $c <>EOO foo bar @@ -248,6 +309,19 @@ psr = ($cxx.target.class != 'windows' ? '/' : '\\') # Path separator in regex. : inerr : $c <>?EOE + foo + bar + EOF + foo + bar + EOE + EOI + + + : inerr-alias + : + $c <>EOE foo bar @@ -260,6 +334,14 @@ psr = ($cxx.target.class != 'windows' ? '/' : '\\') # Path separator in regex. : empty : $c <>?EOO + EOF + EOO + EOI + + : empty-alias + : + $c <>EOO EOF EOO @@ -268,16 +350,43 @@ psr = ($cxx.target.class != 'windows' ? '/' : '\\') # Path separator in regex. : shared : $c <>?EOF + foo + bar + EOF + EOI + + : shared-alias + : + $c <>EOF foo bar EOF EOI + : shared-in-alias + : + $c <>?EOF + foo + bar + EOF + EOI + + : shared-out-alias + : + $c <>EOF + foo + bar + EOF + EOI + : extra-newline : $c <>EOO + $* -i 1 <<=EOF >>?EOO EOF @@ -287,7 +396,7 @@ psr = ($cxx.target.class != 'windows' ? '/' : '\\') # Path separator in regex. : no-newline : $c <>:EOO + $* -i 1 <<=:EOF >>?:EOO foo EOF foo @@ -297,7 +406,7 @@ psr = ($cxx.target.class != 'windows' ? '/' : '\\') # Path separator in regex. : no-newline-fail1 : $c <>~/EOE/ != 0 - $* -i 1 <<:EOF >>EOO + $* -i 1 <<=:EOF >>?EOO foo EOF foo @@ -314,7 +423,7 @@ psr = ($cxx.target.class != 'windows' ? '/' : '\\') # Path separator in regex. : no-newline-fail2 : $c <>~/EOE/ != 0 - $* -i 1 <>:EOO + $* -i 1 <<=EOF >>?:EOO foo EOF foo @@ -331,7 +440,7 @@ psr = ($cxx.target.class != 'windows' ? '/' : '\\') # Path separator in regex. : no-newline-empty : $c <>:EOO + $* -i 1 <<=:EOF >>?:EOO EOF EOO EOI @@ -339,7 +448,7 @@ psr = ($cxx.target.class != 'windows' ? '/' : '\\') # Path separator in regex. : no-newline-extra-newline : $c <>:EOO + $* -i 1 <<=:EOF >>?:EOO EOF @@ -349,7 +458,7 @@ psr = ($cxx.target.class != 'windows' ? '/' : '\\') # Path separator in regex. : merge : $c <>EOO 2>&1 + $* -i 1 <<=EOF -e baz >>?EOO 2>&1 foo bar EOF @@ -387,6 +496,14 @@ psr = ($cxx.target.class != 'windows' ? '/' : '\\') # Path separator in regex. : in : $c <<"EOI" && $b + \$* -i 1 <<=/EOF >>>?'foo$ps' + foo/ + EOF + EOI + + : in-alias + : + $c <<"EOI" && $b \$* -i 1 <'foo$ps' foo/ EOF @@ -395,6 +512,14 @@ psr = ($cxx.target.class != 'windows' ? '/' : '\\') # Path separator in regex. : out : $c <<"EOI" && $b + \$* -i 1 <<<='foo$ps' >>?/EOO + foo/ + EOO + EOI + + : out-alias + : + $c <<"EOI" && $b \$* -i 1 <'foo$ps' >>/EOO foo/ EOO @@ -403,6 +528,14 @@ psr = ($cxx.target.class != 'windows' ? '/' : '\\') # Path separator in regex. : err : $c <<"EOI" && $b + \$* -i 2 <<<='foo$ps' 2>>?/EOE + foo/ + EOE + EOI + + : err-alias + : + $c <<"EOI" && $b \$* -i 2 <'foo$ps' 2>>/EOE foo/ EOE @@ -419,7 +552,7 @@ psr = ($cxx.target.class != 'windows' ? '/' : '\\') # Path separator in regex. : match : $c <>~/EOO/i + $* -o foo -o foo -o bar >>?~/EOO/i /FO*/* bar /* @@ -429,7 +562,7 @@ psr = ($cxx.target.class != 'windows' ? '/' : '\\') # Path separator in regex. : match-empty : $c <>:~/EOO/ + $* >>?:~/EOO/ /.{0} EOO EOI @@ -437,7 +570,7 @@ psr = ($cxx.target.class != 'windows' ? '/' : '\\') # Path separator in regex. : shared : $c <>~/EOF/ 2>>~/EOF/ + $* -o foo -e foo >>?~/EOF/ 2>>?~/EOF/ foo EOF EOI @@ -445,7 +578,7 @@ psr = ($cxx.target.class != 'windows' ? '/' : '\\') # Path separator in regex. : mismatch : $c <>/~%EOE%d != 0 - $* -o foo >>~/EOO/ + $* -o foo >>?~/EOO/ bar EOO EOI @@ -459,7 +592,7 @@ psr = ($cxx.target.class != 'windows' ? '/' : '\\') # Path separator in regex. : mismatch-icase : $c <>/~%EOE%d != 0 - $* -o foo >>~/EOO/i + $* -o foo >>?~/EOO/i bar EOO EOI @@ -479,6 +612,13 @@ psr = ($cxx.target.class != 'windows' ? '/' : '\\') # Path separator in regex. : $c <=out; + $* -i 1 <=out >foo + EOI + + : in-alias + : + $c <=out; $* -i 1 <<foo EOI @@ -490,6 +630,17 @@ psr = ($cxx.target.class != 'windows' ? '/' : '\\') # Path separator in regex. $c <=out; $* -e bar 2>+out; + $* -i 1 <?out + foo + bar + EOF + EOI + + : match-alias + : + $c <=out; + $* -e bar 2>+out; $* -i 1 <>>out foo bar @@ -500,7 +651,7 @@ psr = ($cxx.target.class != 'windows' ? '/' : '\\') # Path separator in regex. : $c <>/~%EOE%d != 0 $* -o foo >=out; - $* -o bar >>>out + $* -o bar >?out EOI %testscript:2: error: ../../../../../driver(.exe)? stdout doesn't match expected% info: stdout: test/1/stdout-2 -- cgit v1.1