948 lines
20 KiB
C++
948 lines
20 KiB
C++
// 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 "types.h"
|
|
#include "expressions.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;
|
|
}
|
|
|
|
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
|
|
// 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);
|
|
}
|
|
|
|
// 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. Add those file names 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(this->embed_files_[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);
|
|
}
|