# file      : tests/recipe/buildscript/testscript
# license   : MIT; see accompanying LICENSE file

posix = ($cxx.target.class != 'windows')

+mkdir build
+cat <<EOI >=build/bootstrap.build
  project = test
  amalgamation =
  subprojects =

  using config
  using test
  EOI

# @@ TMP Drop testscript.syntax assignment when support for syntax 1 is
#        dropped (see libbuild2/script/parser.hxx for details).
#
+cat <<EOI >=build/root.build
  buildscript.syntax = 2
  EOI

: update
:
{{
  : success
  :
  {
    echo 'bar' >=bar

    cat <<EOI >=buildfile
      foo: bar
      {{
        cp $path($<) $path($>)
      }}
      EOI

    $* 2>'cp file{bar} -> file{foo}'

    cat <<<foo >'bar'

    # While at it, make sure there is no rebuild.
    #
    $* 2>/'info: dir{./} is up to date'

    $* clean 2>-
  }

  : error
  :
  : Test that the target file is removed on error and is created on subsequent
  : successful update.
  :
  {
    echo 'bar' >=bar

    cat <<EOI >=buildfile
      foo: bar
      {{
        diag concat $<

        t = $path($>)
        p = $path($<)

        cp $p $t

        cat $(p).baz >>$t
      }}
      EOI

    $* 2>>~%EOE% != 0
      concat file{bar}
      %cat: unable to print '.+bar.baz': .+%
      buildfile:10:3: error: builtin cat exited with code 1
      %.+
      EOE

    test -f foo != 0

    echo 'baz' >=bar.baz

    $* 2>'concat file{bar}'

    cat <<<foo >>EOO
      bar
      baz
      EOO

    $* clean 2>-
  }

  : mutual-redirects
  :
  {
    echo 'bar' >=bar

    cat <<EOI >=buildfile
      foo: bar
      % [diag=cp]
      {{
        echo 'copying' 2>&1
        cp $path($<) $path($>)
      }}
      EOI

    $* 2>>~%EOE% != 0
      cp file{bar} -> file{foo}
      buildfile:4:3: error: stdout and stderr redirected to each other
      %.+
      EOE

    $* clean 2>-
  }

  : computed-var
  :
  {
    cat <<EOI >=buildfile
      a = a
      b = b
      foo:
      {{
        x = true
        echo "$($x ? a : b)" >$path($>)
      }}
      EOI

    $* 2>>EOE != 0
      buildfile:6:10: error: expansion of computed variable is only allowed in depdb preamble
        info: consider using 'depdb' builtin to track its value changes
      EOE
  }

  : untracked-var
  :
  {
    cat <<EOI >=buildfile
      a = a
      b = b
      foo:
      {{
        x = true
        y = $($x ? a : b)
        depdb env BOGUS
        echo $y >$path($>)
      }}
      EOI

    $* 2>>~%EOE% != 0
      buildfile:6:8: error: use of untracked variable 'a'
        info: use the 'depdb' builtin to manually track it
      %.+
      EOE

    $* clean 2>-
  }

  : export
  :
  if $posix
  {
    cat <<EOI >=bar
      #!/bin/sh
      echo "$message"
      EOI

    cat <<EOI >=buildfile
      exe{foo}: bar
      {{
        cp $path($<) $path($>)
      }}
      % test
      {{
        diag test $>
        export message=text1
        $> >>>?'text1'
        env message=text2 -- $> >>>?'text2'
      }}
      EOI

    $* test 2>>EOE
      cp file{bar} -> exe{foo}
      test exe{foo}
      EOE

    $* clean 2>-
  }

  : diag
  :
  {
    cat <<EOI >=buildfile
      foo:
      {{
        v1 = foo
        echo bar | set v2
        diag echo "$v1 $v2" -> $>
        echo "$v1 $v2" >$path($>)
      }}
      EOI

      $* 2>'echo foo bar -> file{foo}'
      cat <<<foo >'foo bar'

      $* clean 2>-
  }

  : depdb
  :
  {{
    : track-var-auto
    :
    {
      echo 'bar' >=bar

      cat <<EOI >=buildfile
        s = $process.run(cat bar)
        foo:
        {{
          echo "$s" >$path($>)
        }}
        EOI

      $* 2>'echo file{foo}'
      cat <<<foo >'bar'

      $* 2>/'info: dir{./} is up to date'

      echo 'baz' >=bar

      $* 2>'echo file{foo}'
      cat <<<foo >'baz'

      $* clean 2>-
    }

    : track-var-manual
    :
    {
      echo 'bar' >=bar
      echo 'baz' >=baz

      cat <<EOI >=buildfile
        a = $process.run(cat baz)
        foo: bar
        {{
          x = true
          y = $($x ? a : b)
          depdb hash "$a"

          diag compose $>

          cp $path($<) $path($>)

          echo $y >>$path($>)
        }}
        EOI

      $* 2>'compose file{foo}'

      cat <<<foo >>EOO
        bar
        baz
        EOO

      $* 2>/'info: dir{./} is up to date'

      # Make sure that on filesystems with a low file timestamps resolution
      # (for example HFS+) the file is considered as changed.
      #
      sleep 1

      echo 'BAR' >=bar

      $* 2>'compose file{foo}'

      cat <<<foo >>EOO
        BAR
        baz
        EOO

      $* 2>/'info: dir{./} is up to date'

      echo 'BAZ' >=baz

      $* 2>'compose file{foo}'

      cat <<<foo >>EOO
        BAR
        BAZ
        EOO

      $* 2>/'info: dir{./} is up to date'

      $* clean 2>-
    }

    : preamble
    :
    {{
      : valid
      :
      {
        echo 'bar' >=bar

        cat <<EOI >=buildfile
          s = $process.run(cat bar)
          foo:
          {{
            depdb clear

            s1 = 'abc'
            s2 = 'xyz'

            if echo "$s" >>>? 'bar'
              v = "$s1"
            else
              echo "$s2" | set v

            depdb string "$v"

            echo "$v" >$path($>)
          }}
          EOI

        $* 2>'echo file{foo}'
        cat <<<foo >'abc'

        $* 2>/'info: dir{./} is up to date'

        echo 'baz' >=bar
        $* 2>'echo file{foo}'
        cat <<<foo >'xyz'

        $* clean 2>-
      }

      : invalid
      :
      {
        cat <<EOI >=buildfile
          foo:
          {{
            v = 'abc'
            echo "$v" >$path($>)
            depdb string "$v"
          }}
          EOI

        $* 2>>~%EOE% != 0
          buildfile:4:3: error: disallowed command in depdb preamble
            info: only variable assignments are allowed in depdb preamble
            buildfile:5:3: info: depdb preamble ends here
          %.+
          EOE

        $* clean 2>-
      }

      : temp-dir
      :
      {
        cat <<EOI >=buildfile
          foo:
          {{
            touch $~/f | set dummy

            if test -f $~/f
              v = "yes"
            else
              v = "no"

            depdb string "$v"
            diag echo $>

            test -f $~/f
            echo "$v" >$path($>)
          }}
          EOI

        $* 2>'echo file{foo}'

        $* clean 2>-
      }
    }}

    : string
    :
    {
      echo 'bar' >=bar

      cat <<EOI >=buildfile
        s = $process.run(cat bar)
        foo:
        {{
          depdb clear
          depdb string "$s"
          echo "$s" >$path($>)
        }}
        EOI

      $* 2>'echo file{foo}'
      cat <<<foo >'bar'

      $* 2>/'info: dir{./} is up to date'

      echo 'baz' >=bar

      $* 2>'echo file{foo}'
      cat <<<foo >'baz'

      $* clean 2>-
    }

    : hash
    :
    {
      echo 'bar' >=bar

      cat <<EOI >=buildfile
        s = $process.run(cat bar)
        foo:
        {{
          depdb clear
          depdb hash "$s"
          echo "$s" >$path($>)
        }}
        EOI

      $* 2>'echo file{foo}'
      cat <<<foo >'bar'

      $* 2>/'info: dir{./} is up to date'

      echo 'baz' >=bar

      $* 2>'echo file{foo}'
      cat <<<foo >'baz'

      $* clean 2>-
    }

    : env
    :
    {{
      : invalid
      :
      {
        cat <<EOI >=buildfile
          foo:
          {{
            s = $getenv(FOO)

            depdb clear
            depdb env FOO=bar
            echo "$s" >$path($>)
          }}
          EOI

        $* 2>>/EOE != 0
          buildfile:6:3: error: invalid 'depdb env' argument: invalid variable name 'FOO=bar': contains '='
            info: while updating file{foo}
            info: while updating dir{./}
          info: failed to update dir{./}
          EOE

        $* clean 2>-
      }

      : valid
      :
      {
        cat <<EOI >=buildfile
          foo:
          {{
            s = $getenv(FOO)

            depdb clear
            depdb env FOO
            echo "$s" >$path($>)
          }}
          EOI

        export FOO=foo

        $* 2>'echo file{foo}'
        cat <<<foo >'foo'

        $* 2>/'info: dir{./} is up to date'

        export FOO=bar

        $* 2>'echo file{foo}'
        cat <<<foo >'bar'

        export -u FOO

        $* 2>'echo file{foo}'
        cat <<<foo >''

        $* clean 2>-
      }
    }}

    : dyndep
    :
    {{
      : normal
      :
      {
        cat <<EOI >=bar.h
          bar
          EOI

        cat <<EOI >=buildfile
          define h: file
          h{*}: extension = h

          ./: h{foo baz}

          h{foo}:
          {{
            # Note that strictly speaking we should return $out_base/baz.h
            # on the second invocation (since it now exists). But our dyndep
            # machinery skips the entry which it has already seen, so this
            # works for now.
            #
            depdb dyndep "-I$out_base" --what=header --default-type=h -- \
              echo "$out_base/foo.h: $src_base/bar.h baz.h"

            diag gen $>

            cat $path($<) >$path($>)
          }}

          h{baz}:
          {{
            diag gen $>
            echo baz >$path($>)
          }}
          EOI

        $* 2>>EOE
          gen h{baz}
          gen h{foo}
          EOE

        cat foo.h >>EOO
          bar
          baz
          EOO

        $* clean 2>-
      }

      : byproduct
      :
      {
        cat <<EOI >=bar.h
          bar
          EOI

        cat <<EOI >=buildfile
          define h: file
          h{*}: extension = h

          h{foo}: h{baz}
          {{
            o = $path($>)
            t = $path($>).t

            depdb dyndep --byproduct --what=header --default-type=h --file $t

            diag gen $>
            cat $src_base/bar.h $out_base/baz.h >$o
            echo "$out_base/foo.h: $src_base/bar.h $out_base/baz.h" >$t
          }}

          h{baz}:
          {{
            diag gen $>
            echo baz >$path($>)
          }}
          EOI

        $* 2>>EOE
          gen h{baz}
          gen h{foo}
          EOE

        cat foo.h >>EOO
          bar
          baz
          EOO

        $* clean 2>-
      }
    }}
  }}
}}

