aboutsummaryrefslogtreecommitdiff
path: root/build2
diff options
context:
space:
mode:
Diffstat (limited to 'build2')
-rw-r--r--build2/.gitignore5
-rw-r--r--build2/buildfile24
-rw-r--r--build2/cc/lexer+char-literal.test.testscript67
-rw-r--r--build2/cc/lexer+comment.test.testscript88
-rw-r--r--build2/cc/lexer+line.test.testscript67
-rw-r--r--build2/cc/lexer+number.test.testscript48
-rw-r--r--build2/cc/lexer+preprocessor.test.testscript73
-rw-r--r--build2/cc/lexer+raw-string-literal.test.testscript90
-rw-r--r--build2/cc/lexer+string-literal.test.testscript65
-rw-r--r--build2/cc/lexer.test.cxx80
-rw-r--r--build2/cc/parser+module.test.testscript149
-rw-r--r--build2/cc/parser.test.cxx66
-rw-r--r--build2/function+call.test.testscript161
-rw-r--r--build2/function+syntax.test.testscript29
-rw-r--r--build2/function.test.cxx131
-rw-r--r--build2/lexer+buildspec.test.testscript16
-rw-r--r--build2/lexer+comment.test.testscript139
-rw-r--r--build2/lexer+eval.test.testscript76
-rw-r--r--build2/lexer+quoting.test.testscript108
-rw-r--r--build2/lexer.test.cxx98
-rw-r--r--build2/name.test.cxx96
-rw-r--r--build2/scheduler.test.cxx187
-rw-r--r--build2/test/script/lexer+command-expansion.test.testscript248
-rw-r--r--build2/test/script/lexer+command-line.test.testscript208
-rw-r--r--build2/test/script/lexer+description-line.test.testscript33
-rw-r--r--build2/test/script/lexer+first-token.test.testscript97
-rw-r--r--build2/test/script/lexer+second-token.test.testscript68
-rw-r--r--build2/test/script/lexer+variable-line.test.testscript28
-rw-r--r--build2/test/script/lexer+variable.test.testscript70
-rw-r--r--build2/test/script/lexer.test.cxx85
-rw-r--r--build2/test/script/parser+cleanup.test.testscript58
-rw-r--r--build2/test/script/parser+command-if.test.testscript548
-rw-r--r--build2/test/script/parser+command-re-parse.test.testscript12
-rw-r--r--build2/test/script/parser+description.test.testscript486
-rw-r--r--build2/test/script/parser+directive.test.testscript74
-rw-r--r--build2/test/script/parser+exit.test.testscript27
-rw-r--r--build2/test/script/parser+expansion.test.testscript36
-rw-r--r--build2/test/script/parser+here-document.test.testscript213
-rw-r--r--build2/test/script/parser+here-string.test.testscript19
-rw-r--r--build2/test/script/parser+include.test.testscript104
-rw-r--r--build2/test/script/parser+pipe-expr.test.testscript133
-rw-r--r--build2/test/script/parser+pre-parse.test.testscript23
-rw-r--r--build2/test/script/parser+redirect.test.testscript356
-rw-r--r--build2/test/script/parser+regex.test.testscript223
-rw-r--r--build2/test/script/parser+scope-if.test.testscript554
-rw-r--r--build2/test/script/parser+scope.test.testscript280
-rw-r--r--build2/test/script/parser+setup-teardown.test.testscript151
-rw-r--r--build2/test/script/parser.test.cxx242
-rw-r--r--build2/test/script/regex.test.cxx301
49 files changed, 6537 insertions, 3 deletions
diff --git a/build2/.gitignore b/build2/.gitignore
index 5ea3913..51b4c16 100644
--- a/build2/.gitignore
+++ b/build2/.gitignore
@@ -1,6 +1,11 @@
b
b-boot
+*.test
#*-options
#*-options.?xx
config.hxx
version.hxx
+
+# Testscript output directory (can be symlink).
+#
+test-*.test
diff --git a/build2/buildfile b/build2/buildfile
index bd730c3..64935f3 100644
--- a/build2/buildfile
+++ b/build2/buildfile
@@ -5,15 +5,33 @@
import libs = libbutl%lib{butl}
import libs += libpkgconf%lib{pkgconf}
-exe{b}: cxx{b} libue{b}
+./: exe{b}: cxx{b} libue{b}
-libue{b}: {hxx ixx txx cxx}{** -b -b-options -config -version} \
- {hxx ixx cxx}{b-options} {hxx}{config version} \
+libue{b}: {hxx ixx txx cxx}{** -b -b-options -config -version -**.test...} \
+ {hxx ixx cxx}{b-options} {hxx}{config version} \
$libs
hxx{config}: in{config}
hxx{version}: in{version} $src_root/manifest
+# Unit tests.
+#
+exe{*.test}:
+{
+ test = true
+ install = false
+}
+
+for t: cxx{**.test...}
+{
+ d = $directory($t)
+ n = $name($t)...
+ b = $path.base($name($t))
+
+ ./: $d/exe{$n}: $t $d/{hxx ixx txx}{+$n} $d/testscript{+$n +$b+*.test...}
+ $d/exe{$n}: libue{b}: bin.whole = false
+}
+
# Build options.
#
# Pass our compiler target to be used as build2 host.
diff --git a/build2/cc/lexer+char-literal.test.testscript b/build2/cc/lexer+char-literal.test.testscript
new file mode 100644
index 0000000..6a0a036
--- /dev/null
+++ b/build2/cc/lexer+char-literal.test.testscript
@@ -0,0 +1,67 @@
+# file : build2/cc/lexer+char-literal.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+# Test character literals.
+#
+
+: normal
+:
+$* <<EOI >>EOO
+'a'
+'aa'
+'"'
+EOI
+<char literal>
+<char literal>
+<char literal>
+EOO
+
+: prefix
+:
+$* <<EOI >>EOO
+L'a'
+U'a'
+u'a'
+u8'a'
+u8R'a'
+EOI
+<char literal>
+<char literal>
+<char literal>
+<char literal>
+'u8R'
+<char literal>
+EOO
+
+: suffix
+:
+$* <<EOI >>EOO
+'a'x
+'a'_X123
+EOI
+<char literal>
+<char literal>
+EOO
+
+: escape
+:
+$* <<EOI >>EOO
+'\''
+'\\'
+'\\\''
+'\n'
+U'\U0001f34c'
+EOI
+<char literal>
+<char literal>
+<char literal>
+<char literal>
+<char literal>
+EOO
+
+: unterminated
+:
+$* <"'a" 2>>EOE != 0
+stdin:1:1: error: unterminated character literal
+EOE
diff --git a/build2/cc/lexer+comment.test.testscript b/build2/cc/lexer+comment.test.testscript
new file mode 100644
index 0000000..493c295
--- /dev/null
+++ b/build2/cc/lexer+comment.test.testscript
@@ -0,0 +1,88 @@
+# file : build2/cc/lexer+comment.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+# Test C and C++ comments.
+#
+
+: c-comment
+:
+$* <<EOI
+/* 'one' */
+/* "two" // three
+*/
+/**
+four
+// five */
+/**
+six /*
+*/
+EOI
+
+: cxx-comment
+:
+$* <<EOI
+// 'one'
+// "two" // three
+// four /* five */
+EOI
+
+: commented-out
+:
+$* <<EOI >"';'"
+// /*
+;
+// */
+EOI
+
+: c-unterminated
+:
+$* <<EOI 2>>EOE != 0
+/*
+comment
+EOI
+stdin:1:2: error: unterminated comment
+EOE
+
+: cxx-unterminated
+:
+$* <<:EOI
+// comment
+EOI
+
+: in-char-literal
+:
+$* <<EOI >>EOO
+'//'
+'/*'*/
+EOI
+<char literal>
+<char literal>
+<punctuation>
+<punctuation>
+EOO
+
+: in-string-literal
+:
+$* <<EOI >>EOO
+"//foo"
+"/*"*/
+EOI
+<string literal>
+<string literal>
+<punctuation>
+<punctuation>
+EOO
+
+: in-raw-string-literal
+:
+$* <<EOI >>EOO
+R"X(
+// foo
+/* bar
+)X"*/
+EOI
+<string literal>
+<punctuation>
+<punctuation>
+EOO
diff --git a/build2/cc/lexer+line.test.testscript b/build2/cc/lexer+line.test.testscript
new file mode 100644
index 0000000..abcc587
--- /dev/null
+++ b/build2/cc/lexer+line.test.testscript
@@ -0,0 +1,67 @@
+# file : build2/cc/lexer+line.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+# Test line continuations.
+#
+
+: identifier
+:
+$* <<EOI >"'foo123'"
+fo\
+o\
+1\
+2\
+3
+EOI
+
+: punctuation
+:
+$* <<EOI >'<punctuation>'
+.\
+.\
+.
+EOI
+
+: c-comment
+:
+$* <<EOI
+/\
+*
+comment
+*\
+/\
+
+EOI
+
+: cxx-comment
+:
+$* <<EOI
+/\
+/ comment\
+more\
+more
+EOI
+
+: other
+:
+$* <<EOI >>EOO
+\abc
+EOI
+<punctuation>
+'abc'
+EOO
+
+: multiple
+:
+$* <<EOI >>EOO
+\\
+EOI
+<punctuation>
+EOO
+
+: unterminated
+:
+$* <<:EOI >'<punctuation>'
+\
+EOI
diff --git a/build2/cc/lexer+number.test.testscript b/build2/cc/lexer+number.test.testscript
new file mode 100644
index 0000000..c342818
--- /dev/null
+++ b/build2/cc/lexer+number.test.testscript
@@ -0,0 +1,48 @@
+# file : build2/cc/lexer+number.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+# Test numbers.
+#
+
+$* <'1' >'<number literal>'
+$* <'.1' >'<number literal>'
+$* <'1.' >'<number literal>'
+
+$* <'0b101' >'<number literal>'
+$* <'0123' >'<number literal>'
+$* <'0X12AB' >'<number literal>'
+
+$* <'1e10' >'<number literal>'
+$* <'1E+10' >'<number literal>'
+$* <'0x1.p10' >'<number literal>'
+$* <'0x1.P-10' >'<number literal>'
+
+$* <"123'456" >'<number literal>'
+$* <"0xff00'00ff" >'<number literal>'
+
+$* <'123f' >'<number literal>'
+$* <'123UL' >'<number literal>'
+$* <'123_X' >'<number literal>'
+
+: separate-punctuation
+:
+$* <'123;' >>EOO
+<number literal>
+';'
+EOO
+
+: separate-plus-minus
+:
+$* <'1.0_a+2.0' >>EOO
+<number literal>
+<punctuation>
+<number literal>
+EOO
+
+: separate-whitespace
+:
+$* <'123 abc' >>EOO
+<number literal>
+'abc'
+EOO
diff --git a/build2/cc/lexer+preprocessor.test.testscript b/build2/cc/lexer+preprocessor.test.testscript
new file mode 100644
index 0000000..fc061cb
--- /dev/null
+++ b/build2/cc/lexer+preprocessor.test.testscript
@@ -0,0 +1,73 @@
+# file : build2/cc/lexer+preprocessor.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+# Test preprocessor lines.
+#
+
+: normal
+:
+$* <<EOI
+#pragma message("abc")
+EOI
+
+: multiline
+:
+$* <<EOI
+#pragma message \
+( \
+"abc" \
+)
+EOI
+
+: comment
+:
+$* <<EOI
+#pragma foo /*
+bar
+baz
+*/
+#pragma foo // bar baz
+EOI
+
+: line
+:
+$* -l <<EOI >>EOO
+;
+# 1 "test.cxx" 2
+;
+ ;
+# 4
+;
+#line 8 "z:\\tmp\\test.hxx"
+;
+#line 10
+;
+# 5 "test.cxx"
+;
+EOI
+';' stdin:1:1
+';' test.cxx:1:1
+';' test.cxx:2:3
+';' test.cxx:4:1
+';' z:\tmp\test.hxx:8:1
+';' z:\tmp\test.hxx:10:1
+';' test.cxx:5:1
+EOO
+
+: include
+:
+$* <<EOI 2>>EOE != 0
+#include <foo/bar>
+EOI
+stdin:1:1: error: unexpected #include directive
+EOE
+
+: nested
+:
+$* <<EOI >>EOO
+#define FOO(x) #y
+;
+EOI
+';'
+EOO
diff --git a/build2/cc/lexer+raw-string-literal.test.testscript b/build2/cc/lexer+raw-string-literal.test.testscript
new file mode 100644
index 0000000..e72d77b
--- /dev/null
+++ b/build2/cc/lexer+raw-string-literal.test.testscript
@@ -0,0 +1,90 @@
+# file : build2/cc/lexer+raw-string-literal.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+# Test raw string literals.
+#
+
+: normal
+:
+$* <<EOI >>EOO
+R"()"
+R"(ab)"
+R"(a"b)"
+R"(a)b)"
+R"%(a%)b)%"
+R"X(a
+ b)X"
+R"X(a\
+ b)X"
+EOI
+<string literal>
+<string literal>
+<string literal>
+<string literal>
+<string literal>
+<string literal>
+<string literal>
+EOO
+
+: prefix
+:
+$* <<EOI >>EOO
+LR"(ab)"
+UR"(ab)"
+uR"(ab)"
+u8R"(ab)"
+EOI
+<string literal>
+<string literal>
+<string literal>
+<string literal>
+EOO
+
+: suffix
+:
+$* <<EOI >>EOO
+R"(ab)"x
+R"(ab)"_X123
+EOI
+<string literal>
+<string literal>
+EOO
+
+: escape
+:
+$* <<EOI >>EOO
+R"(\)"
+EOI
+<string literal>
+EOO
+
+: invalid-no-paren
+:
+$* <'R"a"' 2>>EOE != 0
+stdin:1:2: error: invalid raw string literal
+EOE
+
+: invalid-paren
+:
+$* <'R")()("' 2>>EOE != 0
+stdin:1:2: error: invalid raw string literal
+EOE
+
+: invalid-unterminated-paren
+:
+$* <'R"(abc"' 2>>EOE != 0
+stdin:1:2: error: invalid raw string literal
+EOE
+
+: invalid-unterminated-delimiter
+:
+$* <'R"X(abc)"' 2>>EOE != 0
+stdin:1:2: error: invalid raw string literal
+EOE
+
+: invalid-unterminated-quote
+:
+$* <'R"X(abc)X' 2>>EOE != 0
+stdin:1:2: error: invalid raw string literal
+EOE
diff --git a/build2/cc/lexer+string-literal.test.testscript b/build2/cc/lexer+string-literal.test.testscript
new file mode 100644
index 0000000..c486aa1
--- /dev/null
+++ b/build2/cc/lexer+string-literal.test.testscript
@@ -0,0 +1,65 @@
+# file : build2/cc/lexer+string-literal.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+# Test string literals (except raw).
+#
+
+: normal
+:
+$* <<EOI >>EOO
+"aa"
+"'"
+"a""b"
+EOI
+<string literal>
+<string literal>
+<string literal>
+<string literal>
+EOO
+
+: prefix
+:
+$* <<EOI >>EOO
+L"ab"
+U"ab"
+u"ab"
+u8"ab"
+EOI
+<string literal>
+<string literal>
+<string literal>
+<string literal>
+EOO
+
+: suffix
+:
+$* <<EOI >>EOO
+"ab"x
+"ab"_X123
+EOI
+<string literal>
+<string literal>
+EOO
+
+: escape
+:
+$* <<EOI >>EOO
+"\"\""
+"\\\\"
+"\\\"\\"
+"\n\t"
+U"a\U0001f34c"
+EOI
+<string literal>
+<string literal>
+<string literal>
+<string literal>
+<string literal>
+EOO
+
+: unterminated
+:
+$* <'"ab' 2>>EOE != 0
+stdin:1:1: error: unterminated string literal
+EOE
diff --git a/build2/cc/lexer.test.cxx b/build2/cc/lexer.test.cxx
new file mode 100644
index 0000000..a2e33b7
--- /dev/null
+++ b/build2/cc/lexer.test.cxx
@@ -0,0 +1,80 @@
+// file : build2/cc/lexer.test.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <cassert>
+#include <iostream>
+
+#include <build2/types.hxx>
+#include <build2/utility.hxx>
+
+#include <build2/cc/lexer.hxx>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ namespace cc
+ {
+ // Usage: argv[0] [-l] [<file>]
+ //
+ int
+ main (int argc, char* argv[])
+ {
+ bool loc (false);
+ const char* file (nullptr);
+
+ for (int i (1); i != argc; ++i)
+ {
+ string a (argv[i]);
+
+ if (a == "-l")
+ loc = true;
+ else
+ {
+ file = argv[i];
+ break;
+ }
+ }
+
+ try
+ {
+ ifdstream is;
+ if (file != nullptr)
+ is.open (file);
+ else
+ {
+ file = "stdin";
+ is.open (fddup (stdin_fd ()));
+ }
+
+ lexer l (is, path (file));
+
+ // No use printing eos since we will either get it or loop forever.
+ //
+ for (token t; l.next (t) != token_type::eos; )
+ {
+ cout << t;
+
+ if (loc)
+ cout << ' ' << t.file << ':' << t.line << ':' << t.column;
+
+ cout << endl;
+ }
+ }
+ catch (const failed&)
+ {
+ return 1;
+ }
+
+ return 0;
+ }
+ }
+}
+
+int
+main (int argc, char* argv[])
+{
+ return build2::cc::main (argc, argv);
+}
diff --git a/build2/cc/parser+module.test.testscript b/build2/cc/parser+module.test.testscript
new file mode 100644
index 0000000..0e2e52e
--- /dev/null
+++ b/build2/cc/parser+module.test.testscript
@@ -0,0 +1,149 @@
+# file : build2/cc/parser+module.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+# Test C++ module constructs.
+#
+
+: import
+:
+$* <<EOI >>EOI
+import foo;
+import foo.bar;
+import foo.bar.baz;
+EOI
+
+: module-implementation
+:
+$* <<EOI >>EOI
+module foo;
+EOI
+
+: module-interface
+:
+$* <<EOI >>EOI
+export module foo;
+EOI
+
+: export-imported
+:
+$* <<EOI >>EOO
+export import foo;
+EOI
+export import foo;
+EOO
+
+: export-imported-block
+:
+$* <<EOI >>EOO
+import bar;
+
+export {import foo;}
+
+export
+{
+ namespace foo
+ {
+ class c {};
+ }
+
+ template <typename T> int f ();
+
+ import bar;
+}
+EOI
+export import bar;
+export import foo;
+EOO
+
+: non-module
+:
+$* <<EOI
+#pragma import module foo;
+#pragma export module foo;
+#pragma module foo;
+extern module foo: int foo ();
+export namespace bar {int fox ();}
+EOI
+
+: attribute
+:
+$* <<EOI >>EOO
+import foo [[export({import})]];
+module bar [[module({module})]];
+EOI
+import foo;
+module bar;
+EOO
+
+: import-duplicate
+:
+$* <<EOI >>EOO
+import foo;
+import bar.baz;
+import foo;
+import bar . baz;
+EOI
+import foo;
+import bar.baz;
+EOO
+
+: brace-missing
+:
+$* <<EOI 2>>EOE != 0
+export
+{
+ class foo
+ {
+ //};
+ module foo;
+}
+EOI
+stdin:8:1: error: {}-imbalance detected
+EOE
+
+: brace-stray
+:
+$* <<EOI 2>>EOE != 0
+export
+{
+ class foo
+ {
+ };}
+}
+module foo;
+EOI
+stdin:6:1: error: {}-imbalance detected
+EOE
+
+: import-missing-name
+:
+$* <<EOI 2>>EOE != 0
+import ;
+EOI
+stdin:1:8: error: module name expected instead of ';'
+EOE
+
+: module-missing-name
+:
+$* <<EOI 2>>EOE != 0
+module ;
+EOI
+stdin:1:1: error: module declaration expected after leading module marker
+EOE
+
+: import-missing-semi
+:
+$* <<EOI 2>>EOE != 0
+import foo
+EOI
+stdin:2:1: error: ';' expected instead of <end of file>
+EOE
+
+: module-missing-semi
+:
+$* <<EOI 2>>EOE != 0
+export module foo
+EOI
+stdin:2:1: error: ';' expected instead of <end of file>
+EOE
diff --git a/build2/cc/parser.test.cxx b/build2/cc/parser.test.cxx
new file mode 100644
index 0000000..ab42e31
--- /dev/null
+++ b/build2/cc/parser.test.cxx
@@ -0,0 +1,66 @@
+// file : build2/cc/parser.test.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <cassert>
+#include <iostream>
+
+#include <build2/types.hxx>
+#include <build2/utility.hxx>
+
+#include <build2/cc/parser.hxx>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ namespace cc
+ {
+ // Usage: argv[0] [<file>]
+ //
+ int
+ main (int argc, char* argv[])
+ {
+ try
+ {
+ const char* file;
+
+ ifdstream is;
+ if (argc > 1)
+ {
+ file = argv[1];
+ is.open (file);
+ }
+ else
+ {
+ file = "stdin";
+ is.open (fddup (stdin_fd ()));
+ }
+
+ parser p;
+ translation_unit u (p.parse (is, path (file)));
+
+ for (const module_import& m: u.mod.imports)
+ cout << (m.exported ? "export " : "")
+ << "import " << m.name << ';' << endl;
+
+ if (!u.mod.name.empty ())
+ cout << (u.mod.iface ? "export " : "")
+ << "module " << u.mod.name << ';' << endl;
+ }
+ catch (const failed&)
+ {
+ return 1;
+ }
+
+ return 0;
+ }
+ }
+}
+
+int
+main (int argc, char* argv[])
+{
+ return build2::cc::main (argc, argv);
+}
diff --git a/build2/function+call.test.testscript b/build2/function+call.test.testscript
new file mode 100644
index 0000000..1678c28
--- /dev/null
+++ b/build2/function+call.test.testscript
@@ -0,0 +1,161 @@
+# file : build2/function+call.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+: qual-implicit
+:
+$* <'print $dummy.dummy0()' >'abc'
+
+: qual-explicit
+:
+$* <'print $dummy.qual()' >'abc'
+
+: qual-fail
+:
+$* <'print $qual()' 2>>EOE != 0
+buildfile:1:8: error: unmatched call to qual()
+ info: candidate: dummy.qual()
+EOE
+
+: derived-base
+: Test derived-to-base overload resolution
+:
+$* <'print $dummy.abs([dir_path] .)' >'false';
+$* <'print $dummy.abs([abs_dir_path] .)' >'true'
+
+: variadic
+:
+$* <'print $variadic([bool] true, foo, bar)' >'3'
+
+: fail
+:
+$* <'$fail()' 2>>EOE != 0
+error: failed
+buildfile:1:2: info: while calling fail()
+EOE
+
+: fail-invalid-arg
+:
+$* <'$fail_arg(abc)' 2>>EOE != 0
+error: invalid argument: invalid uint64 value: 'abc'
+buildfile:1:2: info: while calling fail_arg(<untyped>)
+EOE
+
+: no-match-name
+:
+$* <'$bogus()' 2>>EOE != 0
+buildfile:1:2: error: unmatched call to bogus()
+EOE
+
+: no-match-count
+:
+$* <'$dummy0(abc)' 2>>EOE != 0
+buildfile:1:2: error: unmatched call to dummy0(<untyped>)
+ info: candidate: dummy0(), qualified name dummy.dummy0
+EOE
+
+: no-match-type
+:
+$* <'$dummy1([uint64] 123)' 2>>EOE != 0
+buildfile:1:2: error: unmatched call to dummy1(uint64)
+ info: candidate: dummy1(string), qualified name dummy.dummy1
+EOE
+
+: ambig
+:
+$* <'$ambig(abc)' 2>>~/EOE/ != 0
+buildfile:1:2: error: ambiguous call to ambig(<untyped>)
+/((
+ info: candidate: ambig(<untyped> [, uint64]), qualified name dummy.ambig
+ info: candidate: ambig(<untyped> [, string]), qualified name dummy.ambig
+/)|(
+ info: candidate: ambig(<untyped> [, string]), qualified name dummy.ambig
+ info: candidate: ambig(<untyped> [, uint64]), qualified name dummy.ambig
+/))
+EOE
+
+: unmatched
+:
+$* <'$ambig(abc, def)' 2>>~/EOE/ != 0
+buildfile:1:2: error: unmatched call to ambig(<untyped>, <untyped>)
+/((
+ info: candidate: ambig(<untyped> [, uint64]), qualified name dummy.ambig
+ info: candidate: ambig(<untyped> [, string]), qualified name dummy.ambig
+/)|(
+ info: candidate: ambig(<untyped> [, string]), qualified name dummy.ambig
+ info: candidate: ambig(<untyped> [, uint64]), qualified name dummy.ambig
+/))
+EOE
+
+: reverse
+:
+$* <'print $reverse([string] abc)' >'abc'
+
+: optional-absent
+:
+$* <'print $optional()' >'true'
+
+: optional-present
+:
+$* <'print $optional(abc)' >'false'
+
+: null-true
+:
+$* <'print $nullable([null])' >'true'
+
+: null-false
+:
+$* <'print $nullable(nonull)' >'false'
+
+: null-fail
+:
+$* <'$dummy1([string null])' 2>>EOE != 0
+error: invalid argument: null value
+buildfile:1:2: info: while calling dummy1(string)
+EOE
+
+: print-call-1-untyped
+:
+$* <'$bogus(abc)' 2>>EOE != 0
+buildfile:1:2: error: unmatched call to bogus(<untyped>)
+EOE
+
+: print-call-1-typed
+:
+$* <'$bogus([uint64] 123)' 2>>EOE != 0
+buildfile:1:2: error: unmatched call to bogus(uint64)
+EOE
+
+: print-call-2
+:
+$* <'$bogus(abc, [uint64] 123)' 2>>EOE != 0
+buildfile:1:2: error: unmatched call to bogus(<untyped>, uint64)
+EOE
+
+: print-fovl
+:
+$* <'$ambig([bool] true)' 2>>~/EOE/ != 0
+buildfile:1:2: error: ambiguous call to ambig(bool)
+/((
+ info: candidate: ambig(<untyped> [, uint64]), qualified name dummy.ambig
+ info: candidate: ambig(<untyped> [, string]), qualified name dummy.ambig
+/)|(
+ info: candidate: ambig(<untyped> [, string]), qualified name dummy.ambig
+ info: candidate: ambig(<untyped> [, uint64]), qualified name dummy.ambig
+/))
+EOE
+
+: print-fovl-variadic
+:
+$* <'$variadic(abc)' 2>>EOE != 0
+buildfile:1:2: error: unmatched call to variadic(<untyped>)
+ info: candidate: variadic(bool [, ...])
+EOE
+
+: member-function
+:
+$* <'print $dummy.length([path] abc)' >'3'
+
+: data-member
+:
+$* <'print $dummy.type([name] cxx{foo})' >'cxx'
diff --git a/build2/function+syntax.test.testscript b/build2/function+syntax.test.testscript
new file mode 100644
index 0000000..bd86dd0
--- /dev/null
+++ b/build2/function+syntax.test.testscript
@@ -0,0 +1,29 @@
+# file : build2/function+syntax.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+$* <'$dump()' >:'' : none
+$* <'$dump( )' >:'' : none-in-spaces
+$* <'$dump("")' >'{}' : one-empty
+$* <'$dump(a)' >'a' : one-single
+$* <'$dump(a b c)' >'a b c' : one-list
+$* <'$dump(d/t{x y z})' >'d/t{x} d/t{y} d/t{z}' : one-names
+
+$* <'print a$dummy1([string] b)c' >'abc' : concat
+$* <'print $dummy2([uint64] 123, [uint64] 321)' >'444' : multi-arg
+
+: quoting
+: Verify we can inhibit function call with quoting
+:
+$* <<EOI >>EOO
+foo = FOO
+bar = BAR
+
+print $foo"($bar)"
+print "$foo"($bar)
+print "$foo""($bar)"
+EOI
+FOOBAR
+FOOBAR
+FOOBAR
+EOO
diff --git a/build2/function.test.cxx b/build2/function.test.cxx
new file mode 100644
index 0000000..a9ba7bb
--- /dev/null
+++ b/build2/function.test.cxx
@@ -0,0 +1,131 @@
+// file : build2/function.test.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <iostream>
+
+#include <build2/types.hxx>
+#include <build2/utility.hxx>
+
+#include <build2/parser.hxx>
+#include <build2/context.hxx>
+#include <build2/function.hxx>
+#include <build2/variable.hxx>
+#include <build2/diagnostics.hxx>
+
+using namespace std;
+
+namespace build2
+{
+ static const optional<const value_type*> arg_bool[1] =
+ {
+ &value_traits<bool>::value_type
+ };
+
+ static dir_path
+ scoped (const scope*, dir_path d)
+ {
+ return d;
+ }
+
+ static void
+ scoped_void (const scope*, dir_path)
+ {
+ }
+
+ int
+ main (int, char* argv[])
+ {
+ init (argv[0], 1); // Fake build system driver, default verbosity.
+ reset (strings ()); // No command line variables.
+
+ function_family f ("dummy");
+
+ f["fail"] = []() {fail << "failed" << endf;};
+ f["fail_arg"] = [](names a) {return convert<uint64_t> (move (a[0]));};
+
+ f["nullable"] = [](names* a) {return a == nullptr;};
+ f["optional"] = [](optional<names> a) {return !a;};
+
+ f["dummy0"] = []() {return "abc";};
+ f["dummy1"] = [](string s) {return s;};
+ f["dummy2"] = [](uint64_t x, uint64_t y) {return x + y;};
+
+ f["ambig"] = [](names a, optional<string>) {return a;};
+ f["ambig"] = [](names a, optional<uint64_t>) {return a;};
+
+ f["reverse"] = [](names a) {return a;};
+
+ f["scoped"] = [](const scope*, names a) {return a;};
+ f["scoped_void"] = [](const scope*, names) {};
+ f["scoped"] = &scoped;
+ f["scoped_void"] = &scoped_void;
+
+ f[".qual"] = []() {return "abc";};
+
+ f[".length"] = &path::size; // Member function.
+ f[".type"] = &name::type; // Data member.
+
+ f[".abs"] = [](dir_path d) {return d.absolute ();};
+
+ // Variadic function with first required argument of type bool. Returns
+ // number of arguments passed.
+ //
+ functions.insert (
+ "variadic",
+ function_overload (
+ nullptr,
+ 1,
+ function_overload::arg_variadic,
+ function_overload::types (arg_bool, 1),
+ [] (const scope*, vector_view<value> args, const function_overload&)
+ {
+ return value (static_cast<uint64_t> (args.size ()));
+ }));
+
+ // Dump arguments.
+ //
+ functions.insert (
+ "dump",
+ function_overload (
+ nullptr,
+ 0,
+ function_overload::arg_variadic,
+ function_overload::types (),
+ [] (const scope*, vector_view<value> args, const function_overload&)
+ {
+ for (value& a: args)
+ {
+ if (a.null)
+ cout << "[null]";
+ else if (!a.empty ())
+ {
+ names storage;
+ cout << reverse (a, storage);
+ }
+ cout << endl;
+ }
+ return value (nullptr);
+ }));
+
+ try
+ {
+ scope& s (*scope::global_);
+
+ parser p;
+ p.parse_buildfile (cin, path ("buildfile"), s, s);
+ }
+ catch (const failed&)
+ {
+ return 1;
+ }
+
+ return 0;
+ }
+}
+
+int
+main (int argc, char* argv[])
+{
+ return build2::main (argc, argv);
+}
diff --git a/build2/lexer+buildspec.test.testscript b/build2/lexer+buildspec.test.testscript
new file mode 100644
index 0000000..9083abe
--- /dev/null
+++ b/build2/lexer+buildspec.test.testscript
@@ -0,0 +1,16 @@
+# file : build2/lexer+buildspec.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+test.arguments = buildspec
+
+: punctuation
+:
+$* <:'x,x(x)' >>EOO
+'x'
+,
+'x'
+ (
+'x'
+)
+EOO
diff --git a/build2/lexer+comment.test.testscript b/build2/lexer+comment.test.testscript
new file mode 100644
index 0000000..4323c84
--- /dev/null
+++ b/build2/lexer+comment.test.testscript
@@ -0,0 +1,139 @@
+# file : build2/lexer+comment.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+: single-line
+:
+{
+ : only
+ :
+ $* <<EOI >>:EOO
+ # comment
+ EOI
+ EOO
+
+ : first
+ :
+ $* <<EOI >>EOO
+ # comment
+ foo
+ EOI
+ 'foo'
+ <newline>
+ EOO
+
+ : last
+ :
+ $* <<EOI >>EOO
+ foo
+ # comment
+ EOI
+ 'foo'
+ <newline>
+ EOO
+
+ : few
+ :
+ $* <<EOI >>EOO
+ foo
+ # comment
+ # comment
+ EOI
+ 'foo'
+ <newline>
+ EOO
+
+ : cont
+ :
+ $* <<EOI >>EOO
+ foo
+ # comment\\
+ bar
+ EOI
+ 'foo'
+ <newline>
+ 'bar'
+ <newline>
+ EOO
+
+ : same
+ :
+ $* <<EOI >>EOO
+ foo # comment
+ bar # comment
+ EOI
+ 'foo'
+ <newline>
+ 'bar'
+ <newline>
+ EOO
+}
+
+: multi-line
+:
+{
+ : only
+ :
+ $* <<EOI >>:EOO
+ #\
+ comment
+ comment
+ #\
+ EOI
+ EOO
+
+ : empty
+ :
+ $* <<EOI >>:EOO
+ #\
+ #\
+ EOI
+ EOO
+
+ : start-same
+ :
+ $* <<EOI >>EOO
+ foo #\
+ comment
+ comment
+ #\
+ EOI
+ 'foo'
+ <newline>
+ EOO
+
+ : end-same
+ :
+ $* <<EOI >>EOO
+ #\
+ comment
+ comment
+ foo #\
+ bar
+ EOI
+ 'bar'
+ <newline>
+ EOO
+
+ : end-not
+ :
+ $* <<EOI >>EOO
+ #\
+ comment
+ #\ not an end
+ foo #\
+ bar
+ EOI
+ 'bar'
+ <newline>
+ EOO
+
+ : unterm
+ :
+ $* <<EOI 2>>EOE != 0
+ #\
+ comment
+ EOI
+ stdin:3:1: error: unterminated multi-line comment
+ EOE
+}
diff --git a/build2/lexer+eval.test.testscript b/build2/lexer+eval.test.testscript
new file mode 100644
index 0000000..eccd029
--- /dev/null
+++ b/build2/lexer+eval.test.testscript
@@ -0,0 +1,76 @@
+# file : build2/lexer+eval.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+test.arguments = eval
+
+: punctuation
+:
+$* <:'x:x{x}x[x]x$x?x,x(x)' >>EOO
+'x'
+:
+'x'
+{
+'x'
+}
+'x'
+[
+'x'
+]
+'x'
+$
+'x'
+?
+'x'
+,
+'x'
+(
+'x'
+)
+EOO
+
+: logical
+:
+$* <:'x|x||x&x&&x!x!!x)' >>EOO
+'x|x'
+||
+'x&x'
+&&
+'x'
+!
+'x'
+!
+!
+'x'
+)
+EOO
+
+: comparison
+:
+$* <:'x=x==x!=x<x<=x>x>=)' >>EOO
+'x=x'
+==
+'x'
+!=
+'x'
+<
+'x'
+<=
+'x'
+>
+'x'
+>=
+)
+EOO
+
+: newline
+:
+$* <'x' >- 2>>EOE != 0
+stdin:1:2: error: newline in evaluation context
+EOE
+
+: eof
+:
+$* <:'' 2>>EOE != 0
+stdin:1:1: error: unterminated evaluation context
+EOE
diff --git a/build2/lexer+quoting.test.testscript b/build2/lexer+quoting.test.testscript
new file mode 100644
index 0000000..21b9046
--- /dev/null
+++ b/build2/lexer+quoting.test.testscript
@@ -0,0 +1,108 @@
+# file : build2/lexer+quoting.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+test.options += -q
+
+: unquoted
+:
+$* <'foo' >>EOO
+'foo'
+<newline>
+EOO
+
+: comp
+:
+{
+ : single
+ :
+ $* <":'foo':" >>EOO
+ :
+ 'foo' [S/C]
+ :
+ <newline>
+ EOO
+
+ : double
+ :
+ $* <':"foo":' >>EOO
+ :
+ 'foo' [D/C]
+ :
+ <newline>
+ EOO
+
+ : single-empty
+ :
+ $* <"''" >>EOO
+ '' [S/C]
+ <newline>
+ EOO
+
+ : double-empty
+ :
+ $* <'""' >>EOO
+ '' [D/C]
+ <newline>
+ EOO
+}
+
+: part
+{
+ : quoted
+ {
+ : start
+ : Token start already quoted
+ :
+ $* <'"$foo"' >>EOO
+ '' [D/P]
+ $ [D/C]
+ 'foo' [D/P]
+ <newline>
+ EOO
+
+ : end
+ : Token end still quoted
+ :
+ $* <'"foo$"' >>EOO
+ 'foo' [D/P]
+ $ [D/C]
+ '' [D/P]
+ <newline>
+ EOO
+ }
+
+ : unquoted
+ {
+ : start
+ : Token starts with unquoted character
+ :
+ $* <'f"oo"' >>EOO
+ 'foo' [D/P]
+ <newline>
+ EOO
+
+ : end
+ : Token continous with unquoted character
+ :
+ $* <'"fo"o' >>EOO
+ 'foo' [D/P]
+ <newline>
+ EOO
+
+ : escape
+ : Token continous with unquoted escaped character
+ :
+ $* <'"fo"\"' >>EOO
+ 'fo"' [D/P]
+ <newline>
+ EOO
+ }
+}
+
+: mixed
+:
+$* <"\"fo\"'o'" >>EOO
+'foo' [M/P]
+<newline>
+EOO
diff --git a/build2/lexer.test.cxx b/build2/lexer.test.cxx
new file mode 100644
index 0000000..8abd5f7
--- /dev/null
+++ b/build2/lexer.test.cxx
@@ -0,0 +1,98 @@
+// file : build2/lexer.test.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <cassert>
+#include <iostream>
+
+#include <build2/types.hxx>
+#include <build2/utility.hxx>
+
+#include <build2/token.hxx>
+#include <build2/lexer.hxx>
+
+using namespace std;
+
+namespace build2
+{
+ // Usage: argv[0] [-q] [<lexer-mode>]
+ //
+ int
+ main (int argc, char* argv[])
+ {
+ bool quote (false);
+ lexer_mode m (lexer_mode::normal);
+
+ for (int i (1); i != argc; ++i)
+ {
+ string a (argv[i]);
+
+ if (a == "-q")
+ quote = true;
+ else
+ {
+ if (a == "normal") m = lexer_mode::normal;
+ else if (a == "variable") m = lexer_mode::variable;
+ else if (a == "value") m = lexer_mode::value;
+ else if (a == "attribute") m = lexer_mode::attribute;
+ else if (a == "eval") m = lexer_mode::eval;
+ else if (a == "buildspec") m = lexer_mode::buildspec;
+ else assert (false);
+ break;
+ }
+ }
+
+ try
+ {
+ cin.exceptions (istream::failbit | istream::badbit);
+
+ // Most alternative modes auto-expire so we need something underneath.
+ //
+ lexer l (cin, path ("stdin"));
+
+ if (m != lexer_mode::normal)
+ l.mode (m);
+
+ // No use printing eos since we will either get it or loop forever.
+ //
+ for (token t (l.next ()); t.type != token_type::eos; t = l.next ())
+ {
+ if (t.separated && t.type != token_type::newline)
+ cout << ' ';
+
+ // Print each token on a separate line without quoting operators.
+ //
+ t.printer (cout, t, false);
+
+ if (quote)
+ {
+ char q ('\0');
+ switch (t.qtype)
+ {
+ case quote_type::single: q = 'S'; break;
+ case quote_type::double_: q = 'D'; break;
+ case quote_type::mixed: q = 'M'; break;
+ case quote_type::unquoted: break;
+ }
+
+ if (q != '\0')
+ cout << " [" << q << (t.qcomp ? "/C" : "/P") << ']';
+ }
+
+ cout << endl;
+ }
+ }
+ catch (const failed&)
+ {
+ return 1;
+ }
+
+ return 0;
+ }
+}
+
+int
+main (int argc, char* argv[])
+{
+ return build2::main (argc, argv);
+}
diff --git a/build2/name.test.cxx b/build2/name.test.cxx
new file mode 100644
index 0000000..0434aac
--- /dev/null
+++ b/build2/name.test.cxx
@@ -0,0 +1,96 @@
+// file : build2/name.test.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <sstream>
+
+#include <cassert>
+#include <iostream>
+
+#include <build2/types.hxx> // Includes name.
+#include <build2/utility.hxx>
+
+#include <build2/diagnostics.hxx>
+
+using namespace std;
+
+namespace build2
+{
+ int
+ main (int, char*[])
+ {
+ using dir = dir_path;
+
+ // Test string representation.
+ //
+ {
+ auto ts = [] (const name& n) {return to_string (n);};
+
+ assert (ts (name ()) == "");
+
+ assert (ts (name ("foo")) == "foo");
+
+ assert (ts (name (dir ("bar/"))) == "bar/");
+ assert (ts (name (dir ("bar/baz/"))) == "bar/baz/");
+
+ assert (ts (name (dir ("bar/"), "dir", "")) == "dir{bar/}");
+ assert (ts (name (dir ("bar/baz/"), "dir", "")) == "bar/dir{baz/}");
+
+ assert (ts (name (dir ("bar/"), "foo")) == "bar/foo");
+
+ assert (ts (name (dir ("bar/"), "dir", "foo")) == "bar/dir{foo}");
+ assert (ts (name (dir ("bar/baz/"), "dir", "foo")) == "bar/baz/dir{foo}");
+ }
+
+ // Test stream representation.
+ //
+ {
+ auto ts = [] (const name& n, bool quote = true)
+ {
+ ostringstream os;
+ stream_verb (os, stream_verbosity (0, 1));
+ to_stream (os, n, quote);
+ return os.str ();
+ };
+
+ assert (ts (name ()) == "''");
+ assert (ts (name (), false) == "{}");
+
+ assert (ts (name ("foo")) == "foo");
+
+ assert (ts (name (dir ("bar/"))) == "bar/");
+ assert (ts (name (dir ("bar/baz/"))) == "bar/baz/");
+
+ assert (ts (name (dir ("bar/"), "dir", "")) == "dir{bar/}");
+ assert (ts (name (dir ("bar/baz/"), "dir", "")) == "bar/dir{baz/}");
+
+ assert (ts (name (dir ("bar/"), "foo")) == "bar/foo");
+
+ assert (ts (name (dir ("bar/"), "dir", "foo")) == "bar/dir{foo}");
+ assert (ts (name (dir ("bar/baz/"), "dir", "foo")) == "bar/baz/dir{foo}");
+
+ // Quoting.
+ //
+ assert (ts (name (dir ("bar baz/"), "dir", "foo fox")) == "'bar baz/'dir{'foo fox'}");
+
+ // Relative logic.
+ //
+#ifndef _WIN32
+ dir rb ("/bar/");
+ relative_base = &rb;
+
+ assert (ts (name (dir ("/bar/"), "dir", "")) == "dir{./}");
+ assert (ts (name (dir ("/bar/"), "", "foo")) == "foo");
+ assert (ts (name (dir ("/bar/baz/"), "dir", "")) == "dir{baz/}");
+#endif
+ }
+
+ return 0;
+ }
+}
+
+int
+main (int argc, char* argv[])
+{
+ return build2::main (argc, argv);
+}
diff --git a/build2/scheduler.test.cxx b/build2/scheduler.test.cxx
new file mode 100644
index 0000000..b088c1d
--- /dev/null
+++ b/build2/scheduler.test.cxx
@@ -0,0 +1,187 @@
+// file : build2/scheduler.test.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <chrono>
+#include <thread>
+
+#include <cassert>
+#include <iostream>
+
+#include <build2/types.hxx>
+#include <build2/utility.hxx>
+
+#include <build2/scheduler.hxx>
+
+using namespace std;
+
+namespace build2
+{
+ // Usage argv[0] [-v <volume>] [-d <difficulty>] [-c <concurrency>]
+ // [-q <queue-depth>]
+ //
+ // -v task tree volume (affects both depth and width), for example 100
+ // -d computational difficulty of each task, for example 10
+ // -c max active threads, if unspecified or 0, then hardware concurrency
+ // -q task queue depth, if unspecified or 0, then appropriate default used
+ //
+ // Specifying any option also turns on the verbose mode.
+ //
+ // Notes on testing:
+ //
+ // 1. Ideally you would want to test things on an SMP machine.
+ //
+ // 2. When need to compare performance, disable turbo boost since its
+ // availability depends on CPU utilization/temperature:
+ //
+ // # echo '1' >/sys/devices/system/cpu/intel_pstate/no_turbo
+ //
+ // 3. Use turbostat(1) to see per-CPU details (utlization, frequency):
+ //
+ // $ sudo turbostat --interval 1 ./driver -d 8 -v 300
+ //
+ static bool
+ prime (uint64_t);
+
+ // Find # of primes in the [x, y) range.
+ //
+ static void
+ inner (uint64_t x, uint64_t y, uint64_t& r)
+ {
+ for (; x != y; ++x)
+ if (prime (x))
+ r++;
+ };
+
+ int
+ main (int argc, char* argv[])
+ {
+ bool verb (false);
+
+ // Adjust assert() below if changing these defaults.
+ //
+ size_t volume (100);
+ uint32_t difficulty (10);
+
+ size_t max_active (0);
+ size_t queue_depth (0);
+
+ for (int i (1); i != argc; ++i)
+ {
+ string a (argv[i]);
+
+ if (a == "-v")
+ volume = stoul (argv[++i]);
+ else if (a == "-d")
+ difficulty = stoul (argv[++i]);
+ else if (a == "-c")
+ max_active = stoul (argv[++i]);
+ else if (a == "-q")
+ queue_depth = stoul (argv[++i]);
+ else
+ assert (false);
+
+ verb = true;
+ }
+
+ if (max_active == 0)
+ max_active = scheduler::hardware_concurrency ();
+
+ scheduler s (max_active, 1, 0, queue_depth);
+
+ // Find # prime counts of primes in [i, d*i*i) ranges for i in (0, n].
+ //
+ auto outer = [difficulty, &s] (size_t n, vector<uint64_t>& o, uint64_t& r)
+ {
+ scheduler::atomic_count task_count (0);
+
+ for (size_t i (1); i <= n; ++i)
+ {
+ o[i - 1] = 0;
+ s.async (task_count,
+ inner,
+ i,
+ i * i * difficulty,
+ ref (o[i - 1]));
+ }
+
+ s.wait (task_count);
+ assert (task_count == 0);
+
+ for (uint64_t v: o)
+ r += prime (v) ? 1 : 0;
+ };
+
+ vector<uint64_t> r (volume, 0);
+ vector<vector<uint64_t>> o (volume, vector<uint64_t> ());
+
+ scheduler::atomic_count task_count (0);
+
+ for (size_t i (0); i != volume; ++i)
+ {
+ o[i].resize (i);
+ s.async (task_count,
+ outer,
+ i,
+ ref (o[i]),
+ ref (r[i]));
+ }
+
+ s.wait (task_count);
+ assert (task_count == 0);
+
+ uint64_t n (0);
+ for (uint64_t v: r)
+ n += v;
+
+ if (volume == 100 && difficulty == 10)
+ assert (n == 580);
+
+ scheduler::stat st (s.shutdown ());
+
+ if (verb)
+ {
+ cerr << "result " << n << endl
+ << endl;
+
+ cerr << "thread_max_active " << st.thread_max_active << endl
+ << "thread_max_total " << st.thread_max_total << endl
+ << "thread_helpers " << st.thread_helpers << endl
+ << "thread_max_waiting " << st.thread_max_waiting << endl
+ << endl
+ << "task_queue_depth " << st.task_queue_depth << endl
+ << "task_queue_full " << st.task_queue_full << endl
+ << endl
+ << "wait_queue_slots " << st.wait_queue_slots << endl
+ << "wait_queue_collisions " << st.wait_queue_collisions << endl;
+ }
+
+ return 0;
+ }
+
+ static bool
+ prime (uint64_t x)
+ {
+ if (x == 2 || x == 3)
+ return true;
+
+ if (x < 2 || x % 2 == 0 || x % 3 == 0)
+ return false;
+
+ // Test divisors starting from 5 and incrementing alternatively by 2/4.
+ //
+ for (uint64_t d (5), i (2); d * d <= x; d += i, i = 6 - i)
+ {
+ if (x % d == 0)
+ return false;
+ }
+
+ return true;
+ }
+}
+
+int
+main (int argc, char* argv[])
+{
+ return build2::main (argc, argv);
+}
diff --git a/build2/test/script/lexer+command-expansion.test.testscript b/build2/test/script/lexer+command-expansion.test.testscript
new file mode 100644
index 0000000..03e3366
--- /dev/null
+++ b/build2/test/script/lexer+command-expansion.test.testscript
@@ -0,0 +1,248 @@
+# file : build2/test/script/lexer+command-expansion.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+test.arguments = command-expansion
+
+: pass-redirect
+:
+{
+ : in
+ :
+ $* <:"0<|" >>EOO
+ '0'
+ <|
+ EOO
+
+ : arg-in
+ :
+ $* <:"0 <|" >>EOO
+ '0 '
+ <|
+ EOO
+
+ : out
+ :
+ $* <:"1>|" >>EOO
+ '1'
+ >|
+ EOO
+
+ : arg-out
+ :
+ $* <:"1 >|" >>EOO
+ '1 '
+ >|
+ EOO
+}
+
+: null-redirect
+:
+{
+ : in
+ :
+ $* <:"0<-" >>EOO
+ '0'
+ <-
+ EOO
+
+ : arg-in
+ :
+ $* <:"0 <-" >>EOO
+ '0 '
+ <-
+ EOO
+
+ : out
+ :
+ $* <:"1>-" >>EOO
+ '1'
+ >-
+ EOO
+
+ : arg-out
+ :
+ $* <:"1 >-" >>EOO
+ '1 '
+ >-
+ EOO
+}
+
+: trace-redirect
+:
+{
+ : out
+ :
+ $* <:"1>!" >>EOO
+ '1'
+ >!
+ EOO
+
+ : arg-out
+ :
+ $* <:"1 >!" >>EOO
+ '1 '
+ >!
+ EOO
+}
+
+: merge-redirect
+:
+{
+ : out
+ :
+ $* <:"1>&2" >>EOO
+ '1'
+ >&
+ '2'
+ EOO
+
+ : arg-out
+ :
+ $* <:"1 >&2" >>EOO
+ '1 '
+ >&
+ '2'
+ EOO
+}
+
+: str-redirect
+:
+{
+ : in
+ :
+ {
+ : newline
+ :
+ $* <:"0<a b" >>EOO
+ '0'
+ <
+ 'a b'
+ EOO
+
+ : no-newline
+ :
+ $* <:"0<:a b" >>EOO
+ '0'
+ <:
+ 'a b'
+ EOO
+ }
+
+ : out
+ :
+ {
+ : newline
+ :
+ $* <:"1>a b" >>EOO
+ '1'
+ >
+ 'a b'
+ EOO
+
+ : no-newline
+ :
+ $* <:"1>:a b" >>EOO
+ '1'
+ >:
+ 'a b'
+ EOO
+ }
+}
+
+: doc-redirect
+:
+{
+ : in
+ :
+ {
+ : newline
+ :
+ $* <:"0<<E O I" >>EOO
+ '0'
+ <<
+ 'E O I'
+ EOO
+
+ : no-newline
+ :
+ $* <:"0<<:E O I" >>EOO
+ '0'
+ <<:
+ 'E O I'
+ EOO
+ }
+
+ : out
+ :
+ {
+ : newline
+ :
+ $* <:"1>>E O O" >>EOO
+ '1'
+ >>
+ 'E O O'
+ EOO
+
+ : no-newline
+ :
+ $* <:"1>>:E O O" >>EOO
+ '1'
+ >>:
+ 'E O O'
+ EOO
+ }
+}
+
+: file-redirect
+:
+{
+ : in
+ :
+ $* <:"0<<<a b" >>EOO
+ '0'
+ <<<
+ 'a b'
+ EOO
+
+ : out
+ :
+ $* <:"1>=a b" >>EOO
+ '1'
+ >=
+ 'a b'
+ EOO
+
+ : out-app
+ :
+ $* <:"1>+a b" >>EOO
+ '1'
+ >+
+ 'a b'
+ EOO
+}
+
+: cleanup
+:
+{
+ : always
+ :
+ $* <:"&file" >>EOO
+ &
+ 'file'
+ EOO
+
+ : maybe
+ :
+ $* <:"&?file" >>EOO
+ &?
+ 'file'
+ EOO
+
+ : never
+ :
+ $* <:"&!file" >>EOO
+ &!
+ 'file'
+ EOO
+}
diff --git a/build2/test/script/lexer+command-line.test.testscript b/build2/test/script/lexer+command-line.test.testscript
new file mode 100644
index 0000000..65be837
--- /dev/null
+++ b/build2/test/script/lexer+command-line.test.testscript
@@ -0,0 +1,208 @@
+# file : build2/test/script/lexer+command-line.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+test.arguments = command-line
+
+: semi
+{
+ : immediate
+ :
+ $* <"cmd;" >>EOO
+ 'cmd'
+ ;
+ <newline>
+ EOO
+
+ : separated
+ :
+ $* <"cmd ;" >>EOO
+ 'cmd'
+ ;
+ <newline>
+ EOO
+
+ : only
+ :
+ $* <";" >>EOO
+ ;
+ <newline>
+ EOO
+}
+
+: colon
+:
+{
+ : immediate
+ :
+ $* <"cmd: dsc" >>EOO
+ 'cmd'
+ :
+ 'dsc'
+ <newline>
+ EOO
+
+ : separated
+ :
+ $* <"cmd :dsc" >>EOO
+ 'cmd'
+ :
+ 'dsc'
+ <newline>
+ EOO
+
+ : only
+ :
+ $* <":" >>EOO
+ :
+ <newline>
+ EOO
+}
+
+: redirect
+:
+{
+ : pass
+ :
+ $* <"cmd <| 1>|" >>EOO
+ 'cmd'
+ <|
+ '1'
+ >|
+ <newline>
+ EOO
+
+ : null
+ :
+ $* <"cmd <- 1>-" >>EOO
+ 'cmd'
+ <-
+ '1'
+ >-
+ <newline>
+ EOO
+
+ : trace
+ :
+ $* <"cmd 1>!" >>EOO
+ 'cmd'
+ '1'
+ >!
+ <newline>
+ EOO
+
+ : merge
+ :
+ $* <"cmd 1>&2" >>EOO
+ 'cmd'
+ '1'
+ >&
+ '2'
+ <newline>
+ EOO
+
+ : str
+ :
+ $* <"cmd <a 1>b" >>EOO
+ 'cmd'
+ <
+ 'a'
+ '1'
+ >
+ 'b'
+ <newline>
+ EOO
+
+ : str-nn
+ :
+ $* <"cmd <:a 1>:b" >>EOO
+ 'cmd'
+ <:
+ 'a'
+ '1'
+ >:
+ 'b'
+ <newline>
+ EOO
+
+ : doc
+ :
+ $* <"cmd <<EOI 1>>EOO" >>EOO
+ 'cmd'
+ <<
+ 'EOI'
+ '1'
+ >>
+ 'EOO'
+ <newline>
+ EOO
+
+ : doc-nn
+ :
+ $* <"cmd <<:EOI 1>>:EOO" >>EOO
+ 'cmd'
+ <<:
+ 'EOI'
+ '1'
+ >>:
+ 'EOO'
+ <newline>
+ EOO
+
+ : file-cmp
+ :
+ $* <"cmd <<<in >>>out 2>>>err" >>EOO
+ 'cmd'
+ <<<
+ 'in'
+ >>>
+ 'out'
+ '2'
+ >>>
+ 'err'
+ <newline>
+ EOO
+
+ : file-write
+ :
+ $* <"cmd >=out 2>+err" >>EOO
+ 'cmd'
+ >=
+ 'out'
+ '2'
+ >+
+ 'err'
+ <newline>
+ EOO
+}
+
+: cleanup
+:
+{
+ : always
+ :
+ $* <"cmd &file" >>EOO
+ 'cmd'
+ &
+ 'file'
+ <newline>
+ EOO
+
+ : maybe
+ :
+ $* <"cmd &?file" >>EOO
+ 'cmd'
+ &?
+ 'file'
+ <newline>
+ EOO
+
+ : never
+ :
+ $* <"cmd &!file" >>EOO
+ 'cmd'
+ &!
+ 'file'
+ <newline>
+ EOO
+}
diff --git a/build2/test/script/lexer+description-line.test.testscript b/build2/test/script/lexer+description-line.test.testscript
new file mode 100644
index 0000000..2d87d24
--- /dev/null
+++ b/build2/test/script/lexer+description-line.test.testscript
@@ -0,0 +1,33 @@
+# file : build2/test/script/lexer+description-line.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+test.arguments = description-line
+
+: full
+:
+$* <" foo bar " >>EOO
+' foo bar '
+<newline>
+EOO
+
+: space
+:
+$* <" " >>EOO
+' '
+<newline>
+EOO
+
+: empty
+:
+$* <"" >>EOO
+<newline>
+EOO
+
+: eof
+:
+$* <:"foo" >>EOO 2>>EOE != 0
+'foo'
+EOO
+stdin:1:4: error: expected newline at the end of description line
+EOE
diff --git a/build2/test/script/lexer+first-token.test.testscript b/build2/test/script/lexer+first-token.test.testscript
new file mode 100644
index 0000000..f20f261
--- /dev/null
+++ b/build2/test/script/lexer+first-token.test.testscript
@@ -0,0 +1,97 @@
+# file : build2/test/script/lexer+first-token.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+# Note: this mode auto-expires after each token.
+#
+test.arguments = first-token
+
+: dot
+:
+$* <"." >>EOO
+.
+<newline>
+EOO
+
+: semi
+:
+$* <";" >>EOO
+;
+<newline>
+EOO
+
+: colon
+:
+$* <":" >>EOO
+:
+<newline>
+EOO
+
+: lcbrace
+:
+$* <"{" >>EOO
+{
+<newline>
+EOO
+
+: rcbrace
+:
+$* <"}" >>EOO
+}
+<newline>
+EOO
+
+: setup
+:
+$* <"+foo" >>EOO
++
+'foo'
+<newline>
+EOO
+
+: tdown
+:
+$* <"- foo" >>EOO
+-
+'foo'
+<newline>
+EOO
+
+: plus-leading
+:
+$* <"foo+bar" >>EOO
+'foo+bar'
+<newline>
+EOO
+
+: minus-leading
+:
+$* <"foo- x" >>EOO
+'foo-'
+'x'
+<newline>
+EOO
+
+: assign
+:
+$* <"foo=" >>EOO
+'foo'
+'='
+<newline>
+EOO
+
+: append
+:
+$* <"foo+=" >>EOO
+'foo'
+'+='
+<newline>
+EOO
+
+: prepend
+:
+$* <"foo=+" >>EOO
+'foo'
+'=+'
+<newline>
+EOO
diff --git a/build2/test/script/lexer+second-token.test.testscript b/build2/test/script/lexer+second-token.test.testscript
new file mode 100644
index 0000000..8fdee23
--- /dev/null
+++ b/build2/test/script/lexer+second-token.test.testscript
@@ -0,0 +1,68 @@
+# file : build2/test/script/lexer+second-token.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+# Note: this mode auto-expires after each token.
+#
+test.arguments = second-token
+
+: semi
+:
+$* <";" >>EOO
+;
+<newline>
+EOO
+
+: colon
+:
+$* <":" >>EOO
+:
+<newline>
+EOO
+
+: assign
+:
+$* <"=foo" >>EOO
+=
+'foo'
+<newline>
+EOO
+
+: append
+:
+$* <"+= foo" >>EOO
++=
+'foo'
+<newline>
+EOO
+
+: prepend
+:
+$* <" =+ foo" >>EOO
+=+
+'foo'
+<newline>
+EOO
+
+: assign-leading
+:
+$* <"foo=bar" >>EOO
+'foo=bar'
+<newline>
+EOO
+
+: append-leading
+:
+$* <"foo+= bar" >>EOO
+'foo+='
+'bar'
+<newline>
+EOO
+
+: prepend-leading
+:
+$* <"foo =+bar" >>EOO
+'foo'
+'=+bar'
+<newline>
+EOO
diff --git a/build2/test/script/lexer+variable-line.test.testscript b/build2/test/script/lexer+variable-line.test.testscript
new file mode 100644
index 0000000..b9c558d
--- /dev/null
+++ b/build2/test/script/lexer+variable-line.test.testscript
@@ -0,0 +1,28 @@
+# file : build2/test/script/lexer+variable-line.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+test.arguments = variable-line
+
+: semi
+:
+$* <"cmd;" >>EOO
+'cmd'
+;
+<newline>
+EOO
+
+: semi-separated
+:
+$* <"cmd ;" >>EOO
+'cmd'
+;
+<newline>
+EOO
+
+: semi-only
+:
+$* <";" >>EOO
+;
+<newline>
+EOO
diff --git a/build2/test/script/lexer+variable.test.testscript b/build2/test/script/lexer+variable.test.testscript
new file mode 100644
index 0000000..0ec323b
--- /dev/null
+++ b/build2/test/script/lexer+variable.test.testscript
@@ -0,0 +1,70 @@
+# file : build2/test/script/lexer+variable.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+# Test handling custom variable names ($*, $~, $NN).
+#
+test.arguments = variable
+
+: command
+:
+{
+ : only
+ :
+ $* <"*" >>EOO
+ '*'
+ <newline>
+ EOO
+
+ : followed
+ :
+ $* <"*abc" >>EOO
+ '*'
+ 'abc'
+ <newline>
+ EOO
+}
+
+: working-dir
+:
+{
+ : only
+ :
+ $* <"~" >>EOO
+ '~'
+ <newline>
+ EOO
+
+ : followed
+ :
+ $* <"~123" >>EOO
+ '~'
+ '123'
+ <newline>
+ EOO
+}
+
+: arg
+:
+{
+ : only
+ :
+ $* <"0" >>EOO
+ '0'
+ <newline>
+ EOO
+
+ : followed
+ :
+ $* <"1abc" >>EOO
+ '1'
+ 'abc'
+ <newline>
+ EOO
+
+ : multi-digit
+ :
+ $* <"10" 2>>EOE != 0
+ stdin:1:1: error: multi-digit special variable name
+ EOE
+}
diff --git a/build2/test/script/lexer.test.cxx b/build2/test/script/lexer.test.cxx
new file mode 100644
index 0000000..56418b7
--- /dev/null
+++ b/build2/test/script/lexer.test.cxx
@@ -0,0 +1,85 @@
+// file : build2/test/script/lexer.test.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <cassert>
+#include <iostream>
+
+#include <build2/types.hxx>
+#include <build2/utility.hxx>
+
+#include <build2/test/script/token.hxx>
+#include <build2/test/script/lexer.hxx>
+
+using namespace std;
+
+namespace build2
+{
+ namespace test
+ {
+ namespace script
+ {
+ // Usage: argv[0] <lexer-mode>
+ //
+ int
+ main (int argc, char* argv[])
+ {
+ lexer_mode m;
+ {
+ assert (argc == 2);
+ string s (argv[1]);
+
+ if (s == "command-line") m = lexer_mode::command_line;
+ else if (s == "first-token") m = lexer_mode::first_token;
+ else if (s == "second-token") m = lexer_mode::second_token;
+ else if (s == "variable-line") m = lexer_mode::variable_line;
+ else if (s == "command-expansion") m = lexer_mode::command_expansion;
+ else if (s == "here-line-single") m = lexer_mode::here_line_single;
+ else if (s == "here-line-double") m = lexer_mode::here_line_double;
+ else if (s == "description-line") m = lexer_mode::description_line;
+ else if (s == "variable") m = lexer_mode::variable;
+ else assert (false);
+ }
+
+ try
+ {
+ cin.exceptions (istream::failbit | istream::badbit);
+
+ // Some modes auto-expire so we need something underneath.
+ //
+ bool u (m == lexer_mode::first_token ||
+ m == lexer_mode::second_token ||
+ m == lexer_mode::variable_line ||
+ m == lexer_mode::description_line ||
+ m == lexer_mode::variable);
+
+ lexer l (cin, path ("stdin"), u ? lexer_mode::command_line : m);
+ if (u)
+ l.mode (m);
+
+ // No use printing eos since we will either get it or loop forever.
+ //
+ for (token t (l.next ()); t.type != token_type::eos; t = l.next ())
+ {
+ // Print each token on a separate line without quoting operators.
+ //
+ t.printer (cout, t, false);
+ cout << endl;
+ }
+ }
+ catch (const failed&)
+ {
+ return 1;
+ }
+
+ return 0;
+ }
+ }
+ }
+}
+
+int
+main (int argc, char* argv[])
+{
+ return build2::test::script::main (argc, argv);
+}
diff --git a/build2/test/script/parser+cleanup.test.testscript b/build2/test/script/parser+cleanup.test.testscript
new file mode 100644
index 0000000..2c94afc
--- /dev/null
+++ b/build2/test/script/parser+cleanup.test.testscript
@@ -0,0 +1,58 @@
+# file : build2/test/script/parser+cleanup.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+: always
+:
+$* <<EOI >>EOO
+cmd &file
+EOI
+cmd &file
+EOO
+
+: maybe
+:
+$* <<EOI >>EOO
+cmd &?file
+EOI
+cmd &?file
+EOO
+
+: never
+:
+$* <<EOI >>EOO
+cmd &!file
+EOI
+cmd &!file
+EOO
+
+: empty
+:
+$* <<EOI 2>>EOE != 0
+cmd &""
+EOI
+testscript:1:6: error: empty cleanup path
+EOE
+
+: missed-before
+:
+{
+ : token
+ :
+ : Path missed before command next token
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd & >file
+ EOI
+ testscript:1:7: error: missing cleanup path
+ EOE
+
+ : end
+ : Test path missed before end of command
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd &
+ EOI
+ testscript:1:6: error: missing cleanup path
+ EOE
+}
diff --git a/build2/test/script/parser+command-if.test.testscript b/build2/test/script/parser+command-if.test.testscript
new file mode 100644
index 0000000..ab6e6d5
--- /dev/null
+++ b/build2/test/script/parser+command-if.test.testscript
@@ -0,0 +1,548 @@
+# file : build2/test/script/parser+command-if.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+: if
+:
+{
+ : true
+ :
+ $* <<EOI >>EOO
+ if true foo
+ cmd1
+ cmd2
+ end
+ EOI
+ ? true foo
+ cmd1
+ cmd2
+ EOO
+
+ : false
+ :
+ $* <<EOI >>EOO
+ if false foo
+ cmd1
+ cmd2
+ end
+ EOI
+ ? false foo
+ EOO
+
+ : not-true
+ :
+ $* <<EOI >>EOO
+ if! true foo
+ cmd1
+ cmd2
+ end
+ EOI
+ ? true foo
+ EOO
+
+ : not-false
+ :
+ $* <<EOI >>EOO
+ if! false foo
+ cmd1
+ cmd2
+ end
+ EOI
+ ? false foo
+ cmd1
+ cmd2
+ EOO
+
+ : without-command
+ :
+ $* <<EOI 2>>EOE != 0
+ if
+ cmd
+ end
+ EOI
+ testscript:1:3: error: missing program
+ EOE
+
+ : after-semi
+ :
+ $* -s <<EOI >>EOO
+ cmd1;
+ if true
+ cmd2
+ end
+ EOI
+ {
+ {
+ cmd1
+ ? true
+ cmd2
+ }
+ }
+ EOO
+
+ : setup
+ :
+ $* -s <<EOI >>EOO
+ +if true
+ cmd
+ end
+ EOI
+ {
+ ? true
+ +cmd
+ }
+ EOO
+
+ : tdown
+ :
+ $* -s <<EOI >>EOO
+ -if true
+ cmd
+ end
+ EOI
+ {
+ ? true
+ -cmd
+ }
+ EOO
+}
+
+: elif
+:
+{
+ : true
+ :
+ $* <<EOI >>EOO
+ if false
+ cmd1
+ cmd2
+ elif true
+ cmd3
+ cmd4
+ end
+ EOI
+ ? false
+ ? true
+ cmd3
+ cmd4
+ EOO
+
+ : false
+ :
+ $* <<EOI >>EOO
+ if false
+ cmd1
+ cmd2
+ elif false
+ cmd3
+ cmd4
+ end
+ EOI
+ ? false
+ ? false
+ EOO
+
+ : not-true
+ :
+ $* <<EOI >>EOO
+ if false
+ cmd1
+ cmd2
+ elif! true
+ cmd3
+ cmd4
+ end
+ EOI
+ ? false
+ ? true
+ EOO
+
+ : not-false
+ :
+ $* <<EOI >>EOO
+ if false
+ cmd1
+ cmd2
+ elif! false
+ cmd3
+ cmd4
+ end
+ EOI
+ ? false
+ ? false
+ cmd3
+ cmd4
+ EOO
+
+ : without-if
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd
+ elif true
+ cmd
+ end
+ EOI
+ testscript:2:1: error: 'elif' without preceding 'if'
+ EOE
+
+ : not-without-if
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd
+ elif! true
+ cmd
+ end
+ EOI
+ testscript:2:1: error: 'elif!' without preceding 'if'
+ EOE
+
+ : after-else
+ :
+ $* <<EOI 2>>EOE != 0
+ if false
+ cmd
+ else
+ cmd
+ elif true
+ cmd
+ end
+ EOI
+ testscript:5:1: error: 'elif' after 'else'
+ EOE
+}
+
+: else
+:
+{
+ : true
+ :
+ $* <<EOI >>EOO
+ if false
+ cmd1
+ cmd2
+ else
+ cmd3
+ cmd4
+ end
+ EOI
+ ? false
+ cmd3
+ cmd4
+ EOO
+
+ : false
+ :
+ $* <<EOI >>EOO
+ if true
+ cmd1
+ cmd2
+ else
+ cmd3
+ cmd4
+ end
+ EOI
+ ? true
+ cmd1
+ cmd2
+ EOO
+
+ : chain
+ :
+ $* <<EOI >>EOO
+ if false
+ cmd
+ cmd
+ elif false
+ cmd
+ cmd
+ elif false
+ cmd
+ cmd
+ elif true
+ cmd1
+ cmd2
+ elif false
+ cmd
+ cmd
+ else
+ cmd
+ cmd
+ end
+ EOI
+ ? false
+ ? false
+ ? false
+ ? true
+ cmd1
+ cmd2
+ EOO
+
+ : command-after
+ :
+ $* <<EOI 2>>EOE != 0
+ if true
+ cmd
+ else cmd
+ cmd
+ end
+ EOI
+ testscript:3:6: error: expected newline instead of 'cmd'
+ EOE
+
+ : without-if
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd
+ else
+ cmd
+ end
+ EOI
+ testscript:2:1: error: 'else' without preceding 'if'
+ EOE
+
+ : after-else
+ :
+ $* <<EOI 2>>EOE != 0
+ if false
+ cmd
+ else
+ cmd
+ else
+ cmd
+ end
+ EOI
+ testscript:5:1: error: 'else' after 'else'
+ EOE
+}
+
+: end
+{
+ : without-if
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd
+ end
+ EOI
+ testscript:2:1: error: 'end' without preceding 'if'
+ EOE
+
+ : before
+ {
+ : semi
+ :
+ $* -s <<EOI >>EOO
+ if true
+ cmd1
+ end;
+ cmd2
+ EOI
+ {
+ {
+ ? true
+ cmd1
+ cmd2
+ }
+ }
+ EOO
+
+ : command
+ :
+ $* <<EOI 2>>EOE != 0
+ if true
+ cmd
+ end cmd
+ EOI
+ testscript:3:5: error: expected newline instead of 'cmd'
+ EOE
+
+ : colon
+ :
+ $* -s <<EOI >>EOO
+ if true
+ cmd1
+ cmd2
+ end : test
+ EOI
+ {
+ : id:test
+ {
+ ? true
+ cmd1
+ cmd2
+ }
+ }
+ EOO
+ }
+}
+
+: nested
+:
+{
+ : take
+ :
+ $* <<EOI >>EOO
+ if true
+ cmd1
+ if false
+ cmd
+ elif false
+ if true
+ cmd
+ end
+ else
+ cmd2
+ end
+ cmd3
+ end
+ EOI
+ ? true
+ cmd1
+ ? false
+ ? false
+ cmd2
+ cmd3
+ EOO
+
+ : skip
+ :
+ $* <<EOI >>EOO
+ if false
+ cmd1
+ if false
+ cmd
+ elif false
+ if true
+ cmd
+ end
+ else
+ cmd2
+ end
+ cmd3
+ else
+ cmd
+ end
+ EOI
+ ? false
+ cmd
+ EOO
+}
+
+: contained
+{
+ : semi
+ :
+ $* <<EOI 2>>EOE != 0
+ if
+ cmd;
+ cmd
+ end
+ EOI
+ testscript:2:3: error: ';' inside 'if'
+ EOE
+
+ : colon-leading
+ :
+ $* <<EOI 2>>EOE != 0
+ if
+ : foo
+ cmd
+ end
+ EOI
+ testscript:2:3: error: description inside 'if'
+ EOE
+
+ : colon-trailing
+ :
+ $* <<EOI 2>>EOE != 0
+ if
+ cmd : foo
+ end
+ EOI
+ testscript:2:3: error: description inside 'if'
+ EOE
+
+ : eos
+ :
+ $* <<EOI 2>>EOE != 0
+ if
+ EOI
+ testscript:2:1: error: expected closing 'end'
+ EOE
+
+ : scope
+ :
+ $* <<EOI 2>>EOE != 0
+ if
+ cmd
+ {
+ }
+ end
+ EOI
+ testscript:3:3: error: expected closing 'end'
+ EOE
+
+ : setup
+ :
+ $* <<EOI 2>>EOE != 0
+ if
+ +cmd
+ end
+ EOI
+ testscript:2:3: error: setup command inside 'if'
+ EOE
+
+ : tdown
+ :
+ $* <<EOI 2>>EOE != 0
+ if
+ -cmd
+ end
+ EOI
+ testscript:2:3: error: teardown command inside 'if'
+ EOE
+}
+
+: line-index
+:
+$* -l <<EOI >>EOO
+if false
+ cmd
+ if true
+ cmd
+ end
+ cmd
+elif false
+ cmd
+else
+ cmd
+end
+EOI
+? false # 1
+? false # 6
+cmd # 8
+EOO
+
+: var
+:
+$* <<EOI >>EOO
+if true
+ x = foo
+else
+ x = bar
+end;
+cmd $x
+EOI
+? true
+cmd foo
+EOO
+
+: leading-and-trailing-description
+:
+$* <<EOI 2>>EOE != 0
+: foo
+if true
+ cmd
+end : bar
+EOI
+testscript:4:1: error: both leading and trailing descriptions
+EOE
diff --git a/build2/test/script/parser+command-re-parse.test.testscript b/build2/test/script/parser+command-re-parse.test.testscript
new file mode 100644
index 0000000..ef030de
--- /dev/null
+++ b/build2/test/script/parser+command-re-parse.test.testscript
@@ -0,0 +1,12 @@
+# file : build2/test/script/parser+command-re-parse.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+: double-quote
+:
+$* <<EOI >>EOO
+x = cmd \">-\" "'<-'"
+$x
+EOI
+cmd '>-' '<-'
+EOO
diff --git a/build2/test/script/parser+description.test.testscript b/build2/test/script/parser+description.test.testscript
new file mode 100644
index 0000000..7d840c3
--- /dev/null
+++ b/build2/test/script/parser+description.test.testscript
@@ -0,0 +1,486 @@
+# file : build2/test/script/parser+description.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+: id
+:
+{
+ : lead
+ :
+ $* <<EOI >>EOO
+ : foo
+ cmd
+ EOI
+ : id:foo
+ cmd
+ EOO
+
+ : trail
+ :
+ $* <<EOI >>EOO
+ cmd : foo
+ EOI
+ : id:foo
+ cmd
+ EOO
+
+ : dup
+ : Id uniqueness
+ :
+ {
+ : test
+ :
+ {
+ : test
+ :
+ $* <<EOI 2>>EOE != 0
+ : foo
+ cmd
+ : foo
+ cmd
+ EOI
+ testscript:3:1: error: duplicate id foo
+ testscript:1:1: info: previously used here
+ EOE
+
+ : group
+ :
+ $* <<EOI 2>>EOE != 0
+ : foo
+ cmd
+ : foo
+ {
+ cmd
+ cmd
+ }
+ EOI
+ testscript:3:1: error: duplicate id foo
+ testscript:1:1: info: previously used here
+ EOE
+
+ : derived
+ :
+ $* <<EOI 2>>EOE != 0
+ : 3
+ cmd
+ cmd
+ EOI
+ testscript:3:1: error: duplicate id 3
+ testscript:1:1: info: previously used here
+ EOE
+ }
+
+ : group
+ :
+ {
+ : test
+ :
+ $* <<EOI 2>>EOE != 0
+ : foo
+ {
+ cmd
+ cmd
+ }
+ : foo
+ cmd
+ EOI
+ testscript:6:1: error: duplicate id foo
+ testscript:1:1: info: previously used here
+ EOE
+
+ : group
+ :
+ $* <<EOI 2>>EOE != 0
+ : foo
+ {
+ cmd
+ cmd
+ }
+ : foo
+ {
+ cmd
+ cmd
+ }
+ EOI
+ testscript:6:1: error: duplicate id foo
+ testscript:1:1: info: previously used here
+ EOE
+
+ : derived
+ :
+ $* <<EOI 2>>EOE != 0
+ : 3
+ cmd
+ {
+ cmd
+ cmd
+ }
+ EOI
+ testscript:3:1: error: duplicate id 3
+ testscript:1:1: info: previously used here
+ EOE
+ }
+ }
+}
+
+: summary
+{
+ : lead
+ :
+ $* <<EOI >>EOO
+ : foo bar
+ cmd
+ EOI
+ : sm:foo bar
+ cmd
+ EOO
+
+ : trail
+ :
+ $* <<EOI >>EOO
+ cmd: foo bar
+ EOI
+ : sm:foo bar
+ cmd
+ EOO
+
+ : id
+ :
+ $* <<EOI >>EOO
+ : foo-bar
+ : foo bar
+ cmd
+ EOI
+ : id:foo-bar
+ : sm:foo bar
+ cmd
+ EOO
+}
+
+: details
+{
+ : id
+ :
+ $* <<EOI >>EOO
+ : foo-bar
+ :
+ : foo bar
+ : bar baz
+ cmd
+ EOI
+ : id:foo-bar
+ :
+ : foo bar
+ : bar baz
+ cmd
+ EOO
+
+ : summary
+ :
+ {
+ : only
+ :
+ $* <<EOI >>EOO
+ : foo bar
+ :
+ : foo bar
+ : bar baz
+ cmd
+ EOI
+ : sm:foo bar
+ :
+ : foo bar
+ : bar baz
+ cmd
+ EOO
+
+ : assumed
+ :
+ $* <<EOI >>EOO
+ : foo bar
+ : bar baz
+ cmd
+ EOI
+ : foo bar
+ : bar baz
+ cmd
+ EOO
+
+ : id
+ :
+ $* <<EOI >>EOO
+ : foo-bar
+ : foo bar
+ :
+ : foo bar
+ : bar baz
+ cmd
+ EOI
+ : id:foo-bar
+ : sm:foo bar
+ :
+ : foo bar
+ : bar baz
+ cmd
+ EOO
+
+ : id-assumed
+ :
+ $* <<EOI >>EOO
+ : foo-bar
+ : bar baz
+ : baz fox
+ cmd
+ EOI
+ : foo-bar
+ : bar baz
+ : baz fox
+ cmd
+ EOO
+ }
+}
+
+: legal
+:
+: Legal places for description.
+:
+{
+ : var
+ :
+ $* <<EOI >>EOO
+ : foo bar
+ x = y;
+ cmd $x
+ EOI
+ : sm:foo bar
+ cmd y
+ EOO
+}
+
+: illegal
+:
+: Illegal places for description.
+:
+{
+ : eof
+ :
+ $* <": foo" 2>>EOE != 0
+ testscript:2:1: error: description before <end of file>
+ EOE
+
+ : rcbrace
+ :
+ $* <<EOI 2>>EOE != 0
+ {
+ cmd
+ : foo
+ }
+ EOI
+ testscript:4:1: error: description before '}'
+ EOE
+
+ : setup
+ :
+ $* <<EOI 2>>EOE != 0
+ : foo
+ +cmd
+ EOI
+ testscript:2:1: error: description before setup command
+ EOE
+
+ : tdown
+ :
+ $* <<EOI 2>>EOE != 0
+ : foo
+ -cmd
+ EOI
+ testscript:2:1: error: description before teardown command
+ EOE
+
+ : var
+ :
+ $* <<EOI 2>>EOE != 0
+ : foo
+ x = y
+ EOI
+ testscript:2:1: error: description before setup/teardown variable
+ EOE
+
+ : var-if
+ :
+ $* <<EOI 2>>EOE != 0
+ : foo
+ if true
+ x = y
+ end
+ EOI
+ testscript:2:1: error: description before/after setup/teardown variable-if
+ EOE
+
+ : var-if-after
+ :
+ $* <<EOI 2>>EOE != 0
+ if true
+ x = y
+ end : foo
+ EOI
+ testscript:1:1: error: description before/after setup/teardown variable-if
+ EOE
+
+ : test
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd1;
+ : foo
+ cmd2
+ EOI
+ testscript:2:1: error: description inside test
+ EOE
+}
+
+: test-scope
+:
+: Interaction with test scope merging.
+:
+{
+ : both
+ :
+ : No merge since both have description.
+ :
+ $* -s -i <<EOI >>EOO
+ : foo
+ {
+ : bar
+ cmd
+ }
+ EOI
+ {
+ : id:foo
+ { # foo
+ : id:bar
+ { # foo/bar
+ cmd
+ }
+ }
+ }
+ EOO
+
+ : test
+ :
+ : No merge since test has description.
+ :
+ $* -s -i <<EOI >>EOO
+ {
+ : foo-bar
+ : foo bar
+ cmd
+ }
+ EOI
+ {
+ { # 1
+ : id:foo-bar
+ : sm:foo bar
+ { # 1/foo-bar
+ cmd
+ }
+ }
+ }
+ EOO
+
+ : group
+ :
+ $* -s -i <<EOI >>EOO
+ : foo-bar
+ : foo bar
+ {
+ cmd
+ }
+ EOI
+ {
+ : id:foo-bar
+ : sm:foo bar
+ { # foo-bar
+ cmd
+ }
+ }
+ EOO
+}
+
+: blanks
+:
+$* <<EOI >>EOO
+:
+:
+: foo bar
+: bar baz
+:
+: baz fox
+:
+:
+cmd
+EOI
+: foo bar
+: bar baz
+:
+: baz fox
+cmd
+EOO
+
+: strip
+:
+$* <<EOI >>EOO
+: foo-bar
+: bar baz
+:
+: baz fox
+: fox biz
+:biz buz
+:
+cmd
+EOI
+: id:foo-bar
+: sm:bar baz
+:
+: baz fox
+: fox biz
+: biz buz
+cmd
+EOO
+
+: trail-compound
+:
+$* <<EOI >>EOO
+cmd1;
+cmd2: foo
+EOI
+: id:foo
+cmd1
+cmd2
+EOO
+
+: empty
+:
+$* <<EOI 2>>EOE != 0
+:
+:
+cmd
+EOI
+testscript:1:1: error: empty description
+EOE
+
+: trail-empty
+:
+$* <<EOI 2>>EOE != 0
+cmd:
+EOI
+testscript:1:4: error: empty description
+EOE
+
+: both
+:
+$* <<EOI 2>>EOE != 0
+: foo
+cmd : bar
+EOI
+testscript:2:1: error: both leading and trailing descriptions
+EOE
diff --git a/build2/test/script/parser+directive.test.testscript b/build2/test/script/parser+directive.test.testscript
new file mode 100644
index 0000000..addd874
--- /dev/null
+++ b/build2/test/script/parser+directive.test.testscript
@@ -0,0 +1,74 @@
+# file : build2/test/script/parser+directive.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+: not-directive
+:
+$* <<EOI >>EOO
+x = x
+".include" foo.testscript
+\.include foo.testscript
+EOI
+.include foo.testscript
+.include foo.testscript
+EOO
+
+: expected-name
+:
+$* <<EOI 2>>EOE != 0
+.$
+EOI
+testscript:1:2: error: expected directive name instead of '$'
+EOE
+
+: unknown-name
+:
+$* <<EOI 2>>EOE != 0
+.bogus
+EOI
+testscript:1:2: error: unknown directive 'bogus'
+EOE
+
+: separated
+:
+touch foo.testscript;
+$* <<EOI
+. include foo.testscript
+EOI
+
+: not-separated
+:
+touch foo.testscript;
+$* <<EOI
+x = foo.testscript
+.include$x
+EOI
+
+: var-expansion
+:
+cat <<EOI >="foo-$(build.verson.project).testscript";
+cmd
+EOI
+$* <<EOI >>EOO
+.include "foo-$(build.verson.project).testscript"
+EOI
+cmd
+EOO
+
+: after-semi
+:
+$* <<EOI 2>>EOE != 0
+cmd;
+.include foo.testscript
+EOI
+testscript:2:1: error: directive after ';'
+EOE
+
+: semi-after
+:
+$* <<EOI 2>>EOE != 0
+.include foo.testscript;
+cmd
+EOI
+testscript:1:24: error: ';' after directive
+EOE
diff --git a/build2/test/script/parser+exit.test.testscript b/build2/test/script/parser+exit.test.testscript
new file mode 100644
index 0000000..014afa4
--- /dev/null
+++ b/build2/test/script/parser+exit.test.testscript
@@ -0,0 +1,27 @@
+# file : build2/test/script/parser+exit.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+: eq
+:
+$* <<EOI >>EOO
+cmd == 1
+EOI
+cmd == 1
+EOO
+
+: ne
+:
+$* <<EOI >>EOO
+cmd!=1
+EOI
+cmd != 1
+EOO
+
+: end
+:
+$* <<EOI 2>>EOE != 0
+cmd != 1 <"foo"
+EOI
+testscript:1:10: error: unexpected '<' after command exit status
+EOE
diff --git a/build2/test/script/parser+expansion.test.testscript b/build2/test/script/parser+expansion.test.testscript
new file mode 100644
index 0000000..71a21b3
--- /dev/null
+++ b/build2/test/script/parser+expansion.test.testscript
@@ -0,0 +1,36 @@
+# file : build2/test/script/parser+expansion.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+: quote
+:
+: Make sure everything expanded as strings.
+:
+$* <<EOI >>EOO
+x = dir/ proj% proj%name proj%proj%dir/type{name name {name}}
+cmd dir/ proj% proj%name proj%proj%dir/type{name name {name}}
+cmd $x
+EOI
+cmd dir/ proj% proj%name proj%proj%dir/type{name name {name}}
+cmd dir/ proj% proj%name proj%proj%dir/type{name name {name}}
+EOO
+
+: unterm-quoted-seq
+:
+$* <<EOI 2>>EOE != 0
+x = "'a bc"
+cmd xy$x
+EOI
+<string>:1:8: error: unterminated single-quoted sequence
+ testscript:2:5: info: while parsing string 'xy'a bc'
+EOE
+
+: invalid-redirect
+:
+$* <<EOI 2>>EOE != 0
+x = "1>&a"
+cmd $x
+EOI
+<string>:1:4: error: stdout merge redirect file descriptor must be 2
+ testscript:2:5: info: while parsing string '1>&a'
+EOE
diff --git a/build2/test/script/parser+here-document.test.testscript b/build2/test/script/parser+here-document.test.testscript
new file mode 100644
index 0000000..5e99a26
--- /dev/null
+++ b/build2/test/script/parser+here-document.test.testscript
@@ -0,0 +1,213 @@
+# file : build2/test/script/parser+here-document.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+: end-marker
+:
+{
+ : missing-newline
+ :
+ $* <'cmd <<' 2>>EOE != 0
+ testscript:1:7: error: expected here-document end marker
+ EOE
+
+ : missing-exit
+ :
+ $* <'cmd << != 0' 2>>EOE != 0
+ testscript:1:8: error: expected here-document end marker
+ EOE
+
+ : missing-empty
+ :
+ $* <'cmd <<""' 2>>EOE != 0
+ testscript:1:7: error: expected here-document end marker
+ EOE
+
+ : unseparated-expansion
+ :
+ $* <'cmd <<FOO$foo' 2>>EOE != 0
+ testscript:1:10: error: here-document end marker must be literal
+ EOE
+
+ : quoted-single-partial
+ :
+ $* <"cmd <<F'O'O" 2>>EOE != 0
+ testscript:1:7: error: partially-quoted here-document end marker
+ EOE
+
+ : quoted-double-partial
+ :
+ $* <'cmd <<"FO"O' 2>>EOE != 0
+ testscript:1:7: error: partially-quoted here-document end marker
+ EOE
+
+ : quoted-mixed
+ :
+ $* <"cmd <<\"FO\"'O'" 2>>EOE != 0
+ testscript:1:7: error: partially-quoted here-document end marker
+ EOE
+
+ : unseparated
+ :
+ $* <<EOI >>EOO
+ cmd <<EOF!=0
+ foo
+ EOF
+ EOI
+ cmd <<EOF != 0
+ foo
+ EOF
+ EOO
+
+ : quoted-single
+ :
+ $* <<EOI >>EOO
+ cmd <<'EOF'
+ foo
+ EOF
+ EOI
+ cmd <<EOF
+ foo
+ EOF
+ EOO
+
+ : quoted-double
+ :
+ $* <<EOI >>EOO
+ cmd <<"EOF"
+ foo
+ EOF
+ EOI
+ cmd <<EOF
+ foo
+ EOF
+ EOO
+}
+
+: indent
+:
+{
+ : basic
+ :
+ $* <<EOI >>EOO
+ cmd <<EOF
+ foo
+ bar
+ baz
+ EOF
+ EOI
+ cmd <<EOF
+ foo
+ bar
+ baz
+ EOF
+ EOO
+
+ : blank
+ :
+ $* <<EOI >>EOO
+ cmd <<EOF
+ foo
+
+
+ bar
+ EOF
+ EOI
+ cmd <<EOF
+ foo
+
+
+ bar
+ EOF
+ EOO
+
+ : non-ws-prefix
+ :
+ $* <<EOI >>EOO
+ cmd <<EOF
+ x EOF
+ EOF
+ EOI
+ cmd <<EOF
+ x EOF
+ EOF
+ EOO
+
+ : whole-token
+ : Test the case where the indentation is a whole token
+ :
+ $* <<EOI >>EOO
+ x = foo bar
+ cmd <<"EOF"
+ $x
+ EOF
+ EOI
+ cmd <<EOF
+ foo bar
+ EOF
+ EOO
+
+ : long-line
+ : Test the case where the line contains multiple tokens
+ :
+ $* <<EOI >>EOO
+ x = foo
+ cmd <<"EOF"
+ $x bar $x
+ EOF
+ EOI
+ cmd <<EOF
+ foo bar foo
+ EOF
+ EOO
+
+ : unindented
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd <<EOF
+ bar
+ EOF
+ EOI
+ testscript:2:1: error: unindented here-document line
+ EOE
+}
+
+: blank
+:
+$* <<EOI >>EOO
+cmd <<EOF
+
+foo
+
+bar
+
+EOF
+EOI
+cmd <<EOF
+
+foo
+
+bar
+
+EOF
+EOO
+
+: quote
+:
+: Note: they are still recognized in eval contexts.
+:
+$* <<EOI >>EOO
+cmd <<"EOF"
+'single'
+"double"
+b'o't"h"
+('single' "double")
+EOF
+EOI
+cmd <<EOF
+'single'
+"double"
+b'o't"h"
+single double
+EOF
+EOO
diff --git a/build2/test/script/parser+here-string.test.testscript b/build2/test/script/parser+here-string.test.testscript
new file mode 100644
index 0000000..16544df
--- /dev/null
+++ b/build2/test/script/parser+here-string.test.testscript
@@ -0,0 +1,19 @@
+# file : build2/test/script/parser+here-string.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+: empty
+:
+$* <<EOI >>EOO
+cmd <""
+EOI
+cmd <''
+EOO
+
+: empty-nn
+:
+$* <<EOI >>EOO
+cmd <:""
+EOI
+cmd <:''
+EOO
diff --git a/build2/test/script/parser+include.test.testscript b/build2/test/script/parser+include.test.testscript
new file mode 100644
index 0000000..65be149
--- /dev/null
+++ b/build2/test/script/parser+include.test.testscript
@@ -0,0 +1,104 @@
+# file : build2/test/script/parser+include.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+: none
+:
+$* <<EOI
+.include
+.include --once
+EOI
+
+: empty
+:
+touch foo.testscript;
+$* <<EOI
+.include foo.testscript
+.include --once foo.testscript
+EOI
+
+: one
+:
+cat <"cmd" >=foo.testscript;
+$* <<EOI >>EOO
+.include foo.testscript
+EOI
+cmd
+EOO
+
+: multiple
+:
+cat <"cmd foo" >=foo.testscript;
+cat <"cmd bar" >=bar.testscript;
+$* <<EOI >>EOO
+.include foo.testscript bar.testscript
+EOI
+cmd foo
+cmd bar
+EOO
+
+: once
+:
+cat <"cmd" >=foo.testscript;
+$* <<EOI >>EOO
+.include foo.testscript
+x
+.include --once foo.testscript
+.include --once bar/../foo.testscript
+y
+.include ../once/foo.testscript
+EOI
+cmd
+x
+y
+cmd
+EOO
+
+: group-id
+:
+cat <<EOI >=foo.testscript;
+{
+ x = b
+}
+EOI
+$* -s -i <<EOI >>EOO
+x = a
+.include foo.testscript
+EOI
+{
+ { # 2-foo-1
+ }
+}
+EOO
+
+: test-id
+:
+cat <<EOI >=foo.testscript;
+cmd
+EOI
+$* -s -i <<EOI >>EOO
+x = a
+.include foo.testscript
+EOI
+{
+ { # 2-foo-1
+ cmd
+ }
+}
+EOO
+
+: invalid-path
+:
+$* <<EOI 2>>EOE != 0
+.include ""
+EOI
+testscript:1:2: error: invalid testscript include path ''
+EOE
+
+: unable-open
+:
+$* <<EOI 2>>~/EOE/ != 0
+.include foo.testscript
+EOI
+/testscript:1:2: error: unable to read testscript foo.testscript: .+/
+EOE
diff --git a/build2/test/script/parser+pipe-expr.test.testscript b/build2/test/script/parser+pipe-expr.test.testscript
new file mode 100644
index 0000000..18eb660
--- /dev/null
+++ b/build2/test/script/parser+pipe-expr.test.testscript
@@ -0,0 +1,133 @@
+# file : build2/test/script/parser+pipe-expr.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+: pipe
+:
+$* <<EOI >>EOO
+cmd1 | cmd2|cmd3
+EOI
+cmd1 | cmd2 | cmd3
+EOO
+
+: log
+:
+$* <<EOI >>EOO
+cmd1 || cmd2&&cmd3
+EOI
+cmd1 || cmd2 && cmd3
+EOO
+
+: pipe-log
+:
+$* <<EOI >>EOO
+cmd1 | cmd2 && cmd3 | cmd4
+EOI
+cmd1 | cmd2 && cmd3 | cmd4
+EOO
+
+: exit
+:
+$* <<EOI >>EOO
+cmd1|cmd2==1&&cmd3!=0|cmd4
+EOI
+cmd1 | cmd2 == 1 && cmd3 != 0 | cmd4
+EOO
+
+: here-doc
+:
+$* <<EOI >>EOO
+cmd1 <<EOI1 | cmd2 >>EOO2 && cmd3 <<EOI3 2>&1 | cmd4 2>>EOE4 >>EOO4
+input
+one
+EOI1
+ouput
+two
+EOO2
+input
+three
+EOI3
+error
+four
+EOE4
+output
+four
+EOO4
+EOI
+cmd1 <<EOI1 | cmd2 >>EOO2 && cmd3 <<EOI3 2>&1 | cmd4 >>EOO4 2>>EOE4
+input
+one
+EOI1
+ouput
+two
+EOO2
+input
+three
+EOI3
+output
+four
+EOO4
+error
+four
+EOE4
+EOO
+
+: leading
+:
+$* <<EOI 2>>EOE != 0
+| cmd
+EOI
+testscript:1:1: error: missing program
+EOE
+
+: trailing
+:
+$* <<EOI 2>>EOE != 0
+cmd &&
+EOI
+testscript:1:7: error: missing program
+EOE
+
+: redirected
+:
+{
+ : input
+ :
+ {
+ : first
+ :
+ $* <<EOI >>EOO
+ cmd1 <foo | cmd2
+ EOI
+ cmd1 <foo | cmd2
+ EOO
+
+ : non-first
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd1 | cmd2 <foo
+ EOI
+ testscript:1:13: error: stdin is both piped and redirected
+ EOE
+ }
+
+ : output
+ :
+ {
+ : last
+ :
+ $* <<EOI >>EOO
+ cmd1 | cmd2 >foo
+ EOI
+ cmd1 | cmd2 >foo
+ EOO
+
+ : non-last
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd1 >foo | cmd2
+ EOI
+ testscript:1:11: error: stdout is both redirected and piped
+ EOE
+ }
+}
diff --git a/build2/test/script/parser+pre-parse.test.testscript b/build2/test/script/parser+pre-parse.test.testscript
new file mode 100644
index 0000000..7d9eb6c
--- /dev/null
+++ b/build2/test/script/parser+pre-parse.test.testscript
@@ -0,0 +1,23 @@
+# file : build2/test/script/parser+pre-parse.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+: attribute
+:
+{
+ : pair
+ :
+ $* <<EOI 2>>EOE != 0
+ x = [foo=bar]
+ EOI
+ testscript:1:5: error: unknown value attribute foo=bar
+ EOE
+
+ : pair-empty
+ :
+ $* <<EOI 2>>EOE != 0
+ x = [foo=]
+ EOI
+ testscript:1:5: error: unknown value attribute foo
+ EOE
+}
diff --git a/build2/test/script/parser+redirect.test.testscript b/build2/test/script/parser+redirect.test.testscript
new file mode 100644
index 0000000..b0b967a
--- /dev/null
+++ b/build2/test/script/parser+redirect.test.testscript
@@ -0,0 +1,356 @@
+# file : build2/test/script/parser+redirect.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+# @@ Add tests for redirects other than trace, here-*, file and merge.
+# @@ Does it make sense to split into separate files - one per redirect type?
+#
+
+: trace
+:
+{
+ $* <'cmd >!' >'cmd >!' : out
+ $* <'cmd 2>!' >'cmd 2>!' : err
+}
+
+: str
+:
+{
+ : literal
+ :
+ {
+ : portable-path
+ :
+ $* <<EOI >>EOO
+ cmd </foo >/bar 2>/baz
+ EOI
+ cmd </foo >/bar 2>/baz
+ EOO
+ }
+
+ : regex
+ :
+ {
+ : portable-path
+ :
+ $* <<EOI >>EOO
+ cmd >/~%foo% 2>/~%bar%
+ EOI
+ cmd >/~%foo% 2>/~%bar%
+ EOO
+ }
+}
+
+: doc
+:
+{
+ : literal
+ :
+ {
+ : portable-path
+ :
+ $* <<EOI >>EOO
+ cmd <</EOI_ >/EOO_ 2>/EOE_
+ foo
+ EOI_
+ bar
+ EOO_
+ baz
+ EOE_
+ EOI
+ cmd <</EOI_ >/EOO_ 2>/EOE_
+ foo
+ EOI_
+ bar
+ EOO_
+ baz
+ EOE_
+ EOO
+
+ : sharing
+ :
+ {
+ : in-out
+ :
+ $* <<EOI >>EOO
+ cmd <<:/EOF >>:/EOF
+ foo
+ EOF
+ EOI
+ cmd <<:/EOF >>:/EOF
+ foo
+ EOF
+ EOO
+
+ : different
+ :
+ {
+ : modifiers
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd <<:/EOF >>:EOF
+ foo
+ EOF
+ EOI
+ testscript:1:16: error: different modifiers for shared here-document 'EOF'
+ EOE
+
+ : quoting
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd <<EOF >>"EOF"
+ foo
+ EOF
+ EOI
+ testscript:1:13: error: different quoting for shared here-document 'EOF'
+ EOE
+ }
+ }
+ }
+
+ : regex
+ :
+ {
+ : portable-path
+ :
+ $* <<EOI >>EOO
+ cmd >/~%EOF% 2>/~%EOE%
+ foo
+ EOF
+ bar
+ EOE
+ EOI
+ cmd >/~%EOF% 2>/~%EOE%
+ foo
+ EOF
+ bar
+ EOE
+ EOO
+
+ : sharing
+ :
+ {
+ : in-out
+ :
+ $* <<EOI >>EOO
+ cmd >>~/EOF/ 2>>~/EOF/
+ foo
+ EOF
+ EOI
+ cmd >>~/EOF/ 2>>~/EOF/
+ foo
+ EOF
+ EOO
+
+ : different
+ :
+ {
+ : introducers
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd >>~/EOF/ 2>>~%EOF%
+ foo
+ EOF
+ EOI
+ testscript:1:18: error: different introducers for shared here-document regex 'EOF'
+ EOE
+
+ : flags
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd >>~/EOF/ 2>>~/EOF/i
+ foo
+ EOF
+ EOI
+ testscript:1:18: error: different global flags for shared here-document regex 'EOF'
+ EOE
+ }
+ }
+ }
+}
+
+: file
+:
+{
+ : cmp
+ :
+ $* <<EOI >>EOO
+ cmd 0<<<a 1>>>b 2>>>c
+ EOI
+ cmd <<<a >>>b 2>>>c
+ EOO
+
+ : write
+ :
+ $* <<EOI >>EOO
+ cmd 1>=b 2>+c
+ EOI
+ cmd >=b 2>+c
+ EOO
+
+ : quote
+ :
+ $* <<EOI >>EOO
+ cmd 0<<<"a f" 1>="b f" 2>+"c f"
+ EOI
+ cmd <<<'a f' >='b f' 2>+'c f'
+ EOO
+
+ : in
+ :
+ {
+ : missed
+ :
+ $* <<EOI 2>>EOE !=0
+ cmd <<<
+ EOI
+ testscript:1:8: error: missing stdin file
+ EOE
+
+ : empty
+ :
+ $* <<EOI 2>>EOE !=0
+ cmd <<<""
+ EOI
+ testscript:1:8: error: empty stdin redirect path
+ EOE
+ }
+
+ : out
+ :
+ {
+ : missed
+ :
+ $* <<EOI 2>>EOE !=0
+ cmd >=
+ EOI
+ testscript:1:7: error: missing stdout file
+ EOE
+
+ : empty
+ :
+ $* <<EOI 2>>EOE !=0
+ cmd >=""
+ EOI
+ testscript:1:7: error: empty stdout redirect path
+ EOE
+ }
+
+ : err
+ :
+ {
+ : missed
+ :
+ $* <<EOI 2>>EOE !=0
+ cmd 2>=
+ EOI
+ testscript:1:8: error: missing stderr file
+ EOE
+
+ : empty
+ :
+ $* <<EOI 2>>EOE !=0
+ cmd 2>=""
+ EOI
+ testscript:1:8: error: empty stderr redirect path
+ EOE
+ }
+}
+
+: merge
+{
+ : out
+ :
+ {
+ : err
+ :
+ $* <<EOI >>EOO
+ cmd 1>&2
+ EOI
+ cmd >&2
+ EOO
+
+ : no-mutual
+ :
+ $* <<EOI >>EOO
+ cmd 1>&2 2>&1 2>a
+ EOI
+ cmd >&2 2>a
+ EOO
+
+ : not-descriptor
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd 1>&a
+ EOI
+ testscript:1:8: error: stdout merge redirect file descriptor must be 2
+ EOE
+
+ : self
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd 1>&1
+ EOI
+ testscript:1:8: error: stdout merge redirect file descriptor must be 2
+ EOE
+
+ : missed
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd 1>&
+ EOI
+ testscript:1:8: error: missing stdout file descriptor
+ EOE
+ }
+
+ : err
+ {
+ : out
+ :
+ $* <<EOI >>EOO
+ cmd 2>&1
+ EOI
+ cmd 2>&1
+ EOO
+
+ : no-mutual
+ :
+ $* <<EOI >>EOO
+ cmd 1>&2 2>&1 >a
+ EOI
+ cmd >a 2>&1
+ EOO
+
+ : not-descriptor
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd 2>&a
+ EOI
+ testscript:1:8: error: stderr merge redirect file descriptor must be 1
+ EOE
+
+ : self
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd 2>&2
+ EOI
+ testscript:1:8: error: stderr merge redirect file descriptor must be 1
+ EOE
+
+ : missed
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd 2>&
+ EOI
+ testscript:1:8: error: missing stderr file descriptor
+ EOE
+ }
+
+ : mutual
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd 1>&2 2>&1
+ EOI
+ testscript:1:14: error: stdout and stderr redirected to each other
+ EOE
+}
diff --git a/build2/test/script/parser+regex.test.testscript b/build2/test/script/parser+regex.test.testscript
new file mode 100644
index 0000000..031492e
--- /dev/null
+++ b/build2/test/script/parser+regex.test.testscript
@@ -0,0 +1,223 @@
+# file : build2/test/script/parser+regex.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+: here-string
+:
+{
+ : stdout
+ :
+ {
+ : missed
+ :
+ $* <'cmd >~' 2>>EOE != 0
+ testscript:1:7: error: missing stdout here-string regex
+ EOE
+
+ : no-introducer
+ :
+ $* <'cmd >~""' 2>>EOE != 0
+ testscript:1:7: error: no introducer character in stdout regex redirect
+ EOE
+
+ : no-term-introducer
+ :
+ $* <'cmd >~/' 2>>EOE != 0
+ testscript:1:7: error: no closing introducer character in stdout regex redirect
+ EOE
+
+ : portable-path-introducer
+ :
+ $* <'cmd >/~/foo/' 2>>EOE != 0
+ testscript:1:8: error: portable path modifier and '/' introducer in stdout regex redirect
+ EOE
+
+ : empty
+ :
+ $* <'cmd >~//' 2>>EOE != 0
+ testscript:1:7: error: stdout regex redirect is empty
+ EOE
+
+ : no-flags
+ :
+ $* <'cmd >~/fo*/' >'cmd >~/fo*/'
+
+ : idot
+ :
+ $* <'cmd >~/fo*/d' >'cmd >~/fo*/d'
+
+ : icase
+ :
+ $* <'cmd >~/fo*/i' >'cmd >~/fo*/i'
+
+ : invalid-flags1
+ :
+ $* <'cmd >~/foo/z' 2>>EOE != 0
+ testscript:1:7: error: junk at the end of stdout regex redirect
+ EOE
+
+ : invalid-flags2
+ :
+ $* <'cmd >~/foo/iz' 2>>EOE != 0
+ testscript:1:7: error: junk at the end of stdout regex redirect
+ EOE
+
+ : no-newline
+ :
+ $* <'cmd >:~/fo*/' >'cmd >:~/fo*/'
+ }
+
+ : stderr
+ :
+ {
+ : missed
+ :
+ $* <'cmd 2>~' 2>>EOE != 0
+ testscript:1:8: error: missing stderr here-string regex
+ EOE
+
+ : no-introducer
+ :
+ : Note that there is no need to reproduce all the errors as for stdout.
+ : All we need is to make sure that the proper description is passed to
+ : the parse_regex() function.
+ :
+ $* <'cmd 2>~""' 2>>EOE != 0
+ testscript:1:8: error: no introducer character in stderr regex redirect
+ EOE
+ }
+
+ : modifier-last
+ :
+ $* <'cmd >~/x' 2>>EOE != 0
+ testscript:1:7: error: no closing introducer character in stdout regex redirect
+ EOE
+}
+
+: here-doc
+:
+{
+ : stdout
+ :
+ {
+ : missed
+ :
+ $* <'cmd >>~' 2>>EOE != 0
+ testscript:1:8: error: expected here-document regex end marker
+ EOE
+
+ : portable-path-introducer
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd >>/~/EOO/
+ foo
+ EOO
+ EOI
+ testscript:1:5: error: portable path modifier and '/' introducer in here-document regex end marker
+ EOE
+
+ : unterminated-line-char
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd >>~/EOO/
+ /
+ EOO
+ EOI
+ testscript:2:1: error: no syntax line characters
+ EOE
+
+ : empty
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd >>:~/EOO/
+ EOO
+ EOI
+ testscript:2:1: error: empty here-document regex
+ EOE
+
+ : no-flags
+ :
+ $* <<EOI >>EOO
+ cmd 2>>~/EOE/
+ foo
+ /?
+ /foo/
+ /foo/*
+ /foo/i
+ /foo/i*
+
+ //
+ //*
+ EOE
+ EOI
+ cmd 2>>~/EOE/
+ foo
+ /?
+ /foo/
+ /foo/*
+ /foo/i
+ /foo/i*
+
+ //
+ //*
+ EOE
+ EOO
+
+ : no-newline
+ :
+ $* <'cmd >:~/fo*/' >'cmd >:~/fo*/'
+ $* <<EOI >>EOO
+ cmd 2>>:~/EOE/
+ foo
+ EOE
+ EOI
+ cmd 2>>:~/EOE/
+ foo
+ EOE
+ EOO
+
+ : end-marker-restore
+ :
+ {
+ : idot
+ :
+ $* <<EOI >>EOO
+ cmd 2>>~/EOE/d
+ foo
+ EOE
+ EOI
+ cmd 2>>~/EOE/d
+ foo
+ EOE
+ EOO
+
+ : icase
+ :
+ $* <<EOI >>EOO
+ cmd 2>>~/EOE/i
+ foo
+ EOE
+ EOI
+ cmd 2>>~/EOE/i
+ foo
+ EOE
+ EOO
+ }
+ }
+
+ : stderr
+ :
+ {
+ : missed
+ :
+ $* <'cmd 2>>~' 2>>EOE != 0
+ testscript:1:9: error: expected here-document regex end marker
+ EOE
+ }
+
+ : modifier-last
+ :
+ $* <'cmd >>~:/FOO/' 2>>EOE != 0
+ testscript:1:8: error: expected here-document regex end marker
+ EOE
+}
diff --git a/build2/test/script/parser+scope-if.test.testscript b/build2/test/script/parser+scope-if.test.testscript
new file mode 100644
index 0000000..faae297
--- /dev/null
+++ b/build2/test/script/parser+scope-if.test.testscript
@@ -0,0 +1,554 @@
+# file : build2/test/script/parser+scope-if.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+: if
+:
+{
+ : true
+ :
+ $* -s <<EOI >>EOO
+ if true foo
+ {
+ cmd
+ }
+ EOI
+ {
+ ? true foo
+ {
+ cmd
+ }
+ }
+ EOO
+
+ : false
+ :
+ $* -s <<EOI >>EOO
+ if false foo
+ {
+ cmd
+ }
+ EOI
+ {
+ ? false foo
+ }
+ EOO
+
+ : not-true
+ :
+ $* -s <<EOI >>EOO
+ if! true
+ {
+ cmd
+ }
+ EOI
+ {
+ ? true
+ }
+ EOO
+
+ : not-false
+ :
+ $* -s <<EOI >>EOO
+ if! false
+ {
+ cmd
+ }
+ EOI
+ {
+ ? false
+ {
+ cmd
+ }
+ }
+ EOO
+
+ : eos-inside
+ :
+ $* <<EOI 2>>EOE != 0
+ if
+ {
+ EOI
+ testscript:3:1: error: expected '}' at the end of the scope
+ EOE
+
+}
+
+: elif
+:
+{
+ : true
+ :
+ $* -s <<EOI >>EOO
+ if false
+ {
+ cmd
+ }
+ elif true
+ {
+ cmd1
+ }
+ EOI
+ {
+ ? false
+ ? true
+ {
+ cmd1
+ }
+ }
+ EOO
+
+ : false
+ :
+ $* -s <<EOI >>EOO
+ if false
+ {
+ cmd
+ }
+ elif false
+ {
+ cmd
+ }
+ EOI
+ {
+ ? false
+ ? false
+ }
+ EOO
+
+ : not-false
+ :
+ $* -s <<EOI >>EOO
+ if false
+ {
+ cmd
+ }
+ elif! false
+ {
+ cmd1
+ }
+ EOI
+ {
+ ? false
+ ? false
+ {
+ cmd1
+ }
+ }
+ EOO
+
+ : not-true
+ :
+ $* -s <<EOI >>EOO
+ if false
+ {
+ cmd
+ }
+ elif! true
+ {
+ cmd
+ }
+ EOI
+ {
+ ? false
+ ? true
+ }
+ EOO
+
+ : after-else
+ :
+ $* <<EOI 2>>EOE != 0
+ if false
+ {
+ cmd
+ }
+ else
+ {
+ cmd
+ }
+ elif true
+ {
+ cmd
+ }
+ EOI
+ testscript:9:1: error: 'elif' after 'else'
+ EOE
+}
+
+: else
+:
+{
+ : true
+ :
+ $* -s <<EOI >>EOO
+ if false
+ {
+ cmd
+ }
+ else
+ {
+ cmd1
+ }
+ EOI
+ {
+ ? false
+ {
+ cmd1
+ }
+ }
+ EOO
+
+ : false
+ :
+ $* -s <<EOI >>EOO
+ if true
+ {
+ cmd1
+ }
+ else
+ {
+ cmd
+ }
+ EOI
+ {
+ ? true
+ {
+ cmd1
+ }
+ }
+ EOO
+
+ : chain
+ :
+ $* -s <<EOI >>EOO
+ if false
+ {
+ cmd
+ }
+ elif false
+ {
+ cmd
+ cmd
+ }
+ elif false
+ {
+ cmd
+ }
+ elif true
+ {
+ cmd1
+ cmd2
+ }
+ elif false
+ {
+ cmd
+ }
+ else
+ {
+ cmd
+ cmd
+ }
+ EOI
+ {
+ ? false
+ ? false
+ ? false
+ ? true
+ {
+ {
+ cmd1
+ }
+ {
+ cmd2
+ }
+ }
+ }
+ EOO
+
+ : scope-expected
+ :
+ $* <<EOI 2>>EOE != 0
+ if
+ {
+ cmd
+ }
+ else
+ cmd
+ EOI
+ testscript:5:1: error: expected scope after 'else'
+ EOE
+
+ : after-else
+ :
+ $* <<EOI 2>>EOE != 0
+ if false
+ {
+ cmd
+ }
+ else
+ {
+ cmd
+ }
+ else
+ {
+ cmd
+ }
+ EOI
+ testscript:9:1: error: 'else' after 'else'
+ EOE
+}
+
+: nested
+:
+{
+ : take
+ :
+ $* -s <<EOI >>EOO
+ if true
+ {
+ cmd1
+ if false
+ {
+ cmd
+ }
+ elif false
+ {
+ if true
+ {
+ cmd
+ }
+ }
+ else
+ {
+ cmd2
+ }
+ cmd3
+ }
+ EOI
+ {
+ ? true
+ {
+ {
+ cmd1
+ }
+ ? false
+ ? false
+ {
+ {
+ cmd2
+ }
+ }
+ {
+ cmd3
+ }
+ }
+ }
+ EOO
+
+ : skip
+ :
+ $* -s <<EOI >>EOO
+ if false
+ {
+ cmd1
+ if false
+ {
+ cmd
+ }
+ elif false
+ {
+ if true
+ {
+ cmd
+ }
+ }
+ else
+ {
+ cmd2
+ }
+ cmd3
+ }
+ else
+ {
+ cmd
+ }
+ EOI
+ {
+ ? false
+ {
+ {
+ cmd
+ }
+ }
+ }
+ EOO
+}
+
+: demote
+:
+{
+ : group
+ : Chain remains a group
+ :
+ $* -s <<EOI >>EOO
+ if false
+ {
+ cmd
+ }
+ elif true
+ {
+ cmd1
+ cmd2
+ }
+ else
+ {
+ cmd
+ }
+ EOI
+ {
+ ? false
+ ? true
+ {
+ {
+ cmd1
+ }
+ {
+ cmd2
+ }
+ }
+ }
+ EOO
+
+ : test
+ : Chain demoted to test
+ :
+ $* -s <<EOI >>EOO
+ if false
+ {
+ cmd
+ }
+ elif true
+ {
+ cmd1
+ }
+ else
+ {
+ cmd
+ }
+ EOI
+ {
+ ? false
+ ? true
+ {
+ cmd1
+ }
+ }
+ EOO
+}
+
+: line-index
+: Make sure command line index spans setup/if/teardown
+:
+$* -s -l <<EOI >>EOO
++setup # 1
+
+if false one # 2
+{
+ cmd
+}
+elif false two # 3
+{
+ cmd
+}
+elif true # 4
+{
+ cmd1
+}
+elif false # 5
+{
+ cmd
+}
+else
+{
+ cmd
+}
+
+if false one # 6
+{
+ cmd
+}
+elif false two # 7
+{
+ cmd
+}
+else
+{
+ cmd2
+}
+
+-tdown # 8
+EOI
+{
+ +setup # 1
+ ? false one # 2
+ ? false two # 3
+ ? true # 4
+ {
+ cmd1 # 0
+ }
+ ? false one # 6
+ ? false two # 7
+ {
+ cmd2 # 0
+ }
+ -tdown # 8
+}
+EOO
+
+: scope-comman-if
+:
+$* -s <<EOI >>EOO
+if true
+{
+ cmd
+}
+if true
+ cmd1
+ cmd2
+end
+EOI
+{
+ ? true
+ {
+ cmd
+ }
+ {
+ ? true
+ cmd1
+ cmd2
+ }
+}
+EOO
+
+: shared-id-desc
+:
+$* -s -i <<EOI >>EOO
+: test summary
+:
+if false
+{
+ cmd
+}
+else
+{
+ cmd1
+}
+EOI
+{
+ ? false
+ : sm:test summary
+ { # 3
+ cmd1
+ }
+}
+EOO
diff --git a/build2/test/script/parser+scope.test.testscript b/build2/test/script/parser+scope.test.testscript
new file mode 100644
index 0000000..9147161
--- /dev/null
+++ b/build2/test/script/parser+scope.test.testscript
@@ -0,0 +1,280 @@
+# file : build2/test/script/parser+scope.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+$* testscript <'cmd $@' >"cmd 1" : id-testscript
+$* foo.testscript <'cmd $@' >"cmd foo/1" : id
+
+: wd-testscript
+:
+$* testscript <'cmd "$~"' >~"%cmd '?.+[/\\\\]test-driver[/\\\\]1'?%"
+
+: wd
+:
+$* foo.testscript <'cmd "$~"' >~"%cmd '?.+[/\\\\]test-driver[/\\\\]foo[/\\\\]1'?%"
+
+: group
+:
+{
+ : empty
+ :
+ $* -s <<EOI
+ {
+ }
+ EOI
+
+ : empty-empty
+ :
+ $* -s <<EOI
+ {
+ {
+ }
+ }
+ EOI
+
+ : non-empty
+ :
+ $* -s <<EOI >>EOO
+ {
+ cmd1
+ cmd2
+ }
+ EOI
+ {
+ {
+ {
+ cmd1
+ }
+ {
+ cmd2
+ }
+ }
+ }
+ EOO
+}
+
+: test
+:
+{
+ : explicit
+ :
+ {
+ : one-level
+ :
+ $* -s -i <<EOI >>EOO
+ {
+ cmd
+ }
+ EOI
+ {
+ { # 1
+ cmd
+ }
+ }
+ EOO
+
+ : nested
+ :
+ $* -s -i <<EOI >>EOO
+ {
+ {
+ cmd
+ }
+ }
+ EOI
+ {
+ { # 1
+ cmd
+ }
+ }
+ EOO
+
+ : var
+ :
+ $* -s -i <<EOI >>EOO
+ {
+ x = abc
+ cmd $x
+ }
+ EOI
+ {
+ { # 1
+ cmd abc
+ }
+ }
+ EOO
+
+ : setup
+ :
+ $* -s -i <<EOI >>EOO
+ {
+ x = abc
+ +setup
+ cmd $x
+ }
+ EOI
+ {
+ { # 1
+ +setup
+ { # 1/4
+ cmd abc
+ }
+ }
+ }
+ EOO
+ }
+
+ : implicit
+ {
+ : one-cmd
+ :
+ $* -s <<EOI >>EOO
+ cmd1
+ EOI
+ {
+ {
+ cmd1
+ }
+ }
+ EOO
+
+ : two-cmd
+ :
+ $* -s <<EOI >>EOO
+ cmd1;
+ cmd2
+ EOI
+ {
+ {
+ cmd1
+ cmd2
+ }
+ }
+ EOO
+
+ : three-cmd
+ :
+ $* -s <<EOI >>EOO
+ cmd1;
+ cmd2;
+ cmd3
+ EOI
+ {
+ {
+ cmd1
+ cmd2
+ cmd3
+ }
+ }
+ EOO
+
+ : var
+ :
+ $* -s <<EOI >>EOO
+ cmd1;
+ x = abc;
+ cmd2 $x
+ EOI
+ {
+ {
+ cmd1
+ cmd2 abc
+ }
+ }
+ EOO
+
+ : var-first
+ :
+ $* -s <<EOI >>EOO
+ x = abc;
+ cmd $x
+ EOI
+ {
+ {
+ cmd abc
+ }
+ }
+ EOO
+
+ : var-setup-tdown
+ :
+ $* -s <<EOI >>EOO
+ x = abc
+ cmd $x
+ y = 123
+ EOI
+ {
+ {
+ cmd abc
+ }
+ }
+ EOO
+
+ : after-tdown
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd1
+ x = abc
+ cmd2
+ EOI
+ testscript:3:1: error: test after teardown
+ testscript:2:1: info: last teardown line appears here
+ EOE
+ }
+}
+
+: expected
+{
+ : newline-lcbrace
+ :
+ $* <:"{x" 2>>EOE != 0
+ testscript:1:2: error: expected newline after '{'
+ EOE
+
+ : rcbrace
+ :
+ $* <"{" 2>>EOE != 0
+ testscript:2:1: error: expected '}' at the end of the scope
+ EOE
+
+ : line-rcbrace
+ :
+ $* <<EOI 2>>EOE != 0
+ {
+ cmd;
+ }
+ EOI
+ testscript:3:1: error: expected another line after ';'
+ EOE
+
+ : newline-rcbrace
+ :
+ $* <<:EOI 2>>EOE != 0
+ {
+ }
+ EOI
+ testscript:2:2: error: expected newline after '}'
+ EOE
+
+ : line-eof
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd;
+ EOI
+ testscript:2:1: error: expected another line after ';'
+ EOE
+
+ : newline-cmd
+ :
+ $* <<:EOI 2>>EOE != 0
+ cmd;
+ EOI
+ testscript:1:5: error: expected newline instead of <end of file>
+ EOE
+
+ : newline-var
+ :
+ $* <:"x = abc;" 2>>EOE != 0
+ testscript:1:9: error: expected newline instead of <end of file>
+ EOE
+}
diff --git a/build2/test/script/parser+setup-teardown.test.testscript b/build2/test/script/parser+setup-teardown.test.testscript
new file mode 100644
index 0000000..9d67309
--- /dev/null
+++ b/build2/test/script/parser+setup-teardown.test.testscript
@@ -0,0 +1,151 @@
+# file : build2/test/script/parser+setup-teardown.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+: setup
+:
+{
+ : followed
+ :
+ {
+ : semi
+ :
+ $* <"+cmd;" 2>>EOE != 0
+ testscript:1:5: error: ';' after setup command
+ EOE
+
+ : colon
+ :
+ $* <"+cmd:" 2>>EOE != 0
+ testscript:1:5: error: ':' after setup command
+ EOE
+ }
+
+ : after
+ :
+ {
+ : test
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd
+ +cmd
+ EOI
+ testscript:2:1: error: setup command after tests
+ EOE
+
+ : after-tdownt
+ :
+ $* <<EOI 2>>EOE != 0
+ -cmd
+ +cmd
+ EOI
+ testscript:2:1: error: setup command after teardown
+ EOE
+ }
+
+ : in-test
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd;
+ +cmd
+ EOI
+ testscript:2:1: error: setup command in test
+ EOE
+}
+
+: tdown
+:
+{
+ : followed
+ :
+ {
+ : semi
+ :
+ $* <"-cmd;" 2>>EOE != 0
+ testscript:1:5: error: ';' after teardown command
+ EOE
+
+ : colon
+ :
+ $* <"-cmd:" 2>>EOE != 0
+ testscript:1:5: error: ':' after teardown command
+ EOE
+ }
+
+ : in-test
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd;
+ -cmd
+ EOI
+ testscript:2:1: error: teardown command in test
+ EOE
+}
+
+: var
+:
+{
+ : between-tests
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd
+ x = y
+ cmd
+ EOI
+ testscript:3:1: error: test after teardown
+ testscript:2:1: info: last teardown line appears here
+ EOE
+
+ : between-tests-scope
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd
+ x = y
+ {
+ cmd
+ }
+ EOI
+ testscript:3:1: error: scope after teardown
+ testscript:2:1: info: last teardown line appears here
+ EOE
+
+ : between-tests-command-if
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd
+ x = y
+ if true
+ cmd
+ end
+ EOI
+ testscript:3:1: error: test after teardown
+ testscript:2:1: info: last teardown line appears here
+ EOE
+
+ : between-tests-scope-if
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd
+ x = y
+ if true
+ {
+ cmd
+ }
+ EOI
+ testscript:3:1: error: scope after teardown
+ testscript:2:1: info: last teardown line appears here
+ EOE
+
+ : between-tests-variable-if
+ :
+ $* <<EOI >>EOO
+ cmd
+ x = y
+ if true
+ y = x
+ end
+ EOI
+ cmd
+ ? true
+ EOO
+}
diff --git a/build2/test/script/parser.test.cxx b/build2/test/script/parser.test.cxx
new file mode 100644
index 0000000..1862f98
--- /dev/null
+++ b/build2/test/script/parser.test.cxx
@@ -0,0 +1,242 @@
+// file : build2/test/script/parser.test.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <cassert>
+#include <iostream>
+
+#include <build2/types.hxx>
+#include <build2/utility.hxx>
+
+#include <build2/target.hxx>
+#include <build2/context.hxx> // reset()
+#include <build2/scheduler.hxx>
+
+#include <build2/test/target.hxx>
+
+#include <build2/test/script/token.hxx>
+#include <build2/test/script/parser.hxx>
+#include <build2/test/script/runner.hxx>
+
+using namespace std;
+
+namespace build2
+{
+ namespace test
+ {
+ namespace script
+ {
+ // Here we assume we are running serially.
+ //
+ class print_runner: public runner
+ {
+ public:
+ print_runner (bool scope, bool id, bool line)
+ : scope_ (scope), id_ (id), line_ (line) {}
+
+ virtual bool
+ test (scope&) const override
+ {
+ return true;
+ }
+
+ virtual void
+ enter (scope& s, const location&) override
+ {
+ if (s.desc)
+ {
+ const auto& d (*s.desc);
+
+ if (!d.id.empty ())
+ cout << ind_ << ": id:" << d.id << endl;
+
+ if (!d.summary.empty ())
+ cout << ind_ << ": sm:" << d.summary << endl;
+
+ if (!d.details.empty ())
+ {
+ if (!d.id.empty () || !d.summary.empty ())
+ cout << ind_ << ":" << endl; // Blank.
+
+ const auto& s (d.details);
+ for (size_t b (0), e (0), n; e != string::npos; b = e + 1)
+ {
+ e = s.find ('\n', b);
+ n = ((e != string::npos ? e : s.size ()) - b);
+
+ cout << ind_ << ':';
+ if (n != 0)
+ {
+ cout << ' ';
+ cout.write (s.c_str () + b, static_cast<streamsize> (n));
+ }
+ cout << endl;
+ }
+ }
+ }
+
+ if (scope_)
+ {
+ cout << ind_ << "{";
+
+ if (id_ && !s.id_path.empty ()) // Skip empty root scope id.
+ cout << " # " << s.id_path.string ();
+
+ cout << endl;
+
+ ind_ += " ";
+ }
+ }
+
+ virtual void
+ run (scope&,
+ const command_expr& e, command_type t,
+ size_t i,
+ const location&) override
+ {
+ const char* s (nullptr);
+
+ switch (t)
+ {
+ case command_type::test: s = ""; break;
+ case command_type::setup: s = "+"; break;
+ case command_type::teardown: s = "-"; break;
+ }
+
+ cout << ind_ << s << e;
+
+ if (line_)
+ cout << " # " << i;
+
+ cout << endl;
+ }
+
+ virtual bool
+ run_if (scope&,
+ const command_expr& e,
+ size_t i,
+ const location&) override
+ {
+ cout << ind_ << "? " << e;
+
+ if (line_)
+ cout << " # " << i;
+
+ cout << endl;
+
+ return e.back ().pipe.back ().program.string () == "true";
+ }
+
+ virtual void
+ leave (scope&, const location&) override
+ {
+ if (scope_)
+ {
+ ind_.resize (ind_.size () - 2);
+ cout << ind_ << "}" << endl;
+ }
+ }
+
+ private:
+ bool scope_;
+ bool id_;
+ bool line_;
+ string ind_;
+ };
+
+ // Usage: argv[0] [-s] [-i] [-l] [<testscript-name>]
+ //
+ int
+ main (int argc, char* argv[])
+ {
+ tracer trace ("main");
+
+ init (argv[0], 1); // Fake build system driver, default verbosity.
+ sched.startup (1); // Serial execution.
+ reset (strings ()); // No command line variables.
+
+ bool scope (false);
+ bool id (false);
+ bool line (false);
+ path name;
+
+ for (int i (1); i != argc; ++i)
+ {
+ string a (argv[i]);
+
+ if (a == "-s")
+ scope = true;
+ else if (a == "-i")
+ id = true;
+ else if (a == "-l")
+ line = true;
+ else
+ {
+ name = path (move (a));
+ break;
+ }
+ }
+
+ if (name.empty ())
+ name = path ("testscript");
+
+ assert (!id || scope); // Id can only be printed with scope.
+
+ try
+ {
+ cin.exceptions (istream::failbit | istream::badbit);
+
+ // Enter mock targets. Use fixed names and paths so that we can use
+ // them in expected results. Strictly speaking target paths should
+ // be absolute. However, the testscript implementation doesn't
+ // really care.
+ //
+ file& tt (
+ targets.insert<file> (work,
+ dir_path (),
+ "driver",
+ string (),
+ trace));
+
+ value& v (
+ tt.assign (
+ var_pool.rw ().insert<target_triplet> (
+ "test.target", variable_visibility::project)));
+
+ v = cast<target_triplet> ((*global_scope)["build.host"]);
+
+ testscript& st (
+ targets.insert<testscript> (work,
+ dir_path (),
+ name.leaf ().base ().string (),
+ name.leaf ().extension (),
+ trace));
+
+ tt.path (path ("driver"));
+ st.path (name);
+
+ // Parse and run.
+ //
+ parser p;
+ script s (tt, st, dir_path (work) /= "test-driver");
+ p.pre_parse (cin, s);
+
+ print_runner r (scope, id, line);
+ p.execute (s, r);
+ }
+ catch (const failed&)
+ {
+ return 1;
+ }
+
+ return 0;
+ }
+ }
+ }
+}
+
+int
+main (int argc, char* argv[])
+{
+ return build2::test::script::main (argc, argv);
+}
diff --git a/build2/test/script/regex.test.cxx b/build2/test/script/regex.test.cxx
new file mode 100644
index 0000000..7b89e4d
--- /dev/null
+++ b/build2/test/script/regex.test.cxx
@@ -0,0 +1,301 @@
+// file : build2/test/script/regex.test.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <regex>
+#include <type_traits> // is_pod, is_array
+
+#include <build2/test/script/regex.hxx>
+
+using namespace std;
+using namespace build2::test::script::regex;
+
+int
+main ()
+{
+ using lc = line_char;
+ using ls = line_string;
+ using lr = line_regex;
+ using cf = char_flags;
+ using cr = char_regex;
+
+ // Test line_char.
+ //
+ {
+ static_assert (is_pod<lc>::value && !is_array<lc>::value,
+ "line_char must be char-like");
+
+ // Zero-initialed line_char should be the null-char as required by
+ // char_traits<>::length() specification.
+ //
+ assert (lc () == lc::nul);
+
+ line_pool p;
+
+ assert (lc::eof == -1);
+ assert (lc::nul == 0);
+
+ enum meta {mn = 'n', mp = 'p'};
+
+ // Special roundtrip.
+ //
+ assert (lc ('0').special () == '0');
+ assert (lc (0).special () == 0);
+ assert (lc (-1).special () == -1);
+ assert (lc ('p').special () == 'p');
+ assert (lc (u'\u2028').special () == u'\u2028');
+
+ // Special comparison.
+ //
+ assert (lc ('0') == lc ('0'));
+ assert (lc ('0') == '0');
+ assert (lc ('n') == mn);
+ assert (mn == static_cast<meta> (lc ('n')));
+
+ assert (lc ('0') != lc ('1'));
+ assert (lc ('0') != '1');
+ assert (lc ('n') != mp);
+ assert (lc ('0') != lc ("0", p));
+ assert (lc ('0') != lc (cr ("0"), p));
+
+ assert (lc ('0') < lc ('1'));
+ assert (lc ('0') < '1');
+ assert (lc ('1') < lc ("0", p));
+ assert (lc ('n') < mp);
+
+ assert (lc ('0') <= '1');
+ assert (lc ('0') <= lc ('1'));
+ assert (lc ('n') <= mn);
+ assert (lc ('1') <= lc ("0", p));
+
+ // Literal roundtrip.
+ //
+ assert (*lc ("abc", p).literal () == "abc");
+
+ // Literal comparison.
+ //
+ assert (lc ("a", p) == lc ("a", p));
+ assert (lc ("a", p).literal () == lc ("a", p).literal ());
+ assert (char (lc ("a", p)) == '\a');
+
+ assert (lc ("a", p) != lc ("b", p));
+ assert (!(lc ("a", p) != lc (cr ("a"), p)));
+ assert (lc ("a", p) != lc (cr ("b"), p));
+
+ assert (lc ("a", p) < lc ("b", p));
+ assert (!(lc ("a", p) < lc (cr ("a"), p)));
+
+ assert (lc ("a", p) <= lc ("b", p));
+ assert (lc ("a", p) <= lc (cr ("a"), p));
+ assert (lc ("a", p) < lc (cr ("c"), p));
+
+ // Regex roundtrip.
+ //
+ assert (regex_match ("abc", *lc (cr ("abc"), p).regex ()));
+
+ // Regex flags.
+ //
+ // icase
+ //
+ assert (regex_match ("ABC", cr ("abc", cf::icase)));
+
+ // idot
+ //
+ assert (!regex_match ("a", cr ("[.]", cf::idot)));
+ assert (!regex_match ("a", cr ("[\\.]", cf::idot)));
+
+ assert (regex_match ("a", cr (".")));
+ assert (!regex_match ("a", cr (".", cf::idot)));
+ assert (regex_match ("a", cr ("\\.", cf::idot)));
+ assert (!regex_match ("a", cr ("\\.")));
+
+ // regex::transform()
+ //
+ // The function is static and we can't test it directly. So we will test
+ // it indirectly via regex matches.
+ //
+ // @@ Would be nice to somehow address the inability to test internals (not
+ // exposed via headers). As a part of utility library support?
+ //
+ assert (regex_match (".a[.", cr (".\\.\\[[.]", cf::idot)));
+ assert (regex_match (".a[.", cr (".\\.\\[[\\.]", cf::idot)));
+ assert (!regex_match ("ba[.", cr (".\\.\\[[.]", cf::idot)));
+ assert (!regex_match (".a[b", cr (".\\.\\[[.]", cf::idot)));
+ assert (!regex_match (".a[b", cr (".\\.\\[[\\.]", cf::idot)));
+
+ // Regex comparison.
+ //
+ assert (lc ("a", p) == lc (cr ("a|b"), p));
+ assert (lc (cr ("a|b"), p) == lc ("a", p));
+ }
+
+ // Test char_traits<line_char>.
+ //
+ {
+ using ct = char_traits<lc>;
+ using vc = vector<lc>;
+
+ lc c;
+ ct::assign (c, '0');
+ assert (c == ct::char_type ('0'));
+
+ assert (ct::to_char_type (c) == c);
+ assert (ct::to_int_type (c) == c);
+
+ assert (ct::eq_int_type (c, c));
+ assert (!ct::eq_int_type (c, lc::eof));
+
+ assert (ct::eof () == lc::eof);
+
+ assert (ct::not_eof (c) == c);
+ assert (ct::not_eof (lc::eof) != lc::eof);
+
+ ct::assign (&c, 1, '1');
+ assert (c == ct::int_type ('1'));
+
+ assert (ct::eq (lc ('0'), lc ('0')));
+ assert (ct::lt (lc ('0'), lc ('1')));
+
+ vc v1 ({'0', '1', '2'});
+ vc v2 (3, lc::nul);
+
+ assert (ct::find (v1.data (), 3, '1') == v1.data () + 1);
+
+ ct::copy (v2.data (), v1.data (), 3);
+ assert (v2 == v1);
+
+ v2.push_back (lc::nul);
+ assert (ct::length (v2.data ()) == 3);
+
+ // Overlaping ranges.
+ //
+ ct::move (v1.data () + 1, v1.data (), 2);
+ assert (v1 == vc ({'0', '0', '1'}));
+
+ v1 = vc ({'0', '1', '2'});
+ ct::move (v1.data (), v1.data () + 1, 2);
+ assert (v1 == vc ({'1', '2', '2'}));
+ }
+
+ // Test line_char_locale and ctype<line_char> (only non-trivial functions).
+ //
+ {
+ using ct = ctype<lc>;
+
+ line_char_locale l;
+ assert (has_facet<ct> (l));
+
+ // It is better not to create q facet on stack as it is
+ // reference-countable.
+ //
+ const ct& t (use_facet<ct> (l));
+ line_pool p;
+
+ assert (t.is (ct::digit, '0'));
+ assert (!t.is (ct::digit, '?'));
+ assert (!t.is (ct::digit, lc ("0", p)));
+
+ const lc chars[] = { '0', '?' };
+ ct::mask m[2];
+
+ const lc* b (chars);
+ const lc* e (chars + 2);
+
+ // Cast flag value to mask type and compare to mask.
+ //
+ auto fl = [] (ct::mask m, ct::mask f) {return m == f;};
+
+ t.is (b, e, m);
+ assert (fl (m[0], ct::digit) && fl (m[1], 0));
+
+ assert (t.scan_is (ct::digit, b, e) == b);
+ assert (t.scan_is (0, b, e) == b + 1);
+
+ assert (t.scan_not (ct::digit, b, e) == b + 1);
+ assert (t.scan_not (0, b, e) == b);
+
+ {
+ char nr[] = "0?";
+ lc wd[2];
+ t.widen (nr, nr + 2, wd);
+ assert (wd[0] == b[0] && wd[1] == b[1]);
+ }
+
+ {
+ lc wd[] = {'0', lc ("a", p)};
+ char nr[2];
+ t.narrow (wd, wd + 2, '-', nr);
+ assert (nr[0] == '0' && nr[1] == '-');
+ }
+ }
+
+ // Test regex_traits<line_char>. Functions other that value() are trivial.
+ //
+ {
+ regex_traits<lc> t;
+
+ const int radix[] = {8, 10}; // Radix 16 is not supported by line_char.
+ const char digits[] = "0123456789ABCDEF";
+
+ for (size_t r (0); r < 2; ++r)
+ {
+ for (int i (0); i < radix[r]; ++i)
+ assert (t.value (digits[i], radix[r]) == i);
+ }
+ }
+
+ // Test line_regex construction.
+ //
+ {
+ line_pool p;
+ lr r1 ({lc ("foo", p), lc (cr ("ba(r|z)"), p)}, move (p));
+
+ lr r2 (move (r1));
+ assert (regex_match (ls ({lc ("foo", r2.pool), lc ("bar", r2.pool)}), r2));
+ assert (!regex_match (ls ({lc ("foo", r2.pool), lc ("ba", r2.pool)}), r2));
+ }
+
+ // Test line_regex match.
+ //
+ {
+ line_pool p;
+
+ const lc foo ("foo", p);
+ const lc bar ("bar", p);
+ const lc baz ("baz", p);
+ const lc blank ("", p);
+
+ assert (regex_match (ls ({foo, bar}), lr ({foo, bar})));
+ assert (!regex_match (ls ({foo, baz}), lr ({foo, bar})));
+
+ assert (regex_match (ls ({bar, foo}),
+ lr ({'(', foo, '|', bar, ')', '+'})));
+
+ assert (regex_match (ls ({foo, foo, bar}),
+ lr ({'(', foo, ')', '\\', '1', bar})));
+
+ assert (regex_match (ls ({foo}), lr ({lc (cr ("fo+"), p)})));
+ assert (regex_match (ls ({foo}), lr ({lc (cr (".*"), p)})));
+ assert (regex_match (ls ({blank}), lr ({lc (cr (".*"), p)})));
+
+ assert (regex_match (ls ({blank, blank, foo}),
+ lr ({blank, '*', foo, blank, '*'})));
+
+ assert (regex_match (ls ({blank, blank, foo}), lr ({'.', '*'})));
+
+ assert (regex_match (ls ({blank, blank}),
+ lr ({blank, '*', foo, '?', blank, '*'})));
+
+ assert (regex_match (ls ({foo}), lr ({foo, '{', '1', '}'})));
+ assert (regex_match (ls ({foo, foo}), lr ({foo, '{', '1', ',', '}'})));
+
+ assert (regex_match (ls ({foo, foo}),
+ lr ({foo, '{', '1', ',', '2', '}'})));
+
+ assert (!regex_match (ls ({foo, foo}),
+ lr ({foo, '{', '3', ',', '4', '}'})));
+
+ assert (regex_match (ls ({foo}), lr ({'(', '?', '=', foo, ')', foo})));
+ assert (regex_match (ls ({foo}), lr ({'(', '?', '!', bar, ')', foo})));
+ }
+}