compiler: initialize variables with go:embed directives
This completes the compiler work for go:embed. Reviewed-on: https://go-review.googlesource.com/c/gofrontend/+/281536
This commit is contained in:
parent
d89b00c095
commit
eed40bca6f
4 changed files with 347 additions and 2 deletions
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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_)
|
||||
|
|
|
@ -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_;
|
||||
|
|
Loading…
Add table
Reference in a new issue