: clean
:
{
  echo 'bar' >=bar

  cat <<EOI >=buildfile
    foo: bar
    {{
      cp $path($<) $path($>)
    }}
    % [diag=clean] clean
    {{
      t = $path($>)
      rm $t $(t).d
    }}
    EOI

  $* 2>-

  # Rely on the cleanup machinery to verify that the build output files are
  # removed.
  #
  $* clean 2>'clean file{foo}'
}

: test
:
{{
  : success
  :
  {
    echo 'bar' >=bar

    cat <<EOI >=buildfile
      foo: bar
      {{
        cp $path($<) $path($>)
      }}
      % [diag=test] test
      {{
        cat <$path($>) >?$path($<)
      }}
      EOI

    $* test 2>>EOE
      cp file{bar} -> file{foo}
      test file{foo}
      EOE

    $* clean 2>-
  }

  : depdb
  :
  {
    echo 'bar' >=bar

    cat <<EOI >=buildfile
      foo: bar
      {{
        cp $path($<) $path($>)
      }}
      % [diag=test] test
      {{
        depdb clear
        cat <$path($>) >?$path($<)
      }}
      EOI

    $* test 2>>EOE != 0
      buildfile:7:3: error: 'depdb' builtin cannot be used to perform test
      EOE
  }

  : runner
  :
  if $posix
  {
    echo 'bar' >=bar

    cat <<EOI >=run
      #!/bin/sh
      if test "$1" = "--trace"; then
        shift
        echo "$*"
      fi
      "$@"
      EOI

    chmod u+x run

    cat <<EOI >=buildfile
      foo: bar
      {{
        cp $path($<) $path($>)
      }}
      % [diag=test] test
      {{
        if ($test.runner.path != [null])
          $test.runner.path $test.runner.options cat <$path($>)
        else
          cat <$path($>)
      }}
      EOI

    $* test 2>>EOE
      cp file{bar} -> file{foo}
      test file{foo}
      bar
      EOE

    $* test config.test.runner="./run --trace" 2>>EOE
      test file{foo}
      cat
      bar
      EOE

    $* clean 2>-
  }
}}

: diff-label
:
{
  echo 'bar' >=bar

  cat <<EOI >=buildfile
    foo: bar
    {{
      echo 'baz' >? $path($<)
    }}
    EOI

  $* 2>>/~%EOE% != 0
    %.+
    %--- .+/bar%
    +++ stdout
    %.+
    EOE

  $* clean 2>-
}

: canned-cmdline
:
{
  cat <<EOI >=buildfile
    ./:
    {{
      x = echo >|
      y = [cmdline] echo >|
      diag update $>
      $x foo
      $y bar
      ([cmdline] $x) baz
    }}
    EOI

  $* >> EOO 2>>/EOE
    bar
    baz
    EOO
    update dir{./}
    >| foo
    EOE
}

: timeout
:
if $posix
{{
  : update
  :
  {{
    : expired
    :
    {
      echo 'bar' >=bar

      cat <<EOI >=buildfile
        foo: bar
        % [diag=update]
        {{
          cp $path($<) $path($>)
          timeout 1
          ^sleep 5
        }}
        EOI

      $* 2>>~%EOE% != 0
        update file{bar} -> file{foo}
        buildfile:6:3: error: process ^sleep terminated: execution timeout expired
          info: command line: sleep 5
          info: while updating file{foo}
        %.+
        EOE

      $* clean 2>-
    }

    : successful-timeout
    :
    {
      echo 'bar' >=bar

      cat <<EOI >=buildfile
        foo: bar
        % [diag=update]
        {{
          cp $path($<) $path($>)
          timeout --success 1
          ^sleep 5
        }}
        EOI

      $* 2>>EOE
        update file{bar} -> file{foo}
        EOE

      $* clean 2>-
    }
  }}

  : test
  :
  {{
    : expired
    :
    {
      echo 'bar' >=bar

      cat <<EOI >=buildfile
        foo: bar
        {{
          cp $path($<) $path($>)
        }}
        % [diag=test] test
        {{
          ^sleep 5
        }}
        EOI

      $* test config.test.timeout=1 2>>~%EOE% != 0
        cp file{bar} -> file{foo}
        test file{foo}
        buildfile:7:3: error: process ^sleep terminated: execution timeout expired
          info: command line: sleep 5
          info: while testing file{foo}
        %.+
        EOE

      $* test config.test.timeout=/1 2>>~%EOE% != 0
        test file{foo}
        buildfile:7:3: error: process ^sleep terminated: execution timeout expired
          info: command line: sleep 5
          info: while testing file{foo}
        %.+
        EOE

      $* clean 2>-
    }

    : not-expired
    :
    {
      echo 'bar' >=bar

      cat <<EOI >=buildfile
        foo: bar
        % [diag=cp]
        {{
          ^sleep 4
          cp $path($<) $path($>)
        }}
        % [diag=test] test
        {{
          ^sleep 1
        }}
        EOI

      $* test config.test.timeout=3 2>>EOE
        cp file{bar} -> file{foo}
        test file{foo}
        EOE

      $* clean 2>-
    }
  }}
}}

# @@ TODO: test $1 when implemented.
#
: rule
:
{
  cat <<EOI >=buildfile
    alias{far}: alias{bar}
    alias{bar}:

    alias{~'/f(.+)/'}: alias{~'/b\1/'}
    {{
      diag frob $< -> $>
    }}
    EOI

  $* 2>>EOE
    frob alias{bar} -> alias{far}
    EOE
}

