compiler: initialize variables with go:embed directives
authorIan Lance Taylor <iant@golang.org>
Tue, 5 Jan 2021 06:13:40 +0000 (22:13 -0800)
committerIan Lance Taylor <iant@golang.org>
Tue, 19 Jan 2021 22:29:18 +0000 (14:29 -0800)
This completes the compiler work for go:embed.

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

gcc/go/gofrontend/MERGE
gcc/go/gofrontend/embed.cc
gcc/go/gofrontend/gogo.cc
gcc/go/gofrontend/gogo.h

index fb4ec30913e6151cc3410a19ec4a1e87b8b4e724..f67c30a5d3a86263b445b6f08d135341eda26e52 100644 (file)
@@ -1,4 +1,4 @@
-9e78cef2b689aa586dbf677fb47ea3f08f197b91
+83eea1930671ce2bba863582a67f2609bc4f9f36
 
 The first line of this file holds the git revision number of the last
 merge done from the gofrontend repository.
index 7ee867462120f197cbeb85354aea6987ca0b68d0..bea1003bc087a79bdada0f6bdabfa5881b46d432 100644 (file)
@@ -9,6 +9,8 @@
 #include "operator.h"
 #include "go-diagnostics.h"
 #include "lex.h"
+#include "types.h"
+#include "expressions.h"
 #include "gogo.h"
 
 #ifndef O_BINARY
@@ -301,7 +303,41 @@ Gogo::read_embedcfg(const char *filename)
       return;
     }
 
-  // TODO: Actually do something with patterns and files.
+  for (Json_value::map_iterator p = patterns->map_begin();
+       p != patterns->map_end();
+       ++p)
+    {
+      if (p->second->classification() != Json_value::JSON_VALUE_ARRAY)
+       {
+         r.error("invalid embedcfg: Patterns entry is not an array");
+         return;
+       }
+      std::vector<std::string> files;
+      p->second->get_and_clear_array(&files);
+
+      std::pair<std::string, std::vector<std::string> > val;
+      val.first = p->first;
+      std::pair<Embed_patterns::iterator, bool> ins =
+       this->embed_patterns_.insert(val);
+      if (!ins.second)
+       {
+         r.error("invalid embedcfg: duplicate Patterns entry");
+         return;
+       }
+      std::swap(ins.first->second, files);
+    }
+
+  for (Json_value::map_iterator p = files->map_begin();
+       p != files->map_end();
+       ++p)
+    {
+      if (p->second->classification() != Json_value::JSON_VALUE_STRING)
+       {
+         r.error("invalid embedcfg: Files entry is not a string");
+         return;
+       }
+      this->embed_files_[p->first] = p->second->to_string();
+    }
 }
 
 // Read the contents of FILENAME into this->data_.  Returns whether it
@@ -641,3 +677,287 @@ Gogo::is_embed_imported() const
   // the package has been imported if there is at least one alias.
   return !p->second->aliases().empty();
 }
