From 4f9022f24c4591391637121c7274d9855b37bd93 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 10 May 2012 11:09:13 +0200 Subject: Add support for options file inclusion New include-path prefixes, c++: and cli:, are now recognized (e.g., include ;). Without a prefix, the include declarations is considered to be c++-include unless the path ends with the .cli extension. The cli-included files are loaded and parsed. Currently, only inclusion relative to the current file is supported. Duplicate inclusions are detected and ignored based on the absolute filesystem path. If a file cli-includes another file, then the runtime code is assumed to come from the included file and is not generated. --- cli/generator.cxx | 31 +++++++-- cli/header.cxx | 28 +++++++- cli/lexer.cxx | 27 +++++++- cli/parser.cxx | 161 +++++++++++++++++++++++++++++++++++++--------- cli/parser.hxx | 7 +- cli/semantics/unit.hxx | 29 ++++++--- cli/token.hxx | 3 +- doc/language.txt | 11 +++- tests/lexer/driver.cxx | 9 ++- tests/lexer/test-004.cli | 3 + tests/lexer/test-004.std | 12 +++- tests/parser/base.cli | 0 tests/parser/common.cli | 1 + tests/parser/test-001.cli | 2 + 14 files changed, 268 insertions(+), 56 deletions(-) create mode 100644 tests/parser/base.cli create mode 100644 tests/parser/common.cli diff --git a/cli/generator.cxx b/cli/generator.cxx index 842fc54..8b4b244 100644 --- a/cli/generator.cxx +++ b/cli/generator.cxx @@ -158,6 +158,19 @@ generate (options const& ops, semantics::cli_unit& unit, path const& p) process_names (ctx); } + // Check if we need to generate the runtime code. If we include + // another options file, then we assume the runtime is generated + // there. + // + bool runtime (true); + for (semantics::cli_unit::includes_iterator i (unit.includes_begin ()); + runtime && i != unit.includes_end (); + ++i) + { + if (i->is_a ()) + runtime = false; + } + // // ofstream hxx (hxx_path.string ().c_str ()); @@ -237,7 +250,9 @@ generate (options const& ops, semantics::cli_unit& unit, path const& p) << "#define " << guard << endl << endl; - generate_runtime_header (ctx); + if (runtime) + generate_runtime_header (ctx); + generate_header (ctx); if (inl) @@ -256,7 +271,10 @@ generate (options const& ops, semantics::cli_unit& unit, path const& p) { cxx_filter filt (ixx); context ctx (ixx, unit, ops); - generate_runtime_inline (ctx); + + if (runtime) + generate_runtime_inline (ctx); + generate_inline (ctx); } @@ -270,10 +288,13 @@ generate (options const& ops, semantics::cli_unit& unit, path const& p) (br ? '>' : '"') << endl << endl; - if (!inl) - generate_runtime_inline (ctx); + if (runtime) + { + if (!inl) + generate_runtime_inline (ctx); - generate_runtime_source (ctx); + generate_runtime_source (ctx); + } if (!inl) generate_inline (ctx); diff --git a/cli/header.cxx b/cli/header.cxx index 3a47c4d..fb714ed 100644 --- a/cli/header.cxx +++ b/cli/header.cxx @@ -185,14 +185,38 @@ namespace // // - struct includes: traversal::cxx_includes, context + struct includes: traversal::cxx_includes, + traversal::cli_includes, + context { includes (context& c) : context (c) {} virtual void traverse (semantics::cxx_includes& i) { - os << "#include " << i.file () << endl + generate (i.kind (), i.file ().string ()); + } + + virtual void + traverse (semantics::cli_includes& i) + { + generate (i.kind (), + i.file ().base ().string () + options.hxx_suffix ()); + } + + void + generate (semantics::includes::kind_type k, string const& f) + { + char b, e; + if (k == semantics::includes::quote) + b = e = '"'; + else + { + b = '<'; + e = '>'; + } + + os << "#include " << b << f << e << endl << endl; } }; diff --git a/cli/lexer.cxx b/cli/lexer.cxx index 3edfc13..baa8423 100644 --- a/cli/lexer.cxx +++ b/cli/lexer.cxx @@ -495,7 +495,32 @@ path_literal (xchar c) break; } - return token (token::t_path_lit, lexeme, ln, cl); + token::token_type tt; + + if (lexeme.compare (1, 4, "c++:") == 0) + { + tt = token::t_cxx_path_lit; + lexeme = lexeme[0] + string (lexeme, 5, string::npos); + } + else if (lexeme.compare (1, 4, "cli:") == 0) + { + tt = token::t_cli_path_lit; + lexeme = lexeme[0] + string (lexeme, 5, string::npos); + } + else + { + // See if the path ends with .cli. If not, then we assume this is + // a C++ inclusion. + // + size_t n (lexeme.size ()); + + if (n > 5 && lexeme.compare (n - 5, 4, ".cli") == 0) + tt = token::t_cli_path_lit; + else + tt = token::t_cxx_path_lit; + } + + return token (tt, lexeme, ln, cl); } token lexer:: diff --git a/cli/parser.cxx b/cli/parser.cxx index 6480946..9cefdbc 100644 --- a/cli/parser.cxx +++ b/cli/parser.cxx @@ -3,6 +3,7 @@ // copyright : Copyright (c) 2009-2011 Code Synthesis Tools CC // license : MIT; see accompanying LICENSE file +#include #include #include "token.hxx" @@ -60,9 +61,14 @@ operator<< (std::ostream& os, token const& t) os << "'" << punctuation[t.punctuation ()] << "'"; break; } - case token::t_path_lit: + case token::t_cxx_path_lit: { - os << "path literal"; + os << "c++ path literal"; + break; + } + case token::t_cli_path_lit: + { + os << "cli path literal"; break; } case token::t_string_lit: @@ -105,6 +111,29 @@ operator<< (std::ostream& os, token const& t) return os; } +// RAII-style set new value on construction, restore old one on destruction. +// +template +struct auto_restore +{ + auto_restore (T*& var, T* new_val = 0) + : var_ (var), old_val_ (var_) + { + if (new_val != 0) + var_ = new_val; + } + + void + set (T* new_val) {var_ = new_val;} + + ~auto_restore () {var_ = old_val_;} + +private: + T*& var_; + T* old_val_; +}; + + void parser:: recover (token& t) { @@ -126,8 +155,16 @@ recover (token& t) auto_ptr parser:: parse (std::istream& is, path const& p) { - auto_ptr unit (new cli_unit (p)); - unit_ = unit.get (); + auto_ptr unit (new cli_unit (p, 1, 1)); + + { + path ap (p); + ap.absolute (); + ap.normalize (); + include_map_[ap] = unit.get (); + } + + root_ = cur_ = unit.get (); lexer l (is, p.string ()); lexer_ = &l; @@ -164,8 +201,7 @@ def_unit () } } - scope* old (scope_); - scope_ = unit_; + auto_restore new_scope (scope_, cur_); // decl-seq // @@ -190,26 +226,97 @@ def_unit () break; // Non-recoverable error. } } - - scope_ = old; } void parser:: include_decl () { token t (lexer_->next ()); + token::token_type tt (t.type ()); - if (t.type () != token::t_path_lit) + if (tt != token::t_cxx_path_lit && tt != token::t_cli_path_lit) { cerr << *path_ << ':' << t.line () << ':' << t.column () << ": error: " << "expected path literal instead of " << t << endl; throw error (); } + string const& l (t.literal ()); + includes::kind_type ik (l[0] == '<' ? includes::bracket : includes::quote); + + path f; + try + { + f = path (string (l, 1, l.size () - 2)); + } + catch (const invalid_path& e) + { + cerr << *path_ << ':' << t.line () << ':' << t.column () << ": error: " + << "'" << e.path () << "' is not a valid filesystem path" << endl; + valid_ = false; + } + if (valid_) { - cxx_unit& n (unit_->new_node (*path_, t.line (), t.column ())); - unit_->new_edge (*unit_, n, t.literal ()); + if (tt == token::t_cxx_path_lit) + { + cxx_unit& n ( + root_->new_node (*path_, t.line (), t.column ())); + root_->new_edge (*cur_, n, ik, f); + } + else + { + // For now we only support inclusion relative to the current file. + // + path p (path_->directory () / f); + p.normalize (); + + // Detect and ignore multiple inclusions. + // + path ap (p); + ap.absolute (); + ap.normalize (); + + include_map::iterator it (include_map_.find (ap)); + if (it == include_map_.end ()) + { + cli_unit& n (root_->new_node (p, 1, 1)); + root_->new_edge (*cur_, n, ik, f); + include_map_[ap] = &n; + + auto_restore new_cur (cur_, &n); + auto_restore new_path (path_, &p); + + ifstream ifs (p.string ().c_str ()); + if (ifs.is_open ()) + { + ifs.exceptions (ifstream::failbit | ifstream::badbit); + + try + { + lexer l (ifs, p.string ()); + auto_restore new_lexer (lexer_, &l); + + def_unit (); + + if (!l.valid ()) + valid_ = false; + } + catch (std::ios_base::failure const&) + { + cerr << p << ": error: read failure" << endl; + valid_ = false; + } + } + else + { + cerr << p << ": error: unable to open in read mode" << endl; + valid_ = false; + } + } + else + root_->new_edge (*cur_, *it->second, ik, f); + } } t = lexer_->next (); @@ -258,14 +365,14 @@ namespace_def () throw error (); } - scope* old (scope_); + auto_restore new_scope (scope_); if (valid_) { namespace_& n ( - unit_->new_node (*path_, t.line (), t.column ())); - unit_->new_edge (*scope_, n, t.identifier ()); - scope_ = &n; + root_->new_node (*path_, t.line (), t.column ())); + root_->new_edge (*scope_, n, t.identifier ()); + new_scope.set (&n); } t = lexer_->next (); @@ -284,8 +391,6 @@ namespace_def () while (decl (t)) t = lexer_->next (); - scope_ = old; - if (t.punctuation () != token::p_rcbrace) { cerr << *path_ << ':' << t.line () << ':' << t.column () << ": error: " @@ -307,13 +412,13 @@ class_def () throw error (); } - scope* old (scope_); + auto_restore new_scope (scope_); if (valid_) { - class_& n (unit_->new_node (*path_, t.line (), t.column ())); - unit_->new_edge (*scope_, n, t.identifier ()); - scope_ = &n; + class_& n (root_->new_node (*path_, t.line (), t.column ())); + root_->new_edge (*scope_, n, t.identifier ()); + new_scope.set (&n); } t = lexer_->next (); @@ -345,8 +450,6 @@ class_def () } } - scope_ = old; - if (t.punctuation () != token::p_rcbrace) { cerr << *path_ << ':' << t.line () << ':' << t.column () << ": error: " @@ -383,9 +486,9 @@ option_def (token& t) if (valid_) { - o = &unit_->new_node