// file      : tests/build/parser/driver.cxx -*- C++ -*-
// license   : MIT; see accompanying LICENSE file

#include <sstream>
#include <iostream>

#include <build/types>
#include <build/scope>
#include <build/target>
#include <build/context>
#include <build/variable>

#include <build/lexer>
#include <build/parser>

#undef NDEBUG
#include <cassert>

using namespace std;
using namespace build;

static bool
parse (const char*);

static names
parse_names (const char* s, lexer_mode m, bool chunk);

static names
chunk_names (const char* s)
{
  return parse_names (s, lexer_mode::pairs, true);
}

int
main ()
{
  ostream cnull (nullptr);
  diag_stream = &cnull;

  reset ();

  global_scope->assign ("foo") = "FOO";
  global_scope->assign ("bar") = "BAR";

  // names() in chunking mode.
  //
  assert (chunk_names ("{}") == names ({name ()}));
  assert (chunk_names ("foo") == names ({name ("foo")}));
  assert (chunk_names ("foo bar") == names ({name ("foo")}));
  assert (chunk_names ("{foo bar}") == names ({name ("foo"), name ("bar")}));
  assert (chunk_names ("dir{foo bar}") == names ({name ("dir", "foo"),
                                                  name ("dir", "bar")}));
  assert (chunk_names ("dir{foo bar} baz") == names ({name ("dir", "foo"),
                                                      name ("dir", "bar")}));
  assert (chunk_names ("dir {foo bar}") == names ({name ("dir", "foo"),
                                                   name ("dir", "bar")}));
  assert (chunk_names ("dir {foo bar} baz") == names ({name ("dir", "foo"),
                                                       name ("dir", "bar")}));
  assert (chunk_names ("{} foo") == names ({name ()}));

  // Expansion.
  //
  assert (chunk_names ("$foo $bar baz") == names ({name ("FOO")}));
  assert (chunk_names ("$foo$bar baz") == names ({name ("FOOBAR")}));

  assert (chunk_names ("foo(bar)") == names ({name ("foobar")}));
  assert (chunk_names ("foo (bar)") == names ({name ("foo")}));

  assert (chunk_names ("\"$foo\"(bar)") == names ({name ("FOObar")}));
  assert (chunk_names ("\"$foo\" (bar)") == names ({name ("FOO")}));

  // Quoting.
  //
  assert (chunk_names ("\"$foo $bar\" baz") == names ({name ("FOO BAR")}));

  // Pairs.
  //
  assert (chunk_names ("foo=bar") == names ({name ("foo"), name ("bar")}));
  assert (chunk_names ("foo = bar x") == names ({name ("foo"), name ("bar")}));

  // General.
  //
  assert (parse (""));
  assert (parse ("foo:"));
  assert (parse ("foo bar:"));
  assert (parse ("foo:\nbar:"));
  assert (parse ("foo: bar"));
  assert (parse ("foo: bar baz"));
  assert (parse ("foo bar: baz biz"));

  assert (parse ("{foo}:"));
  assert (parse ("{foo bar}:"));
  assert (parse ("{{foo bar}}:"));
  assert (parse ("{{foo bar} {baz} {biz fox} fix}:"));

  assert (parse ("file{foo}:"));
  assert (parse ("file{foo bar}:"));
  assert (parse ("{file{foo bar}}:"));
  assert (parse ("file{{foo bar} fox}:"));
  assert (parse ("file{foo}: file{bar baz} biz.o file{fox}"));

  //assert (!parse (":"));
  assert (!parse ("foo"));
  assert (!parse ("{"));
  assert (!parse ("{foo:"));
  assert (!parse ("{foo{:"));
  assert (!parse ("foo: bar:"));
  assert (!parse ("file{foo:"));

  // Directory prefix.
  //
  assert (parse ("../{foo}: ../{bar}"));
  assert (parse ("../file{foo}: ../file{bar}"));
  assert (!parse ("../file{file{foo}}:"));

  // Directory scope.
  //
  assert (parse ("test/:\n{\n}"));
  assert (parse ("test/:\n{\n}\n"));
  assert (parse ("test/:\n{\nfoo:bar\n}"));
  assert (parse ("test/:\n{\nfoo:bar\n}"));
  assert (parse ("test/:\n{\nmore/:\n{\n}\n}"));
  assert (parse ("test/:\n{\nmore/:\n{\nfoo:{bar baz}\n}\n}"));

  assert (!parse ("test/:\n{"));
  assert (!parse ("test/:\n{\n"));
  assert (!parse ("test/:\n{\n:"));
  assert (!parse ("test/:\n{\n} foo: bar\n"));
  assert (!parse ("test/ foo:\n{\n}"));
  assert (!parse ("test foo/:\n{\n}"));
  assert (!parse ("test/ foo/:\n{\n}"));
}

struct test_parser: parser
{
  names_type
  test_names (const char*, lexer_mode, bool chunk);
};

static bool
parse (const char* s)
{
  reset (); // Clear the state.

  // Create a minimal root scope.
  //
  auto i (scopes.insert (path::current_directory (), nullptr, true, true));
  scope& root (*i->second);
  root.src_path_ = root.out_path_ = &i->first;

  istringstream is (s);

  is.exceptions (istream::failbit | istream::badbit);
  parser p;

  try
  {
    p.parse_buildfile (is, path (), root, root);
  }
  catch (const failed&)
  {
    return false;
  }

  return true;
}

// parser::names()
//
names test_parser::
test_names (const char* s, lexer_mode m, bool chunk)
{
  istringstream is (s);
  is.exceptions (istream::failbit | istream::badbit);
  lexer l (is, "");

  if (m != lexer_mode::normal)
    l.mode (m, '=');

  path_ = &l.name ();
  lexer_ = &l;
  target_ = nullptr;
  scope_ = root_ = global_scope;

  token t (token_type::eos, false, 0, 0);
  token_type tt;
  next (t, tt);
  return names (t, tt, chunk);
}

static names
parse_names (const char* s, lexer_mode m, bool chunk)
{
  test_parser p;
  return p.test_names (s, m, chunk);
}