Commit de7f266a authored by Kenton Varda's avatar Kenton Varda

Add utility code for really nice command-line option parsing and start using it…

Add utility code for really nice command-line option parsing and start using it in the compiler (still WIP).
parent b92a28a6
......@@ -110,8 +110,10 @@ includekj_HEADERS = \
src/kj/arena.h \
src/kj/io.h \
src/kj/tuple.h \
src/kj/function.h \
src/kj/mutex.h \
src/kj/thread.h
src/kj/thread.h \
src/kj/main.h
includekjparse_HEADERS = \
src/kj/parse/common.h \
......@@ -150,6 +152,7 @@ libcapnp_la_SOURCES= \
src/kj/io.c++ \
src/kj/mutex.c++ \
src/kj/thread.c++ \
src/kj/main.c++ \
src/kj/parse/char.c++ \
src/capnp/blob.c++ \
src/capnp/arena.h \
......@@ -199,6 +202,7 @@ capnp_test_SOURCES = \
src/kj/arena-test.c++ \
src/kj/units-test.c++ \
src/kj/tuple-test.c++ \
src/kj/function-test.c++ \
src/kj/mutex-test.c++ \
src/kj/parse/common-test.c++ \
src/kj/parse/char-test.c++ \
......
......@@ -24,6 +24,7 @@
#include "lexer.h"
#include "parser.h"
#include "compiler.h"
#include "module-loader.h"
#include <capnp/pretty-print.h>
#include <kj/vector.h>
#include <kj/io.h>
......@@ -31,6 +32,10 @@
#include <kj/debug.h>
#include "../message.h"
#include <iostream>
#include <kj/main.h>
namespace capnp {
namespace compiler {
class DummyModule: public capnp::compiler::Module {
public:
......@@ -53,6 +58,36 @@ public:
}
};
class CompilerMain {
public:
explicit CompilerMain(kj::ProcessContext& context)
: context(context), loader(STDERR_FILENO) {}
kj::MainFunc getMain() {
return kj::MainBuilder(
context, "Cap'n Proto compiler version 0.2",
"Compiles Cap'n Proto schema files and generates corresponding source code in one or "
"more languages.")
.expectOneOrMoreArgs("source", KJ_BIND_METHOD(*this, addSource))
.build();
}
kj::MainBuilder::Validity addSource(kj::StringPtr file) {
KJ_IF_MAYBE(module, loader.loadModule(file, file)) {
compiler.add(*module, Compiler::EAGER);
} else {
return "no such file";
}
return true;
}
private:
kj::ProcessContext& context;
ModuleLoader loader;
Compiler compiler;
};
int main(int argc, char* argv[]) {
// Eventually this will be capnpc. For now it's just a dummy program that tests parsing.
......@@ -109,3 +144,8 @@ int main(int argc, char* argv[]) {
return 0;
}
} // namespace compiler
} // namespace capnp
KJ_MAIN(capnp::compiler::CompilerMain);
......@@ -81,7 +81,7 @@ public:
kj::Maybe<const Node&> lookup(const DeclName::Reader& name) const;
// Resolve an arbitrary DeclName to a Node.
kj::Maybe<Schema> getBootstrapOrFinalSchema() const;
kj::Maybe<Schema> getBootstrapSchema() const;
kj::Maybe<Schema> getFinalSchema() const;
void traverse(Compiler::Mode mode) const;
......@@ -93,7 +93,7 @@ public:
// implements NodeTranslator::Resolver -----------------------------
kj::Maybe<ResolvedName> resolve(const DeclName::Reader& name) const override;
kj::Maybe<Schema> resolveMaybeBootstrapSchema(uint64_t id) const override;
kj::Maybe<Schema> resolveBootstrapSchema(uint64_t id) const override;
kj::Maybe<Schema> resolveFinalSchema(uint64_t id) const override;
private:
......@@ -212,7 +212,7 @@ public:
Impl();
virtual ~Impl();
uint64_t add(Module& module, Mode mode) const;
uint64_t add(const Module& module, Mode mode) const;
const CompiledModule& add(const Module& parsedModule) const;
struct Workspace {
......@@ -473,8 +473,8 @@ const Compiler::Node::Content& Compiler::Node::getContent(Content::State minimum
locked->translator->getBootstrapNode());
})) {
locked->bootstrapSchema = nullptr;
addError(kj::str("Internal compiler bug: Bootstrap schema failed validation: ",
exception->getDescription()));
addError(kj::str("Internal compiler bug: Bootstrap schema failed validation:\n",
*exception));
}
// If the Workspace is destroyed while this Node is still in the BOOTSTRAP state,
......@@ -500,8 +500,8 @@ const Compiler::Node::Content& Compiler::Node::getContent(Content::State minimum
locked->translator->finish());
})) {
locked->finalSchema = nullptr;
addError(kj::str("Internal compiler bug: Schema failed validation: ",
exception->getDescription()));
addError(kj::str("Internal compiler bug: Schema failed validation:\n",
*exception));
}
locked->advanceState(Content::FINISHED);
......@@ -605,12 +605,19 @@ kj::Maybe<const Compiler::Node&> Compiler::Node::lookup(const DeclName::Reader&
return *node;
}
kj::Maybe<Schema> Compiler::Node::getBootstrapOrFinalSchema() const {
kj::Maybe<Schema> Compiler::Node::getBootstrapSchema() const {
auto& content = getContent(Content::BOOTSTRAP);
if (__atomic_load_n(&content.state, __ATOMIC_ACQUIRE) == Content::FINISHED &&
content.finalSchema != nullptr) {
return content.finalSchema;
content.bootstrapSchema == nullptr) {
// The bootstrap schema was discarded. Copy it from the final schema.
// (We can't just return the final schema because using it could trigger schema loader
// callbacks that would deadlock.)
KJ_IF_MAYBE(finalSchema, content.finalSchema) {
return module->getCompiler().getWorkspace().bootstrapLoader.loadOnce(finalSchema->getProto());
} else {
return nullptr;
}
} else {
return content.bootstrapSchema;
}
......@@ -640,9 +647,9 @@ kj::Maybe<NodeTranslator::Resolver::ResolvedName> Compiler::Node::resolve(
});
}
kj::Maybe<Schema> Compiler::Node::resolveMaybeBootstrapSchema(uint64_t id) const {
kj::Maybe<Schema> Compiler::Node::resolveBootstrapSchema(uint64_t id) const {
KJ_IF_MAYBE(node, module->getCompiler().findNode(id)) {
return node->getBootstrapOrFinalSchema();
return node->getBootstrapSchema();
} else {
KJ_FAIL_REQUIRE("Tried to get schema for ID we haven't seen before.");
}
......@@ -743,7 +750,7 @@ kj::Maybe<const Compiler::Node&> Compiler::Impl::lookupBuiltin(kj::StringPtr nam
}
}
uint64_t Compiler::Impl::add(Module& module, Mode mode) const {
uint64_t Compiler::Impl::add(const Module& module, Mode mode) const {
Impl::Workspace workspace;
auto lock = this->workspace.lockExclusive();
*lock = &workspace;
......@@ -766,7 +773,7 @@ void Compiler::Impl::load(const SchemaLoader& loader, uint64_t id) const {
node->getFinalSchema();
} else {
// Must be the bootstrap loader.
node->getBootstrapOrFinalSchema();
node->getBootstrapSchema();
}
}
}
......@@ -776,7 +783,7 @@ void Compiler::Impl::load(const SchemaLoader& loader, uint64_t id) const {
Compiler::Compiler(): impl(kj::heap<Impl>()) {}
Compiler::~Compiler() {}
uint64_t Compiler::add(Module& module, Mode mode) const {
uint64_t Compiler::add(const Module& module, Mode mode) const {
return impl->add(module, mode);
}
......
......@@ -76,7 +76,7 @@ public:
// use EAGER mode.
};
uint64_t add(Module& module, Mode mode) const;
uint64_t add(const Module& module, Mode mode) const;
// Add a module to the Compiler, returning the module's file ID. The ID can then be used to
// look up the schema in the SchemaLoader returned by `getLoader()`. However, if there were any
// errors while compiling (reported via `module.addError()`), then the SchemaLoader may behave as
......
......@@ -263,7 +263,7 @@ public:
uint startLine = findLargestElementBefore(lines, startByte);
uint startCol = startByte - lines[startLine];
loader.writeError(
kj::str(localName, ":", startLine, ":", startCol, ": error: ", message, "\n"));
kj::str(localName, ":", startLine + 1, ":", startCol + 1, ": error: ", message, "\n"));
}
private:
......@@ -291,7 +291,7 @@ kj::Maybe<const Module&> ModuleLoader::Impl::loadModule(
return *iter->second;
}
if (access(canonicalLocalName.cStr(), R_OK) < 0) {
if (access(canonicalLocalName.cStr(), F_OK) < 0) {
// No such file.
return nullptr;
}
......
......@@ -1489,8 +1489,7 @@ void NodeTranslator::compileValue(ValueExpression::Reader source, schema::Type::
}
break;
case schema::Type::Body::STRUCT_TYPE:
KJ_IF_MAYBE(structSchema, resolver.resolveMaybeBootstrapSchema(
type.getBody().getStructType())) {
KJ_IF_MAYBE(structSchema, resolver.resolveBootstrapSchema(type.getBody().getStructType())) {
DynamicSlot slot(valueUnion, member, structSchema->asStruct());
compileValue(source, slot, isBootstrap);
}
......@@ -1527,7 +1526,7 @@ void NodeTranslator::compileValueInner(
kj::StringPtr id = name.getBase().getRelativeName().getValue();
KJ_IF_MAYBE(enumId, dst.getEnumType()) {
KJ_IF_MAYBE(enumSchema, resolver.resolveMaybeBootstrapSchema(*enumId)) {
KJ_IF_MAYBE(enumSchema, resolver.resolveBootstrapSchema(*enumId)) {
KJ_IF_MAYBE(enumerant, enumSchema->asEnum().findEnumerantByName(id)) {
dst.set(DynamicEnum(*enumerant));
wasSet = true;
......@@ -1677,7 +1676,7 @@ kj::Maybe<DynamicValue::Reader> NodeTranslator::readConstant(
// We need to be very careful not to query this Schema's dependencies because if it is
// a final schema then this query could trigger a lazy load which would deadlock.
kj::Maybe<Schema> maybeConstSchema = isBootstrap ?
resolver.resolveMaybeBootstrapSchema(resolved->id) :
resolver.resolveBootstrapSchema(resolved->id) :
resolver.resolveFinalSchema(resolved->id);
KJ_IF_MAYBE(constSchema, maybeConstSchema) {
auto constReader = constSchema->getProto().getBody().getConstNode();
......@@ -1689,7 +1688,7 @@ kj::Maybe<DynamicValue::Reader> NodeTranslator::readConstant(
auto constType = constReader.getType();
switch (constType.getBody().which()) {
case schema::Type::Body::STRUCT_TYPE:
KJ_IF_MAYBE(structSchema, resolver.resolveMaybeBootstrapSchema(
KJ_IF_MAYBE(structSchema, resolver.resolveBootstrapSchema(
constType.getBody().getStructType())) {
constValue = objValue.as(structSchema->asStruct());
} else {
......@@ -1719,7 +1718,7 @@ kj::Maybe<DynamicValue::Reader> NodeTranslator::readConstant(
// A fully unqualified identifier looks like it might refer to a constant visible in the
// current scope, but if that's really what the user wanted, we want them to use a
// qualified name to make it more obvious. Report an error.
KJ_IF_MAYBE(scope, resolver.resolveMaybeBootstrapSchema(
KJ_IF_MAYBE(scope, resolver.resolveBootstrapSchema(
constSchema->getProto().getScopeId())) {
auto scopeReader = scope->getProto();
kj::StringPtr parent;
......@@ -1752,19 +1751,19 @@ kj::Maybe<ListSchema> NodeTranslator::makeListSchemaOf(schema::Type::Reader elem
auto body = elementType.getBody();
switch (body.which()) {
case schema::Type::Body::ENUM_TYPE:
KJ_IF_MAYBE(enumSchema, resolver.resolveMaybeBootstrapSchema(body.getEnumType())) {
KJ_IF_MAYBE(enumSchema, resolver.resolveBootstrapSchema(body.getEnumType())) {
return ListSchema::of(enumSchema->asEnum());
} else {
return nullptr;
}
case schema::Type::Body::STRUCT_TYPE:
KJ_IF_MAYBE(structSchema, resolver.resolveMaybeBootstrapSchema(body.getStructType())) {
KJ_IF_MAYBE(structSchema, resolver.resolveBootstrapSchema(body.getStructType())) {
return ListSchema::of(structSchema->asStruct());
} else {
return nullptr;
}
case schema::Type::Body::INTERFACE_TYPE:
KJ_IF_MAYBE(interfaceSchema, resolver.resolveMaybeBootstrapSchema(body.getInterfaceType())) {
KJ_IF_MAYBE(interfaceSchema, resolver.resolveBootstrapSchema(body.getInterfaceType())) {
return ListSchema::of(interfaceSchema->asInterface());
} else {
return nullptr;
......@@ -1806,7 +1805,7 @@ Orphan<List<schema::Annotation>> NodeTranslator::compileAnnotationApplications(
"'", declNameString(name), "' is not an annotation."));
} else {
annotationBuilder.setId(decl->id);
KJ_IF_MAYBE(annotationSchema, resolver.resolveMaybeBootstrapSchema(decl->id)) {
KJ_IF_MAYBE(annotationSchema, resolver.resolveBootstrapSchema(decl->id)) {
auto node = annotationSchema->getProto().getBody().getAnnotationNode();
if (!toDynamic(node).get(targetsFlagName).as<bool>()) {
errorReporter.addErrorOn(name, kj::str(
......
......@@ -54,17 +54,24 @@ public:
// Look up the given name, relative to this node, and return basic information about the
// target.
virtual kj::Maybe<Schema> resolveMaybeBootstrapSchema(uint64_t id) const = 0;
// Get the schema for the given ID. Returning either a bootstrap schema or a final schema
// is acceptable. Throws an exception if the id is not one that was found by calling resolve()
// or by traversing other schemas. Returns null if the ID is recognized, but the corresponding
virtual kj::Maybe<Schema> resolveBootstrapSchema(uint64_t id) const = 0;
// Get the schema for the given ID. If a schema is returned, it must be safe to traverse its
// dependencies using Schema::getDependency(). A schema that is only at the bootstrap stage
// is acceptable.
//
// Throws an exception if the id is not one that was found by calling resolve() or by
// traversing other schemas. Returns null if the ID is recognized, but the corresponding
// schema node failed to be built for reasons that were already reported.
virtual kj::Maybe<Schema> resolveFinalSchema(uint64_t id) const = 0;
// Get the final schema for the given ID. A bootstrap schema is not acceptable. Throws an
// exception if the id is not one that was found by calling resolve() or by traversing other
// schemas. Returns null if the ID is recognized, but the corresponding schema node failed to
// be built for reasons that were already reported.
// Get the final schema for the given ID. A bootstrap schema is not acceptable. It is NOT
// safe to traverse the schema's dependencies with Schema::getDependency() as doing so may
// trigger lazy loading callbacks that deadlock on the compiler mutex. Instead, the caller
// should carefully look up dependencies through this Resolver.
//
// Throws an exception if the id is not one that was found by calling resolve() or by
// traversing other schemas. Returns null if the ID is recognized, but the corresponding
// schema node failed to be built for reasons that were already reported.
};
NodeTranslator(const Resolver& resolver, const ErrorReporter& errorReporter,
......
......@@ -583,11 +583,23 @@ CapnpParser::CapnpParser(Orphanage orphanageParam, const ErrorReporter& errorRep
// -----------------------------------------------------------------
parsers.usingDecl = arena.copy(p::transform(
p::sequence(keyword("using"), identifier, op("="), parsers.declName),
[this](Located<Text::Reader>&& name, Orphan<DeclName>&& target) -> DeclParserResult {
p::sequence(keyword("using"), p::optional(p::sequence(identifier, op("="))),
parsers.declName),
[this](kj::Maybe<Located<Text::Reader>>&& name, Orphan<DeclName>&& target)
-> DeclParserResult {
auto decl = orphanage.newOrphan<Declaration>();
auto builder = decl.get();
name.copyTo(builder.initName());
KJ_IF_MAYBE(n, name) {
n->copyTo(builder.initName());
} else {
auto targetPath = target.getReader().getMemberPath();
if (targetPath.size() == 0) {
errorReporter.addErrorOn(
target.getReader(), "'using' declaration without '=' must use a qualified path.");
} else {
builder.setName(targetPath[targetPath.size() - 1]);
}
}
// no id, no annotations for using decl
builder.getBody().initUsingDecl().adoptTarget(kj::mv(target));
return DeclParserResult(kj::mv(decl));
......
......@@ -54,6 +54,12 @@ void DestructorOnlyArrayDisposer::disposeImpl(
}
}
const NullArrayDisposer NullArrayDisposer::instance = NullArrayDisposer();
void NullArrayDisposer::disposeImpl(
void* firstElement, size_t elementSize, size_t elementCount,
size_t capacity, void (*destroyElement)(void*)) const {}
namespace _ { // private
void* HeapArrayDisposer::allocateImpl(size_t elementSize, size_t elementCount, size_t capacity,
......
......@@ -108,6 +108,17 @@ public:
size_t capacity, void (*destroyElement)(void*)) const override;
};
class NullArrayDisposer: public ArrayDisposer {
// An ArrayDisposer that does nothing. Can be used to construct a fake Arrays that doesn't
// actually own its content.
public:
static const NullArrayDisposer instance;
void disposeImpl(void* firstElement, size_t elementSize, size_t elementCount,
size_t capacity, void (*destroyElement)(void*)) const override;
};
// =======================================================================================
// Array
......
// Copyright (c) 2013, Kenton Varda <temporal@gmail.com>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "function.h"
#include <gtest/gtest.h>
namespace kj {
namespace {
TEST(Function, Lambda) {
int i = 0;
Function<int(int, int)> f = [&](int a, int b) { return a + b + i++; };
EXPECT_EQ(123 + 456, f(123, 456));
EXPECT_EQ(7 + 8 + 1, f(7, 8));
EXPECT_EQ(9 + 2 + 2, f(2, 9));
EXPECT_EQ(i, 3);
}
struct TestType {
int callCount;
TestType(int callCount = 0): callCount(callCount) {}
~TestType() { callCount = 1234; }
// Make sure we catch invalid post-destruction uses.
int foo(int a, int b) {
return a + b + callCount++;
}
};
TEST(Function, Method) {
TestType obj;
Function<int(int, int)> f = KJ_BIND_METHOD(obj, foo);
Function<uint(uint, uint)> f2 = KJ_BIND_METHOD(obj, foo);
EXPECT_EQ(123 + 456, f(123, 456));
EXPECT_EQ(7 + 8 + 1, f(7, 8));
EXPECT_EQ(9 + 2 + 2, f2(2, 9));
EXPECT_EQ(3, obj.callCount);
// Bind to a temporary.
f = KJ_BIND_METHOD(TestType(10), foo);
EXPECT_EQ(123 + 456 + 10, f(123, 456));
EXPECT_EQ(7 + 8 + 11, f(7, 8));
EXPECT_EQ(9 + 2 + 12, f(2, 9));
// Bind to a move.
f = KJ_BIND_METHOD(kj::mv(obj), foo);
obj.callCount = 1234;
EXPECT_EQ(123 + 456 + 3, f(123, 456));
EXPECT_EQ(7 + 8 + 4, f(7, 8));
EXPECT_EQ(9 + 2 + 5, f(2, 9));
}
} // namespace
} // namespace kj
// Copyright (c) 2013, Kenton Varda <temporal@gmail.com>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef KJ_FUNCTION_H_
#define KJ_FUNCTION_H_
#include "memory.h"
namespace kj {
template <typename Signature>
class Function;
// Function wrapper using virtual-based polymorphism. Use this when template polymorphism is
// not possible. You can, for example, accept a Function as a parameter:
//
// void setFilter(Function<bool(const Widget&)> filter);
//
// The caller of `setFilter()` may then pass any callable object as the parameter. The callable
// object does not have to have the exact signature specified, just one that is "compatible" --
// i.e. the return type is covariant and the parameters are contravariant.
//
// Unlike `std::function`, `kj::Function`s are movable but not copyable, just like `kj::Own`. This
// is to avoid unexpected heap allocation or slow atomic reference counting.
//
// When a `Function` is constructed from an lvalue, it captures only a reference to the value.
// When constructed from an rvalue, it invokes the value's move constructor. So, for example:
//
// struct AddN {
// int n;
// int operator(int i) { return i + n; }
// }
//
// Function<int(int, int)> f1 = AddN{2};
// // f1 owns an instance of AddN. It may safely be moved out
// // of the local scope.
//
// AddN adder(2);
// Function<int(int, int)> f2 = adder;
// // f2 contains a reference to `adder`. Thus, it becomes invalid
// // when `adder` goes out-of-scope.
//
// AddN adder2(2);
// Function<int(int, int)> f3 = kj::mv(adder2);
// // f3 owns an insatnce of AddN moved from `adder2`. f3 may safely
// // be moved out of the local scope.
//
// Additionally, a Function may be bound to a class method using KJ_BIND_METHOD(object, methodName).
// For example:
//
// class Printer {
// public:
// void print(int i);
// void print(kj::StringPtr s);
// };
//
// Printer p;
//
// Function<void(uint)> intPrinter = KJ_BIND_METHOD(p, print);
// // Will call Printer::print(int).
//
// Function<void(const char*)> strPrinter = KJ_BIND_METHOD(p, print);
// // Will call Printer::print(kj::StringPtr).
//
// Notice how KJ_BIND_METHOD is able to figure out which overload to use depending on the kind of
// Function it is binding to.
template <typename Return, typename... Params>
class Function<Return(Params...)> {
public:
template <typename F>
inline Function(F&& f): impl(heap<Impl<F>>(kj::fwd<F>(f))) {}
Function() = default;
inline Return operator()(Params... params) {
return (*impl)(kj::fwd<Params>(params)...);
}
private:
class Iface {
public:
virtual Return operator()(Params... params) = 0;
};
template <typename F>
class Impl final: public Iface {
public:
explicit Impl(F&& f): f(kj::fwd<F>(f)) {}
Return operator()(Params... params) override {
return f(kj::fwd<Params>(params)...);
}
private:
F f;
};
Own<Iface> impl;
};
namespace _ {
template <typename T>
T rvalueOrRef(T&&);
// Hack to help detect if an expression is an lvalue or an rvalue.
//
// int i;
// decltype(i) i1(i); // i1 has type int.
// decltype(rvalueOrRef(i)) i2(i); // i2 has type int&.
// decltype(rvalueOrRef(kj::mv(i)) i3(kj::mv(i)); // i3 has type int.
} // namespace _
#define KJ_BIND_METHOD(obj, method) \
({ \
typedef decltype(::kj::_::rvalueOrRef(obj)) T; \
class F { \
public: \
inline F(T&& t): t(::kj::fwd<T>(t)) {} \
template <typename... Params> \
auto operator()(Params&&... params) \
-> decltype(::kj::instance<T>().method(::kj::fwd<Params>(params)...)) { \
return t.method(::kj::fwd<Params>(params)...); \
} \
private: \
T t; \
}; \
(F(obj)); \
})
// Macro that produces a functor object which forwards to the method `obj.name`. If `obj` is an
// lvalue, the functor will hold a reference to it. If `obj` is an rvalue, the functor will
// contain a copy (by move) of it.
} // namespace kj
#endif // KJ_FUNCTION_H_
// Copyright (c) 2013, Kenton Varda <temporal@gmail.com>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "main.h"
#include "debug.h"
#include "arena.h"
#include <map>
#include <set>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <limits.h>
namespace kj {
// =======================================================================================
TopLevelProcessContext::TopLevelProcessContext(StringPtr programName)
: programName(programName),
cleanShutdown(getenv("KJ_CLEAN_SHUTDOWN") != nullptr) {}
StringPtr TopLevelProcessContext::getProgramName() {
return programName;
}
void TopLevelProcessContext::exit() {
int exitCode = hadErrors ? 1 : 0;
if (cleanShutdown) {
#if KJ_NO_EXCEPTIONS
// This is the best we can do.
warning("warning: KJ_CLEAN_SHUTDOWN may not work correctly when compiled "
"with -fno-exceptions.");
::exit(exitCode);
#else
throw CleanShutdownException { exitCode };
#endif
}
quick_exit(exitCode);
}
void TopLevelProcessContext::warning(StringPtr message) {
const char* pos = message.begin();
while (pos < message.end()) {
ssize_t n = write(STDERR_FILENO, pos, message.end() - pos);
if (n < 0) {
if (errno == EINTR) {
continue;
}
// Apparently we can't write to stderr. Dunno what to do.
return;
}
pos += n;
}
if (message.size() > 0 && message[message.size() - 1] != '\n') {
write(STDERR_FILENO, "\n", 1);
}
}
void TopLevelProcessContext::error(StringPtr message) {
hadErrors = true;
warning(message);
}
void TopLevelProcessContext::exitError(StringPtr message) {
error(message);
exit();
}
void TopLevelProcessContext::exitInfo(StringPtr message) {
const char* pos = message.begin();
while (pos < message.end()) {
ssize_t n;
KJ_SYSCALL(n = write(STDOUT_FILENO, pos, message.end() - pos));
pos += n;
}
if (message.size() > 0 && message[message.size() - 1] != '\n') {
write(STDOUT_FILENO, "\n", 1);
}
exit();
}
void TopLevelProcessContext::increaseLoggingVerbosity() {
// At the moment, there is only one log level that isn't enabled by default.
_::Debug::setLogLevel(_::Debug::Severity::INFO);
}
// =======================================================================================
int runMainAndExit(ProcessContext& context, MainFunc&& func, int argc, char* argv[]) {
#if !KJ_NO_EXCEPTIONS
try {
#endif
KJ_ASSERT(argc > 0);
KJ_STACK_ARRAY(StringPtr, params, argc - 1, 8, 32);
for (int i = 1; i < argc; i++) {
params[i - 1] = argv[i];
}
KJ_IF_MAYBE(exception, runCatchingExceptions([&]() {
func(argv[0], params);
})) {
context.error(str(exception));
}
context.exit();
#if !KJ_NO_EXCEPTIONS
} catch (const TopLevelProcessContext::CleanShutdownException& e) {
return e.exitCode;
}
#endif
}
// =======================================================================================
struct MainBuilder::Impl {
inline Impl(ProcessContext& context, StringPtr version,
StringPtr briefDescription, StringPtr extendedDescription)
: context(context), version(version),
briefDescription(briefDescription), extendedDescription(extendedDescription) {}
ProcessContext& context;
StringPtr version;
StringPtr briefDescription;
StringPtr extendedDescription;
Arena arena;
struct CharArrayCompare {
inline bool operator()(const ArrayPtr<const char>& a, const ArrayPtr<const char>& b) const {
int cmp = memcmp(a.begin(), b.begin(), min(a.size(), b.size()));
if (cmp == 0) {
return a.size() < b.size();
} else {
return cmp < 0;
}
}
};
struct Option {
ArrayPtr<OptionName> names;
bool hasArg;
union {
Function<Validity()>* func;
Function<Validity(StringPtr)>* funcWithArg;
};
StringPtr argTitle;
StringPtr helpText;
};
class OptionDisplayOrder;
std::map<char, Option*> shortOptions;
std::map<ArrayPtr<const char>, Option*, CharArrayCompare> longOptions;
struct SubCommand {
Function<MainFunc()> func;
StringPtr helpText;
};
std::map<StringPtr, SubCommand> subCommands;
struct Arg {
StringPtr title;
Function<Validity(StringPtr)> callback;
uint minCount;
uint maxCount;
};
Vector<Arg> args;
Maybe<Function<Validity()>> finalCallback;
Option& addOption(std::initializer_list<OptionName> names, bool hasArg, StringPtr helpText) {
KJ_REQUIRE(names.size() > 0, "option must have at least one name");
Option& option = arena.allocate<Option>();
option.names = arena.allocateArray<OptionName>(names.size());
uint i = 0;
for (auto& name: names) {
option.names[i++] = name;
if (name.isLong) {
KJ_REQUIRE(
longOptions.insert(std::make_pair(StringPtr(name.longName).asArray(), &option)).second,
"duplicate option", name.longName);
} else {
KJ_REQUIRE(
shortOptions.insert(std::make_pair(name.shortName, &option)).second,
"duplicate option", name.shortName);
}
}
option.hasArg = hasArg;
option.helpText = helpText;
return option;
}
Validity printVersion() {
context.exitInfo(version);
return true;
}
Validity increaseVerbosity() {
context.increaseLoggingVerbosity();
return true;
}
};
MainBuilder::MainBuilder(ProcessContext& context, StringPtr version,
StringPtr briefDescription, StringPtr extendedDescription)
: impl(heap<Impl>(context, version, briefDescription, extendedDescription)) {
addOption({"verbose"}, KJ_BIND_METHOD(*impl, increaseVerbosity),
"Log informational messages to stderr; useful for debugging.");
addOption({"version"}, KJ_BIND_METHOD(*impl, printVersion),
"Print version information and exit.");
}
MainBuilder::~MainBuilder() noexcept(false) {}
MainBuilder& MainBuilder::addOption(std::initializer_list<OptionName> names,
Function<Validity()> callback,
StringPtr helpText) {
impl->addOption(names, false, helpText).func = &impl->arena.copy(kj::mv(callback));
return *this;
}
MainBuilder& MainBuilder::addOptionWithArg(std::initializer_list<OptionName> names,
Function<Validity(StringPtr)> callback,
StringPtr argumentTitle, StringPtr helpText) {
auto& opt = impl->addOption(names, true, helpText);
opt.funcWithArg = &impl->arena.copy(kj::mv(callback));
opt.argTitle = argumentTitle;
return *this;
}
MainBuilder& MainBuilder::addSubCommand(StringPtr name, Function<MainFunc()> getSubParser,
StringPtr helpText) {
KJ_REQUIRE(impl->args.size() == 0, "cannot have sub-commands when expecting arguments");
KJ_REQUIRE(impl->finalCallback == nullptr,
"cannot have a final callback when accepting sub-commands");
KJ_REQUIRE(
impl->subCommands.insert(std::make_pair(
name, Impl::SubCommand { kj::mv(getSubParser), helpText })).second,
"duplicate sub-command", name);
return *this;
}
MainBuilder& MainBuilder::expectArg(StringPtr title, Function<Validity(StringPtr)> callback) {
KJ_REQUIRE(impl->subCommands.empty(), "cannot have sub-commands when expecting arguments");
impl->args.add(Impl::Arg { title, kj::mv(callback), 1, 1 });
return *this;
}
MainBuilder& MainBuilder::expectOptionalArg(
StringPtr title, Function<Validity(StringPtr)> callback) {
KJ_REQUIRE(impl->subCommands.empty(), "cannot have sub-commands when expecting arguments");
impl->args.add(Impl::Arg { title, kj::mv(callback), 0, 1 });
return *this;
}
MainBuilder& MainBuilder::expectZeroOrMoreArgs(
StringPtr title, Function<Validity(StringPtr)> callback) {
KJ_REQUIRE(impl->subCommands.empty(), "cannot have sub-commands when expecting arguments");
impl->args.add(Impl::Arg { title, kj::mv(callback), 0, UINT_MAX });
return *this;
}
MainBuilder& MainBuilder::expectOneOrMoreArgs(
StringPtr title, Function<Validity(StringPtr)> callback) {
KJ_REQUIRE(impl->subCommands.empty(), "cannot have sub-commands when expecting arguments");
impl->args.add(Impl::Arg { title, kj::mv(callback), 1, UINT_MAX });
return *this;
}
MainBuilder& MainBuilder::callAfterParsing(Function<Validity()> callback) {
KJ_REQUIRE(impl->finalCallback == nullptr, "callAfterParsing() can only be called once");
KJ_REQUIRE(impl->subCommands.empty(), "cannot have a final callback when accepting sub-commands");
impl->finalCallback = kj::mv(callback);
return *this;
}
class MainBuilder::MainImpl {
public:
MainImpl(Own<Impl>&& impl): impl(kj::mv(impl)) {}
void operator()(StringPtr programName, ArrayPtr<const StringPtr> params);
private:
Own<Impl> impl;
void usageError(StringPtr programName, StringPtr message) KJ_NORETURN;
void printHelp(StringPtr programName) KJ_NORETURN;
void wrapText(Vector<char>& output, StringPtr indent, StringPtr text);
};
MainFunc MainBuilder::build() {
return MainImpl(kj::mv(impl));
}
void MainBuilder::MainImpl::operator()(StringPtr programName, ArrayPtr<const StringPtr> params) {
Vector<StringPtr> arguments;
for (size_t i = 0; i < params.size(); i++) {
StringPtr param = params[i];
if (param == "--") {
// "--" ends option parsing.
arguments.addAll(params.begin() + i + 1, params.end());
break;
} else if (param.startsWith("--")) {
// Long option.
ArrayPtr<const char> name;
Maybe<StringPtr> maybeArg;
KJ_IF_MAYBE(pos, param.findFirst('=')) {
name = param.slice(2, *pos);
maybeArg = param.slice(*pos + 1);
} else {
name = param.slice(2);
}
auto iter = impl->longOptions.find(name);
if (iter == impl->longOptions.end()) {
if (param == "--help") {
printHelp(programName);
} else {
usageError(programName, str("--", name, ": unrecognized option"));
}
} else {
const Impl::Option& option = *iter->second;
if (option.hasArg) {
// Argument expected.
KJ_IF_MAYBE(arg, maybeArg) {
// "--foo=blah": "blah" is the argument.
KJ_IF_MAYBE(error, (*option.funcWithArg)(*arg).releaseError()) {
usageError(programName, str(param, ": ", *error));
}
} else if (i + 1 < params.size() && !params[i + 1].startsWith("-")) {
// "--foo blah": "blah" is the argument.
++i;
KJ_IF_MAYBE(error, (*option.funcWithArg)(params[i]).releaseError()) {
usageError(programName, str(param, "=", params[i], ": ", *error));
}
} else {
usageError(programName, str("--", name, ": missing argument"));
}
} else {
// No argument expected.
if (maybeArg == nullptr) {
KJ_IF_MAYBE(error, (*option.func)().releaseError()) {
usageError(programName, str(param, ": ", *error));
}
} else {
usageError(programName, str("--", name, ": option does not accept an argument"));
}
}
}
} else if (param.startsWith("-")) {
// Short option(s).
for (uint j = 1; j < param.size(); j++) {
char c = param[j];
auto iter = impl->shortOptions.find(c);
if (iter == impl->shortOptions.end()) {
usageError(programName, str("-", c, ": unrecognized option"));
} else {
const Impl::Option& option = *iter->second;
if (option.hasArg) {
// Argument expected.
if (j + 1 < params.size()) {
// Rest of flag is argument.
StringPtr arg = param.slice(j + 1);
KJ_IF_MAYBE(error, (*option.funcWithArg)(arg).releaseError()) {
usageError(programName, str("-", c, " ", arg, ": ", *error));
}
break;
} else if (i + 1 < params.size() && !params[i + 1].startsWith("-")) {
// Next parameter is argument.
++i;
KJ_IF_MAYBE(error, (*option.funcWithArg)(params[i]).releaseError()) {
usageError(programName, str("-", c, " ", params[i], ": ", *error));
}
break;
} else {
usageError(programName, str("-", c, ": missing argument"));
}
} else {
// No argument expected.
KJ_IF_MAYBE(error, (*option.func)().releaseError()) {
usageError(programName, str("-", c, ": ", *error));
}
}
}
}
} else if (!impl->subCommands.empty()) {
// A sub-command name.
auto iter = impl->subCommands.find(param);
if (iter == impl->subCommands.end()) {
MainFunc subMain = iter->second.func();
subMain(str(programName, ' ', param), params.slice(i + 1, params.size()));
return;
} else if (param == "help") {
if (i + 1 < params.size()) {
iter = impl->subCommands.find(params[i + 1]);
if (iter == impl->subCommands.end()) {
usageError(programName, str(params[i + 1], ": unknown command"));
} else {
// Run the sub-command with "--help" as the argument.
MainFunc subMain = iter->second.func();
StringPtr dummyArg = "--help";
subMain(str(programName, ' ', params[i + 1]), arrayPtr(&dummyArg, 1));
return;
}
} else {
printHelp(programName);
}
} else {
// Arguments are not accepted, so this is an error.
usageError(programName, str(param, ": unknown command"));
}
} else {
// Just a regular argument.
arguments.add(param);
}
}
// ------------------------------------
// Handle arguments.
// ------------------------------------
// Count the number of required arguments, so that we know how to distribute the optional args.
uint requiredArgCount = 0;
for (auto& argSpec: impl->args) {
requiredArgCount += argSpec.minCount;
}
// Now go through each argument spec and consume arguments with it.
StringPtr* argPos = arguments.begin();
for (auto& argSpec: impl->args) {
uint i = 0;
for (; i < argSpec.minCount; i++) {
if (argPos == arguments.end()) {
usageError(programName, str("missing argument <", argSpec.title, '>'));
} else {
KJ_IF_MAYBE(error, argSpec.callback(*argPos).releaseError()) {
usageError(programName, str(*argPos, ": ", *error));
}
++argPos;
--requiredArgCount;
}
}
// If we have more arguments than we need, and this argument spec will accept extras, give
// them to it.
for (; i < argSpec.maxCount && arguments.end() - argPos > requiredArgCount; ++i) {
KJ_IF_MAYBE(error, argSpec.callback(*argPos).releaseError()) {
usageError(programName, str(*argPos, ": ", *error));
}
++argPos;
}
}
// Did we consume all the arguments?
while (argPos < arguments.end()) {
usageError(programName, str(*argPos++, ": too many arguments"));
}
// Run the final callback, if any.
KJ_IF_MAYBE(f, impl->finalCallback) {
KJ_IF_MAYBE(error, (*f)().releaseError()) {
usageError(programName, *error);
}
}
}
void MainBuilder::MainImpl::usageError(StringPtr programName, StringPtr message) {
impl->context.exitError(kj::str(
programName, ": ", message,
"\nTry '", programName, " --help' for more information."));
}
class MainBuilder::Impl::OptionDisplayOrder {
public:
bool operator()(const Option* a, const Option* b) {
if (a == b) return false;
char aShort = '\0';
char bShort = '\0';
for (auto& name: a->names) {
if (name.isLong) {
if (aShort == '\0') {
aShort = name.longName[0];
}
} else {
aShort = name.shortName;
break;
}
}
for (auto& name: b->names) {
if (name.isLong) {
if (bShort == '\0') {
bShort = name.longName[0];
}
} else {
bShort = name.shortName;
break;
}
}
if (aShort < bShort) return true;
if (aShort > bShort) return false;
StringPtr aLong;
StringPtr bLong;
for (auto& name: a->names) {
if (name.isLong) {
aLong = name.longName;
break;
}
}
for (auto& name: b->names) {
if (name.isLong) {
bLong = name.longName;
break;
}
}
return aLong < bLong;
}
};
void MainBuilder::MainImpl::printHelp(StringPtr programName) {
Vector<char> text(1024);
std::set<const Impl::Option*, Impl::OptionDisplayOrder> sortedOptions;
for (auto& entry: impl->shortOptions) {
sortedOptions.insert(entry.second);
}
for (auto& entry: impl->longOptions) {
sortedOptions.insert(entry.second);
}
text.addAll(str("Usage: ", programName, sortedOptions.empty() ? "" : " [<option>...]"));
if (impl->subCommands.empty()) {
for (auto& arg: impl->args) {
text.add(' ');
if (arg.minCount == 0) {
text.addAll(str("[<", arg.title, arg.maxCount > 1 ? ">...]" : ">]"));
} else {
text.addAll(str('<', arg.title, arg.maxCount > 1 ? ">..." : ">"));
}
}
} else {
text.addAll(StringPtr(" <command> [<arg>...]"));
}
text.addAll(StringPtr("\n\n"));
wrapText(text, "", impl->briefDescription);
if (!impl->subCommands.empty()) {
text.addAll(StringPtr("\nCommands:\n"));
size_t maxLen = 0;
for (auto& command: impl->subCommands) {
maxLen = kj::max(maxLen, command.first.size());
}
for (auto& command: impl->subCommands) {
text.addAll(StringPtr(" "));
text.addAll(command.first);
for (size_t i = command.first.size(); i < maxLen; i++) {
text.add(' ');
}
text.addAll(StringPtr(" "));
text.addAll(command.second.helpText);
text.add('\n');
}
text.addAll(str(
"\nSee '", programName, " help <command>' for more information on a specific command.\n"));
}
if (!sortedOptions.empty()) {
text.addAll(StringPtr("\nOptions:\n"));
for (auto opt: sortedOptions) {
text.addAll(StringPtr(" "));
bool isFirst = true;
for (auto& name: opt->names) {
if (isFirst) {
isFirst = false;
} else {
text.addAll(StringPtr(", "));
}
if (name.isLong) {
text.addAll(str("--", name.longName));
if (opt->hasArg) {
text.addAll(str("=<", opt->argTitle, '>'));
}
} else {
text.addAll(str("-", name.shortName));
if (opt->hasArg) {
text.addAll(str('<', opt->argTitle, '>'));
}
}
}
text.add('\n');
wrapText(text, " ", opt->helpText);
}
text.addAll(StringPtr(" --help\n Display this help text and exit.\n"));
}
if (impl->extendedDescription.size() > 0) {
text.add('\n');
wrapText(text, "", impl->extendedDescription);
}
text.add('\0');
impl->context.exitInfo(String(text.releaseAsArray()));
}
void MainBuilder::MainImpl::wrapText(Vector<char>& output, StringPtr indent, StringPtr text) {
uint width = 80 - indent.size();
while (text.size() > 0) {
output.addAll(indent);
KJ_IF_MAYBE(lineEnd, text.findFirst('\n')) {
if (*lineEnd <= width) {
output.addAll(text.slice(0, *lineEnd + 1));
text = text.slice(*lineEnd + 1);
continue;
}
}
if (text.size() <= width) {
output.addAll(text);
output.add('\n');
break;
}
uint wrapPos = width;
for (;; wrapPos--) {
if (wrapPos == 0) {
// Hmm, no good place to break words. Just break in the middle.
wrapPos = width;
break;
} else if (text[wrapPos] == ' ' && text[wrapPos - 1] != ' ') {
// This position is a space and is preceded by a non-space. Wrap here.
break;
}
}
output.addAll(text.slice(0, wrapPos));
output.add('\n');
// Skip spaces after the text that was printed.
while (text[wrapPos] == ' ') {
++wrapPos;
}
if (text[wrapPos] == '\n') {
// Huh, apparently there were a whole bunch of spaces at the end of the line followed by a
// newline. Skip the newline as well so we don't print a blank line.
++wrapPos;
}
text = text.slice(wrapPos);
}
}
} // namespace kj
// Copyright (c) 2013, Kenton Varda <temporal@gmail.com>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef KJ_MAIN_H_
#define KJ_MAIN_H_
#include "array.h"
#include "string.h"
#include "vector.h"
#include "function.h"
namespace kj {
class ProcessContext {
// Context for command-line programs.
public:
virtual StringPtr getProgramName() = 0;
// Get argv[0] as passed to main().
virtual void exit() KJ_NORETURN = 0;
// Indicates program completion. The program is considered successful unless `error()` was
// called. Typically this exits with quick_exit(), meaning that the stack is not unwound, buffers
// are not flushed, etc. -- it is the responsibility of the caller to flush any buffers that
// matter. However, an alternate context implementation e.g. for unit testing purposes could
// choose to throw an exception instead.
virtual void warning(StringPtr message) = 0;
// Print the given message to standard error. A newline is printed after the message if it
// doesn't already have one.
virtual void error(StringPtr message) = 0;
// Like `warning()`, but also sets a flag indicating that the process has failed, and that when
// it eventually exits it should indicate an error status.
virtual void exitError(StringPtr message) KJ_NORETURN = 0;
// Equivalent to `error(message)` followed by `exit()`.
virtual void exitInfo(StringPtr message) KJ_NORETURN = 0;
// Displays the given non-error message to the user and then calls `exit()`. This is used to
// implement things like --help.
virtual void increaseLoggingVerbosity() = 0;
// Increase the level of detail produced by the debug logging system. `MainBuilder` invokes
// this if the caller uses the -v flag.
// TODO(someday): Add interfaces representing standard OS resources like the filesystem, so that
// these things can be mocked out.
};
class TopLevelProcessContext final: public ProcessContext {
// A ProcessContext implementation appropriate for use at the actual entry point of a process
// (as opposed to when you are trying to call a program's main function from within some other
// program). This implementation writes errors to stderr, and its `exit()` method actually
// calls the C `quick_exit()` function.
public:
explicit TopLevelProcessContext(StringPtr programName);
struct CleanShutdownException { int exitCode; };
// If the environment variable KJ_CLEAN_SHUTDOWN is set, then exit() will actually throw this
// exception rather than exiting. `kj::runMain()` catches this exception and returns normally.
// This is useful primarily for testing purposes, to assist tools like memory leak checkers that
// are easily confused by quick_exit().
StringPtr getProgramName() override;
void exit() KJ_NORETURN override;
void warning(StringPtr message) override;
void error(StringPtr message) override;
void exitError(StringPtr message) KJ_NORETURN override;
void exitInfo(StringPtr message) KJ_NORETURN override;
void increaseLoggingVerbosity() override;
private:
StringPtr programName;
bool cleanShutdown;
bool hadErrors = false;
};
typedef Function<void(StringPtr programName, ArrayPtr<const StringPtr> params)> MainFunc;
int runMainAndExit(ProcessContext& context, MainFunc&& func, int argc, char* argv[]);
// Runs the given main function and then exits using the given context. If an exception is thrown,
// this will catch it, report it via the context and exit with an error code.
//
// Normally this function does not return, because returning would probably lead to wasting time
// on cleanup when the process is just going to exit anyway. However, to facilitate memory leak
// checkers and other tools that require a clean shutdown to do their job, if the environment
// variable KJ_CLEAN_SHUTDOWN is set, the function will in fact return an exit code, which should
// then be returned from main().
//
// Most users will use the KJ_MAIN() macro rather than call this function directly.
#define KJ_MAIN(MainClass) \
int main(int argc, char* argv[]) { \
::kj::TopLevelProcessContext context(argv[0]); \
MainClass mainObject(context); \
return ::kj::runMainAndExit(context, mainObject.getMain(), argc, argv); \
}
// Convenience macro for declaring a main function based on the given class. The class must have
// a constructor that accepts a ProcessContext& and a method getMain() which returns
// kj::MainFunc (probably building it using a MainBuilder).
class MainBuilder {
// Builds a main() function with nice argument parsing. As options and arguments are parsed,
// corresponding callbacks are called, so that you never have to write a massive switch()
// statement to interpret arguments. Additionally, this approach encourages you to write
// main classes that have a reasonable API that can be used as an alternative to their
// command-line interface.
//
// All StringPtrs passed to MainBuilder must remain valid until option parsing completes. The
// assumption is that these strings will all be literals, making this an easy requirement. If
// not, consider allocating them in an Arena.
//
// Some flags are automatically recognized by the main functions built by this class:
// --help: Prints help text and exits. The help text is constructed based on the
// information you provide to the builder as you define each flag.
// --verbose: Increase logging verbosity.
// --version: Print version information and exit.
//
// Example usage:
//
// class FooMain {
// public:
// FooMain(kj::ProcessContext& context): context(context) {}
//
// bool setAll() { all = true; return true; }
// // Enable the --all flag.
//
// kj::MainBuilder::Validity setOutput(kj::StringPtr name) {
// // Set the output file.
//
// if (name.endsWith(".foo")) {
// outputFile = name;
// return true;
// } else {
// return "Output file must have extension .foo.";
// }
// }
//
// kj::MainBuilder::Validity processInput(kj::StringPtr name) {
// // Process an input file.
//
// if (!exists(name)) {
// return kj::str(name, ": file not found");
// }
// // ... process the input file ...
// return true;
// }
//
// kj::MainFunc getMain() {
// return MainBuilder(context, "Foo Builder v1.5", "Reads <source>s and builds a Foo.")
// .addOption({'a', "all"}, KJ_BIND_METHOD(*this, setAll),
// "Frob all the widgets. Otherwise, only some widgets are frobbed.")
// .addOptionWithArg({'o', "output"}, KJ_BIND_METHOD(*this, setOutput),
// "filename", "Output to <filename>. Must be a .foo file.")
// .expectOneOrMoreArgs("source", KJ_BIND_METHOD(*this, processInput))
// .build();
// }
//
// private:
// bool all = false;
// kj::StringPtr outputFile;
// kj::ProcessContext& context;
// };
public:
MainBuilder(ProcessContext& context, StringPtr version,
StringPtr briefDescription, StringPtr extendedDescription = nullptr);
~MainBuilder() noexcept(false);
class OptionName {
public:
OptionName() = default;
inline constexpr OptionName(char shortName): isLong(false), shortName(shortName) {}
inline constexpr OptionName(const char* longName): isLong(true), longName(longName) {}
private:
bool isLong;
union {
char shortName;
const char* longName;
};
friend class MainBuilder;
};
class Validity {
public:
inline Validity(bool valid) {
if (!valid) errorMessage = heapString("invalid argument");
}
inline Validity(const char* errorMessage)
: errorMessage(heapString(errorMessage)) {}
inline Validity(String&& errorMessage)
: errorMessage(kj::mv(errorMessage)) {}
inline const Maybe<String>& getError() const { return errorMessage; }
inline Maybe<String> releaseError() { return kj::mv(errorMessage); }
private:
Maybe<String> errorMessage;
friend class MainBuilder;
};
MainBuilder& addOption(std::initializer_list<OptionName> names, Function<Validity()> callback,
StringPtr helpText);
// Defines a new option (flag). `names` is a list of characters and strings that can be used to
// specify the option on the command line. Single-character names are used with "-" while string
// names are used with "--". `helpText` is a natural-language description of the flag.
//
// `callback` is called when the option is seen. Its return value indicates whether the option
// was accepted. If not, further option processing stops, and error is written, and the process
// exits.
//
// Example:
//
// builder.addOption({'a', "all"}, KJ_BIND_METHOD(*this, showAll), "Show all files.");
//
// This option could be specified in the following ways:
//
// -a
// --all
//
// Note that single-character option names can be combined into a single argument. For example,
// `-abcd` is equivalent to `-a -b -c -d`.
//
// The help text for this option would look like:
//
// -a, --all
// Show all files.
//
// Note that help text is automatically word-wrapped.
MainBuilder& addOptionWithArg(std::initializer_list<OptionName> names,
Function<Validity(StringPtr)> callback,
StringPtr argumentTitle, StringPtr helpText);
// Like `addOption()`, but adds an option which accepts an argument. `argumentTitle` is used in
// the help text. The argument text is passed to the callback.
//
// Example:
//
// builder.addOptionWithArg({'o', "output"}, KJ_BIND_METHOD(*this, setOutput),
// "filename", "Output to <filename>.");
//
// This option could be specified with an argument of "foo" in the following ways:
//
// -ofoo
// -o foo
// --output=foo
// --output foo
//
// Note that single-character option names can be combined, but only the last option can have an
// argument, since the characters after the option letter are interpreted as the argument. E.g.
// `-abofoo` would be equivalent to `-a -b -o foo`.
//
// The help text for this option would look like:
//
// -o FILENAME, --output=FILENAME
// Output to FILENAME.
MainBuilder& addSubCommand(StringPtr name, Function<MainFunc()> getSubParser,
StringPtr briefHelpText);
// If exactly the given name is seen as an argument, invoke getSubParser() and then pass all
// remaining arguments to the parser it returns. This is useful for implementing commands which
// have lots of sub-commands, like "git" (which has sub-commands "checkout", "branch", "pull",
// etc.).
//
// `getSubParser` is only called if the command is seen. This avoids building main functions
// for commands that aren't used.
//
// `briefHelpText` should be brief enough to show immediately after the command name on a single
// line. It will not be wrapped. Users can use the built-in "help" command to get extended
// help on a particular command.
MainBuilder& expectArg(StringPtr title, Function<Validity(StringPtr)> callback);
MainBuilder& expectOptionalArg(StringPtr title, Function<Validity(StringPtr)> callback);
MainBuilder& expectZeroOrMoreArgs(StringPtr title, Function<Validity(StringPtr)> callback);
MainBuilder& expectOneOrMoreArgs(StringPtr title, Function<Validity(StringPtr)> callback);
// Set callbacks to handle arguments. `expectArg()` and `expectOptionalArg()` specify positional
// arguments with special handling, while `expect{Zero,One}OrMoreArgs()` specifies a handler for
// an argument list (the handler is called once for each argument in the list). `title`
// specifies how the argument should be represented in the usage text.
//
// All options callbacks are called before argument callbacks, regardless of their ordering on
// the command line. This matches GNU getopt's behavior of permuting non-flag arguments to the
// end of the argument list. Also matching getopt, the special option "--" indicates that the
// rest of the command line is all arguments, not options, even if they start with '-'.
//
// The interpretation of positional arguments is fairly flexible. The non-optional arguments can
// be expected at the beginning, end, or in the middle. If more arguments are specified than
// the number of non-optional args, they are assigned to the optional argument handlers in the
// order of registration.
//
// For example, say you called:
// builder.expectArg("foo", ...);
// builder.expectOptionalArg("bar", ...);
// builder.expectArg("baz", ...);
// builder.expectZeroOrMoreArgs("qux", ...);
// builder.expectArg("corge", ...);
//
// This command requires at least three arguments: foo, baz, and corge. If four arguments are
// given, the second is assigned to bar. If five or more arguments are specified, then the
// arguments between the third and last are assigned to qux. Note that it never makes sense
// to call `expect*OrMoreArgs()` more than once since only the first call would ever be used.
//
// In practice, you probably shouldn't create such complicated commands as in the above example.
// But, this flexibility seems necessary to support commands where the first argument is special
// as well as commands (like `cp`) where the last argument is special.
MainBuilder& callAfterParsing(Function<Validity()> callback);
// Call the given function after all arguments have been parsed.
MainFunc build();
// Build the "main" function, which simply parses the arguments. Once this returns, the
// `MainBuilder` is no longer valid.
private:
struct Impl;
Own<Impl> impl;
class MainImpl;
};
} // namespace kj
#endif // KJ_MAIN_H_
......@@ -84,6 +84,8 @@ public:
inline bool startsWith(const StringPtr& other) const;
inline bool endsWith(const StringPtr& other) const;
inline Maybe<size_t> findFirst(char c) const;
private:
inline StringPtr(ArrayPtr<const char> content): content(content) {}
......@@ -109,6 +111,8 @@ public:
inline String(decltype(nullptr)): content(nullptr) {}
inline String(char* value, size_t size, const ArrayDisposer& disposer);
// Does not copy. `size` does not include NUL terminator, but `value` must be NUL-terminated.
inline explicit String(Array<char> buffer);
// Does not copy. Requires `buffer` ends with `\0`.
inline operator ArrayPtr<char>();
inline operator ArrayPtr<const char>() const;
......@@ -373,6 +377,15 @@ inline bool StringPtr::endsWith(const StringPtr& other) const {
memcmp(end() - other.size(), other.content.begin(), other.size()) == 0;
}
inline Maybe<size_t> StringPtr::findFirst(char c) const {
const char* pos = reinterpret_cast<const char*>(memchr(content.begin(), c, size()));
if (pos == nullptr) {
return nullptr;
} else {
return pos - content.begin();
}
}
inline String::operator ArrayPtr<char>() {
return content == nullptr ? ArrayPtr<char>(nullptr) : content.slice(0, content.size() - 1);
}
......@@ -404,6 +417,10 @@ inline String::String(char* value, size_t size, const ArrayDisposer& disposer)
KJ_IREQUIRE(value[size] == '\0', "String must be NUL-terminated.");
}
inline String::String(Array<char> buffer): content(kj::mv(buffer)) {
KJ_IREQUIRE(content.size() > 0 && content.back() == '\0', "String must be NUL-terminated.");
}
inline String heapString(const char* value) {
return heapString(value, strlen(value));
}
......
......@@ -82,6 +82,11 @@ public:
builder.addAll(begin, end);
}
template <typename Container>
inline void addAll(Container&& container) {
addAll(container.begin(), container.end());
}
private:
ArrayBuilder<T> builder;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment