From 58b920ec1afb8ec9dcd1f696de398f2543c14323 Mon Sep 17 00:00:00 2001
From: Kenton Varda <temporal@gmail.com>
Date: Wed, 28 Aug 2013 04:01:06 -0700
Subject: [PATCH] Extend 'capnp const' to support evaluating field default
 values as well as inner members of a value.

---
 c++/src/capnp/compiler/capnp.c++ | 165 ++++++++++++++++++++++++++-----
 c++/src/capnp/schema.c++         |   5 +
 c++/src/capnp/schema.h           |   3 +
 3 files changed, 149 insertions(+), 24 deletions(-)

diff --git a/c++/src/capnp/compiler/capnp.c++ b/c++/src/capnp/compiler/capnp.c++
index 57c536e6..ea14f934 100644
--- a/c++/src/capnp/compiler/capnp.c++
+++ b/c++/src/capnp/compiler/capnp.c++
@@ -35,6 +35,7 @@
 #include "../message.h"
 #include <iostream>
 #include <kj/main.h>
+#include <kj/parse/char.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <sys/wait.h>
@@ -204,10 +205,17 @@ public:
     annotationFlag = Compiler::DROP_ANNOTATIONS;
 
     kj::MainBuilder builder(context, VERSION_STRING,
-          "Evaluates the `const` declaration <name> defined in <schema-file> and outputs the "
-          "value in text or binary format.  Since consts can have complex struct types, and "
-          "since you can build a const using other const values, this can be a convenient way "
-          "to write text-format config files which are compiled to binary before deployment.",
+          "Prints (or encodes) the value of <name>, which must be defined in <schema-file>.  "
+          "<name> must refer to a const declaration, a field of a struct type (prints the default "
+          "value), or a field or list element nested within some other value.  Examples:\n"
+          "    capnp eval myschema.capnp MyType.someField\n"
+          "    capnp eval myschema.capnp someConstant\n"
+          "    capnp eval myschema.capnp someConstant.someField\n"
+          "    capnp eval myschema.capnp someConstant.someList[4]\n"
+          "    capnp eval myschema.capnp someConstant.someList[4].anotherField[1][2][3]\n"
+          "Since consts can have complex struct types, and since you can define a const using "
+          "imporst and variable substitution, this can be a convenient way to write text-format "
+          "config files which are compiled to binary before deployment.",
 
           "By default the value is written in text format and can have any type.  The -b, -p, "
           "and --flat flags specify binary output, in which case the const must be of struct "
@@ -1036,38 +1044,147 @@ public:
     KJ_CLANG_KNOWS_THIS_IS_UNREACHABLE_BUT_GCC_DOESNT;
   }
 
