compiler: add support for reading embedcfg files
authorIan Lance Taylor <iant@golang.org>
Tue, 5 Jan 2021 02:37:08 +0000 (18:37 -0800)
committerIan Lance Taylor <iant@golang.org>
Fri, 15 Jan 2021 01:28:37 +0000 (17:28 -0800)
This is the code that parses an embedcfg file, which is a JSON file
created by the go command when it sees go:embed directives.  This code
is not yet called, and does not yet do anything.  It's being sent as a
separate CL to isolate just the JSON parsing code.

* Make-lang.in (GO_OBJS): Add go/embed.o.

Reviewed-on: https://go-review.googlesource.com/c/gofrontend/+/281532

gcc/go/Make-lang.in
gcc/go/gofrontend/MERGE
gcc/go/gofrontend/embed.cc [new file with mode: 0644]
gcc/go/gofrontend/gogo.h

index 84dd6cc0f18475ad0af750a63140b530cc2c56dd..4bdc8f6ea5ac944134f8f29bfe5bd57eea7dfa0d 100644 (file)
@@ -51,6 +51,7 @@ go-warn = $(STRICT_WARN)
 
 GO_OBJS = \
        go/ast-dump.o \
+       go/embed.o \
        go/escape.o \
        go/export.o \
        go/expressions.o \
index 8cfc63248a72639455580293d340e7e79a4885a5..82f43f5f21fb442f2911a333be7ba4246edf2b5a 100644 (file)
@@ -1,4 +1,4 @@
-fd5396b1af389a55d1e3612702cfdad6755084e9
+22ce16e28220d446c4557f47129024e3561f3d77
 
 The first line of this file holds the git revision number of the last
 merge done from the gofrontend repository.