+
+// Implement the sort order for a list of embedded files, as discussed
+// at the docs for embed.FS.
+
+class Embedfs_sort
+{
+ public:
+  bool
+  operator()(const std::string& p1, const std::string& p2) const;
+
+ private:
+  void
+  split(const std::string&, size_t*, size_t*, size_t*) const;
+};
+
+bool
+Embedfs_sort::operator()(const std::string& p1, const std::string& p2) const
+{
+  size_t dirlen1, elem1, elemlen1;
+  this->split(p1, &dirlen1, &elem1, &elemlen1);
+  size_t dirlen2, elem2, elemlen2;
+  this->split(p2, &dirlen2, &elem2, &elemlen2);
+
+  if (dirlen1 == 0)
+    {
+      if (dirlen2 > 0)
+       {
+         int i = p2.compare(0, dirlen2, ".");
+         if (i != 0)
+           return i > 0;
+       }
+    }
+  else if (dirlen2 == 0)
+    {
+      int i = p1.compare(0, dirlen1, ".");
+      if (i != 0)
+       return i < 0;
+    }
+  else
+    {
+      int i = p1.compare(0, dirlen1, p2, 0, dirlen2);
+      if (i != 0)
+       return i < 0;
+    }
+
+  int i = p1.compare(elem1, elemlen1, p2, elem2, elemlen2);
+  return i < 0;
+}
+
+// Pick out the directory and file name components for comparison.
+
+void
+Embedfs_sort::split(const std::string& s, size_t* dirlen, size_t* elem,
+                   size_t* elemlen) const
+{
+  size_t len = s.size();
+  if (len > 0 && s[len - 1] == '/')
+    --len;
+  size_t slash = s.rfind('/', len - 1);
+  if (slash == std::string::npos)
+    {
+      *dirlen = 0;
+      *elem = 0;
+      *elemlen = len;
+    }
+  else
+    {
+      *dirlen = slash;
+      *elem = slash + 1;
+      *elemlen = len - (slash + 1);
+    }
+}
+
+// Convert the go:embed directives for a variable into an initializer
+// for that variable.
+
+Expression*
+Gogo::initializer_for_embeds(Type* type,
+                            const std::vector<std::string>* embeds,
+                            Location loc)
+{
+  if (this->embed_patterns_.empty())
+    {
+      go_error_at(loc,
+                 ("invalid go:embed: build system did not "
+                  "supply embed configuration"));
+      return Expression::make_error(loc);
+    }
+
+  type = type->unalias();
+
+  enum {
+    EMBED_STRING = 0,
+    EMBED_BYTES = 1,
+    EMBED_FS = 2
+  } embed_kind;
+
+  const Named_type* nt = type->named_type();
+  if (nt != NULL
+      && nt->named_object()->package() != NULL
+      && nt->named_object()->package()->pkgpath() == "embed"
+      && nt->name() == "FS")
+    embed_kind = EMBED_FS;
+  else if (type->is_string_type())
+    embed_kind = EMBED_STRING;
+  else if (type->is_slice_type()
+          && type->array_type()->element_type()->integer_type() != NULL
+          && type->array_type()->element_type()->integer_type()->is_byte())
+    embed_kind = EMBED_BYTES;
+  else
+    {
+      go_error_at(loc, "invalid type for go:embed");
+      return Expression::make_error(loc);
+    }
+
+  // The patterns in the go:embed directive(s) are in EMBEDS.  Find
+  // them in the patterns in the embedcfg file.
+
+  Unordered_set(std::string) have;
+  std::vector<std::string> paths;
+  for (std::vector<std::string>::const_iterator pe = embeds->begin();
+       pe != embeds->end();
+       pe++)
+    {
+      Embed_patterns::const_iterator pp = this->embed_patterns_.find(*pe);
+      if (pp == this->embed_patterns_.end())
+       {
+         go_error_at(loc,
+                     ("invalid go:embed: build system did not "
+                      "map pattern %<%s%>"),
+                     pe->c_str());
+         continue;
+       }
+
+      // Each pattern in the embedcfg file maps to a list of file
+      // names.  For each file name, the embedcfg file records an
+      // absolute path.  Add those absolute paths to PATHS.
+      for (std::vector<std::string>::const_iterator pf = pp->second.begin();
+          pf != pp->second.end();
+          pf++)
+       {
+         if (this->embed_files_.find(*pf) == this->embed_files_.end())
+           {
+             go_error_at(loc,
+                         ("invalid go:embed: build system did not "
+                          "map file %<%s%>"),
+                         pf->c_str());
+             continue;
+           }
+
+         std::pair<Unordered_set(std::string)::iterator, bool> ins
+           = have.insert(*pf);
+         if (ins.second)
+           {
+             const std::string& path(*pf);
+             paths.push_back(path);
+
+             if (embed_kind == EMBED_FS)
+               {
+                 // Add each required directory, with a trailing slash.
+                 size_t i = std::string::npos;
+                 while (i > 0)
+                   {
+                     i = path.rfind('/', i);
+                     if (i == std::string::npos)
+                       break;
+                     std::string dir = path.substr(0, i + 1);
+                     ins = have.insert(dir);
+                     if (ins.second)
+                       paths.push_back(dir);
+                     --i;
+                   }
+               }
+           }
+       }
+    }
+
+  if (embed_kind == EMBED_STRING || embed_kind == EMBED_BYTES)
+    {
+      if (paths.size() > 1)
+       {
+         go_error_at(loc,
+                     ("invalid go:embed: multiple files for "
+                      "string or byte slice"));;
+         return Expression::make_error(loc);
+       }
+
+      std::string data;
+      if (!read_file(paths[0].c_str(), loc, &data))
+       return Expression::make_error(loc);
+
+      Expression* e = Expression::make_string(data, loc);
+      if (embed_kind == EMBED_BYTES)
+       e = Expression::make_cast(type, e, loc);
+      return e;
+    }
+
+  std::sort(paths.begin(), paths.end(), Embedfs_sort());
+
+  if (type->struct_type() == NULL
+      || type->struct_type()->field_count() != 1)
+    {
+      go_error_at(loc,
+                 ("internal error: embed.FS should be struct type "
+                  "with one field"));
+      return Expression::make_error(loc);
+    }
+
+  Type* ptr_type = type->struct_type()->field(0)->type();
+  if (ptr_type->points_to() == NULL)
+    {
+      go_error_at(loc,
+                 "internal error: embed.FS struct field should be pointer");
+      return Expression::make_error(loc);
+    }
+
+  Type* slice_type = ptr_type->points_to();
+  if (!slice_type->is_slice_type())
+    {
+      go_error_at(loc,
+                 ("internal error: embed.FS struct field should be "
+                  "pointer to slice"));
+      return Expression::make_error(loc);
+    }
+
+  Type* file_type = slice_type->array_type()->element_type();
+  if (file_type->struct_type() == NULL
+      || (file_type->struct_type()->find_local_field(".embed.name", NULL)
+         == NULL)
+      || (file_type->struct_type()->find_local_field(".embed.data", NULL)
+         == NULL))
+    {
+      go_error_at(loc,
+                 ("internal error: embed.FS slice element should be struct "
+                  "with name and data fields"));
+      return Expression::make_error(loc);
+    }
+
+  const Struct_field_list* file_fields = file_type->struct_type()->fields();
+  Expression_list* file_vals = new(Expression_list);
+  file_vals->reserve(paths.size());
+  for (std::vector<std::string>::const_iterator pp = paths.begin();
+       pp != paths.end();
+       ++pp)
+    {
+      std::string data;
+      if ((*pp)[pp->size() - 1] != '/')
+       {
+         if (!read_file(this->embed_files_[*pp].c_str(), loc, &data))
+           return Expression::make_error(loc);
+       }
+
+      Expression_list* field_vals = new(Expression_list);
+      for (Struct_field_list::const_iterator pf = file_fields->begin();
+          pf != file_fields->end();
+          ++pf)
+       {
+         if (pf->is_field_name(".embed.name"))
+           field_vals->push_back(Expression::make_string(*pp, loc));
+         else if (pf->is_field_name(".embed.data"))
+           field_vals->push_back(Expression::make_string(data, loc));
+         else
+           {
+             // FIXME: The embed.file type has a hash field, which is
+             // currently unused.  We should fill it in, but don't.
+             // The hash is a SHA256, and we don't have convenient
+             // SHA256 code.  Do this later when the field is
+             // actually used.
+             field_vals->push_back(NULL);
+           }
+       }
+
+      Expression* file_val =
+       Expression::make_struct_composite_literal(file_type, field_vals, loc);
+      file_vals->push_back(file_val);
+    }
+
+  Expression* slice_init =
+    Expression::make_slice_composite_literal(slice_type, file_vals, loc);
+  Expression* fs_init = Expression::make_heap_expression(slice_init, loc);
+  Expression_list* fs_vals = new Expression_list();
+  fs_vals->push_back(fs_init);
+  return Expression::make_struct_composite_literal(type, fs_vals, loc);
+}
index 4c795a2b49527d2819c6a514b557e7a6133ef478..62b06be827b7f56865dc1f769039ec26c3bd7f60 100644 (file)
@@ -7502,6 +7502,17 @@ Variable::lower_init_expression(Gogo* gogo, Named_object* function,
   if (dep != NULL && dep->is_variable())
     dep->var_value()->lower_init_expression(gogo, function, inserter);
 
+  if (this->embeds_ != NULL)
+    {
+      // Now that we have seen any possible type aliases, convert the
+      // go:embed directives into an initializer.
+      go_assert(this->init_ == NULL && this->type_ != NULL);
+      this->init_ = gogo->initializer_for_embeds(this->type_, this->embeds_,
+                                                this->location_);
+      delete this->embeds_;
+      this->embeds_ = NULL;
+    }
+
   if (this->init_ != NULL && !this->init_is_lowered_)
     {
       if (this->seen_)
index 891ef697ffe433c138f8859a53c5818f7ab266f7..51b6575ba47f36c617d2c526837b622f86f3ed5d 100644 (file)
@@ -401,6 +401,10 @@ class Gogo
   bool
   is_embed_imported() const;
 
+  // Build an initializer for a variable with a go:embed directive.
+  Expression*
+  initializer_for_embeds(Type*, const std::vector<std::string>*, Location);
+
   // Return whether to check for division by zero in binary operations.
   bool
   check_divide_by_zero() const
@@ -1178,6 +1182,12 @@ class Gogo
   static bool
   is_digits(const std::string&);
 
+  // Type used to map go:embed patterns to a list of files.
+  typedef Unordered_map(std::string, std::vector<std::string>) Embed_patterns;
+
+  // Type used to map go:embed file names to their full path.
+  typedef Unordered_map(std::string, std::string) Embed_files;
+
   // Type used to map import names to packages.
   typedef std::map<std::string, Package*> Imports;
 
@@ -1273,6 +1283,10 @@ class Gogo
   std::string relative_import_path_;
   // The C header file to write, from the -fgo-c-header option.
   std::string c_header_;
+  // Patterns from an embedcfg file.
+  Embed_patterns embed_patterns_;
+  // Mapping from file to full path from an embedcfg file.
+  Embed_files embed_files_;
   // Whether or not to check for division by zero, from the
   // -fgo-check-divide-zero option.
   bool check_divide_by_zero_;