Browse Source

cppparser: support directives __LINE__, __FILE__, __has_include

rdb 9 years ago
parent
commit
40ea0ab786
2 changed files with 176 additions and 72 deletions
  1. 173 72
      dtool/src/cppparser/cppPreprocessor.cxx
  2. 3 0
      dtool/src/cppparser/cppPreprocessor.h

+ 173 - 72
dtool/src/cppparser/cppPreprocessor.cxx

@@ -809,6 +809,8 @@ expand_manifests(const string &input_expr, bool expand_undefined,
         // Here's an identifier.  Is it "defined"?
         if (ident == "defined") {
           expand_defined_function(expr, q, p);
+        } else if (expand_undefined && ident == "__has_include") {
+          expand_has_include_function(expr, q, p, loc);
         } else {
           // Is it a manifest?
           Manifests::const_iterator mi = _manifests.find(ident);
@@ -817,6 +819,20 @@ expand_manifests(const string &input_expr, bool expand_undefined,
             expand_manifest_inline(expr, q, p, manifest);
             manifest_found = true;
 
+          } else if (ident == "__FILE__") {
+            // Special case: this is a dynamic definition.
+            string file = string("\"") + loc.file._filename_as_referenced.get_fullpath() + "\"";
+            expr = expr.substr(0, q) + file + expr.substr(p);
+            p = q + file.size();
+            manifest_found = true;
+
+          } else if (ident == "__LINE__") {
+            // So is this.
+            string line = format_string(loc.first_line);
+            expr = expr.substr(0, q) + line + expr.substr(p);
+            p = q + line.size();
+            manifest_found = true;
+
           } else if (expand_undefined && ident != "true" && ident != "false") {
             // It is not found.  Expand it to 0, but only if we are currently
             // parsing an #if expression.
@@ -1486,14 +1502,10 @@ handle_undef_directive(const string &args, const YYLTYPE &loc) {
  */
 void CPPPreprocessor::
 handle_ifdef_directive(const string &args, const YYLTYPE &loc) {
-  Manifests::const_iterator mi = _manifests.find(args);
-  if (mi != _manifests.end()) {
-    // The macro is defined.  We continue.
-    return;
+  if (!is_manifest_defined(args)) {
+    // The macro is undefined.  Skip stuff.
+    skip_false_if_block(true);
   }
-
-  // The macro is undefined.  Skip stuff.
-  skip_false_if_block(true);
 }
 
 /**
@@ -1501,17 +1513,12 @@ handle_ifdef_directive(const string &args, const YYLTYPE &loc) {
  */
 void CPPPreprocessor::
 handle_ifndef_directive(const string &args, const YYLTYPE &loc) {
-  Manifests::const_iterator mi = _manifests.find(args);
-  if (mi == _manifests.end()) {
-    // The macro is undefined.  We continue.
-    return;
+  if (is_manifest_defined(args)) {
+    // The macro is defined.  Skip stuff.
+    skip_false_if_block(true);
   }
-
-  // The macro is defined.  Skip stuff.
-  skip_false_if_block(true);
 }
 
-
 /**
  *
  */
@@ -1593,6 +1600,8 @@ handle_include_directive(const string &args, const YYLTYPE &loc) {
         _angle_includes.insert(filename);
       }
     }
+  } else {
+    warning("Ignoring invalid #include directive", loc);
   }
 
   filename.set_text();
@@ -1600,71 +1609,30 @@ handle_include_directive(const string &args, const YYLTYPE &loc) {
 
   // Now look for the filename.  If we didn't use angle quotes, look first in
   // the current directory.
-  bool found_file = false;
   CPPFile::Source source = CPPFile::S_none;
 
-  if (okflag) {
-    found_file = false;
+  if (find_include(filename, angle_quotes, source)) {
+    _last_c = '\0';
 
-    // Search the current directory.
-    if (!angle_quotes && !found_file && filename.exists()) {
-      found_file = true;
+    // If it was explicitly named on the command-line, mark it S_local.
+    filename.make_canonical();
+    if (_explicit_files.count(filename)) {
       source = CPPFile::S_local;
     }
 
-    // Search the same directory as the includer.
-    if (!angle_quotes && !found_file) {
-      Filename match(get_file()._filename.get_dirname(), filename);
-      if (match.exists()) {
-        filename = match;
-        found_file = true;
-        source = CPPFile::S_alternate;
-      }
-    }
+    CPPFile file(filename, filename_as_referenced, source);
 
-    // Now search the angle-include-path
-    if (angle_quotes && !found_file && filename.resolve_filename(_angle_include_path)) {
-      found_file = true;
-      source = CPPFile::S_system;
+    // Don't include it if we included it before and it had #pragma once.
+    ParsedFiles::const_iterator it = _parsed_files.find(file);
+    if (it != _parsed_files.end() && it->_pragma_once) {
+      return;
     }
 
-    // Now search the quote-include-path
-    if (!angle_quotes && !found_file) {
-      for (size_t dir=0; dir<_quote_include_path.get_num_directories(); dir++) {
-        Filename match(_quote_include_path.get_directory(dir), filename);
-        if (match.exists()) {
-          filename = match;
-          found_file = true;
-          source = _quote_include_kind[dir];
-        }
-      }
-    }
-
-    if (!found_file) {
-      warning("Cannot find " + filename.get_fullpath(), loc);
-    } else {
-      _last_c = '\0';
-
-      // If it was explicitly named on the command-line, mark it S_local.
-      filename.make_canonical();
-      if (_explicit_files.count(filename)) {
-        source = CPPFile::S_local;
-      }
-
-      CPPFile file(filename, filename_as_referenced, source);
-
-      // Don't include it if we included it before and it had #pragma once.
-      ParsedFiles::const_iterator it = _parsed_files.find(file);
-      if (it != _parsed_files.end() && it->_pragma_once) {
-        return;
-      }
-
-      if (!push_file(file)) {
-        warning("Unable to read " + filename.get_fullpath(), loc);
-      }
+    if (!push_file(file)) {
+      warning("Unable to read " + filename.get_fullpath(), loc);
     }
   } else {
-    warning("Ignoring invalid #include directive", loc);
+    warning("Cannot find " + filename.get_fullpath(), loc);
   }
 }
 
@@ -1779,6 +1747,69 @@ skip_false_if_block(bool consider_elifs) {
   _save_comments = true;
 }
 
+/**
+ * Returns true if the given manifest is defined.
+ */
+bool CPPPreprocessor::
+is_manifest_defined(const string &manifest_name) {
+  Manifests::const_iterator mi = _manifests.find(manifest_name);
+  if (mi != _manifests.end()) {
+    return true;
+  }
+
+  if (manifest_name == "__has_include" ||
+      manifest_name == "__FILE__" ||
+      manifest_name == "__LINE__") {
+    // Special built-in directives that are considered "defined".
+    return true;
+  }
+
+  return false;
+}
+
+/**
+ * Locates the given filename.  Changes the first argument to the full path.
+ */
+bool CPPPreprocessor::
+find_include(Filename &filename, bool angle_quotes, CPPFile::Source &source) {
+  // Now look for the filename.  If we didn't use angle quotes, look first in
+  // the current directory.
+  if (!angle_quotes && filename.exists()) {
+    source = CPPFile::S_local;
+    return true;
+  }
+
+  // Search the same directory as the includer.
+  if (!angle_quotes) {
+    Filename match(get_file()._filename.get_dirname(), filename);
+    if (match.exists()) {
+      filename = match;
+      source = CPPFile::S_alternate;
+      return true;
+    }
+  }
+
+  // Now search the angle-include-path
+  if (angle_quotes && filename.resolve_filename(_angle_include_path)) {
+    source = CPPFile::S_system;
+    return true;
+  }
+
+  // Now search the quote-include-path
+  if (!angle_quotes) {
+    for (size_t dir = 0; dir < _quote_include_path.get_num_directories(); ++dir) {
+      Filename match(_quote_include_path.get_directory(dir), filename);
+      if (match.exists()) {
+        filename = match;
+        source = _quote_include_kind[dir];
+        return true;
+      }
+    }
+  }
+
+  return false;
+}
+
 /**
  *
  */
@@ -1885,6 +1916,14 @@ get_identifier(int c) {
   if (mi != _manifests.end() && !should_ignore_manifest((*mi).second)) {
     return expand_manifest((*mi).second);
   }
+  if (name == "__FILE__") {
+    return get_literal(SIMPLE_STRING, loc, loc.file._filename_as_referenced);
+  }
+  if (name == "__LINE__") {
+    YYSTYPE result;
+    result.u.integer = loc.first_line;
+    return CPPToken(INTEGER, loc, "", result);
+  }
 
   // Check for keywords.
   int kw = check_keyword(name);
@@ -2227,9 +2266,7 @@ expand_defined_function(string &expr, size_t q, size_t &p) {
   vector_string args;
   extract_manifest_args_inline("defined", 1, -1, args, expr, p);
   if (args.size() >= 1) {
-    const string &manifest_name = args[0];
-    Manifests::const_iterator mi = _manifests.find(manifest_name);
-    if (mi != _manifests.end()) {
+    if (is_manifest_defined(args[0])) {
       // The macro is defined; the result is "1".
       result = "1";
     } else {
@@ -2242,6 +2279,70 @@ expand_defined_function(string &expr, size_t q, size_t &p) {
   p = q + result.size();
 }
 
+/**
+ * Expands the __has_include(manifest) function to either 1 or 0, depending on
+ * whether the include file exists.
+ */
+void CPPPreprocessor::
+expand_has_include_function(string &expr, size_t q, size_t &p, YYLTYPE loc) {
+  bool found_file = false;
+
+  // Skip whitespace till paren.
+  while (p < expr.size() && isspace(expr[p])) {
+    p++;
+  }
+  size_t args_begin = p + 1;
+
+  vector_string args;
+  extract_manifest_args_inline("__has_include", 1, -1, args, expr, p);
+
+  if (!args.empty() && args[0].size() >= 2) {
+    Filename filename;
+    bool angle_quotes = false;
+
+    string inc = args[0];
+
+    // Just to play things safe, since our manifest-expansion logic might not
+    // filter out quotes and angle brackets properly, we'll only expand
+    // manifests if we don't begin with a quote or bracket.
+    if (!inc.empty() && (inc[0] != '"' && inc[0] != '<')) {
+      inc = expand_manifests(inc, false, loc);
+    }
+
+    if (inc[0] == '"' && inc[inc.size() - 1] == '"') {
+      filename = inc.substr(1, inc.size() - 2);
+    } else if (inc[0] == '<' && inc[inc.size() - 1] == '>') {
+      filename = inc.substr(1, inc.size() - 2);
+      if (!_noangles) {
+        // If _noangles is true, we don't make a distinction between angle
+        // brackets and quote marks--all #inc statements are treated the
+        // same, as if they used quote marks.
+        angle_quotes = true;
+      }
+    } else {
+      loc.last_column += loc.first_column + p - 2;
+      loc.first_column += args_begin;
+      warning("invalid argument for __has_include() directive", loc);
+      expr = expr.substr(0, q) + "0" + expr.substr(p);
+      p = q + 1;
+      return;
+    }
+
+    filename.set_text();
+
+    CPPFile::Source source = CPPFile::S_none;
+    found_file = find_include(filename, angle_quotes, source);
+  } else {
+    loc.last_column += loc.first_column + p - 2;
+    loc.first_column += args_begin;
+    warning("invalid argument for __has_include() directive", loc);
+  }
+
+  string result = found_file ? "1" : "0";
+  expr = expr.substr(0, q) + result + expr.substr(p);
+  p = q + result.size();
+}
+
 /**
  *
  */

+ 3 - 0
dtool/src/cppparser/cppPreprocessor.h

@@ -143,6 +143,8 @@ private:
   void handle_error_directive(const string &args, const YYLTYPE &loc);
 
   void skip_false_if_block(bool consider_elifs);
+  bool is_manifest_defined(const string &manifest_name);
+  bool find_include(Filename &filename, bool angle_quotes, CPPFile::Source &source);
 
   CPPToken get_quoted_char(int c);
   CPPToken get_quoted_string(int c);
@@ -153,6 +155,7 @@ private:
   void extract_manifest_args(const string &name, int num_args,
                              int va_arg, vector_string &args);
   void expand_defined_function(string &expr, size_t q, size_t &p);
+  void expand_has_include_function(string &expr, size_t q, size_t &p, YYLTYPE loc);
   void expand_manifest_inline(string &expr, size_t q, size_t &p,
                               const CPPManifest *manifest);
   void extract_manifest_args_inline(const string &name, int num_args,