blob: 4583e5dfe14ff1f65e43f92c3f7f6e82aa0c1fff [file] [log] [blame]
// Copyright (c) 2015 Philip Quinn.
// Licensed under the MIT License:
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#include "serialize-text.h"
#include <kj/debug.h>
#include "pretty-print.h"
#include "compiler/lexer.capnp.h"
#include "compiler/lexer.h"
#include "compiler/node-translator.h"
#include "compiler/parser.h"
namespace capnp {
namespace {
class ThrowingErrorReporter final: public capnp::compiler::ErrorReporter {
// Throws all errors as assertion failures.
public:
ThrowingErrorReporter(kj::StringPtr input): input(input) {}
void addError(uint32_t startByte, uint32_t endByte, kj::StringPtr message) override {
// Note: Line and column numbers are usually 1-based.
uint line = 1;
uint32_t lineStart = 0;
for (auto i: kj::zeroTo(startByte)) {
if (input[i] == '\n') {
++line;
lineStart = i; // Omit +1 so that column is 1-based.
}
}
kj::throwRecoverableException(kj::Exception(
kj::Exception::Type::FAILED, "(capnp text input)", line,
kj::str(startByte - lineStart, "-", endByte - lineStart, ": ", message)
));
}
bool hadErrors() override { return false; }
private:
kj::StringPtr input;
};
class ExternalResolver final: public capnp::compiler::ValueTranslator::Resolver {
// Throws all external resolution requests as assertion failures.
public:
kj::Maybe<capnp::DynamicValue::Reader>
resolveConstant(capnp::compiler::Expression::Reader name) override {
KJ_FAIL_REQUIRE("External constants not allowed.");
}
kj::Maybe<kj::Array<const capnp::byte>>
readEmbed(capnp::compiler::LocatedText::Reader filename) override {
KJ_FAIL_REQUIRE("External embeds not allowed.");
}
};
template <typename Function>
void lexAndParseExpression(kj::StringPtr input, Function f) {
// Parses a single expression from the input and calls `f(expression)`.
ThrowingErrorReporter errorReporter(input);
capnp::MallocMessageBuilder tokenArena;
auto lexedTokens = tokenArena.initRoot<capnp::compiler::LexedTokens>();
capnp::compiler::lex(input, lexedTokens, errorReporter);
capnp::compiler::CapnpParser parser(tokenArena.getOrphanage(), errorReporter);
auto tokens = lexedTokens.asReader().getTokens();
capnp::compiler::CapnpParser::ParserInput parserInput(tokens.begin(), tokens.end());
if (parserInput.getPosition() != tokens.end()) {
KJ_IF_MAYBE(expression, parser.getParsers().expression(parserInput)) {
// The input is expected to contain a *single* message.
KJ_REQUIRE(parserInput.getPosition() == tokens.end(), "Extra tokens in input.");
f(expression->getReader());
} else {
auto best = parserInput.getBest();
if (best == tokens.end()) {
KJ_FAIL_REQUIRE("Premature end of input.");
} else {
errorReporter.addErrorOn(*best, "Parse error");
}
}
} else {
KJ_FAIL_REQUIRE("Failed to read input.");
}
}
} // namespace
TextCodec::TextCodec() : prettyPrint(false) {}
TextCodec::~TextCodec() noexcept(true) {}
void TextCodec::setPrettyPrint(bool enabled) { prettyPrint = enabled; }
kj::String TextCodec::encode(DynamicValue::Reader value) const {
if (!prettyPrint) {
return kj::str(value);
} else {
if (value.getType() == DynamicValue::Type::STRUCT) {
return capnp::prettyPrint(value.as<DynamicStruct>()).flatten();
} else if (value.getType() == DynamicValue::Type::LIST) {
return capnp::prettyPrint(value.as<DynamicList>()).flatten();
} else {
return kj::str(value);
}
}
}
void TextCodec::decode(kj::StringPtr input, DynamicStruct::Builder output) const {
lexAndParseExpression(input, [&](compiler::Expression::Reader expression) {
KJ_REQUIRE(expression.isTuple(), "Input does not contain a struct.") { return; }
ThrowingErrorReporter errorReporter(input);
ExternalResolver nullResolver;
Orphanage orphanage = Orphanage::getForMessageContaining(output);
compiler::ValueTranslator translator(nullResolver, errorReporter, orphanage);
translator.fillStructValue(output, expression.getTuple());
});
}
Orphan<DynamicValue> TextCodec::decode(kj::StringPtr input, Type type, Orphanage orphanage) const {
Orphan<DynamicValue> output;
lexAndParseExpression(input, [&](compiler::Expression::Reader expression) {
ThrowingErrorReporter errorReporter(input);
ExternalResolver nullResolver;
compiler::ValueTranslator translator(nullResolver, errorReporter, orphanage);
KJ_IF_MAYBE(value, translator.compileValue(expression, type)) {
output = *kj::mv(value);
} else {
// An error should have already been given to the errorReporter.
}
});
return output;
}
} // namespace capnp