diff --git a/gcc/go/gofrontend/embed.cc b/gcc/go/gofrontend/embed.cc
new file mode 100644 (file)
index 0000000..19c6930
--- /dev/null
@@ -0,0 +1,628 @@
+// embed.cc -- Go frontend go:embed handling.
+
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#include "go-system.h"
+
+#include "operator.h"
+#include "go-diagnostics.h"
+#include "lex.h"
+#include "gogo.h"
+
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+
+// Read a file into *DATA.  Returns false on error.
+
+static bool
+read_file(const char* filename, Location loc, std::string* data)
+{
+  int fd = open(filename, O_RDONLY | O_BINARY);
+  if (fd < 0)
+    {
+      go_error_at(loc, "%s: %m", filename);
+      return false;
+    }
+
+  struct stat st;
+  if (fstat(fd, &st) < 0)
+    {
+      go_error_at(loc, "%s: %m", filename);
+      return false;
+    }
+  off_t want = st.st_size;
+
+  // Most files read here are going to be incorporated into the object file
+  // and then the executable.  Set a limit on the size we will accept.
+  if (want > 2000000000)
+    {
+      go_error_at(loc, "%s: file too large", filename);
+      return false;
+    }
+
+  data->resize(want);
+  off_t got = 0;
+  while (want > 0)
+    {
+      // C++11 requires that std::string use contiguous bytes, so this
+      // is safe.
+      ssize_t n = read(fd, &(*data)[got], want);
+      if (n < 0)
+       {
+         close(fd);
+         go_error_at(loc, "%s: %m", filename);
+         return false;
+       }
+      if (n == 0)
+       {
+         data->resize(got);
+         break;
+       }
+      got += n;
+      want -= n;
+    }
+
+  close(fd);
+  return true;
+}
+
+// A JSON value as read from an embedcfg file.  For our purposes a
+// JSON value is a string, or a list of strings, or a mapping from
+// strings to values.  We don't expect any numbers.  We also don't
+// expect an array of anything other than strings; that is, we don't
+// accept an array of general JSON values.
+
+class Json_value
+{
+ public:
+  // The types of values.
+  enum Json_value_classification
+    {
+      JSON_VALUE_UNKNOWN,
+      JSON_VALUE_STRING,
+      JSON_VALUE_ARRAY,
+      JSON_VALUE_MAP
+    };
+
+  Json_value()
+    : classification_(JSON_VALUE_UNKNOWN), string_(), array_(), map_()
+  { }
+
+  ~Json_value();
+
+  Json_value_classification
+  classification() const
+  { return this->classification_; }
+
+  // Set to a string value.
+  void
+  set_string(const std::string& str)
+  {
+    go_assert(this->classification_ == JSON_VALUE_UNKNOWN);
+    this->classification_ = JSON_VALUE_STRING;
+    this->string_ = str;
+  }
+
+  // Start an array value.
+  void
+  start_array()
+  {
+    go_assert(this->classification_ == JSON_VALUE_UNKNOWN);
+    this->classification_ = JSON_VALUE_ARRAY;
+  }
+
+  // Add an array entry.
+  void
+  add_array_entry(const std::string& s)
+  {
+    go_assert(this->classification_ == JSON_VALUE_ARRAY);
+    this->array_.push_back(s);
+  }
+
+  // Start a map value.
+  void
+  start_map()
+  {
+    go_assert(this->classification_ == JSON_VALUE_UNKNOWN);
+    this->classification_ = JSON_VALUE_MAP;
+  }
+
+  // Add a map entry.
+  void
+  add_map_entry(const std::string& key, Json_value* val)
+  {
+    go_assert(this->classification_ == JSON_VALUE_MAP);
+    this->map_[key] = val;
+  }
+
+  // Return the strings from a string value.
+  const std::string&
+  to_string() const
+  {
+    go_assert(this->classification_ == JSON_VALUE_STRING);
+    return this->string_;
+  }
+
+  // Fetch a vector of strings, and drop them from the JSON value.
+  void
+  get_and_clear_array(std::vector<std::string>* v)
+  {
+    go_assert(this->classification_ == JSON_VALUE_ARRAY);
+    std::swap(*v, this->array_);
+  }
+
+  // Look up a map entry.  Returns NULL if not found.
+  Json_value*
+  lookup_map_entry(const std::string& key);
+
+  // Iterate over a map.
+  typedef Unordered_map(std::string, Json_value*)::iterator map_iterator;
+
+  map_iterator
+  map_begin()
+  {
+    go_assert(this->classification_ == JSON_VALUE_MAP);
+    return this->map_.begin();
+  }
+
+  map_iterator
+  map_end()
+  { return this->map_.end(); }
+
+ private:
+  // Classification.
+  Json_value_classification classification_;
+  // A string, for JSON_VALUE_STRING.
+  std::string string_;
+  // Array, for JSON_VALUE_ARRAY.
+  std::vector<std::string> array_;
+  // Mapping, for JSON_VALUE_MAP.
+  Unordered_map(std::string, Json_value*) map_;
+};
+
+// Delete a JSON value.
+
+Json_value::~Json_value()
+{
+  if (this->classification_ == JSON_VALUE_MAP)
+    {
+      for (map_iterator p = this->map_begin();
+          p != this->map_end();
+          ++p)
+       delete p->second;
+    }
+}
+
+// Look up a map entry in a JSON value.
+
+Json_value*
+Json_value::lookup_map_entry(const std::string& key)
+{
+  go_assert(this->classification_ == JSON_VALUE_MAP);
+  Unordered_map(std::string, Json_value*)::iterator p = this->map_.find(key);
+  if (p == this->map_.end())
+    return NULL;
+  return p->second;
+}
+
+// Manage reading the embedcfg file.
+
+class Embedcfg_reader
+{
+ public:
+  Embedcfg_reader(const char* filename)
+    : filename_(filename), data_(), p_(NULL), pend_(NULL)
+  {}
+
+  // Read the contents of FILENAME.  Return whether it succeeded.
+  bool
+  initialize_from_file();
+
+  // Read a JSON object.
+  bool
+  read_object(Json_value*);
+
+  // Report an error if not at EOF.
+  void
+  check_eof();
+
+  // Report an error for the embedcfg file.
+  void
+  error(const char* msg);
+
+ private:
+  bool
+  read_value(Json_value*);
+
+  bool
+  read_array(Json_value*);
+
+  bool
+  read_string(std::string*);
+
+  bool
+  skip_whitespace(bool eof_ok);
+
+  // File name.
+  const char* filename_;
+  // File contents.
+  std::string data_;
+  // Next character to process.
+  const char *p_;
+  // End of data.
+  const char *pend_;
+};
+
+// Read the embedcfg file.
+
+void
+Gogo::read_embedcfg(const char *filename)
+{
+  class Embedcfg_reader r(filename);
+  if (!r.initialize_from_file())
+    return;
+
+  Json_value val;
+  if (!r.read_object(&val))
+    return;
+
+  r.check_eof();
+
+  if (val.classification() != Json_value::JSON_VALUE_MAP)
+    {
+      r.error("invalid embedcfg: not a JSON object");
+      return;
+    }
+
+  Json_value* patterns = val.lookup_map_entry("Patterns");
+  if (patterns == NULL)
+    {
+      r.error("invalid embedcfg: missing Patterns");
+      return;
+    }
+  if (patterns->classification() != Json_value::JSON_VALUE_MAP)
+    {
+      r.error("invalid embedcfg: Patterns is not a JSON object");
+      return;
+    }
+
+  Json_value* files = val.lookup_map_entry("Files");
+  if (files == NULL)
+    {
+      r.error("invalid embedcfg: missing Files");
+      return;
+    }
+  if (files->classification() != Json_value::JSON_VALUE_MAP)
+    {
+      r.error("invalid embedcfg: Files is not a JSON object");
+      return;
+    }
+
+  // TODO: Actually do something with patterns and files.
+}
+
+// Read the contents of FILENAME into this->data_.  Returns whether it
+// succeeded.
+
+bool
+Embedcfg_reader::initialize_from_file()
+{
+  if (!read_file(this->filename_, Linemap::unknown_location(), &this->data_))
+    return false;
+  if (this->data_.empty())
+    {
+      this->error("empty file");
+      return false;
+    }
+  this->p_ = this->data_.data();
+  this->pend_ = this->p_ + this->data_.size();
+  return true;
+}
+
+// Read a JSON object into VAL.  Return whether it succeeded.
+
+bool
+Embedcfg_reader::read_object(Json_value* val)
+{
+  if (!this->skip_whitespace(false))
+    return false;
+  if (*this->p_ != '{')
+    {
+      this->error("expected %<{%>");
+      return false;
+    }
+  ++this->p_;
+
+  val->start_map();
+
+  if (!this->skip_whitespace(false))
+    return false;
+  if (*this->p_ == '}')
+    {
+      ++this->p_;
+      return true;
+    }
+
+  while (true)
+    {
+      if (!this->skip_whitespace(false))
+       return false;
+      if (*this->p_ != '"')
+       {
+         this->error("expected %<\"%>");
+         return false;
+       }
+
+      std::string key;
+      if (!this->read_string(&key))
+       return false;
+
+      if (!this->skip_whitespace(false))
+       return false;
+      if (*this->p_ != ':')
+       {
+         this->error("expected %<:%>");
+         return false;
+       }
+      ++this->p_;
+
+      Json_value* subval = new Json_value();
+      if (!this->read_value(subval))
+       return false;
+
+      val->add_map_entry(key, subval);
+
+      if (!this->skip_whitespace(false))
+       return false;
+      if (*this->p_ == '}')
+       {
+         ++this->p_;
+         return true;
+       }
+      if (*this->p_ != ',')
+       {
+         this->error("expected %<,%> or %<}%>");
+         return false;
+       }
+      ++this->p_;
+    }
+}
+
+// Read a JSON array into VAL.  Return whether it succeeded.
+
+bool
+Embedcfg_reader::read_array(Json_value* val)
+{
+  if (!this->skip_whitespace(false))
+    return false;
+  if (*this->p_ != '[')
+    {
+      this->error("expected %<[%>");
+      return false;
+    }
+  ++this->p_;
+
+  val->start_array();
+
+  if (!this->skip_whitespace(false))
+    return false;
+  if (*this->p_ == ']')
+    {
+      ++this->p_;
+      return true;
+    }
+
+  while (true)
+    {
+      // If we were parsing full JSON we would call read_value here,
+      // not read_string.
+
+      std::string s;
+      if (!this->read_string(&s))
+       return false;
+
+      val->add_array_entry(s);
+
+      if (!this->skip_whitespace(false))
+       return false;
+      if (*this->p_ == ']')
+       {
+         ++this->p_;
+         return true;
+       }
+      if (*this->p_ != ',')
+       {
+         this->error("expected %<,%> or %<]%>");
+         return false;
+       }
+      ++this->p_;
+    }
+}
+
+// Read a JSON value into VAL.  Return whether it succeeded.
+
+bool
+Embedcfg_reader::read_value(Json_value* val)
+{
+  if (!this->skip_whitespace(false))
+    return false;
+  switch (*this->p_)
+    {
+    case '"':
+      {
+       std::string s;
+       if (!this->read_string(&s))
+         return false;
+       val->set_string(s);
+       return true;
+      }
+
+    case '{':
+      return this->read_object(val);
+
+    case '[':
+      return this->read_array(val);
+
+    default:
+      this->error("invalid JSON syntax");
+      return false;
+    }
+}
+
+// Read a JSON string.  Return whether it succeeded.
+
+bool
+Embedcfg_reader::read_string(std::string* str)
+{
+  if (!this->skip_whitespace(false))
+    return false;
+  if (*this->p_ != '"')
+    {
+      this->error("expected %<\"%>");
+      return false;
+    }
+  ++this->p_;
+
+  str->clear();
+  while (this->p_ < this->pend_ && *this->p_ != '"')
+    {
+      if (*this->p_ != '\\')
+       {
+         str->push_back(*this->p_);
+         ++this->p_;
+         continue;
+       }
+
+      ++this->p_;
+      if (this->p_ >= this->pend_)
+       {
+         this->error("unterminated string");
+         return false;
+       }
+      switch (*this->p_)
+       {
+       case '"': case '\\': case '/':
+         str->push_back(*this->p_);
+         ++this->p_;
+         break;
+
+       case 'b':
+         str->push_back('\b');
+         ++this->p_;
+         break;
+
+       case 'f':
+         str->push_back('\f');
+         ++this->p_;
+         break;
+
+       case 'n':
+         str->push_back('\n');
+         ++this->p_;
+         break;
+
+       case 'r':
+         str->push_back('\r');
+         ++this->p_;
+         break;
+
+       case 't':
+         str->push_back('\t');
+         ++this->p_;
+         break;
+
+       case 'u':
+         {
+           ++this->p_;
+           unsigned int rune = 0;
+           for (int i = 0; i < 4; i++)
+             {
+               if (this->p_ >= this->pend_)
+                 {
+                   this->error("unterminated string");
+                   return false;
+                 }
+               unsigned char c = *this->p_;
+               ++this->p_;
+               rune <<= 4;
+               if (c >= '0' && c <= '9')
+                 rune += c - '0';
+               else if (c >= 'A' && c <= 'F')
+                 rune += c - 'A' + 10;
+               else if (c >= 'a' && c <= 'f')
+                 rune += c - 'a' + 10;
+               else
+                 {
+                   this->error("invalid hex digit");
+                   return false;
+                 }
+             }
+           Lex::append_char(rune, false, str, Linemap::unknown_location());
+         }
+         break;
+
+       default:
+         this->error("unrecognized string escape");
+         return false;
+       }
+    }
+
+  if (*this->p_ == '"')
+    {
+      ++this->p_;
+      return true;
+    }
+
+  this->error("unterminated string");
+  return false;
+}
+
+// Report an error if not at EOF.
+
+void
+Embedcfg_reader::check_eof()
+{
+  if (this->skip_whitespace(true))
+    this->error("extraneous data at end of file");
+}
+
+// Skip whitespace.  Return whether there is more to read.
+
+bool
+Embedcfg_reader::skip_whitespace(bool eof_ok)
+{
+  while (this->p_ < this->pend_)
+    {
+      switch (*this->p_)
+       {
+       case ' ': case '\t': case '\n': case '\r':
+         ++this->p_;
+         break;
+       default:
+         return true;
+       }
+    }
+  if (!eof_ok)
+    this->error("unexpected EOF");
+  return false;
+}
+
+// Report an error.
+
+void
+Embedcfg_reader::error(const char* msg)
+{
+  if (!this->data_.empty() && this->p_ != NULL)
+    go_error_at(Linemap::unknown_location(),
+               "%<-fgo-embedcfg%>: %s: %lu: %s",
+               this->filename_,
+               static_cast<unsigned long>(this->p_ - this->data_.data()),
+               msg);
+  else
+    go_error_at(Linemap::unknown_location(),
+               "%<-fgo-embedcfg%>: %s: %s",
+               this->filename_, msg);
+}
index bdb3166006d068f654faed725ada4698b79d0341..0d80bdeda4ff3b06a740af1329764dad9844e529 100644 (file)
@@ -393,6 +393,10 @@ class Gogo
   set_c_header(const std::string& s)
   { this->c_header_ = s; }
 
+  // Read an embedcfg file.
+  void
+  read_embedcfg(const char* filename);
+
   // Return whether to check for division by zero in binary operations.
   bool
   check_divide_by_zero() const