: loop
:
{{
  : while
  :
  {{
    : basics
    :
    {
      echo 'bar' >=bar

      cat <<EOI >=buildfile
        foo: bar
        {{
          p = $path($>)
          while test -f $p != 0
            cp $path($<) $p
        }}
        EOI

      $* 2>'cp file{bar} -> file{foo}'

      cat <<<foo >'bar'

      $* clean 2>-
    }

    : continue
    :
    {
      echo 'bar' >=bar

      cat <<EOI >=buildfile
        foo: bar
        {{
          p = $path($>)
          rm -f $p

          cat $path($<) >>$p

          i = [uint64] 0

          while ($i != 3)
          {
            if ($i == '1')
            {
              i += 1
              continue
            }

            cat <<<$i >>$p

            i += 1
          }
        }}
        EOI

      $* 2>'cat file{bar} -> file{foo}'

      cat <<<foo >>EOO
        bar
        0
        2
        EOO

      $* clean 2>-
    }

    : break
    :
    {
      echo 'bar' >=bar

      cat <<EOI >=buildfile
        foo: bar
        {{
          p = $path($>)
          rm -f $p

          cat $path($<) >>$p

          i = [uint64] 0

          while ($i != 3)
          {
            cat <<<$i >>$p

            if ($i == '1')
              break

            i += 1
          }
        }}
        EOI

      $* 2>'cat file{bar} -> file{foo}'

      cat <<<foo >>EOO
        bar
        0
        1
        EOO

      $* clean 2>-
    }

    : exit
    :
    {
      echo 'bar' >=bar

      cat <<EOI >=buildfile
        foo: bar
        {{
          diag gen ($>)

          p = $path($>)
          while test -f $p != 0
          {
            touch $p
            exit
            cp $path($<) $p
          }
        }}
        EOI

      $* 2>'gen file{foo}'

      cat <<<foo >:''

      $* clean 2>-
    }

    : error
    :
    {
      echo 'bar' >=bar

      cat <<EOI >=buildfile
        foo: bar
        {{
          diag gen ($>)

          p = $path($>)
          while test -f $p != 0
          {
            touch $p
            exit 'fed up'
            cp $path($<) $p
          }
        }}
        EOI

      $* 2>>~%EOE% != 0
        gen file{foo}
        buildfile:9:5: error: fed up
        %.{3}
        EOE

      $* clean 2>-
    }

    : depdb
    :
    {{
      : inside
      :
      {
        echo 'bar' >=bar

        cat <<EOI >=buildfile
          foo: bar
          {{
            p = $path($>)
            while test -f $p != 0
            {
              depdb hash $p
              cp $path($<) $p
            }
          }}
          EOI

        $* 2>>EOE != 0
          buildfile:6:5: error: 'depdb' call inside flow control construct
          EOE
      }

      : after-commands
      :
      {
        echo 'bar' >=bar

        cat <<EOI >=buildfile
          foo: bar
          {{
            p = $path($>)
            while test -f $p != 0
              cp $path($<) $p

            depdb hash $p
          }}
          EOI

        $* 2>>~%EOE% != 0
          buildfile:5:5: error: disallowed command in depdb preamble
            info: only variable assignments are allowed in depdb preamble
            buildfile:7:3: info: depdb preamble ends here
          %.{3}
          EOE

        $* clean 2>-
      }

      : after-vars
      :
      {
        echo 'bar' >=bar

        cat <<EOI >=buildfile
          foo: bar
          {{
            p = $path($<)

            h =
            while test -f $p != 0
              h += $p

            depdb hash $p

            cat $p >$path($>)
          }}
          EOI

        $* 2>'cat file{bar} -> file{foo}'
        $* clean 2>-
      }
    }}
  }}

  : for
  :
  {{
    : form-1
    :
    : for x: ...
    :
    {{
      : basics
      :
      {
        echo 'bar' >=bar
        echo 'baz' >=baz

        cat <<EOI >=buildfile
          foo: bar baz
          {{
            p = $path($>)
            rm -f $p

            for f: $<
              cat $path($f) >>$p
          }}
          EOI

        $* 2>'cat file{bar} -> file{foo}'

        cat <<<foo >>EOO
          bar
          baz
          EOO

        $* clean 2>-
      }

      : continue
      :
      {
        echo 'bar' >=bar

        cat <<EOI >=buildfile
          foo: bar
          {{
            p = $path($>)
            rm -f $p

            cat $path($<) >>$p

            for s: a b c
            {
              if ($s == 'b')
                continue

              cat <<<$s >>$p
            }
          }}
          EOI

        $* 2>'cat file{bar} -> file{foo}'

        cat <<<foo >>EOO
          bar
          a
          c
          EOO

        $* clean 2>-
      }

      : break
      :
      {
        echo 'bar' >=bar

        cat <<EOI >=buildfile
          foo: bar
          {{
            p = $path($>)
            rm -f $p

            cat $path($<) >>$p

            for s: a b c
            {
              cat <<<$s >>$p

              if ($s == 'b')
                break
            }
          }}
          EOI

        $* 2>'cat file{bar} -> file{foo}'

        cat <<<foo >>EOO
          bar
          a
          b
          EOO

        $* clean 2>-
      }

      : pair
      :
      {
        mkdir -p src/build
        echo 'bar' >=src/bar
        echo 'baz' >=src/baz

        echo 'project =' >=src/build/bootstrap.build

        # @@ TMP Drop this when support for syntax 1 is dropped (see
        #        libbuild2/script/parser.hxx for details).
        #
        echo 'buildscript.syntax = 2' >=src/build/root.build

        cat <<EOI >=src/buildfile
          foo: file{bar}@./ file{baz}@./
          {{
            p = $path($>)
            rm -f $p

            for f: $<
              cat $path($f) >>$p
          }}
          EOI

        $* src/@out/ 2>>/EOE
          mkdir fsdir{out/}
          cat src/file{bar} -> out/file{foo}
          EOE

        cat <<<out/foo >>EOO
          bar
          baz
          EOO

        $* 'clean:' src/@out/ 2>-
      }

      : special-var
      :
      {
        echo 'bar' >=bar
        echo 'baz' >=baz

        cat <<EOI >=buildfile
          foo: bar
          {{
            p = $path($>)
            rm -f $p

            for ~: $<
              cat $path($f) >>$p
          }}
          EOI

        $* 2>>EOE != 0
          buildfile:6:7: error: attempt to set '~' special variable
          EOE
      }

      : exit
      :
      {
        echo 'bar' >=bar
        echo 'baz' >=baz

        cat <<EOI >=buildfile
          foo: bar
          {{
            p = $path($>)
            rm -f $p

            for f: $<
              cat $path($f) >>$p
              exit
          }}
          EOI

        $* 2>'cat file{bar} -> file{foo}'

        cat <<<foo >>EOO
          bar
          EOO

        $* clean 2>-
      }

      : error
      :
      {
        echo 'bar' >=bar
        echo 'baz' >=baz

        cat <<EOI >=buildfile
          foo: bar
          {{
            p = $path($>)
            rm -f $p

            for f: $<
            {
              cat $path($f) >>$p
              exit 'fed up'
            }
          }}
          EOI

        $* 2>>~%EOE% != 0
          cat file{bar} -> file{foo}
          buildfile:9:5: error: fed up
          %.{3}
          EOE

        $* clean 2>-
      }

      : depdb
      :
      {{
        : inside
        :
        {
          echo 'bar' >=bar

          cat <<EOI >=buildfile
            foo: bar
            {{
              for f: $<
                depdb hash $f

              p = $path($>)
              rm -f $p

              for f: $<
                cat $path($f) >>$p
            }}
            EOI

          $* 2>>EOE != 0
            buildfile:4:5: error: 'depdb' call inside flow control construct
            EOE
        }

        : after-commands
        :
        {
          echo 'bar' >=bar

          cat <<EOI >=buildfile
            foo: bar
            {{
              for f: $<
                echo $path($f) >-

              depdb hash a
            }}
            EOI

          $* 2>>~%EOE% != 0
            buildfile:4:5: error: disallowed command in depdb preamble
              info: only variable assignments are allowed in depdb preamble
              buildfile:6:3: info: depdb preamble ends here
            %.{3}
            EOE

          $* clean 2>-
        }

        : after-vars
        :
        {
          echo 'bar' >=bar

          cat <<EOI >=buildfile
            foo: bar
            {{
              h =
              for f: $<
                h += $path($f)

              depdb hash $h

              p = $path($>)
              rm -f $p

              for f: $<
                cat $path($f) >>$p
            }}
            EOI

          $* 2>'cat file{bar} -> file{foo}'
          $* clean 2>-
        }

        : after-continue
        :
        {
          echo 'bar' >=bar
          echo 'baz' >=baz.bin

          cat <<EOI >=buildfile
            foo: bar baz.bin
            {{
              fs = [paths]
              for f: $<
              {
                p = $path($f)

                if ($extension($p) == 'bin')
                  continue

                fs += $p
              }

              depdb hash $fs

              p = $path($>)
              rm -f $p

              for f: $fs
                cat $f >>$p
            }}
            EOI

          $* 2>'cat file{bar} -> file{foo}'

          cat <<<foo >>EOO
            bar
            EOO

          $* clean 2>-
        }

        : after-break
        :
        {
          echo 'bar' >=bar
          echo 'baz' >=baz.bin

          cat <<EOI >=buildfile
            foo: bar baz.bin
            {{
              fs = [paths]
              for f: $<
              {
                p = $path($f)

                if ($extension($p) == 'bin')
                  break

                fs += $p
              }

              depdb hash $fs

              p = $path($>)
              rm -f $p

              for f: $fs
                cat $f >>$p
            }}
            EOI

          $* 2>'cat file{bar} -> file{foo}'

          cat <<<foo >>EOO
            bar
            EOO

          $* clean 2>-
        }
      }}
    }}

    : form-2
    :
    : ... | for x
    :
    {{
      : basics
      :
      {
        echo 'bar' >=bar
        echo 'baz' >=baz

        cat <<EOI >=buildfile
          foo: bar baz
          {{
            diag gen ($>)

            p = $path($>)
            rm -f $p

            echo $path($<) | for -w f
              cat $f >>$p
          }}
          EOI

        $* 2>'gen file{foo}'

        cat <<<foo >>EOO
          bar
          baz
          EOO

        $* clean 2>-
      }

      : continue
      :
      {
        echo 'bar' >=bar

        cat <<EOI >=buildfile
          foo: bar
          {{
            p = $path($>)
            rm -f $p

            cat $path($<) >>$p

            cat <<<'a b c' | for -w s
            {
              if ($s == 'b')
                continue

              cat <<<$s >>$p
            }
          }}
          EOI

        $* 2>'cat file{bar} -> file{foo}'

        cat <<<foo >>EOO
          bar
          a
          c
          EOO

        $* clean 2>-
      }

      : break
      :
      {
        echo 'bar' >=bar

        cat <<EOI >=buildfile
          foo: bar
          {{
            p = $path($>)
            rm -f $p

            cat $path($<) >>$p

            cat <<<'a b c' | for -w s
            {
              cat <<<$s >>$p

              if ($s == 'b')
                break
            }
          }}
          EOI

        $* 2>'cat file{bar} -> file{foo}'

        cat <<<foo >>EOO
          bar
          a
          b
          EOO

        $* clean 2>-
      }

      : special-var
      :
      {
        echo 'bar' >=bar
        echo 'baz' >=baz

        cat <<EOI >=buildfile
          foo: bar
          {{
            diag gen ($>)

            p = $path($>)
            rm -f $p

            echo $path($<) | for ~
              cat $f >>$p
          }}
          EOI

        $* 2>>~%EOE% != 0
          gen file{foo}
          buildfile:8:3: error: attempt to set '~' special variable
          %.{3}
          EOE

        $* clean 2>-
      }

      : misuse
      :
      {{
        : after-var
        {
          echo 'bar' >=bar
          echo 'baz' >=baz

          cat <<EOI >=buildfile
            foo: bar
            {{
              diag gen ($>)

              p = $path($>)
              rm -f $p

              echo $path($<) | for x:
                cat $f >>$p
            }}
            EOI

          $* 2>>~%EOE% != 0
            gen file{foo}
            buildfile:8:3: error: for: ':' after variable name
            %.+
            EOE

          $* clean 2>-
        }

        : after-attrs
        {
          echo 'bar' >=bar
          echo 'baz' >=baz

          cat <<EOI >=buildfile
            foo: bar
            {{
              diag gen ($>)

              p = $path($>)
              rm -f $p

              echo $path($<) | for x [path]:
                cat $f >>$p
            }}
            EOI

          $* 2>>~%EOE% != 0
            gen file{foo}
            <attributes>:1:7: error: whitespace required after attributes
              <attributes>:1:1: info: use the '\[' escape sequence if this is a wildcard pattern
              buildfile:8:3: info: while parsing attributes '[path]:'
            %.+
            EOE

          $* clean 2>-
        }
      }}

      : exit
      :
      {
        echo 'bar' >=bar
        echo 'baz' >=baz

        cat <<EOI >=buildfile
          foo: bar
          {{
            diag gen ($>)

            p = $path($>)
            rm -f $p

            echo $path($<) | for -w f
            {
              cat $f >>$p
              exit
            }
          }}
          EOI

        $* 2>'gen file{foo}'

        cat <<<foo >>EOO
          bar
          EOO

        $* clean 2>-
      }

      : error
      :
      {
        echo 'bar' >=bar
        echo 'baz' >=baz

        cat <<EOI >=buildfile
          foo: bar
          {{
            diag gen ($>)

            p = $path($>)
            rm -f $p

            echo $path($<) | for -w f
            {
              cat $f >>$p
              exit 'fed up'
            }
          }}
          EOI

        $* 2>>~%EOE% != 0
          gen file{foo}
          buildfile:11:5: error: fed up
          %.{3}
          EOE

        $* clean 2>-
      }

      : depdb
      :
      {{
        : inside
        :
        {
          echo 'bar' >=bar

          cat <<EOI >=buildfile
            foo: bar
            {{
              echo $path($<) | for -w f
                depdb hash $f

              p = $path($>)
              rm -f $p

              echo $path($<) | for -w f
                cat $f >>$p
            }}
            EOI

          $* 2>>EOE != 0
            buildfile:4:5: error: 'depdb' call inside flow control construct
            EOE
        }

        : after-commands
        :
        {
          echo 'bar' >=bar

          cat <<EOI >=buildfile
            foo: bar
            {{
              echo $path($<) | for -w f
                echo $f >-

              depdb hash $p
            }}
            EOI

          $* 2>>~%EOE% != 0
            buildfile:4:5: error: disallowed command in depdb preamble
              info: only variable assignments are allowed in depdb preamble
              buildfile:6:3: info: depdb preamble ends here
            %.{3}
            EOE

          $* clean 2>-
        }

        : after-vars
        :
        {
          echo 'bar' >=bar

          cat <<EOI >=buildfile
            foo: bar
            {{
              h =
              echo $path($<) | for -w f
                h += $f

              depdb hash $h

              diag gen ($>)

              p = $path($>)
              rm -f $p

              for f: $<
                cat $path($f) >>$p
            }}
            EOI

          $* 2>'gen file{foo}'
          $* clean 2>-
        }
      }}
    }}

    : form-3
    :
    : for x <...
    :
    {{
      : basics
      :
      {
        echo 'bar' >=bar
        echo 'baz' >=baz

        cat <<EOI >=buildfile
          foo: bar baz
          {{
            diag gen ($>)

            p = $path($>)
            rm -f $p

            for -w f <<"EOF"
            $path($<)
            EOF
              cat $f >>$p

            for <<"EOF" -w f
            $path($<)
            EOF
              cat $f >>$p
          }}
          EOI

        $* 2>'gen file{foo}'

        cat <<<foo >>EOO
          bar
          baz
          bar
          baz
          EOO

        $* clean 2>-
      }

      : continue
      :
      {
        echo 'bar' >=bar

        cat <<EOI >=buildfile
          foo: bar
          {{
            p = $path($>)
            rm -f $p

            cat $path($<) >>$p

            for s <<<'a b c'
            {
              if ($s == 'b')
                continue

              cat <<<$s >>$p
            }
          }}
          EOI

        $* 2>'cat file{bar} -> file{foo}'

        cat <<<foo >>EOO
          bar
          a
          c
          EOO

        $* clean 2>-
      }

      : break
      :
      {
        echo 'bar' >=bar

        cat <<EOI >=buildfile
          foo: bar
          {{
            p = $path($>)
            rm -f $p

            cat $path($<) >>$p

            for s <<<'a b c'
            {
              cat <<<$s >>$p

              if ($s == 'b')
                break
            }
          }}
          EOI

        $* 2>'cat file{bar} -> file{foo}'

        cat <<<foo >>EOO
          bar
          a
          b
          EOO

        $* clean 2>-
      }

      : quoting
      :
      {
        echo 'bar' >=bar
        echo 'baz' >=baz

        cat <<EOI >=buildfile
          foo: bar baz
          {{
            n = 'gen'
            diag "($n)" ($>)

            p = $path($>)
            rm -f $p

            o = -w
            for "$o" f <<"EOF"
            $path($<)
            EOF
              cat $f >>$p

            o = -n
            for "($o)" f <<"EOF"
            $path($<)
            EOF
              echo $f >>$p
          }}
          EOI

        $* 2>'gen file{foo}'

        cat <<<foo >>~%EOO%
          bar
          baz
          %.+bar .+baz%
          EOO

        $* clean 2>-
      }

      : special-var
      :
      {
        echo 'bar' >=bar
        echo 'baz' >=baz

        cat <<EOI >=buildfile
          foo: bar
          {{
            p = $path($>)
            rm -f $p

            for ~ <<<$path($<)
              cat $f >>$p
          }}
          EOI

        $* 2>>EOE != 0
          buildfile:6:7: error: attempt to set '~' special variable
          EOE
      }

      : exit
      :
      {
        echo 'bar' >=bar
        echo 'baz' >=baz

        cat <<EOI >=buildfile
          foo: bar
          {{
            p = $path($>)
            rm -f $p

           for f <<<$path($<)
           {
              cat $f >>$p
              exit
           }
          }}
          EOI

        $* 2>'cat file{bar} -> file{foo}'

        cat <<<foo >>EOO
          bar
          EOO

        $* clean 2>-
      }

      : error
      :
      {
        echo 'bar' >=bar
        echo 'baz' >=baz

        cat <<EOI >=buildfile
          foo: bar
          {{
            p = $path($>)
            rm -f $p

           for f <<<$path($<)
           {
              cat $f >>$p
              exit 'fed up'
           }
          }}
          EOI

        $* 2>>~%EOE% != 0
          cat file{bar} -> file{foo}
          buildfile:9:5: error: fed up
          %.{3}
          EOE

        $* clean 2>-
      }

      : depdb
      :
      {{
        : inside
        :
        {
          echo 'bar' >=bar

          cat <<EOI >=buildfile
            foo: bar
            {{
              for -w f <<<$path($<)
                depdb hash $f

              p = $path($>)
              rm -f $p

              echo $path($<) | for -w f
                cat $f >>$p
            }}
            EOI

          $* 2>>EOE != 0
            buildfile:4:5: error: 'depdb' call inside flow control construct
            EOE
        }

        : after-commands
        :
        {
          echo 'bar' >=bar

          cat <<EOI >=buildfile
            foo: bar
            {{
              for -w f <<<$path($<)
                echo $f >-

              depdb hash a
            }}
            EOI

          $* 2>>~%EOE% != 0
            buildfile:4:5: error: disallowed command in depdb preamble
              info: only variable assignments are allowed in depdb preamble
              buildfile:6:3: info: depdb preamble ends here
            %.{3}
            EOE

          $* clean 2>-
        }

        : after-vars
        :
        {
          echo 'bar' >=bar

          cat <<EOI >=buildfile
            foo: bar
            {{
              h =
              for -w f <<<$path($<)
                h += $f

              depdb hash $h

              diag gen ($>)

              p = $path($>)
              rm -f $p

              for f: $<
                cat $path($f) >>$p
            }}
            EOI

          $* 2>'gen file{foo}'
          $* clean 2>-
        }
      }}
    }}
  }}
}}