-  kj::MainBuilder::Validity evalConst(kj::StringPtr type) {
+  kj::MainBuilder::Validity evalConst(kj::StringPtr name) {
     KJ_ASSERT(sourceFiles.size() == 1);
 
-    KJ_IF_MAYBE(schema, resolveName(sourceFiles[0].id, type)) {
-      if (schema->getProto().which() != schema::Node::CONST) {
-        return "not a const";
+    auto parser = kj::parse::sequence(
+        kj::parse::many(
+            kj::parse::sequence(
+                kj::parse::identifier,
+                kj::parse::many(
+                    kj::parse::sequence(
+                        kj::parse::exactChar<'['>(),
+                        kj::parse::integer,
+                        kj::parse::exactChar<']'>())),
+                kj::parse::oneOf(
+                    kj::parse::endOfInput,
+                    kj::parse::sequence(
+                        kj::parse::exactChar<'.'>(),
+                        kj::parse::notLookingAt(kj::parse::endOfInput))))),
+        kj::parse::endOfInput);
+
+    kj::parse::IteratorInput<char, const char*> input(name.begin(), name.end());
+
+    kj::Array<kj::Tuple<kj::String, kj::Array<uint64_t>>> nameParts;
+    KJ_IF_MAYBE(p, parser(input)) {
+      nameParts = kj::mv(*p);
+    } else {
+      return "invalid syntax";
+    }
+
+    auto pos = nameParts.begin();
+
+    // Traverse the path to find a schema.
+    uint64_t scopeId = sourceFiles[0].id;
+    bool stoppedAtSubscript = false;
+    for (; pos != nameParts.end(); ++pos) {
+      kj::StringPtr part = kj::get<0>(*pos);
+
+      KJ_IF_MAYBE(childId, compiler->lookup(scopeId, part)) {
+        scopeId = *childId;
+
+        if (kj::get<1>(*pos).size() > 0) {
+          stoppedAtSubscript = true;
+          break;
+        }
+      } else {
+        break;
       }
+    }
+    Schema schema = compiler->getLoader().get(scopeId);
+
+    // Evaluate this schema to a DynamicValue.
+    DynamicValue::Reader value;
+    word zeroWord[1];
+    memset(&zeroWord, 0, sizeof(zeroWord));
+    kj::ArrayPtr<const word> segments[1] = { zeroWord };
+    SegmentArrayMessageReader emptyMessage(segments);
+    switch (schema.getProto().which()) {
+      case schema::Node::CONST:
+        value = schema.asConst();
+        break;
+
+      case schema::Node::STRUCT:
+        if (pos == nameParts.end()) {
+          return kj::str("'", schema.getShortDisplayName(), "' cannot be evaluated.");
+        }
 
-      DynamicValue::Reader value = schema->asConst();
+        // Use the struct's default value.
+        value = emptyMessage.getRoot<DynamicStruct>(schema.asStruct());
+        break;
 
-      if (binary || packed || flat) {
-        if (value.getType() != DynamicValue::STRUCT) {
-          return "not a struct; binary output is only available on structs";
+      default:
+        if (stoppedAtSubscript) {
+          return kj::str("'", schema.getShortDisplayName(), "' is not a list.");
+        } else if (pos != nameParts.end()) {
+          return kj::str("'", kj::get<0>(*pos), "' is not defined.");
+        } else {
+          return kj::str("'", schema.getShortDisplayName(), "' cannot be evaluated.");
         }
+    }
 
-        kj::FdOutputStream rawOutput(STDOUT_FILENO);
-        kj::BufferedOutputStreamWrapper output(rawOutput);
-        writeFlat(value.as<DynamicStruct>(), output);
-        output.flush();
-        context.exit();
-      } else {
-        if (pretty && value.getType() == DynamicValue::STRUCT) {
-          context.exitInfo(prettyPrint(value.as<DynamicStruct>()).flatten());
-        } else if (pretty && value.getType() == DynamicValue::LIST) {
-          context.exitInfo(prettyPrint(value.as<DynamicList>()).flatten());
+    // Traverse the rest of the path as struct fields.
+    for (; pos != nameParts.end(); ++pos) {
+      kj::StringPtr partName = kj::get<0>(*pos);
+
+      if (!stoppedAtSubscript) {
+        if (value.getType() == DynamicValue::STRUCT) {
+          auto structValue = value.as<DynamicStruct>();
+          KJ_IF_MAYBE(field, structValue.getSchema().findFieldByName(partName)) {
+            value = structValue.get(*field);
+          } else {
+            return kj::str("'", kj::get<0>(pos[-1]), "' has no member '", partName, "'.");
+          }
+        } else {
+          return kj::str("'", kj::get<0>(pos[-1]), "' is not a struct.");
+        }
+      }
+
+      auto& subscripts = kj::get<1>(*pos);
+      for (uint i = 0; i < subscripts.size(); i++) {
+        uint64_t subscript = subscripts[i];
+        if (value.getType() == DynamicValue::LIST) {
+          auto listValue = value.as<DynamicList>();
+          if (subscript < listValue.size()) {
+            value = listValue[subscript];
+          } else {
+            return kj::str("'", partName, "[", kj::strArray(subscripts.slice(0, i + 1), "]["),
+                           "]' is out-of-bounds.");
+          }
         } else {
-          context.exitInfo(kj::str(value));
+          if (i > 0) {
+            return kj::str("'", partName, "[", kj::strArray(subscripts.slice(0, i), "]["),
+                           "]' is not a list.");
+          } else {
+            return kj::str("'", partName, "' is not a list.");
+          }
         }
       }
+
+      stoppedAtSubscript = false;
+    }
+
+    // OK, we have a value.  Print it.
+    if (binary || packed || flat) {
+      if (value.getType() != DynamicValue::STRUCT) {
+        return "not a struct; binary output is only available on structs";
+      }
+
+      kj::FdOutputStream rawOutput(STDOUT_FILENO);
+      kj::BufferedOutputStreamWrapper output(rawOutput);
+      writeFlat(value.as<DynamicStruct>(), output);
+      output.flush();
+      context.exit();
     } else {
-      return "no such type";
+      if (pretty && value.getType() == DynamicValue::STRUCT) {
+        context.exitInfo(prettyPrint(value.as<DynamicStruct>()).flatten());
+      } else if (pretty && value.getType() == DynamicValue::LIST) {
+        context.exitInfo(prettyPrint(value.as<DynamicList>()).flatten());
+      } else {
+        context.exitInfo(kj::str(value));
+      }
     }
+
     KJ_CLANG_KNOWS_THIS_IS_UNREACHABLE_BUT_GCC_DOESNT;
   }
 
diff --git a/c++/src/capnp/schema.c++ b/c++/src/capnp/schema.c++
index a394083f..38abb31c 100644
--- a/c++/src/capnp/schema.c++
+++ b/c++/src/capnp/schema.c++
@@ -83,6 +83,11 @@ ConstSchema Schema::asConst() const {
   return ConstSchema(raw);
 }
 
+kj::StringPtr Schema::getShortDisplayName() const {
+  auto proto = getProto();
+  return proto.getDisplayName().slice(proto.getDisplayNamePrefixLength());
+}
+
 void Schema::requireUsableAs(const _::RawSchema* expected) const {
   KJ_REQUIRE(raw == expected ||
           (raw != nullptr && expected != nullptr && raw->canCastTo == expected),
diff --git a/c++/src/capnp/schema.h b/c++/src/capnp/schema.h
index c411d309..054909b2 100644
--- a/c++/src/capnp/schema.h
+++ b/c++/src/capnp/schema.h
@@ -105,6 +105,9 @@ public:
   // - This schema was loaded with SchemaLoader, the type ID matches typeId<T>(), and
   //   loadCompiledTypeAndDependencies<T>() was called on the SchemaLoader.
 
+  kj::StringPtr getShortDisplayName() const;
+  // Get the short version of the node's display name.
+
 private:
   const _::RawSchema* raw;
 
-- 
2.18.0