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. * Make-lang.in (GO_OBJS): Add go/embed.o. Reviewed-on: https://go-review.googlesource.com/c/gofrontend/+/281532
This commit is contained in:
parent
5fff80fd79
commit
b0ccd3922f
@ -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 \
|
||||
|
@ -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.
|
||||
|
628
gcc/go/gofrontend/embed.cc
Normal file
628
gcc/go/gofrontend/embed.cc
Normal file
@ -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);
|
||||
}
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user