: syntax-version
:
{{
  +mkdir build

  +cat <<EOI >=build/bootstrap.build
    project = test
    amalgamation =
    subprojects =

    using config
    using test
    using version
    EOI

  : depends-build2-0.17.0
  :
  {
    cp -r ../build ./

    cat <<EOI >=manifest
      : 1
      name: test
      version: 1.0.0
      depends: * build2 >= 0.17.0
      depends: * bpkg >= 0.17.0
      EOI

    cat <<EOI >=buildfile
      foo:
      {{
        for i: 1
          echo $i >= $path($>)
        end
      }}
      EOI

    $* 2>'echo file{foo}'
    cat foo >'1'
    $* clean 2>-
  }

  : depends-build2-0.18.0
  :
  {
    cp -r ../build ./

    cat <<EOI >=manifest
      : 1
      name: test
      version: 1.0.0
      depends: * build2 >= 0.18.0-
      depends: * bpkg >= 0.18.0-
      EOI

    cat <<EOI >=buildfile
      foo:
      {{
        for i: 1
        {
          echo $i >= $path($>)
        }
      }}
      EOI

    $* 2>'echo file{foo}'
    cat foo >'1'
    $* clean 2>-
  }

  : variable
  :
  {
    cp -r ../build ./

    cat <<EOI >=manifest
      : 1
      name: test
      version: 1.0.0
      depends: * build2 >= 0.18.0-
      depends: * bpkg >= 0.18.0-
      EOI

    cat <<EOI >=buildfile
      buildscript.syntax = 1

      foo:
      {{
        for i: 1
          echo $i >= $path($>)
        end
      }}
      EOI

    $* 2>'echo file{foo}'
    cat foo >'1'
    $* clean 2>-
  }
}}

