compiler: add support for reading embedcfg files

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.

Change-Id: I3b13e7434eecc6367bb465e4c29491801bdd24d1
Reviewed-on: https://go-review.googlesource.com/c/gofrontend/+/281532
Trust: Ian Lance Taylor <iant@golang.org>
Reviewed-by: Than McIntosh <thanm@google.com>
diff --git a/go/embed.cc b/go/embed.cc
new file mode 100644
index 0000000..19c6930
--- /dev/null
+++ b/go/embed.cc
@@ -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);
+}
diff --git a/go/gogo.h b/go/gogo.h
index bdb3166..0d80bde 100644
--- a/go/gogo.h
+++ b/go/gogo.h
@@ -393,6 +393,10 @@
   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