2015-06-21 23:42:29 -07:00
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
|
/* Copyright (c) FIRST 2015. All Rights Reserved. */
|
|
|
|
|
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
|
|
|
|
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
|
|
|
|
/* the project. */
|
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
|
|
2015-06-25 23:12:49 -07:00
|
|
|
#include "Storage.h"
|
2015-06-21 23:42:29 -07:00
|
|
|
|
2015-06-27 10:22:59 -07:00
|
|
|
#include <string>
|
|
|
|
|
#include <tuple>
|
|
|
|
|
|
|
|
|
|
#include "llvm/StringExtras.h"
|
2015-06-28 22:25:17 -07:00
|
|
|
#include "Base64.h"
|
2015-06-27 10:22:59 -07:00
|
|
|
|
2015-07-17 07:21:07 -07:00
|
|
|
using namespace nt;
|
2015-06-21 23:42:29 -07:00
|
|
|
|
2015-07-14 23:15:08 -07:00
|
|
|
std::unique_ptr<Storage> Storage::m_instance;
|
2015-06-21 23:42:29 -07:00
|
|
|
|
2015-06-25 22:57:43 -07:00
|
|
|
Storage::Storage() {}
|
2015-06-21 23:42:29 -07:00
|
|
|
|
2015-06-25 22:57:43 -07:00
|
|
|
Storage::~Storage() {}
|
2015-06-27 10:22:59 -07:00
|
|
|
|
2015-07-19 16:02:21 -07:00
|
|
|
std::shared_ptr<StorageEntry> Storage::FindEntry(StringRef name) const {
|
2015-07-18 01:29:51 -07:00
|
|
|
std::lock_guard<std::mutex> lock(m_mutex);
|
|
|
|
|
auto i = m_entries.find(name);
|
2015-07-19 16:02:21 -07:00
|
|
|
return i == m_entries.end() ? nullptr : i->getValue();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::shared_ptr<StorageEntry> Storage::GetEntry(StringRef name) {
|
|
|
|
|
std::lock_guard<std::mutex> lock(m_mutex);
|
2015-07-19 16:46:59 -07:00
|
|
|
auto& entry = m_entries[name];
|
|
|
|
|
if (!entry) entry = std::make_shared<StorageEntry>();
|
2015-07-19 16:02:21 -07:00
|
|
|
return entry;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::shared_ptr<Value> Storage::GetEntryValue(StringRef name) const {
|
|
|
|
|
auto entry = FindEntry(name);
|
|
|
|
|
if (!entry) return nullptr;
|
|
|
|
|
return entry->value();
|
2015-07-18 01:29:51 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Storage::SetEntryValue(StringRef name, std::shared_ptr<Value> value) {
|
|
|
|
|
if (name.empty()) return true;
|
|
|
|
|
if (!value) return true;
|
2015-07-19 16:02:21 -07:00
|
|
|
auto entry = GetEntry(name);
|
|
|
|
|
auto old_value = entry->value();
|
|
|
|
|
if (old_value && old_value->type() != value->type())
|
2015-07-18 01:29:51 -07:00
|
|
|
return false; // error on type mismatch
|
2015-07-19 16:02:21 -07:00
|
|
|
entry->set_value(value);
|
|
|
|
|
// put on update queue
|
|
|
|
|
if (!old_value)
|
|
|
|
|
m_updates.push(Update{entry, Update::kAssign});
|
|
|
|
|
else if (*old_value != *value)
|
|
|
|
|
m_updates.push(Update{entry, Update::kValueUpdate});
|
2015-07-18 01:29:51 -07:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Storage::SetEntryTypeValue(StringRef name, std::shared_ptr<Value> value) {
|
|
|
|
|
if (name.empty()) return;
|
|
|
|
|
if (!value) return;
|
2015-07-19 16:02:21 -07:00
|
|
|
auto entry = GetEntry(name);
|
|
|
|
|
auto old_value = entry->value();
|
|
|
|
|
entry->set_value(value);
|
|
|
|
|
if (!old_value || *old_value != *value) {
|
2015-07-18 01:29:51 -07:00
|
|
|
// put on update queue
|
2015-07-19 16:02:21 -07:00
|
|
|
if (!old_value || old_value->type() != value->type())
|
|
|
|
|
m_updates.push(Update{entry, Update::kAssign});
|
2015-07-18 01:29:51 -07:00
|
|
|
else
|
2015-07-19 16:02:21 -07:00
|
|
|
m_updates.push(Update{entry, Update::kValueUpdate});
|
2015-07-18 01:29:51 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Storage::SetEntryFlags(StringRef name, unsigned int flags) {
|
|
|
|
|
std::lock_guard<std::mutex> lock(m_mutex);
|
2015-07-19 16:02:21 -07:00
|
|
|
auto entry = FindEntry(name);
|
|
|
|
|
if (!entry) return;
|
|
|
|
|
if (entry->flags() != flags) {
|
|
|
|
|
entry->set_flags(flags);
|
|
|
|
|
m_updates.push(Update{entry, Update::kFlagsUpdate}); // put on update queue
|
|
|
|
|
}
|
2015-07-18 01:29:51 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unsigned int Storage::GetEntryFlags(StringRef name) const {
|
2015-07-19 16:02:21 -07:00
|
|
|
auto entry = FindEntry(name);
|
|
|
|
|
if (!entry) return 0;
|
|
|
|
|
return entry->flags();
|
2015-07-18 01:29:51 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Storage::DeleteEntry(StringRef name) {
|
2015-07-19 16:02:21 -07:00
|
|
|
auto entry = FindEntry(name);
|
|
|
|
|
if (!entry) return;
|
|
|
|
|
if (entry->value()) {
|
|
|
|
|
entry->set_value(nullptr);
|
|
|
|
|
m_updates.push(Update{entry, Update::kDelete}); // put on update queue
|
|
|
|
|
}
|
2015-07-18 01:29:51 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Storage::DeleteAllEntries() {
|
|
|
|
|
std::lock_guard<std::mutex> lock(m_mutex);
|
2015-07-19 16:02:21 -07:00
|
|
|
for (auto& i : m_entries) i.getValue()->set_value(nullptr);
|
|
|
|
|
m_updates.push(Update{nullptr, Update::kDeleteAll}); // put on update queue
|
2015-07-18 01:29:51 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<EntryInfo> Storage::GetEntryInfo(StringRef prefix,
|
|
|
|
|
unsigned int types) {
|
|
|
|
|
std::lock_guard<std::mutex> lock(m_mutex);
|
|
|
|
|
std::vector<EntryInfo> infos;
|
|
|
|
|
for (auto& i : m_entries) {
|
|
|
|
|
if (!i.getKey().startswith(prefix)) continue;
|
2015-07-19 16:02:21 -07:00
|
|
|
auto entry = i.getValue();
|
|
|
|
|
auto value = entry->value();
|
|
|
|
|
if (!value) continue;
|
2015-07-18 01:29:51 -07:00
|
|
|
EntryInfo info;
|
|
|
|
|
info.name = i.getKey();
|
2015-07-19 16:02:21 -07:00
|
|
|
info.type = value->type();
|
|
|
|
|
info.flags = entry->flags();
|
|
|
|
|
info.last_change = value->last_change();
|
2015-07-18 01:29:51 -07:00
|
|
|
infos.push_back(std::move(info));
|
|
|
|
|
}
|
|
|
|
|
return infos;
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-28 21:52:06 -07:00
|
|
|
/* Escapes and writes a string, including start and end double quotes */
|
2015-06-27 10:22:59 -07:00
|
|
|
static void WriteString(std::ostream& os, llvm::StringRef str) {
|
|
|
|
|
os << '"';
|
|
|
|
|
for (auto c : str) {
|
|
|
|
|
switch (c) {
|
|
|
|
|
case '\\':
|
|
|
|
|
os << "\\\\";
|
|
|
|
|
break;
|
|
|
|
|
case '\t':
|
|
|
|
|
os << "\\t";
|
|
|
|
|
break;
|
|
|
|
|
case '\n':
|
|
|
|
|
os << "\\n";
|
|
|
|
|
break;
|
|
|
|
|
case '"':
|
|
|
|
|
os << "\\\"";
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
if (std::isprint(c)) {
|
|
|
|
|
os << c;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Write out the escaped representation.
|
|
|
|
|
os << "\\x";
|
|
|
|
|
os << llvm::hexdigit((c >> 4) & 0xF);
|
|
|
|
|
os << llvm::hexdigit((c >> 0) & 0xF);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
os << '"';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Storage::SavePersistent(std::ostream& os) const {
|
2015-07-18 01:29:51 -07:00
|
|
|
// copy values out of storage as quickly as possible so lock isn't held
|
|
|
|
|
std::vector<std::pair<std::string, std::shared_ptr<Value>>> entries;
|
|
|
|
|
{
|
|
|
|
|
std::lock_guard<std::mutex> lock(m_mutex);
|
|
|
|
|
entries.reserve(m_entries.size());
|
|
|
|
|
for (auto& i : m_entries) {
|
2015-07-19 16:02:21 -07:00
|
|
|
auto entry = i.getValue();
|
2015-07-18 01:29:51 -07:00
|
|
|
// only write persistent-flagged values
|
2015-07-19 16:02:21 -07:00
|
|
|
if (!entry->IsPersistent()) continue;
|
|
|
|
|
entries.push_back(std::make_pair(i.getKey(), entry->value()));
|
2015-07-18 01:29:51 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-28 22:24:14 -07:00
|
|
|
std::string base64_encoded;
|
|
|
|
|
|
2015-06-28 21:52:06 -07:00
|
|
|
// header
|
2015-06-27 10:22:59 -07:00
|
|
|
os << "[NetworkTables Storage 3.0]\n";
|
|
|
|
|
|
2015-07-18 01:29:51 -07:00
|
|
|
for (auto& i : entries) {
|
2015-06-27 10:22:59 -07:00
|
|
|
// type
|
2015-07-18 01:29:51 -07:00
|
|
|
auto v = i.second;
|
2015-07-16 01:38:27 -07:00
|
|
|
if (!v) continue;
|
|
|
|
|
switch (v->type()) {
|
2015-06-27 10:22:59 -07:00
|
|
|
case NT_BOOLEAN:
|
|
|
|
|
os << "boolean ";
|
|
|
|
|
break;
|
|
|
|
|
case NT_DOUBLE:
|
|
|
|
|
os << "double ";
|
|
|
|
|
break;
|
|
|
|
|
case NT_STRING:
|
|
|
|
|
os << "string ";
|
|
|
|
|
break;
|
|
|
|
|
case NT_RAW:
|
|
|
|
|
os << "raw ";
|
|
|
|
|
break;
|
|
|
|
|
case NT_BOOLEAN_ARRAY:
|
|
|
|
|
os << "array boolean ";
|
|
|
|
|
break;
|
|
|
|
|
case NT_DOUBLE_ARRAY:
|
|
|
|
|
os << "array double ";
|
|
|
|
|
break;
|
|
|
|
|
case NT_STRING_ARRAY:
|
|
|
|
|
os << "array string ";
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// name
|
2015-07-18 01:29:51 -07:00
|
|
|
WriteString(os, i.first);
|
2015-06-27 10:22:59 -07:00
|
|
|
|
|
|
|
|
// =
|
|
|
|
|
os << '=';
|
|
|
|
|
|
|
|
|
|
// value
|
2015-07-16 01:38:27 -07:00
|
|
|
switch (v->type()) {
|
2015-06-27 10:22:59 -07:00
|
|
|
case NT_BOOLEAN:
|
2015-07-16 01:38:27 -07:00
|
|
|
os << (v->GetBoolean() ? "true" : "false");
|
2015-06-27 10:22:59 -07:00
|
|
|
break;
|
|
|
|
|
case NT_DOUBLE:
|
2015-07-16 01:38:27 -07:00
|
|
|
os << v->GetDouble();
|
2015-06-27 10:22:59 -07:00
|
|
|
break;
|
|
|
|
|
case NT_STRING:
|
2015-07-16 01:38:27 -07:00
|
|
|
WriteString(os, v->GetString());
|
2015-06-27 10:22:59 -07:00
|
|
|
break;
|
2015-06-28 22:24:14 -07:00
|
|
|
case NT_RAW:
|
2015-07-16 01:38:27 -07:00
|
|
|
Base64Encode(v->GetRaw(), &base64_encoded);
|
2015-06-28 22:24:14 -07:00
|
|
|
os << base64_encoded;
|
2015-06-27 10:22:59 -07:00
|
|
|
break;
|
2015-06-28 21:52:06 -07:00
|
|
|
case NT_BOOLEAN_ARRAY: {
|
|
|
|
|
bool first = true;
|
2015-07-16 01:38:27 -07:00
|
|
|
for (auto elem : v->GetBooleanArray()) {
|
2015-06-28 21:52:06 -07:00
|
|
|
if (!first) {
|
2015-06-27 10:22:59 -07:00
|
|
|
os << ',';
|
2015-06-28 21:52:06 -07:00
|
|
|
first = false;
|
|
|
|
|
}
|
|
|
|
|
os << (elem ? "true" : "false");
|
2015-06-27 10:22:59 -07:00
|
|
|
}
|
|
|
|
|
break;
|
2015-06-28 21:52:06 -07:00
|
|
|
}
|
|
|
|
|
case NT_DOUBLE_ARRAY: {
|
|
|
|
|
bool first = true;
|
2015-07-16 01:38:27 -07:00
|
|
|
for (auto elem : v->GetDoubleArray()) {
|
2015-06-28 21:52:06 -07:00
|
|
|
if (!first) {
|
2015-06-27 10:22:59 -07:00
|
|
|
os << ',';
|
2015-06-28 21:52:06 -07:00
|
|
|
first = false;
|
|
|
|
|
}
|
|
|
|
|
os << elem;
|
2015-06-27 10:22:59 -07:00
|
|
|
}
|
|
|
|
|
break;
|
2015-06-28 21:52:06 -07:00
|
|
|
}
|
|
|
|
|
case NT_STRING_ARRAY: {
|
|
|
|
|
bool first = true;
|
2015-07-16 01:38:27 -07:00
|
|
|
for (auto& elem : v->GetStringArray()) {
|
2015-06-28 21:52:06 -07:00
|
|
|
if (!first) {
|
2015-06-27 10:22:59 -07:00
|
|
|
os << ',';
|
2015-06-28 21:52:06 -07:00
|
|
|
first = false;
|
|
|
|
|
}
|
|
|
|
|
WriteString(os, elem);
|
2015-06-27 10:22:59 -07:00
|
|
|
}
|
|
|
|
|
break;
|
2015-06-28 21:52:06 -07:00
|
|
|
}
|
2015-06-27 10:22:59 -07:00
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// eol
|
|
|
|
|
os << '\n';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-28 21:52:06 -07:00
|
|
|
/* Extracts an escaped string token. Does not unescape the string.
|
|
|
|
|
* If a string cannot be matched, an empty string is returned.
|
|
|
|
|
* If the string is unterminated, an empty tail string is returned.
|
|
|
|
|
* The returned token includes the starting and trailing quotes (unless the
|
|
|
|
|
* string is unterminated).
|
|
|
|
|
* Returns a pair containing the extracted token (if any) and the remaining
|
|
|
|
|
* tail string.
|
|
|
|
|
*/
|
|
|
|
|
static std::pair<llvm::StringRef, llvm::StringRef> ReadStringToken(
|
|
|
|
|
llvm::StringRef source) {
|
|
|
|
|
// Match opening quote
|
|
|
|
|
if (source.empty() || source.front() != '"')
|
|
|
|
|
return std::make_pair(llvm::StringRef(), source);
|
|
|
|
|
|
|
|
|
|
// Scan for ending double quote, checking for escaped as we go.
|
|
|
|
|
std::size_t size = source.size();
|
|
|
|
|
std::size_t pos;
|
|
|
|
|
for (pos = 1; pos < size; ++pos) {
|
|
|
|
|
if (source[pos] == '"' && source[pos - 1] != '\\') {
|
|
|
|
|
++pos; // we want to include the trailing quote in the result
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return std::make_pair(source.slice(0, pos), source.substr(pos));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int fromxdigit(char ch) {
|
|
|
|
|
if (ch >= 'a' && ch <= 'f')
|
|
|
|
|
return (ch - 'a' + 10);
|
|
|
|
|
else if (ch >= 'A' && ch <= 'F')
|
|
|
|
|
return (ch - 'A' + 10);
|
|
|
|
|
else
|
|
|
|
|
return ch - '0';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void UnescapeString(llvm::StringRef source, std::string* dest) {
|
|
|
|
|
assert(source.size() > 2 && source.front() == '"' && source.back() == '"');
|
|
|
|
|
dest->clear();
|
|
|
|
|
dest->reserve(source.size() - 2);
|
|
|
|
|
for (auto s = source.begin() + 1, end = source.end() - 1; s != end; ++s) {
|
|
|
|
|
if (*s != '\\') {
|
|
|
|
|
dest->push_back(*s);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
switch (*s++) {
|
|
|
|
|
case '\\':
|
|
|
|
|
case '"':
|
|
|
|
|
dest->push_back(s[-1]);
|
|
|
|
|
break;
|
|
|
|
|
case 't':
|
|
|
|
|
dest->push_back('\t');
|
|
|
|
|
break;
|
|
|
|
|
case 'n':
|
|
|
|
|
dest->push_back('\n');
|
|
|
|
|
break;
|
|
|
|
|
case 'x': {
|
|
|
|
|
if (!isxdigit(*s)) {
|
|
|
|
|
dest->push_back('x'); // treat it like a unknown escape
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
int ch = fromxdigit(*s);
|
|
|
|
|
++s;
|
|
|
|
|
if (isxdigit(*s)) {
|
|
|
|
|
ch <<= 4;
|
|
|
|
|
ch |= fromxdigit(*s);
|
|
|
|
|
++s;
|
|
|
|
|
}
|
|
|
|
|
dest->push_back(static_cast<char>(ch));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
dest->push_back(s[-1]);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-17 07:21:07 -07:00
|
|
|
bool Storage::LoadPersistent(
|
|
|
|
|
std::istream& is,
|
|
|
|
|
std::function<void(std::size_t line, const char* msg)> warn) {
|
2015-06-27 10:22:59 -07:00
|
|
|
std::string line_str;
|
2015-06-28 21:52:06 -07:00
|
|
|
std::size_t line_num = 1;
|
2015-06-29 23:08:35 -07:00
|
|
|
|
2015-07-18 01:29:51 -07:00
|
|
|
// entries to add
|
|
|
|
|
std::vector<std::pair<std::string, std::shared_ptr<Value>>> entries;
|
|
|
|
|
|
2015-06-29 23:08:35 -07:00
|
|
|
// declare these outside the loop to reduce reallocs
|
|
|
|
|
std::string name, str;
|
|
|
|
|
std::vector<int> boolean_array;
|
|
|
|
|
std::vector<double> double_array;
|
2015-07-16 01:38:27 -07:00
|
|
|
std::vector<std::string> string_array;
|
2015-06-29 23:08:35 -07:00
|
|
|
|
|
|
|
|
// ignore blank lines and lines that start with ; or # (comments)
|
|
|
|
|
while (std::getline(is, line_str)) {
|
|
|
|
|
llvm::StringRef line = llvm::StringRef(line_str).trim();
|
|
|
|
|
if (!line.empty() && line.front() != ';' && line.front() != '#')
|
|
|
|
|
break;
|
|
|
|
|
}
|
2015-06-28 21:52:06 -07:00
|
|
|
|
|
|
|
|
// header
|
|
|
|
|
if (line_str != "[NetworkTables Storage 3.0]") {
|
|
|
|
|
if (warn) warn(line_num, "header line mismatch, ignoring rest of file");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-27 10:22:59 -07:00
|
|
|
while (std::getline(is, line_str)) {
|
2015-06-28 21:52:06 -07:00
|
|
|
llvm::StringRef line = llvm::StringRef(line_str).trim();
|
2015-06-27 10:22:59 -07:00
|
|
|
++line_num;
|
|
|
|
|
|
2015-06-29 23:08:35 -07:00
|
|
|
// ignore blank lines and lines that start with ; or # (comments)
|
|
|
|
|
if (line.empty() || line.front() == ';' || line.front() == '#')
|
|
|
|
|
continue;
|
|
|
|
|
|
2015-06-27 10:22:59 -07:00
|
|
|
// type
|
2015-06-28 21:52:06 -07:00
|
|
|
llvm::StringRef type_tok;
|
|
|
|
|
std::tie(type_tok, line) = line.split(' ');
|
2015-06-27 10:22:59 -07:00
|
|
|
NT_Type type = NT_UNASSIGNED;
|
2015-06-28 21:52:06 -07:00
|
|
|
if (type_tok == "boolean") type = NT_BOOLEAN;
|
|
|
|
|
else if (type_tok == "double") type = NT_DOUBLE;
|
|
|
|
|
else if (type_tok == "string") type = NT_STRING;
|
|
|
|
|
else if (type_tok == "raw") type = NT_RAW;
|
|
|
|
|
else if (type_tok == "array") {
|
|
|
|
|
llvm::StringRef array_tok;
|
|
|
|
|
std::tie(array_tok, line) = line.split(' ');
|
|
|
|
|
if (array_tok == "boolean") type = NT_BOOLEAN_ARRAY;
|
|
|
|
|
else if (array_tok == "double") type = NT_DOUBLE_ARRAY;
|
|
|
|
|
else if (array_tok == "string") type = NT_STRING_ARRAY;
|
2015-06-27 10:22:59 -07:00
|
|
|
}
|
|
|
|
|
if (type == NT_UNASSIGNED) {
|
|
|
|
|
if (warn) warn(line_num, "unrecognized type");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// name
|
2015-06-28 21:52:06 -07:00
|
|
|
llvm::StringRef name_tok;
|
|
|
|
|
std::tie(name_tok, line) = ReadStringToken(line);
|
|
|
|
|
if (name_tok.empty()) {
|
|
|
|
|
if (warn) warn(line_num, "missing name");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (name_tok.back() != '"') {
|
|
|
|
|
if (warn) warn(line_num, "unterminated name string");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
UnescapeString(name_tok, &name);
|
|
|
|
|
|
|
|
|
|
// =
|
|
|
|
|
line = line.ltrim(" \t");
|
|
|
|
|
if (line.empty() || line.front() != '=') {
|
|
|
|
|
if (warn) warn(line_num, "expected = after name");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
line = line.drop_front().ltrim(" \t");
|
|
|
|
|
|
|
|
|
|
// value
|
2015-07-18 01:29:51 -07:00
|
|
|
std::shared_ptr<Value> value;
|
2015-06-28 21:52:06 -07:00
|
|
|
switch (type) {
|
|
|
|
|
case NT_BOOLEAN:
|
2015-06-29 23:08:35 -07:00
|
|
|
// only true or false is accepted
|
|
|
|
|
if (line == "true")
|
2015-07-18 01:29:51 -07:00
|
|
|
value = Value::MakeBoolean(true);
|
2015-06-29 23:08:35 -07:00
|
|
|
else if (line == "false")
|
2015-07-18 01:29:51 -07:00
|
|
|
value = Value::MakeBoolean(false);
|
2015-06-29 23:08:35 -07:00
|
|
|
else {
|
|
|
|
|
if (warn)
|
|
|
|
|
warn(line_num, "unrecognized boolean value, not 'true' or 'false'");
|
|
|
|
|
goto next_line;
|
|
|
|
|
}
|
2015-06-28 21:52:06 -07:00
|
|
|
break;
|
2015-06-29 23:08:35 -07:00
|
|
|
case NT_DOUBLE: {
|
|
|
|
|
// need to convert to null-terminated string for strtod()
|
|
|
|
|
str.clear();
|
|
|
|
|
str += line;
|
|
|
|
|
char* end;
|
|
|
|
|
double v = std::strtod(str.c_str(), &end);
|
|
|
|
|
if (*end != '\0') {
|
|
|
|
|
if (warn) warn(line_num, "invalid double value");
|
|
|
|
|
goto next_line;
|
|
|
|
|
}
|
2015-07-18 01:29:51 -07:00
|
|
|
value = Value::MakeDouble(v);
|
2015-06-28 21:52:06 -07:00
|
|
|
break;
|
2015-06-29 23:08:35 -07:00
|
|
|
}
|
|
|
|
|
case NT_STRING: {
|
|
|
|
|
llvm::StringRef str_tok;
|
|
|
|
|
std::tie(str_tok, line) = ReadStringToken(line);
|
|
|
|
|
if (str_tok.empty()) {
|
|
|
|
|
if (warn) warn(line_num, "missing string value");
|
|
|
|
|
goto next_line;
|
|
|
|
|
}
|
|
|
|
|
if (str_tok.back() != '"') {
|
|
|
|
|
if (warn) warn(line_num, "unterminated string value");
|
|
|
|
|
goto next_line;
|
|
|
|
|
}
|
|
|
|
|
UnescapeString(str_tok, &str);
|
2015-07-18 01:29:51 -07:00
|
|
|
value = Value::MakeString(std::move(str));
|
2015-06-28 21:52:06 -07:00
|
|
|
break;
|
|
|
|
|
}
|
2015-06-29 23:08:35 -07:00
|
|
|
case NT_RAW:
|
|
|
|
|
Base64Decode(line, &str);
|
2015-07-18 01:29:51 -07:00
|
|
|
value = Value::MakeRaw(std::move(str));
|
2015-06-28 21:52:06 -07:00
|
|
|
break;
|
2015-06-29 23:08:35 -07:00
|
|
|
case NT_BOOLEAN_ARRAY: {
|
|
|
|
|
llvm::StringRef elem_tok;
|
|
|
|
|
boolean_array.clear();
|
|
|
|
|
while (!line.empty()) {
|
|
|
|
|
std::tie(elem_tok, line) = line.split(',');
|
|
|
|
|
elem_tok = elem_tok.trim(" \t");
|
|
|
|
|
if (elem_tok == "true")
|
|
|
|
|
boolean_array.push_back(1);
|
|
|
|
|
else if (elem_tok == "false")
|
|
|
|
|
boolean_array.push_back(0);
|
|
|
|
|
else {
|
|
|
|
|
if (warn)
|
|
|
|
|
warn(line_num,
|
|
|
|
|
"unrecognized boolean value, not 'true' or 'false'");
|
|
|
|
|
goto next_line;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-18 01:29:51 -07:00
|
|
|
value = Value::MakeBooleanArray(std::move(boolean_array));
|
2015-06-28 21:52:06 -07:00
|
|
|
break;
|
2015-06-29 23:08:35 -07:00
|
|
|
}
|
|
|
|
|
case NT_DOUBLE_ARRAY: {
|
|
|
|
|
llvm::StringRef elem_tok;
|
|
|
|
|
double_array.clear();
|
|
|
|
|
while (!line.empty()) {
|
|
|
|
|
std::tie(elem_tok, line) = line.split(',');
|
|
|
|
|
elem_tok = elem_tok.trim(" \t");
|
|
|
|
|
// need to convert to null-terminated string for strtod()
|
|
|
|
|
str.clear();
|
|
|
|
|
str += elem_tok;
|
|
|
|
|
char* end;
|
|
|
|
|
double v = std::strtod(str.c_str(), &end);
|
|
|
|
|
if (*end != '\0') {
|
|
|
|
|
if (warn) warn(line_num, "invalid double value");
|
|
|
|
|
goto next_line;
|
|
|
|
|
}
|
|
|
|
|
double_array.push_back(v);
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-18 01:29:51 -07:00
|
|
|
value = Value::MakeDoubleArray(std::move(double_array));
|
2015-06-29 23:08:35 -07:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case NT_STRING_ARRAY: {
|
|
|
|
|
llvm::StringRef elem_tok;
|
|
|
|
|
double_array.clear();
|
|
|
|
|
while (!line.empty()) {
|
|
|
|
|
std::tie(elem_tok, line) = ReadStringToken(line);
|
|
|
|
|
if (elem_tok.empty()) {
|
|
|
|
|
if (warn) warn(line_num, "missing string value");
|
|
|
|
|
goto next_line;
|
|
|
|
|
}
|
|
|
|
|
if (elem_tok.back() != '"') {
|
|
|
|
|
if (warn) warn(line_num, "unterminated string value");
|
|
|
|
|
goto next_line;
|
|
|
|
|
}
|
|
|
|
|
line = line.ltrim(" \t");
|
|
|
|
|
if (line.empty()) break;
|
|
|
|
|
if (line.front() != ',') {
|
|
|
|
|
if (warn) warn(line_num, "expected comma between strings");
|
|
|
|
|
goto next_line;
|
|
|
|
|
}
|
|
|
|
|
line = line.drop_front().ltrim(" \t");
|
|
|
|
|
|
|
|
|
|
UnescapeString(elem_tok, &str);
|
2015-07-16 01:38:27 -07:00
|
|
|
string_array.push_back(std::move(str));
|
2015-06-29 23:08:35 -07:00
|
|
|
}
|
|
|
|
|
|
2015-07-18 01:29:51 -07:00
|
|
|
value = Value::MakeStringArray(std::move(string_array));
|
2015-06-28 21:52:06 -07:00
|
|
|
break;
|
2015-06-29 23:08:35 -07:00
|
|
|
}
|
2015-06-28 21:52:06 -07:00
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
2015-07-18 01:29:51 -07:00
|
|
|
if (!name.empty() && value)
|
|
|
|
|
entries.push_back(std::make_pair(std::move(name), std::move(value)));
|
2015-06-29 23:08:35 -07:00
|
|
|
next_line:
|
|
|
|
|
;
|
2015-06-27 10:22:59 -07:00
|
|
|
}
|
2015-07-18 01:29:51 -07:00
|
|
|
|
|
|
|
|
// copy values into storage as quickly as possible so lock isn't held
|
|
|
|
|
{
|
|
|
|
|
std::lock_guard<std::mutex> lock(m_mutex);
|
|
|
|
|
for (auto& i : entries) {
|
2015-07-19 16:46:59 -07:00
|
|
|
auto& entry = m_entries[i.first];
|
|
|
|
|
if (!entry) entry = std::make_shared<StorageEntry>();
|
2015-07-19 16:02:21 -07:00
|
|
|
auto old_value = entry->value();
|
|
|
|
|
entry->set_value(i.second);
|
2015-07-18 01:29:51 -07:00
|
|
|
|
|
|
|
|
// put on update queue
|
2015-07-19 16:02:21 -07:00
|
|
|
if (!old_value || old_value->type() != i.second->type())
|
|
|
|
|
m_updates.push(Update{entry, Update::kAssign});
|
|
|
|
|
else if (*old_value != *i.second)
|
|
|
|
|
m_updates.push(Update{entry, Update::kValueUpdate});
|
|
|
|
|
|
|
|
|
|
if (!entry->IsPersistent()) {
|
|
|
|
|
entry->set_flags(entry->flags() | NT_PERSISTENT);
|
|
|
|
|
m_updates.push(Update{entry, Update::kFlagsUpdate});
|
|
|
|
|
}
|
2015-07-18 01:29:51 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-28 21:52:06 -07:00
|
|
|
return true;
|
2015-06-27 10:22:59 -07:00
|
|
|
}
|