: syntax-1
:
{{
  +mkdir build
  +cat <<EOI >=build/bootstrap.build
    project = test
    amalgamation =
    subprojects =

    using config
    using test
    EOI

  +cat <<EOI >=build/root.build
    buildscript.syntax = 1
    EOI

  : update
  :
  {{
    : success
    :
    {
      echo 'bar' >=bar

      cat <<EOI >=buildfile
        foo: bar
        {{
          cp $path($<) $path($>)
        }}
        EOI

      $* 2>'cp file{bar} -> file{foo}'

      cat <<<foo >'bar'

      # While at it, make sure there is no rebuild.
      #
      $* 2>/'info: dir{./} is up to date'

      $* clean 2>-
    }

    : error
    :
    : Test that the target file is removed on error and is created on subsequent
    : successful update.
    :
    {
      echo 'bar' >=bar

      cat <<EOI >=buildfile
        foo: bar
        {{
          diag concat $<

          t = $path($>)
          p = $path($<)

          cp $p $t

          cat $(p).baz >>$t
        }}
        EOI

      $* 2>>~%EOE% != 0
        concat file{bar}
        %cat: unable to print '.+bar.baz': .+%
        buildfile:10:3: error: builtin cat exited with code 1
        %.+
        EOE

      test -f foo != 0

      echo 'baz' >=bar.baz

      $* 2>'concat file{bar}'

      cat <<<foo >>EOO
        bar
        baz
        EOO

      $* clean 2>-
    }

    : mutual-redirects
    :
    {
      echo 'bar' >=bar

      cat <<EOI >=buildfile
        foo: bar
        % [diag=cp]
        {{
          echo 'copying' 2>&1
          cp $path($<) $path($>)
        }}
        EOI

      $* 2>>~%EOE% != 0
        cp file{bar} -> file{foo}
        buildfile:4:3: error: stdout and stderr redirected to each other
        %.+
        EOE

      $* clean 2>-
    }

    : computed-var
    :
    {
      cat <<EOI >=buildfile
        a = a
        b = b
        foo:
        {{
          x = true
          echo "$($x ? a : b)" >$path($>)
        }}
        EOI

      $* 2>>EOE != 0
        buildfile:6:10: error: expansion of computed variable is only allowed in depdb preamble
          info: consider using 'depdb' builtin to track its value changes
        EOE
    }

    : untracked-var
    :
    {
      cat <<EOI >=buildfile
        a = a
        b = b
        foo:
        {{
          x = true
          y = $($x ? a : b)
          depdb env BOGUS
          echo $y >$path($>)
        }}
        EOI

      $* 2>>~%EOE% != 0
        buildfile:6:8: error: use of untracked variable 'a'
          info: use the 'depdb' builtin to manually track it
        %.+
        EOE

      $* clean 2>-
    }

    : export
    :
    if $posix
    {
      cat <<EOI >=bar
        #!/bin/sh
        echo "$message"
        EOI

      cat <<EOI >=buildfile
        exe{foo}: bar
        {{
          cp $path($<) $path($>)
        }}
        % test
        {{
          diag test $>
          export message=text1
          $> >>>?'text1'
          env message=text2 -- $> >>>?'text2'
        }}
        EOI

      $* test 2>>EOE
        cp file{bar} -> exe{foo}
        test exe{foo}
        EOE

      $* clean 2>-
    }

    : diag
    :
    {
      cat <<EOI >=buildfile
        foo:
        {{
          v1 = foo
          echo bar | set v2
          diag echo "$v1 $v2" -> $>
          echo "$v1 $v2" >$path($>)
        }}
        EOI

        $* 2>'echo foo bar -> file{foo}'
        cat <<<foo >'foo bar'

        $* clean 2>-
    }

    : depdb
    :
    {{
      : track-var-auto
      :
      {
        echo 'bar' >=bar

        cat <<EOI >=buildfile
          s = $process.run(cat bar)
          foo:
          {{
            echo "$s" >$path($>)
          }}
          EOI

        $* 2>'echo file{foo}'
        cat <<<foo >'bar'

        $* 2>/'info: dir{./} is up to date'

        echo 'baz' >=bar

        $* 2>'echo file{foo}'
        cat <<<foo >'baz'

        $* clean 2>-
      }

      : track-var-manual
      :
      {
        echo 'bar' >=bar
        echo 'baz' >=baz

        cat <<EOI >=buildfile
          a = $process.run(cat baz)
          foo: bar
          {{
            x = true
            y = $($x ? a : b)
            depdb hash "$a"

            diag compose $>

            cp $path($<) $path($>)

            echo $y >>$path($>)
          }}
          EOI

        $* 2>'compose file{foo}'

        cat <<<foo >>EOO
          bar
          baz
          EOO

        $* 2>/'info: dir{./} is up to date'

        # Make sure that on filesystems with a low file timestamps resolution
        # (for example HFS+) the file is considered as changed.
        #
        sleep 1

        echo 'BAR' >=bar

        $* 2>'compose file{foo}'

        cat <<<foo >>EOO
          BAR
          baz
          EOO

        $* 2>/'info: dir{./} is up to date'

        echo 'BAZ' >=baz

        $* 2>'compose file{foo}'

        cat <<<foo >>EOO
          BAR
          BAZ
          EOO

        $* 2>/'info: dir{./} is up to date'

        $* clean 2>-
      }

      : preamble
      :
      {{
        : valid
        :
        {
          echo 'bar' >=bar

          cat <<EOI >=buildfile
            s = $process.run(cat bar)
            foo:
            {{
              depdb clear

              s1 = 'abc'
              s2 = 'xyz'

              if echo "$s" >>>? 'bar'
                v = "$s1"
              else
                echo "$s2" | set v
              end

              depdb string "$v"

              echo "$v" >$path($>)
            }}
            EOI

          $* 2>'echo file{foo}'
          cat <<<foo >'abc'

          $* 2>/'info: dir{./} is up to date'

          echo 'baz' >=bar
          $* 2>'echo file{foo}'
          cat <<<foo >'xyz'

          $* clean 2>-
        }

        : invalid
        :
        {
          cat <<EOI >=buildfile
            foo:
            {{
              v = 'abc'
              echo "$v" >$path($>)
              depdb string "$v"
            }}
            EOI

          $* 2>>~%EOE% != 0
            buildfile:4:3: error: disallowed command in depdb preamble
              info: only variable assignments are allowed in depdb preamble
              buildfile:5:3: info: depdb preamble ends here
            %.+
            EOE

          $* clean 2>-
        }

        : temp-dir
        :
        {
          cat <<EOI >=buildfile
            foo:
            {{
              touch $~/f | set dummy

              if test -f $~/f
                v = "yes"
              else
                v = "no"
              end

              depdb string "$v"
              diag echo $>

              test -f $~/f
              echo "$v" >$path($>)
            }}
            EOI

          $* 2>'echo file{foo}'

          $* clean 2>-
        }
      }}

      : string
      :
      {
        echo 'bar' >=bar

        cat <<EOI >=buildfile
          s = $process.run(cat bar)
          foo:
          {{
            depdb clear
            depdb string "$s"
            echo "$s" >$path($>)
          }}
          EOI

        $* 2>'echo file{foo}'
        cat <<<foo >'bar'

        $* 2>/'info: dir{./} is up to date'

        echo 'baz' >=bar

        $* 2>'echo file{foo}'
        cat <<<foo >'baz'

        $* clean 2>-
      }

      : hash
      :
      {
        echo 'bar' >=bar

        cat <<EOI >=buildfile
          s = $process.run(cat bar)
          foo:
          {{
            depdb clear
            depdb hash "$s"
            echo "$s" >$path($>)
          }}
          EOI

        $* 2>'echo file{foo}'
        cat <<<foo >'bar'

        $* 2>/'info: dir{./} is up to date'

        echo 'baz' >=bar

        $* 2>'echo file{foo}'
        cat <<<foo >'baz'

        $* clean 2>-
      }

      : env
      :
      {{
        : invalid
        :
        {
          cat <<EOI >=buildfile
            foo:
            {{
              s = $getenv(FOO)

              depdb clear
              depdb env FOO=bar
              echo "$s" >$path($>)
            }}
            EOI

          $* 2>>/EOE != 0
            buildfile:6:3: error: invalid 'depdb env' argument: invalid variable name 'FOO=bar': contains '='
              info: while updating file{foo}
              info: while updating dir{./}
            info: failed to update dir{./}
            EOE

          $* clean 2>-
        }

        : valid
        :
        {
          cat <<EOI >=buildfile
            foo:
            {{
              s = $getenv(FOO)

              depdb clear
              depdb env FOO
              echo "$s" >$path($>)
            }}
            EOI

          export FOO=foo

          $* 2>'echo file{foo}'
          cat <<<foo >'foo'

          $* 2>/'info: dir{./} is up to date'

          export FOO=bar

          $* 2>'echo file{foo}'
          cat <<<foo >'bar'

          export -u FOO

          $* 2>'echo file{foo}'
          cat <<<foo >''

          $* clean 2>-
        }
      }}

      : dyndep
      :
      {{
        : normal
        :
        {
          cat <<EOI >=bar.h
            bar
            EOI

          cat <<EOI >=buildfile
            define h: file
            h{*}: extension = h

            ./: h{foo baz}

            h{foo}:
            {{
              # Note that strictly speaking we should return $out_base/baz.h
              # on the second invocation (since it now exists). But our dyndep
              # machinery skips the entry which it has already seen, so this
              # works for now.
              #
              depdb dyndep "-I$out_base" --what=header --default-type=h -- \
                echo "$out_base/foo.h: $src_base/bar.h baz.h"

              diag gen $>

              cat $path($<) >$path($>)
            }}

            h{baz}:
            {{
              diag gen $>
              echo baz >$path($>)
            }}
            EOI

          $* 2>>EOE
            gen h{baz}
            gen h{foo}
            EOE

          cat foo.h >>EOO
            bar
            baz
            EOO

          $* clean 2>-
        }

        : byproduct
        :
        {
          cat <<EOI >=bar.h
            bar
            EOI

          cat <<EOI >=buildfile
            define h: file
            h{*}: extension = h

            h{foo}: h{baz}
            {{
              o = $path($>)
              t = $path($>).t

              depdb dyndep --byproduct --what=header --default-type=h --file $t

              diag gen $>
              cat $src_base/bar.h $out_base/baz.h >$o
              echo "$out_base/foo.h: $src_base/bar.h $out_base/baz.h" >$t
            }}

            h{baz}:
            {{
              diag gen $>
              echo baz >$path($>)
            }}
            EOI

          $* 2>>EOE
            gen h{baz}
            gen h{foo}
            EOE

          cat foo.h >>EOO
            bar
            baz
            EOO

          $* clean 2>-
        }
      }}
    }}
  }}

  : clean
  :
  {
    echo 'bar' >=bar

    cat <<EOI >=buildfile
      foo: bar
      {{
        cp $path($<) $path($>)
      }}
      % [diag=clean] clean
      {{
        t = $path($>)
        rm $t $(t).d
      }}
      EOI

    $* 2>-

    # Rely on the cleanup machinery to verify that the build output files are
    # removed.
    #
    $* clean 2>'clean file{foo}'
  }

  : test
  :
  {{
    : success
    :
    {
      echo 'bar' >=bar

      cat <<EOI >=buildfile
        foo: bar
        {{
          cp $path($<) $path($>)
        }}
        % [diag=test] test
        {{
          cat <$path($>) >?$path($<)
        }}
        EOI

      $* test 2>>EOE
        cp file{bar} -> file{foo}
        test file{foo}
        EOE

      $* clean 2>-
    }

    : depdb
    :
    {
      echo 'bar' >=bar

      cat <<EOI >=buildfile
        foo: bar
        {{
          cp $path($<) $path($>)
        }}
        % [diag=test] test
        {{
          depdb clear
          cat <$path($>) >?$path($<)
        }}
        EOI

      $* test 2>>EOE != 0
        buildfile:7:3: error: 'depdb' builtin cannot be used to perform test
        EOE
    }

    : runner
    :
    if $posix
    {
      echo 'bar' >=bar

      cat <<EOI >=run
        #!/bin/sh
        if test "$1" = "--trace"; then
          shift
          echo "$*"
        fi
        "$@"
        EOI

      chmod u+x run

      cat <<EOI >=buildfile
        foo: bar
        {{
          cp $path($<) $path($>)
        }}
        % [diag=test] test
        {{
          if ($test.runner.path != [null])
            $test.runner.path $test.runner.options cat <$path($>)
          else
            cat <$path($>)
          end
        }}
        EOI

      $* test 2>>EOE
        cp file{bar} -> file{foo}
        test file{foo}
        bar
        EOE

      $* test config.test.runner="./run --trace" 2>>EOE
        test file{foo}
        cat
        bar
        EOE

      $* clean 2>-
    }
  }}

  : diff-label
  :
  {
    echo 'bar' >=bar

    cat <<EOI >=buildfile
      foo: bar
      {{
        echo 'baz' >? $path($<)
      }}
      EOI

    $* 2>>/~%EOE% != 0
      %.+
      %--- .+/bar%
      +++ stdout
      %.+
      EOE

    $* clean 2>-
  }

  : canned-cmdline
  :
  {
    cat <<EOI >=buildfile
      ./:
      {{
        x = echo >|
        y = [cmdline] echo >|
        diag update $>
        $x foo
        $y bar
        ([cmdline] $x) baz
      }}
      EOI

    $* >> EOO 2>>/EOE
      bar
      baz
      EOO
      update dir{./}
      >| foo
      EOE
  }

  : timeout
  :
  if $posix
  {{
    : update
    :
    {{
      : expired
      :
      {
        echo 'bar' >=bar

        cat <<EOI >=buildfile
          foo: bar
          % [diag=update]
          {{
            cp $path($<) $path($>)
            timeout 1
            ^sleep 5
          }}
          EOI

        $* 2>>~%EOE% != 0
          update file{bar} -> file{foo}
          buildfile:6:3: error: process ^sleep terminated: execution timeout expired
            info: command line: sleep 5
            info: while updating file{foo}
          %.+
          EOE

        $* clean 2>-
      }

      : successful-timeout
      :
      {
        echo 'bar' >=bar

        cat <<EOI >=buildfile
          foo: bar
          % [diag=update]
          {{
            cp $path($<) $path($>)
            timeout --success 1
            ^sleep 5
          }}
          EOI

        $* 2>>EOE
          update file{bar} -> file{foo}
          EOE

        $* clean 2>-
      }
    }}

    : test
    :
    {{
      : expired
      :
      {
        echo 'bar' >=bar

        cat <<EOI >=buildfile
          foo: bar
          {{
            cp $path($<) $path($>)
          }}
          % [diag=test] test
          {{
            ^sleep 5
          }}
          EOI

        $* test config.test.timeout=1 2>>~%EOE% != 0
          cp file{bar} -> file{foo}
          test file{foo}
          buildfile:7:3: error: process ^sleep terminated: execution timeout expired
            info: command line: sleep 5
            info: while testing file{foo}
          %.+
          EOE

        $* test config.test.timeout=/1 2>>~%EOE% != 0
          test file{foo}
          buildfile:7:3: error: process ^sleep terminated: execution timeout expired
            info: command line: sleep 5
            info: while testing file{foo}
          %.+
          EOE

        $* clean 2>-
      }

      : not-expired
      :
      {
        echo 'bar' >=bar

        cat <<EOI >=buildfile
          foo: bar
          % [diag=cp]
          {{
            ^sleep 4
            cp $path($<) $path($>)
          }}
          % [diag=test] test
          {{
            ^sleep 1
          }}
          EOI

        $* test config.test.timeout=3 2>>EOE
          cp file{bar} -> file{foo}
          test file{foo}
          EOE

        $* clean 2>-
      }
    }}
  }}

  # @@ TODO: test $1 when implemented.
  #
  : rule
  :
  {
    cat <<EOI >=buildfile
      alias{far}: alias{bar}
      alias{bar}:

      alias{~'/f(.+)/'}: alias{~'/b\1/'}
      {{
        diag frob $< -> $>
      }}
      EOI

    $* 2>>EOE
      frob alias{bar} -> alias{far}
      EOE
  }

  : loop
  :
  {{
    : while
    :
    {{
      : basics
      :
      {
        echo 'bar' >=bar

        cat <<EOI >=buildfile
          foo: bar
          {{
            p = $path($>)
            while test -f $p != 0
              cp $path($<) $p
            end
          }}
          EOI

        $* 2>'cp file{bar} -> file{foo}'

        cat <<<foo >'bar'

        $* clean 2>-
      }

      : exit
      :
      {
        echo 'bar' >=bar

        cat <<EOI >=buildfile
          foo: bar
          {{
            diag gen ($>)

            p = $path($>)
            while test -f $p != 0
              touch $p
              exit
              cp $path($<) $p
            end
          }}
          EOI

        $* 2>'gen file{foo}'

        cat <<<foo >:''

        $* clean 2>-
      }

      : error
      :
      {
        echo 'bar' >=bar

        cat <<EOI >=buildfile
          foo: bar
          {{
            diag gen ($>)

            p = $path($>)
            while test -f $p != 0
              touch $p
              exit 'fed up'
              cp $path($<) $p
            end
          }}
          EOI

        $* 2>>~%EOE% != 0
          gen file{foo}
          buildfile:8:5: error: fed up
          %.{3}
          EOE

        $* clean 2>-
      }

      : depdb
      :
      {{
        : inside
        :
        {
          echo 'bar' >=bar

          cat <<EOI >=buildfile
            foo: bar
            {{
              p = $path($>)
              while test -f $p != 0
                depdb hash $p
                cp $path($<) $p
              end
            }}
            EOI

          $* 2>>EOE != 0
            buildfile:5:5: error: 'depdb' call inside flow control construct
            EOE
        }

        : after-commands
        :
        {
          echo 'bar' >=bar

          cat <<EOI >=buildfile
            foo: bar
            {{
              p = $path($>)
              while test -f $p != 0
                cp $path($<) $p
              end

              depdb hash $p
            }}
            EOI

          $* 2>>~%EOE% != 0
            buildfile:5:5: error: disallowed command in depdb preamble
              info: only variable assignments are allowed in depdb preamble
              buildfile:8:3: info: depdb preamble ends here
            %.{3}
            EOE

          $* clean 2>-
        }

        : after-vars
        :
        {
          echo 'bar' >=bar

          cat <<EOI >=buildfile
            foo: bar
            {{
              p = $path($<)

              h =
              while test -f $p != 0
                h += $p
              end

              depdb hash $p

              cat $p >$path($>)
            }}
            EOI

          $* 2>'cat file{bar} -> file{foo}'
          $* clean 2>-
        }
      }}
    }}

    : for
    :
    {{
      : form-1
      :
      : for x: ...
      :
      {{
        : basics
        :
        {
          echo 'bar' >=bar
          echo 'baz' >=baz

          cat <<EOI >=buildfile
            foo: bar baz
            {{
              p = $path($>)
              rm -f $p

              for f: $<
                cat $path($f) >>$p
              end
            }}
            EOI

          $* 2>'cat file{bar} -> file{foo}'

          cat <<<foo >>EOO
            bar
            baz
            EOO

          $* clean 2>-
        }

        : pair
        :
        {
          mkdir -p src/build
          echo 'bar' >=src/bar
          echo 'baz' >=src/baz

          echo 'project =' >=src/build/bootstrap.build
          echo 'buildscript.syntax = 1' >=src/build/root.build

          cat <<EOI >=src/buildfile
            foo: file{bar}@./ file{baz}@./
            {{
              p = $path($>)
              rm -f $p

              for f: $<
                cat $path($f) >>$p
              end
            }}
            EOI

          $* src/@out/ 2>>/EOE
            mkdir fsdir{out/}
            cat src/file{bar} -> out/file{foo}
            EOE

          cat <<<out/foo >>EOO
            bar
            baz
            EOO

          $* 'clean:' src/@out/ 2>-
        }

        : special-var
        :
        {
          echo 'bar' >=bar
          echo 'baz' >=baz

          cat <<EOI >=buildfile
            foo: bar
            {{
              p = $path($>)
              rm -f $p

              for ~: $<
                cat $path($f) >>$p
              end
            }}
            EOI

          $* 2>>EOE != 0
            buildfile:6:7: error: attempt to set '~' special variable
            EOE
        }

        : exit
        :
        {
          echo 'bar' >=bar
          echo 'baz' >=baz

          cat <<EOI >=buildfile
            foo: bar
            {{
              p = $path($>)
              rm -f $p

              for f: $<
                cat $path($f) >>$p
                exit
              end
            }}
            EOI

          $* 2>'cat file{bar} -> file{foo}'

          cat <<<foo >>EOO
            bar
            EOO

          $* clean 2>-
        }

        : error
        :
        {
          echo 'bar' >=bar
          echo 'baz' >=baz

          cat <<EOI >=buildfile
            foo: bar
            {{
              p = $path($>)
              rm -f $p

              for f: $<
                cat $path($f) >>$p
                exit 'fed up'
              end
            }}
            EOI

          $* 2>>~%EOE% != 0
            cat file{bar} -> file{foo}
            buildfile:8:5: error: fed up
            %.{3}
            EOE

          $* clean 2>-
        }

        : depdb
        :
        {{
          : inside
          :
          {
            echo 'bar' >=bar

            cat <<EOI >=buildfile
              foo: bar
              {{
                for f: $<
                  depdb hash $f
                end

                p = $path($>)
                rm -f $p

                for f: $<
                  cat $path($f) >>$p
                end
              }}
              EOI

            $* 2>>EOE != 0
              buildfile:4:5: error: 'depdb' call inside flow control construct
              EOE
          }

          : after-commands
          :
          {
            echo 'bar' >=bar

            cat <<EOI >=buildfile
              foo: bar
              {{
                for f: $<
                  echo $path($f) >-
                end

                depdb hash a
              }}
              EOI

            $* 2>>~%EOE% != 0
              buildfile:4:5: error: disallowed command in depdb preamble
                info: only variable assignments are allowed in depdb preamble
                buildfile:7:3: info: depdb preamble ends here
              %.{3}
              EOE

            $* clean 2>-
          }

          : after-vars
          :
          {
            echo 'bar' >=bar

            cat <<EOI >=buildfile
              foo: bar
              {{
                h =
                for f: $<
                  h += $path($f)
                end

                depdb hash $h

                p = $path($>)
                rm -f $p

                for f: $<
                  cat $path($f) >>$p
                end
              }}
              EOI

            $* 2>'cat file{bar} -> file{foo}'
            $* clean 2>-
          }
        }}
      }}

      : form-2
      :
      : ... | for x
      :
      {{
        : basics
        :
        {
          echo 'bar' >=bar
          echo 'baz' >=baz

          cat <<EOI >=buildfile
            foo: bar baz
            {{
              diag gen ($>)

              p = $path($>)
              rm -f $p

              echo $path($<) | for -w f
                cat $f >>$p
              end
            }}
            EOI

          $* 2>'gen file{foo}'

          cat <<<foo >>EOO
            bar
            baz
            EOO

          $* clean 2>-
        }

        : special-var
        :
        {
          echo 'bar' >=bar
          echo 'baz' >=baz

          cat <<EOI >=buildfile
            foo: bar
            {{
              diag gen ($>)

              p = $path($>)
              rm -f $p

              echo $path($<) | for ~
                cat $f >>$p
              end
            }}
            EOI

          $* 2>>~%EOE% != 0
            gen file{foo}
            buildfile:8:3: error: attempt to set '~' special variable
            %.{3}
            EOE

          $* clean 2>-
        }

        : misuse
        :
        {{
          : after-var
          {
            echo 'bar' >=bar
            echo 'baz' >=baz

            cat <<EOI >=buildfile
              foo: bar
              {{
                diag gen ($>)

                p = $path($>)
                rm -f $p

                echo $path($<) | for x:
                  cat $f >>$p
                end
              }}
              EOI

            $* 2>>~%EOE% != 0
              gen file{foo}
              buildfile:8:3: error: for: ':' after variable name
              %.+
              EOE

            $* clean 2>-
          }

          : after-attrs
          {
            echo 'bar' >=bar
            echo 'baz' >=baz

            cat <<EOI >=buildfile
              foo: bar
              {{
                diag gen ($>)

                p = $path($>)
                rm -f $p

                echo $path($<) | for x [path]:
                  cat $f >>$p
                end
              }}
              EOI

            $* 2>>~%EOE% != 0
              gen file{foo}
              <attributes>:1:7: error: whitespace required after attributes
                <attributes>:1:1: info: use the '\[' escape sequence if this is a wildcard pattern
                buildfile:8:3: info: while parsing attributes '[path]:'
              %.+
              EOE

            $* clean 2>-
          }
        }}

        : exit
        :
        {
          echo 'bar' >=bar
          echo 'baz' >=baz

          cat <<EOI >=buildfile
            foo: bar
            {{
              diag gen ($>)

              p = $path($>)
              rm -f $p

              echo $path($<) | for -w f
                cat $f >>$p
                exit
              end
            }}
            EOI

          $* 2>'gen file{foo}'

          cat <<<foo >>EOO
            bar
            EOO

          $* clean 2>-
        }

        : error
        :
        {
          echo 'bar' >=bar
          echo 'baz' >=baz

          cat <<EOI >=buildfile
            foo: bar
            {{
              diag gen ($>)

              p = $path($>)
              rm -f $p

              echo $path($<) | for -w f
                cat $f >>$p
                exit 'fed up'
              end
            }}
            EOI

          $* 2>>~%EOE% != 0
            gen file{foo}
            buildfile:10:5: error: fed up
            %.{3}
            EOE

          $* clean 2>-
        }

        : depdb
        :
        {{
          : inside
          :
          {
            echo 'bar' >=bar

            cat <<EOI >=buildfile
              foo: bar
              {{
                echo $path($<) | for -w f
                  depdb hash $f
                end

                p = $path($>)
                rm -f $p

                echo $path($<) | for -w f
                  cat $f >>$p
                end
              }}
              EOI

            $* 2>>EOE != 0
              buildfile:4:5: error: 'depdb' call inside flow control construct
              EOE
          }

          : after-commands
          :
          {
            echo 'bar' >=bar

            cat <<EOI >=buildfile
              foo: bar
              {{
                echo $path($<) | for -w f
                  echo $f >-
                end

                depdb hash $p
              }}
              EOI

            $* 2>>~%EOE% != 0
              buildfile:4:5: error: disallowed command in depdb preamble
                info: only variable assignments are allowed in depdb preamble
                buildfile:7:3: info: depdb preamble ends here
              %.{3}
              EOE

            $* clean 2>-
          }

          : after-vars
          :
          {
            echo 'bar' >=bar

            cat <<EOI >=buildfile
              foo: bar
              {{
                h =
                echo $path($<) | for -w f
                  h += $f
                end

                depdb hash $h

                diag gen ($>)

                p = $path($>)
                rm -f $p

                for f: $<
                  cat $path($f) >>$p
                end
              }}
              EOI

            $* 2>'gen file{foo}'
            $* clean 2>-
          }
        }}
      }}

      : form-3
      :
      : for x <...
      :
      {{
        : basics
        :
        {
          echo 'bar' >=bar
          echo 'baz' >=baz

          cat <<EOI >=buildfile
            foo: bar baz
            {{
              diag gen ($>)

              p = $path($>)
              rm -f $p

              for -w f <<"EOF"
              $path($<)
              EOF
                cat $f >>$p
              end

              for <<"EOF" -w f
              $path($<)
              EOF
                cat $f >>$p
              end
            }}
            EOI

          $* 2>'gen file{foo}'

          cat <<<foo >>EOO
            bar
            baz
            bar
            baz
            EOO

          $* clean 2>-
        }

        : quoting
        :
        {
          echo 'bar' >=bar
          echo 'baz' >=baz

          cat <<EOI >=buildfile
            foo: bar baz
            {{
              n = 'gen'
              diag "($n)" ($>)

              p = $path($>)
              rm -f $p

              o = -w
              for "$o" f <<"EOF"
              $path($<)
              EOF
                cat $f >>$p
              end

              o = -n
              for "($o)" f <<"EOF"
              $path($<)
              EOF
                echo $f >>$p
              end
            }}
            EOI

          $* 2>'gen file{foo}'

          cat <<<foo >>~%EOO%
            bar
            baz
            %.+bar .+baz%
            EOO

          $* clean 2>-
        }

        : special-var
        :
        {
          echo 'bar' >=bar
          echo 'baz' >=baz

          cat <<EOI >=buildfile
            foo: bar
            {{
              p = $path($>)
              rm -f $p

             for ~ <<<$path($<)
                cat $f >>$p
              end
            }}
            EOI

          $* 2>>EOE != 0
            buildfile:6:6: error: attempt to set '~' special variable
            EOE
        }

        : exit
        :
        {
          echo 'bar' >=bar
          echo 'baz' >=baz

          cat <<EOI >=buildfile
            foo: bar
            {{
              p = $path($>)
              rm -f $p

             for f <<<$path($<)
                cat $f >>$p
                exit
              end
            }}
            EOI

          $* 2>'cat file{bar} -> file{foo}'

          cat <<<foo >>EOO
            bar
            EOO

          $* clean 2>-
        }

        : error
        :
        {
          echo 'bar' >=bar
          echo 'baz' >=baz

          cat <<EOI >=buildfile
            foo: bar
            {{
              p = $path($>)
              rm -f $p

             for f <<<$path($<)
                cat $f >>$p
                exit 'fed up'
              end
            }}
            EOI

          $* 2>>~%EOE% != 0
            cat file{bar} -> file{foo}
            buildfile:8:5: error: fed up
            %.{3}
            EOE

          $* clean 2>-
        }

        : depdb
        :
        {{
          : inside
          :
          {
            echo 'bar' >=bar

            cat <<EOI >=buildfile
              foo: bar
              {{
                for -w f <<<$path($<)
                  depdb hash $f
                end

                p = $path($>)
                rm -f $p

                echo $path($<) | for -w f
                  cat $f >>$p
                end
              }}
              EOI

            $* 2>>EOE != 0
              buildfile:4:5: error: 'depdb' call inside flow control construct
              EOE
          }

          : after-commands
          :
          {
            echo 'bar' >=bar

            cat <<EOI >=buildfile
              foo: bar
              {{
                for -w f <<<$path($<)
                  echo $f >-
                end

                depdb hash a
              }}
              EOI

            $* 2>>~%EOE% != 0
              buildfile:4:5: error: disallowed command in depdb preamble
                info: only variable assignments are allowed in depdb preamble
                buildfile:7:3: info: depdb preamble ends here
              %.{3}
              EOE

            $* clean 2>-
          }

          : after-vars
          :
          {
            echo 'bar' >=bar

            cat <<EOI >=buildfile
              foo: bar
              {{
                h =
                for -w f <<<$path($<)
                  h += $f
                end

                depdb hash $h

                diag gen ($>)

                p = $path($>)
                rm -f $p

                for f: $<
                  cat $path($f) >>$p
                end
              }}
              EOI

            $* 2>'gen file{foo}'
            $* clean 2>-
          }
        }}
      }}
    }}
  }}
}}
