Repository: microsoft/rego-cpp (language: python)

Issue:
C API



Discussion / hints:

Fix patch:
diff --git a/CHANGELOG b/CHANGELOG
index 121e5d24..99b619d9 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,20 @@
 # Changelog
 
+## 2023-09-07 - Version 0.3.3
+Adding a C API.
+
+**New Features**
+- `rego_c.h` contains a C API for the library
+- `examples/c` contains an example that builds an interpreter using the C API
+
+**Breaking Change**
+The project was incorrectly configured to set the PUBLIC CMake build directory
+to `include/rego` instead of `include`. This meant that projects consuming the
+library using CMake FetchContent would include header files without a `rego/`
+prefix, which would cause problems long term. This has been fixed, but as a result
+any projects using the library via CMake FetchContent will need to update their
+`#include` statements to include the `rego/` prefix.
+
 ## 2023-09-03 - Version 0.3.2
 Adding tests based upon the Confidential ACI Rego framework.
 
diff --git a/CMakeLists.txt b/CMakeLists.txt
index df6968d7..c7ef096d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -19,6 +19,12 @@ set ( REGOCPP_OPA_VERSION 0.55.0 )
 
 set( REGOCPP_VERSION ${REGOCPP_VERSION_MAJOR}.${REGOCPP_VERSION_MINOR}.${REGOCPP_VERSION_REVISION} )
 
+message("Configure REGOCPP_VERSION at ${REGOCPP_VERSION}")
+
+project( regocpp VERSION ${REGOCPP_VERSION} LANGUAGES CXX)
+
+# -------------------- Get version info --------------------------
+
 find_package(Git REQUIRED)
 
 execute_process(
@@ -28,9 +34,28 @@ execute_process(
   OUTPUT_STRIP_TRAILING_WHITESPACE
   )
 
-message("Configure REGOCPP_VERSION at ${REGOCPP_VERSION}")
+execute_process(
+  COMMAND git rev-parse --abbrev-ref HEAD
+  WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
+  OUTPUT_VARIABLE REGOCPP_GIT_BRANCH
+  OUTPUT_STRIP_TRAILING_WHITESPACE
+)
 
-project( regocpp VERSION ${REGOCPP_VERSION} LANGUAGES CXX)
+execute_process(
+  COMMAND git log -1 --format=%h
+  WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
+  OUTPUT_VARIABLE REGOCPP_GIT_SHORT_HASH
+  OUTPUT_STRIP_TRAILING_WHITESPACE
+  )
+
+execute_process(
+  COMMAND git log -1 --format=%aD
+  WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
+  OUTPUT_VARIABLE REGOCPP_BUILD_DATE
+  OUTPUT_STRIP_TRAILING_WHITESPACE
+  )
+
+set(REGOCPP_BUILD_TOOLCHAIN "${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}")
 
 set(CMAKE_CXX_STANDARD 20)
 set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
@@ -41,6 +66,7 @@ option(REGOCPP_BUILD_TOOLS "Specifies whether to build the command line tools" O
 option(REGOCPP_BUILD_TESTS "Specifies whether to build the tests" OFF)
 option(REGOCPP_OPA_TESTS "Specifies whether to load and run the OPA built-ins tests" OFF)
 option(REGOCPP_SPECULATIVE_LOAD_HARDENING "Specifies whether to enable speculative load hardening, only supported with Clang" OFF)
+option(REGOCPP_COPY_EXAMPLES "Specifies whether to copy the examples to the install directory" OFF)
 
 # -------------------- Find packages --------------------------
 
@@ -87,6 +113,7 @@ else()
         tools/*.cc
         tests/*.cc
         tests/*.h
+        examples/c/main.c
   )
 
   add_custom_target(regocpp_format
@@ -157,6 +184,9 @@ install(FILES ${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake
 
 if (REGOCPP_BUILD_TOOLS)
   install(TARGETS rego_interpreter rego_trieste RUNTIME)
+endif()
+
+if (REGOCPP_COPY_EXAMPLES)
   install(DIRECTORY tools/examples DESTINATION .)
 endif()
 
diff --git a/CMakePresets.json b/CMakePresets.json
index fd4fa5c6..f7dbe60d 100644
--- a/CMakePresets.json
+++ b/CMakePresets.json
@@ -11,7 +11,8 @@
         "CMAKE_INSTALL_PREFIX": "${sourceDir}/build/dist",
         "CMAKE_CXX_COMPILER": "clang++",
         "REGOCPP_BUILD_TESTS": "ON",
-        "REGOCPP_BUILD_TOOLS": "ON"
+        "REGOCPP_BUILD_TOOLS": "ON",
+        "REGOCPP_COPY_EXAMPLES": "ON"
       }
     },
     {
@@ -25,6 +26,7 @@
         "CMAKE_CXX_COMPILER": "clang++",
         "REGOCPP_BUILD_TESTS": "ON",
         "REGOCPP_BUILD_TOOLS": "ON",
+        "REGOCPP_COPY_EXAMPLES": "ON",
         "REGOCPP_OPA_TESTS": "ON"
       }
     },
@@ -36,7 +38,8 @@
         "CMAKE_BUILD_TYPE": "Debug",
         "CMAKE_INSTALL_PREFIX": "${sourceDir}/build/dist",
         "REGOCPP_BUILD_TESTS": "ON",
-        "REGOCPP_BUILD_TOOLS": "ON"
+        "REGOCPP_BUILD_TOOLS": "ON",
+        "REGOCPP_COPY_EXAMPLES": "ON"
       }
     },
     {
@@ -48,6 +51,7 @@
         "CMAKE_INSTALL_PREFIX": "${sourceDir}/build/dist",
         "REGOCPP_BUILD_TESTS": "ON",
         "REGOCPP_BUILD_TOOLS": "ON",
+        "REGOCPP_COPY_EXAMPLES": "ON",
         "REGOCPP_OPA_TESTS": "ON"
       }
     },
@@ -61,7 +65,8 @@
         "CMAKE_INSTALL_PREFIX": "${sourceDir}/build/dist",
         "CMAKE_CXX_COMPILER": "clang++",
         "REGOCPP_BUILD_TESTS": "ON",
-        "REGOCPP_BUILD_TOOLS": "ON"
+        "REGOCPP_BUILD_TOOLS": "ON",
+        "REGOCPP_COPY_EXAMPLES": "ON"
       }
     },
     {
@@ -75,6 +80,7 @@
         "CMAKE_CXX_COMPILER": "clang++",
         "REGOCPP_BUILD_TESTS": "ON",
         "REGOCPP_BUILD_TOOLS": "ON",
+        "REGOCPP_COPY_EXAMPLES": "ON",
         "REGOCPP_OPA_TESTS": "ON"
       }
     },
@@ -86,7 +92,8 @@
         "CMAKE_BUILD_TYPE": "Release",
         "CMAKE_INSTALL_PREFIX": "${sourceDir}/build/dist",
         "REGOCPP_BUILD_TESTS": "ON",
-        "REGOCPP_BUILD_TOOLS": "ON"
+        "REGOCPP_BUILD_TOOLS": "ON",
+        "REGOCPP_COPY_EXAMPLES": "ON"
       }
     },
     {
@@ -98,6 +105,7 @@
         "CMAKE_INSTALL_PREFIX": "${sourceDir}/build/dist",
         "REGOCPP_BUILD_TESTS": "ON",
         "REGOCPP_BUILD_TOOLS": "ON",
+        "REGOCPP_COPY_EXAMPLES": "ON",
         "REGOCPP_OPA_TESTS": "ON"
       }
     }
diff --git a/VERSION b/VERSION
index 9fc80f93..87a08711 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-0.3.2
\ No newline at end of file
+0.3.3
\ No newline at end of file
diff --git a/examples/README.md b/examples/README.md
new file mode 100644
index 00000000..8e0959f9
--- /dev/null
+++ b/examples/README.md
@@ -0,0 +1,38 @@
+# Examples
+
+This directory contains examples of how to use the library from the different
+languages we support (other than C++. For C++ examples see the
+[tests](../tests) or [tools](../tools) directories).
+
+At the moment we support C and C++ only, but we plan to support Rust, Python,
+and .NET wrappers, with more as demand dictates.
+
+## C
+The C example is a [simple command line tool](c/main.c) that takes one or more
+Rego data, module, and input files and evaluates a query against them.
+
+To build it on Linux:
+```bash
+cd examples/c
+mkdir build
+cd build
+cmake .. --preset release-clang
+ninja install
+```
+
+and on Windows:
+
+```cmd
+cd examples\c
+mkdir build
+cd build
+cmake .. --preset release
+cmake --build . --config Release --target INSTALL
+```
+
+The resulting executable can be called from within the `dist` directory:
+
+```bash
+cd dist
+./bin/regoc -d examples/scalars.rego -q data.scalars.greeting
+```
diff --git a/examples/c/CMakeLists.txt b/examples/c/CMakeLists.txt
new file mode 100644
index 00000000..ad19557e
--- /dev/null
+++ b/examples/c/CMakeLists.txt
@@ -0,0 +1,32 @@
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+
+cmake_minimum_required(VERSION 3.15)
+
+include(FetchContent)
+
+project( regoc VERSION 0.1.0 LANGUAGES C CXX )
+
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
+FetchContent_Declare(
+  regocpp
+  GIT_REPOSITORY https://github.com/matajoh/rego-cpp
+  GIT_TAG        46101f7e89ca36b8b2c79f12ea411cb74827bb72
+)
+
+FetchContent_Declare(
+  cargs
+  GIT_REPOSITORY https://github.com/likle/cargs
+  GIT_TAG        v1.0.3
+)
+
+set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) 
+
+FetchContent_MakeAvailable(regocpp)
+FetchContent_MakeAvailable(cargs)
+
+add_executable(regoc main.c)
+target_link_libraries(regoc PRIVATE regocpp::rego cargs)
+
+install(TARGETS regoc DESTINATION bin)
\ No newline at end of file
diff --git a/examples/c/CMakePresets.json b/examples/c/CMakePresets.json
new file mode 100644
index 00000000..49db770b
--- /dev/null
+++ b/examples/c/CMakePresets.json
@@ -0,0 +1,51 @@
+{
+  "version": 3,
+  "configurePresets": [
+    {
+      "name": "debug-clang",
+      "displayName": "Debug Build using clang",
+      "description": "Sets up a debug build that uses Clang",
+      "generator": "Ninja",
+      "cacheVariables": {
+        "CMAKE_BUILD_TYPE": "Debug",
+        "CMAKE_INSTALL_PREFIX": "${sourceDir}/build/dist",
+        "CMAKE_C_COMPILER": "clang",
+        "CMAKE_CXX_COMPILER": "clang++",
+        "REGOCPP_COPY_EXAMPLES": "ON"
+      }
+    },
+    {
+      "name": "debug",
+      "displayName": "Debug Build",
+      "description": "Sets up a debug build that uses the default compiler and generator",
+      "cacheVariables": {
+        "CMAKE_BUILD_TYPE": "Debug",
+        "CMAKE_INSTALL_PREFIX": "${sourceDir}/build/dist",
+        "REGOCPP_COPY_EXAMPLES": "ON"
+      }
+    },
+    {
+      "name": "release-clang",
+      "displayName": "Release Build using clang",
+      "description": "Sets up a release build that uses Clang",
+      "generator": "Ninja",
+      "cacheVariables": {
+        "CMAKE_BUILD_TYPE": "Release",
+        "CMAKE_INSTALL_PREFIX": "${sourceDir}/build/dist",
+        "CMAKE_C_COMPILER": "clang",
+        "CMAKE_CXX_COMPILER": "clang++",
+        "REGOCPP_COPY_EXAMPLES": "ON"
+      }
+    },
+    {
+      "name": "release",
+      "displayName": "Release Build",
+      "description": "Sets up a release build that uses the default compiler and generator",
+      "cacheVariables": {
+        "CMAKE_BUILD_TYPE": "Release",
+        "CMAKE_INSTALL_PREFIX": "${sourceDir}/build/dist",
+        "REGOCPP_COPY_EXAMPLES": "ON"
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/examples/c/main.c b/examples/c/main.c
new file mode 100644
index 00000000..da8ec7e1
--- /dev/null
+++ b/examples/c/main.c
@@ -0,0 +1,321 @@
+#include <cargs.h>
+#include <rego/rego_c.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define MAX_DATA_FILES 128
+#define MAX_VALUE_LENGTH 256
+
+/**
+ * This is the main configuration of all options available.
+ */
+static struct cag_option options[] = {
+  {.identifier = 'l',
+   .access_letters = "l",
+   .access_name = "logging",
+   .value_name = NULL,
+   .description = "Enable logging"},
+
+  {.identifier = 'r',
+   .access_letters = "r",
+   .access_name = "raw",
+   .value_name = NULL,
+   .description = "Use the raw query interface (print node objects)"},
+
+  {.identifier = 'q',
+   .access_letters = "q",
+   .access_name = "query",
+   .value_name = "VALUE",
+   .description = "Query"},
+
+  {.identifier = 'i',
+   .access_letters = "i",
+   .access_name = "input",
+   .value_name = "VALUE",
+   .description = "Input JSON file"},
+
+  {.identifier = 'd',
+   .access_letters = "d",
+   .access_name = "data",
+   .value_name = "VALUE",
+   .description = "Data/Policy Files"},
+
+  {.identifier = 'h',
+   .access_letters = "h",
+   .access_name = "help",
+   .value_name = NULL,
+   .description = "Shows the command help"}};
+
+/**
+ * This is a custom project configuration structure where you can store the
+ * parsed information.
+ */
+struct regoc_configuration
+{
+  bool logging_enabled;
+  bool use_raw_query;
+  const char* data_files[MAX_DATA_FILES];
+  unsigned int data_files_count;
+  const char* input_file;
+  const char* query;
+};
+
+bool is_json(const char* file)
+{
+  size_t len;
+
+  len = strlen(file);
+  if (
+    file[len - 1] != 'n' || file[len - 2] != 'o' || file[len - 3] != 's' ||
+    file[len - 4] != 'j' || file[len - 5] != '.')
+  {
+    return false;
+  }
+
+  return true;
+}
+
+void print_value(regoNode* node, const char* name)
+{
+  char value[MAX_VALUE_LENGTH];
+  regoSize value_size;
+  char* big_value;
+  regoEnum err;
+
+  value_size = regoNodeValueSize(node);
+  if (value_size > MAX_VALUE_LENGTH)
+  {
+    big_value = malloc(value_size);
+    if (big_value == NULL)
+    {
+      printf("Out of memory\n");
+      exit(1);
+    }
+    err = regoNodeValue(node, big_value, value_size);
+    if (err != REGO_OK)
+    {
+      printf("Unable to get node value\n");
+      exit(1);
+    }
+    printf("(var %s)\n", big_value);
+    free(big_value);
+  }
+  else
+  {
+    err = regoNodeValue(node, value, MAX_VALUE_LENGTH);
+    if (err != REGO_OK)
+    {
+      printf("Unable to get node value\n");
+      exit(1);
+    }
+    printf("(%s %s)\n", name, value);
+  }
+}
+
+void print_node(regoNode* node, unsigned int indent)
+{
+  regoSize i;
+  regoSize size;
+  regoEnum type;
+
+  for (i = 0; i < indent; ++i)
+  {
+    printf(" ");
+  }
+
+  if (node == NULL)
+  {
+    printf("<null>\n");
+    return;
+  }
+
+  type = regoNodeType(node);
+  switch (type)
+  {
+    case REGO_NODE_VAR:
+      print_value(node, "var");
+      return;
+
+    case REGO_NODE_INT:
+    case REGO_NODE_FLOAT:
+      print_value(node, "number");
+      return;
+
+    case REGO_NODE_STRING:
+      print_value(node, "string");
+      return;
+
+    case REGO_NODE_TRUE:
+      printf("(boolean true)\n");
+      return;
+
+    case REGO_NODE_FALSE:
+      printf("(boolean false)\n");
+      return;
+
+    case REGO_NODE_NULL:
+      printf("(null)\n");
+      return;
+
+    default:
+      printf("(%s", regoNodeTypeName(node));
+      break;
+  }
+
+  size = regoNodeSize(node);
+
+  printf("\n");
+  for (i = 0; i < size; ++i)
+  {
+    print_node(regoNodeGet(node, i), indent + 2);
+  }
+
+  if (size == 0)
+  {
+    printf(")\n");
+    return;
+  }
+}
+
+int main(int argc, char** argv)
+{
+  printf(
+    "regoc %s (%s, %s) [%s] on %s\n",
+    REGOCPP_VERSION,
+    REGOCPP_BUILD_NAME,
+    REGOCPP_BUILD_DATE,
+    REGOCPP_BUILD_TOOLCHAIN,
+    REGOCPP_PLATFORM);
+  char identifier;
+  cag_option_context context;
+  struct regoc_configuration config = {
+    .logging_enabled = false,
+    .use_raw_query = false,
+    .data_files_count = 0,
+    .input_file = NULL,
+    .query = NULL};
+  unsigned int data_index;
+  regoInterpreter* rego = NULL;
+  regoResult* result = NULL;
+  int rc = EXIT_SUCCESS;
+  regoEnum err;
+
+  cag_option_prepare(&context, options, CAG_ARRAY_SIZE(options), argc, argv);
+  while (cag_option_fetch(&context))
+  {
+    identifier = cag_option_get(&context);
+    switch (identifier)
+    {
+      case 'l':
+        config.logging_enabled = true;
+        break;
+
+      case 'r':
+        config.use_raw_query = true;
+        break;
+
+      case 'd':
+        config.data_files[config.data_files_count] =
+          cag_option_get_value(&context);
+        config.data_files_count++;
+        break;
+
+      case 'i':
+        config.input_file = cag_option_get_value(&context);
+        break;
+
+      case 'q':
+        config.query = cag_option_get_value(&context);
+        break;
+
+      case 'h':
+        printf("Usage: regoc [OPTION]...\n");
+        printf("A C rego interpreter.\n\n");
+        cag_option_print(options, CAG_ARRAY_SIZE(options), stdout);
+        return EXIT_SUCCESS;
+    }
+  }
+
+  if (config.query == NULL)
+  {
+    if (context.index < argc)
+    {
+      config.query = argv[context.index];
+    }
+
+    if (config.query == NULL)
+    {
+      printf("No query provided.\n");
+      return EXIT_FAILURE;
+    }
+  }
+
+  rego = regoNew();
+  if (config.logging_enabled)
+  {
+    regoSetLoggingEnabled(true);
+  }
+
+  regoSetExecutable(rego, argv[0]);
+
+  for (data_index = 0; data_index < config.data_files_count; data_index++)
+  {
+    const char* file = config.data_files[data_index];
+    if (is_json(file))
+    {
+      err = regoAddDataJSONFile(rego, file);
+    }
+    else
+    {
+      err = regoAddModuleFile(rego, file);
+    }
+    if (err == REGO_ERROR)
+    {
+      goto error;
+    }
+  }
+
+  if (config.input_file != NULL)
+  {
+    err = regoAddInputJSONFile(rego, config.input_file);
+    if (err == REGO_ERROR)
+    {
+      goto error;
+    }
+  }
+
+  result = regoQuery(rego, config.query);
+  if (result == NULL)
+  {
+    goto error;
+  }
+  if (config.use_raw_query)
+  {
+    print_node(regoResultNode(result), 0);
+    goto exit;
+  }
+  else
+  {
+    printf("%s\n", regoResultString(result));
+    goto exit;
+  }
+
+error:
+  printf("%s\n", regoGetError(rego));
+  rc = EXIT_FAILURE;
+
+exit:
+  if (result != NULL)
+  {
+    regoFreeResult(result);
+  }
+
+  if (rego != NULL)
+  {
+    regoFree(rego);
+  }
+
+  return rc;
+}
diff --git a/include/rego/args.h b/include/rego/args.h
index b147f6b4..5818b2d5 100644
--- a/include/rego/args.h
+++ b/include/rego/args.h
@@ -1,3 +1,6 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
 #pragma once
 
 #include "value.h"
diff --git a/include/rego/bigint.h b/include/rego/bigint.h
index eaf3b086..324e34af 100644
--- a/include/rego/bigint.h
+++ b/include/rego/bigint.h
@@ -1,9 +1,13 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
 #pragma once
 
 #include <trieste/source.h>
 
 namespace rego
 {
+  using namespace trieste;
   /**
    * This is a basic, non-optimized implementation of a big integer using
    * strings. In most circumstances this would be considerably slower than other
diff --git a/include/rego/builtins.h b/include/rego/builtins.h
index 435bb27d..47943b16 100644
--- a/include/rego/builtins.h
+++ b/include/rego/builtins.h
@@ -1,3 +1,6 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
 #pragma once
 
 #include <functional>
diff --git a/include/rego/encoding.h b/include/rego/encoding.h
new file mode 100644
index 00000000..d18fc1c8
--- /dev/null
+++ b/include/rego/encoding.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#include <trieste/source.h>
+
+namespace rego
+{
+  std::string to_json(
+    const trieste::Node& node, bool sort = false, bool rego_set = true);
+}
\ No newline at end of file
diff --git a/include/rego/errors.h b/include/rego/errors.h
index 1294d6eb..ede7f705 100644
--- a/include/rego/errors.h
+++ b/include/rego/errors.h
@@ -1,9 +1,15 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
 #pragma once
 
 #include <string>
+#include <trieste/ast.h>
 
 namespace rego
 {
+  using namespace trieste;
+
   const std::string EvalTypeError = "eval_type_error";
   const std::string EvalBuiltInError = "eval_builtin_error";
   const std::string RegoTypeError = "rego_type_error";
diff --git a/include/rego/helpers.h b/include/rego/helpers.h
index 48562c20..9a4fcf5e 100644
--- a/include/rego/helpers.h
+++ b/include/rego/helpers.h
@@ -1,7 +1,10 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
 #pragma once
 
 #include "bigint.h"
-#include "rego.h"
+#include "encoding.h"
 #include "tokens.h"
 
 namespace rego
diff --git a/include/rego/interpreter.h b/include/rego/interpreter.h
index e34fd7b7..3e481cea 100644
--- a/include/rego/interpreter.h
+++ b/include/rego/interpreter.h
@@ -1,6 +1,10 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
 #pragma once
 
 #include "builtins.h"
+#include "rego_c.h"
 
 #include <string>
 #include <trieste/parse.h>
@@ -36,6 +40,12 @@ namespace rego
     const BuiltIns& builtins() const;
 
   private:
+    friend const char* ::regoGetError(regoInterpreter* rego);
+    friend void setError(regoInterpreter* rego, const std::string& error);
+    friend regoResult* ::regoQuery(
+      regoInterpreter* rego, const char* query_expr);
+
+    std::string result_to_string(const Node& result) const;
     void write_ast(
       std::size_t index, const std::string& pass, const Node& ast) const;
     Node get_errors(const Node& ast) const;
@@ -48,5 +58,7 @@ namespace rego
     bool m_debug_enabled;
     bool m_well_formed_checks_enabled;
     BuiltIns m_builtins;
+
+    std::string m_c_error;
   };
 }
\ No newline at end of file
diff --git a/include/rego/log.h b/include/rego/log.h
index 162d87ff..878158ff 100644
--- a/include/rego/log.h
+++ b/include/rego/log.h
@@ -1,9 +1,13 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
 #pragma once
 
 #include <functional>
 #include <iostream>
 #include <map>
 #include <string>
+#include <trieste/source.h>
 #include <vector>
 
 namespace rego
diff --git a/include/rego/passes.h b/include/rego/passes.h
index 02b0856b..81df1ace 100644
--- a/include/rego/passes.h
+++ b/include/rego/passes.h
@@ -1,5 +1,10 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
 #pragma once
 
+#include "builtins.h"
+
 #include <trieste/pass.h>
 
 namespace rego
diff --git a/include/rego/rego.h b/include/rego/rego.h
index 7f1d7f22..78afe12e 100644
--- a/include/rego/rego.h
+++ b/include/rego/rego.h
@@ -1,5 +1,9 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
 #pragma once
 
+#include "encoding.h"
 #include "interpreter.h"
 #include "log.h"
 #include "tokens.h"
@@ -15,6 +19,4 @@ namespace rego
   using PassCheck = std::tuple<std::string, Pass, const wf::Wellformed*>;
   std::vector<PassCheck> passes(const BuiltIns& builtins);
   Node version();
-  std::string to_json(
-    const Node& node, bool sort = false, bool rego_set = true);
 }
\ No newline at end of file
diff --git a/include/rego/rego_c.h b/include/rego/rego_c.h
new file mode 100644
index 00000000..b3fc6ced
--- /dev/null
+++ b/include/rego/rego_c.h
@@ -0,0 +1,91 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#ifndef _REGOCPP_WRAPPER_H_
+#define _REGOCPP_WRAPPER_H_
+
+#include "version.h"
+
+typedef void regoInterpreter;
+typedef void regoNode;
+typedef void regoResult;
+typedef unsigned int regoEnum;
+typedef unsigned char regoBoolean;
+typedef unsigned int regoSize;
+
+// error codes
+#define REGO_OK 0
+#define REGO_ERROR 1
+#define REGO_ERROR_BUFFER_TOO_SMALL 2
+
+// term node types
+#define REGO_NODE_BINDING 1000
+#define REGO_NODE_VAR 1001
+#define REGO_NODE_TERM 1002
+#define REGO_NODE_SCALAR 1003
+#define REGO_NODE_ARRAY 1004
+#define REGO_NODE_SET 1005
+#define REGO_NODE_OBJECT 1006
+#define REGO_NODE_OBJECT_ITEM 1007
+#define REGO_NODE_INT 1008
+#define REGO_NODE_FLOAT 1009
+#define REGO_NODE_STRING 1010
+#define REGO_NODE_TRUE 1011
+#define REGO_NODE_FALSE 1012
+#define REGO_NODE_NULL 1013
+#define REGO_NODE_UNDEFINED 1014
+
+#define REGO_NODE_ERROR 1800
+#define REGO_NODE_ERROR_MESSAGE 1801
+#define REGO_NODE_ERROR_AST 1802
+#define REGO_NODE_ERROR_CODE 1803
+
+#define REGO_NODE_INTERNAL 1999
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+  // Interpreter functions
+  void regoSetLoggingEnabled(regoBoolean enabled);
+  regoInterpreter* regoNew();
+  void regoFree(regoInterpreter* rego);
+  regoEnum regoAddModuleFile(regoInterpreter* rego, const char* path);
+  regoEnum regoAddModule(
+    regoInterpreter* rego, const char* name, const char* contents);
+  regoEnum regoAddDataJSONFile(regoInterpreter* rego, const char* path);
+  regoEnum regoAddDataJSON(regoInterpreter* rego, const char* contents);
+  regoEnum regoAddInputJSONFile(regoInterpreter* rego, const char* path);
+  regoEnum regoAddInputJSON(regoInterpreter* rego, const char* contents);
+  void regoSetDebugEnabled(regoInterpreter* rego, regoBoolean enabled);
+  regoBoolean regoGetDebugEnabled(regoInterpreter* rego);
+  void regoSetDebugPath(regoInterpreter* rego, const char* path);
+  void regoSetWellFormedChecksEnabled(
+    regoInterpreter* rego, regoBoolean enabled);
+  regoBoolean regoGetWellFormedChecksEnabled(regoInterpreter* rego);
+  void regoSetExecutable(regoInterpreter* rego, const char* path);
+  regoResult* regoQuery(regoInterpreter* rego, const char* query_expr);
+  const char* regoGetError(regoInterpreter* rego);
+
+  // Result functions
+  regoBoolean regoResultOk(regoResult* result);
+  regoNode* regoResultNode(regoResult* result);
+  regoNode* regoResultBinding(regoResult* result, const char* name);
+  const char* regoResultString(regoResult* result);
+  void regoFreeResult(regoResult* result);
+
+  // Node functions
+  regoEnum regoNodeType(regoNode* node);
+  const char* regoNodeTypeName(regoNode* node);
+  regoSize regoNodeValueSize(regoNode* node);
+  regoEnum regoNodeValue(regoNode* node, char* buffer, regoSize size);
+  regoSize regoNodeSize(regoNode* node);
+  regoNode* regoNodeGet(regoNode* node, regoSize index);
+  regoSize regoNodeJSONSize(regoNode* node);
+  regoEnum regoNodeJSON(regoNode* node, char* buffer, regoSize size);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/rego/resolver.h b/include/rego/resolver.h
index 99539451..cbeb5e40 100644
--- a/include/rego/resolver.h
+++ b/include/rego/resolver.h
@@ -1,3 +1,6 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
 #pragma once
 
 #include "bigint.h"
diff --git a/include/rego/tokens.h b/include/rego/tokens.h
index 4d5ee64d..9732f8be 100644
--- a/include/rego/tokens.h
+++ b/include/rego/tokens.h
@@ -1,8 +1,9 @@
-#pragma once
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
 
-#include "builtins.h"
+#pragma once
 
-#include <trieste/token.h>
+#include <trieste/driver.h>
 
 namespace rego
 {
diff --git a/include/rego/unifier.h b/include/rego/unifier.h
index 51dd137b..5e879608 100644
--- a/include/rego/unifier.h
+++ b/include/rego/unifier.h
@@ -1,3 +1,6 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
 #pragma once
 
 #include "args.h"
diff --git a/include/rego/value.h b/include/rego/value.h
index b5425ffb..c266d213 100644
--- a/include/rego/value.h
+++ b/include/rego/value.h
@@ -1,3 +1,6 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
 #pragma once
 
 #include <trieste/source.h>
diff --git a/include/rego/value_map.h b/include/rego/value_map.h
index e82580fe..823ef0a5 100644
--- a/include/rego/value_map.h
+++ b/include/rego/value_map.h
@@ -1,3 +1,6 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
 #pragma once
 
 #include "value.h"
diff --git a/include/rego/variable.h b/include/rego/variable.h
index 6c46aa84..ff454033 100644
--- a/include/rego/variable.h
+++ b/include/rego/variable.h
@@ -1,3 +1,6 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
 #pragma once
 
 #include "value.h"
diff --git a/include/rego/wf.h b/include/rego/wf.h
index 0bada43e..4059dca7 100644
--- a/include/rego/wf.h
+++ b/include/rego/wf.h
@@ -1,3 +1,6 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
 #pragma once
 
 #include "tokens.h"
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 8535f96e..6c9164d6 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -14,6 +14,8 @@ variable.cc
 wf.cc
 builtins.cc
 helpers.cc
+rego_c.cc
+encoding.cc
 passes/input_data.cc
 passes/modules.cc
 passes/lists.cc
@@ -83,7 +85,9 @@ target_link_libraries(rego
 
 target_precompile_headers(rego
   PRIVATE
-    ${CMAKE_CURRENT_SOURCE_DIR}/../include/rego/rego.h
+    ${CMAKE_CURRENT_SOURCE_DIR}/../include/rego/errors.h
+    ${CMAKE_CURRENT_SOURCE_DIR}/../include/rego/helpers.h
+    ${CMAKE_CURRENT_SOURCE_DIR}/../include/rego/passes.h
 )
 
 
@@ -95,8 +99,9 @@ endif()
 target_include_directories( rego
   PUBLIC
     $<INSTALL_INTERFACE:include>
-    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../include/rego>
+    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../include>
     $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
   PRIVATE
     ${CMAKE_CURRENT_SOURCE_DIR}
+    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../include/rego>
 )
diff --git a/src/builtins.cc b/src/builtins.cc
index 2447dce2..91bd42af 100644
--- a/src/builtins.cc
+++ b/src/builtins.cc
@@ -3,6 +3,7 @@
 #include "builtins/register.h"
 #include "errors.h"
 #include "helpers.h"
+#include "rego.h"
 #include "resolver.h"
 #include "version.h"
 
diff --git a/src/encoding.cc b/src/encoding.cc
new file mode 100644
index 00000000..af329c90
--- /dev/null
+++ b/src/encoding.cc
@@ -0,0 +1,184 @@
+#include "encoding.h"
+
+#include "helpers.h"
+
+namespace rego
+{
+  std::string to_json(const Node& node, bool sort, bool rego_set)
+  {
+    std::ostringstream buf;
+    if (node->type() == JSONInt)
+    {
+      buf << node->location().view();
+    }
+    else if (node->type() == JSONFloat)
+    {
+      try
+      {
+        double value = std::stod(std::string(node->location().view()));
+        buf << std::setprecision(std::numeric_limits<double>::max_digits10 - 1)
+            << std::noshowpoint << value;
+      }
+      catch (...)
+      {
+        buf << node->location().view();
+      }
+    }
+    else if (node->type() == JSONString)
+    {
+      std::string str = std::string(node->location().view());
+      if (!str.starts_with('"'))
+      {
+        buf << '"' << str << '"';
+      }
+      else
+      {
+        buf << str;
+      }
+    }
+    else if (node->type() == JSONTrue)
+    {
+      buf << "true";
+    }
+    else if (node->type() == JSONFalse)
+    {
+      buf << "false";
+    }
+    else if (node->type() == JSONNull)
+    {
+      buf << "null";
+    }
+    else if (node->type() == Undefined)
+    {
+      buf << "undefined";
+    }
+    else if (node->type() == Key)
+    {
+      buf << '"' << node->location().view() << '"';
+    }
+    else if (node->type() == Array || node->type() == DataArray)
+    {
+      std::vector<std::string> items;
+      for (const auto& child : *node)
+      {
+        items.push_back(to_json(child, sort, rego_set));
+      }
+
+      if (sort)
+      {
+        std::sort(items.begin(), items.end());
+      }
+      buf << "[";
+      std::string sep = "";
+      for (const auto& item : items)
+      {
+        buf << sep << item;
+        sep = ", ";
+      }
+      buf << "]";
+    }
+    else if (node->type() == Set || node->type() == DataSet)
+    {
+      std::vector<std::string> items;
+      for (const auto& child : *node)
+      {
+        items.push_back(to_json(child, sort, rego_set));
+      }
+
+      if (sort)
+      {
+        std::sort(items.begin(), items.end());
+      }
+
+      if (rego_set)
+      {
+        buf << "<";
+      }
+      else
+      {
+        buf << "[";
+      }
+      std::string sep = "";
+      for (const auto& item : items)
+      {
+        buf << sep << item;
+        sep = ", ";
+      }
+
+      if (rego_set)
+      {
+        buf << ">";
+      }
+      else
+      {
+        buf << "]";
+      }
+    }
+    else if (node->type() == Object || node->type() == DataObject)
+    {
+      std::map<std::string, std::string> items;
+      for (const auto& child : *node)
+      {
+        auto key = child / Key;
+        auto value = child / Val;
+        std::string key_str = to_json(key, sort, rego_set);
+        if (!key_str.starts_with('"') || !key_str.ends_with('"'))
+        {
+          key_str = '"' + key_str + '"';
+        }
+        items[key_str] = to_json(value, sort, rego_set);
+      }
+
+      buf << "{";
+      std::string sep = "";
+      for (const auto& [key, value] : items)
+      {
+        buf << sep << key << ":" << value;
+        sep = ", ";
+      }
+
+      buf << "}";
+    }
+    else if (
+      node->type() == Scalar || node->type() == Term ||
+      node->type() == DataTerm)
+    {
+      return to_json(node->front(), sort, rego_set);
+    }
+    else if (node->type() == Binding)
+    {
+      buf << (node / Var)->location().view() << " = "
+          << to_json(node / Term, sort, rego_set);
+    }
+    else if (node->type() == TermSet)
+    {
+      buf << "termset{";
+      std::string sep = "";
+      for (const auto& child : *node)
+      {
+        buf << sep << to_json(child, sort, rego_set);
+        sep = ", ";
+      }
+      buf << "}";
+    }
+    else if (node->type() == BuiltInHook)
+    {
+      buf << "builtin(" << node->location().view() << ")";
+    }
+    else if (node->type() == Error)
+    {
+      buf << node;
+    }
+    else if (RuleTypes.contains(node->type()))
+    {
+      buf << node->type().str() << "(" << (node / Var)->location().view() << ":"
+          << static_cast<void*>(node.get()) << ")";
+    }
+    else
+    {
+      buf << node->type().str() << "(" << static_cast<void*>(node.get()) << ")";
+    }
+
+    return buf.str();
+  }
+}
\ No newline at end of file
diff --git a/src/helpers.cc b/src/helpers.cc
index a57a853a..bba554a7 100644
--- a/src/helpers.cc
+++ b/src/helpers.cc
@@ -76,185 +76,6 @@ namespace
 
 namespace rego
 {
-
-  std::string to_json(const Node& node, bool sort, bool rego_set)
-  {
-    std::ostringstream buf;
-    if (node->type() == JSONInt)
-    {
-      buf << node->location().view();
-    }
-    else if (node->type() == JSONFloat)
-    {
-      try
-      {
-        double value = std::stod(std::string(node->location().view()));
-        buf << std::setprecision(std::numeric_limits<double>::max_digits10 - 1)
-            << std::noshowpoint << value;
-      }
-      catch (...)
-      {
-        buf << node->location().view();
-      }
-    }
-    else if (node->type() == JSONString)
-    {
-      std::string str = std::string(node->location().view());
-      if (!str.starts_with('"'))
-      {
-        buf << '"' << str << '"';
-      }
-      else
-      {
-        buf << str;
-      }
-    }
-    else if (node->type() == JSONTrue)
-    {
-      buf << "true";
-    }
-    else if (node->type() == JSONFalse)
-    {
-      buf << "false";
-    }
-    else if (node->type() == JSONNull)
-    {
-      buf << "null";
-    }
-    else if (node->type() == Undefined)
-    {
-      buf << "undefined";
-    }
-    else if (node->type() == Key)
-    {
-      buf << '"' << node->location().view() << '"';
-    }
-    else if (node->type() == Array || node->type() == DataArray)
-    {
-      std::vector<std::string> items;
-      for (const auto& child : *node)
-      {
-        items.push_back(to_json(child, sort, rego_set));
-      }
-
-      if (sort)
-      {
-        std::sort(items.begin(), items.end());
-      }
-      buf << "[";
-      std::string sep = "";
-      for (const auto& item : items)
-      {
-        buf << sep << item;
-        sep = ", ";
-      }
-      buf << "]";
-    }
-    else if (node->type() == Set || node->type() == DataSet)
-    {
-      std::vector<std::string> items;
-      for (const auto& child : *node)
-      {
-        items.push_back(to_json(child, sort, rego_set));
-      }
-
-      if (sort)
-      {
-        std::sort(items.begin(), items.end());
-      }
-
-      if (rego_set)
-      {
-        buf << "<";
-      }
-      else
-      {
-        buf << "[";
-      }
-      std::string sep = "";
-      for (const auto& item : items)
-      {
-        buf << sep << item;
-        sep = ", ";
-      }
-
-      if (rego_set)
-      {
-        buf << ">";
-      }
-      else
-      {
-        buf << "]";
-      }
-    }
-    else if (node->type() == Object || node->type() == DataObject)
-    {
-      std::map<std::string, std::string> items;
-      for (const auto& child : *node)
-      {
-        auto key = child / Key;
-        auto value = child / Val;
-        std::string key_str = to_json(key, sort, rego_set);
-        if (!key_str.starts_with('"') || !key_str.ends_with('"'))
-        {
-          key_str = '"' + key_str + '"';
-        }
-        items[key_str] = to_json(value, sort, rego_set);
-      }
-
-      buf << "{";
-      std::string sep = "";
-      for (const auto& [key, value] : items)
-      {
-        buf << sep << key << ":" << value;
-        sep = ", ";
-      }
-
-      buf << "}";
-    }
-    else if (
-      node->type() == Scalar || node->type() == Term ||
-      node->type() == DataTerm)
-    {
-      return to_json(node->front(), sort, rego_set);
-    }
-    else if (node->type() == Binding)
-    {
-      buf << (node / Var)->location().view() << " = "
-          << to_json(node / Term, sort, rego_set);
-    }
-    else if (node->type() == TermSet)
-    {
-      buf << "termset{";
-      std::string sep = "";
-      for (const auto& child : *node)
-      {
-        buf << sep << to_json(child, sort, rego_set);
-        sep = ", ";
-      }
-      buf << "}";
-    }
-    else if (node->type() == BuiltInHook)
-    {
-      buf << "builtin(" << node->location().view() << ")";
-    }
-    else if (node->type() == Error)
-    {
-      buf << node;
-    }
-    else if (RuleTypes.contains(node->type()))
-    {
-      buf << node->type().str() << "(" << (node / Var)->location().view() << ":"
-          << static_cast<void*>(node.get()) << ")";
-    }
-    else
-    {
-      buf << node->type().str() << "(" << static_cast<void*>(node.get()) << ")";
-    }
-
-    return buf.str();
-  }
-
   bool contains_ref(const Node& node)
   {
     if (node->type() == NestedBody)
diff --git a/src/interpreter.cc b/src/interpreter.cc
index 111f4d1a..476d9839 100644
--- a/src/interpreter.cc
+++ b/src/interpreter.cc
@@ -2,7 +2,7 @@
 
 #include "errors.h"
 #include "helpers.h"
-#include "trieste/ast.h"
+#include "rego.h"
 #include "wf.h"
 
 #include <iostream>
@@ -215,7 +215,11 @@ namespace rego
 
   std::string Interpreter::query(const std::string& query_expr) const
   {
-    Node ast = raw_query(query_expr);
+    return result_to_string(raw_query(query_expr));
+  }
+
+  std::string Interpreter::result_to_string(const Node& ast) const
+  {
     std::ostringstream result_buf;
     if (ast->type() == ErrorSeq)
     {
diff --git a/src/lang.cc b/src/lang.cc
index e9b8804c..bd014053 100644
--- a/src/lang.cc
+++ b/src/lang.cc
@@ -1,10 +1,9 @@
 #include "log.h"
 #include "passes.h"
+#include "rego.h"
 #include "tokens.h"
 #include "wf.h"
 
-#include <charconv>
-
 namespace rego
 {
   bool Logger::enabled = false;
diff --git a/src/passes/enumerate.cc b/src/passes/enumerate.cc
index 9e2b4cf7..33d03993 100644
--- a/src/passes/enumerate.cc
+++ b/src/passes/enumerate.cc
@@ -144,7 +144,14 @@ namespace rego
           Node idx = _(Idx)->front();
           if (idx->type() == Expr)
           {
-            idx = idx->front();
+            if (idx->size() == 1 && is_constant(idx->front()))
+            {
+              idx = idx->front();
+            }
+            else
+            {
+              return err(idx, "Invalid index for enumeration");
+            }
           }
 
           if (idx->type() == NumTerm)
@@ -212,7 +219,26 @@ namespace rego
           LOG("val = ref[idx]");
 
           Node idx = _(Idx)->front();
-          if (idx->type() != RefTerm)
+          if (idx->type() == Expr)
+          {
+            if (idx->size() == 1 && is_constant(idx->front()))
+            {
+              idx = idx->front();
+            }
+            else
+            {
+              return err(idx, "Invalid index for enumeration");
+            }
+          }
+
+          if (idx->type() == NumTerm)
+          {
+            idx = Term << (Scalar << idx->front());
+          }
+
+          std::set<Token> term_types = {
+            Scalar, Array, Set, Object, ArrayCompr, SetCompr, ObjectCompr};
+          if (term_types.contains(idx->type()))
           {
             idx = Term << idx;
           }
diff --git a/src/rego_c.cc b/src/rego_c.cc
new file mode 100644
index 00000000..80aafbd8
--- /dev/null
+++ b/src/rego_c.cc
@@ -0,0 +1,385 @@
+#include "rego_c.h"
+
+#include "helpers.h"
+#include "interpreter.h"
+#include "log.h"
+
+namespace rego
+{
+  void setError(regoInterpreter* rego, const std::string& error)
+  {
+    reinterpret_cast<rego::Interpreter*>(rego)->m_c_error = error;
+  }
+
+  struct RegoResult
+  {
+    Node node;
+    std::string value;
+  };
+}
+
+extern "C"
+{
+  const char* regoGetError(regoInterpreter* rego)
+  {
+    return reinterpret_cast<rego::Interpreter*>(rego)->m_c_error.c_str();
+  }
+
+  void regoSetLoggingEnabled(regoBoolean enabled)
+  {
+    rego::Logger::enabled = enabled;
+  }
+
+  regoInterpreter* regoNew()
+  {
+    return reinterpret_cast<regoInterpreter*>(new rego::Interpreter());
+  }
+
+  void regoFree(regoInterpreter* rego)
+  {
+    delete reinterpret_cast<rego::Interpreter*>(rego);
+  }
+
+  regoEnum regoAddModuleFile(regoInterpreter* rego, const char* path)
+  {
+    try
+    {
+      reinterpret_cast<rego::Interpreter*>(rego)->add_module_file(path);
+      return REGO_OK;
+    }
+    catch (const std::exception& e)
+    {
+      rego::setError(rego, e.what());
+      return REGO_ERROR;
+    }
+  }
+
+  regoEnum regoAddModule(
+    regoInterpreter* rego, const char* name, const char* contents)
+  {
+    try
+    {
+      reinterpret_cast<rego::Interpreter*>(rego)->add_module(name, contents);
+      return REGO_OK;
+    }
+    catch (const std::exception& e)
+    {
+      rego::setError(rego, e.what());
+      return REGO_ERROR;
+    }
+  }
+
+  regoEnum regoAddDataJSONFile(regoInterpreter* rego, const char* path)
+  {
+    try
+    {
+      reinterpret_cast<rego::Interpreter*>(rego)->add_data_json_file(path);
+      return REGO_OK;
+    }
+    catch (const std::exception& e)
+    {
+      rego::setError(rego, e.what());
+      return REGO_ERROR;
+    }
+  }
+
+  regoEnum regoAddDataJSON(regoInterpreter* rego, const char* contents)
+  {
+    try
+    {
+      reinterpret_cast<rego::Interpreter*>(rego)->add_data_json(contents);
+      return REGO_OK;
+    }
+    catch (const std::exception& e)
+    {
+      rego::setError(rego, e.what());
+      return REGO_ERROR;
+    }
+  }
+
+  regoEnum regoAddInputJSONFile(regoInterpreter* rego, const char* path)
+  {
+    try
+    {
+      reinterpret_cast<rego::Interpreter*>(rego)->add_input_json_file(path);
+      return REGO_OK;
+    }
+    catch (const std::exception& e)
+    {
+      rego::setError(rego, e.what());
+      return REGO_ERROR;
+    }
+  }
+
+  regoEnum regoAddInputJSON(regoInterpreter* rego, const char* contents)
+  {
+    try
+    {
+      reinterpret_cast<rego::Interpreter*>(rego)->add_input_json(contents);
+      return REGO_OK;
+    }
+    catch (const std::exception& e)
+    {
+      rego::setError(rego, e.what());
+      return REGO_ERROR;
+    }
+  }
+
+  void regoSetDebugEnabled(regoInterpreter* rego, regoBoolean enabled)
+  {
+    reinterpret_cast<rego::Interpreter*>(rego)->debug_enabled(enabled);
+  }
+
+  regoBoolean regoGetDebugEnabled(regoInterpreter* rego)
+  {
+    return reinterpret_cast<rego::Interpreter*>(rego)->debug_enabled();
+  }
+
+  void regoSetDebugPath(regoInterpreter* rego, const char* path)
+  {
+    reinterpret_cast<rego::Interpreter*>(rego)->debug_path(path);
+  }
+
+  void regoSetWellFormedChecksEnabled(
+    regoInterpreter* rego, regoBoolean enabled)
+  {
+    reinterpret_cast<rego::Interpreter*>(rego)->well_formed_checks_enabled(
+      enabled);
+  }
+
+  regoBoolean regoGetWellFormedChecksEnabled(regoInterpreter* rego)
+  {
+    return reinterpret_cast<rego::Interpreter*>(rego)
+      ->well_formed_checks_enabled();
+  }
+
+  void regoSetExecutable(regoInterpreter* rego, const char* path)
+  {
+    reinterpret_cast<rego::Interpreter*>(rego)->executable(path);
+  }
+
+  regoResult* regoQuery(regoInterpreter* rego, const char* query_expr)
+  {
+    try
+    {
+      auto interpreter = reinterpret_cast<rego::Interpreter*>(rego);
+      rego::RegoResult* result = new rego::RegoResult();
+      result->node = interpreter->raw_query(query_expr);
+      result->value = interpreter->result_to_string(result->node);
+
+      return reinterpret_cast<regoResult*>(result);
+    }
+    catch (const std::exception& e)
+    {
+      rego::setError(rego, e.what());
+      return nullptr;
+    }
+  }
+
+  // Result functions
+  regoBoolean regoResultOk(regoResult* result)
+  {
+    return reinterpret_cast<rego::RegoResult*>(result)->node->type() !=
+      rego::ErrorSeq;
+  }
+
+  regoNode* regoResultNode(regoResult* result)
+  {
+    return reinterpret_cast<regoNode*>(
+      reinterpret_cast<rego::RegoResult*>(result)->node.get());
+  }
+
+  regoNode* regoResultBinding(regoResult* result, const char* name)
+  {
+    auto result_ptr = reinterpret_cast<rego::RegoResult*>(result);
+    auto node_ptr = reinterpret_cast<trieste::NodeDef*>(result_ptr->node.get());
+    if (node_ptr->type() == rego::ErrorSeq)
+    {
+      return nullptr;
+    }
+
+    for (auto binding : *node_ptr)
+    {
+      rego::Node var = binding / rego::Var;
+      if (var->location().view() == name)
+      {
+        rego::Node val = binding / rego::Term;
+        return reinterpret_cast<regoNode*>(val.get());
+      }
+    }
+
+    return nullptr;
+  }
+
+  const char* regoResultString(regoResult* result)
+  {
+    return reinterpret_cast<rego::RegoResult*>(result)->value.c_str();
+  }
+
+  void regoFreeResult(regoResult* result)
+  {
+    delete reinterpret_cast<rego::RegoResult*>(result);
+  }
+
+  // Node functions
+  regoEnum regoNodeType(regoNode* node_ptr)
+  {
+    auto node = reinterpret_cast<trieste::NodeDef*>(node_ptr);
+
+    if (node->type() == rego::Binding)
+    {
+      return REGO_NODE_BINDING;
+    }
+
+    if (node->type() == rego::Var)
+    {
+      return REGO_NODE_VAR;
+    }
+
+    if (node->type() == rego::Term)
+    {
+      return REGO_NODE_TERM;
+    }
+
+    if (node->type() == rego::Scalar)
+    {
+      return REGO_NODE_SCALAR;
+    }
+
+    if (node->type() == rego::Array)
+    {
+      return REGO_NODE_ARRAY;
+    }
+
+    if (node->type() == rego::Set)
+    {
+      return REGO_NODE_SET;
+    }
+
+    if (node->type() == rego::Object)
+    {
+      return REGO_NODE_OBJECT;
+    }
+
+    if (node->type() == rego::ObjectItem)
+    {
+      return REGO_NODE_OBJECT_ITEM;
+    }
+
+    if (node->type() == rego::JSONInt)
+    {
+      return REGO_NODE_INT;
+    }
+
+    if (node->type() == rego::JSONFloat)
+    {
+      return REGO_NODE_FLOAT;
+    }
+
+    if (node->type() == rego::JSONString)
+    {
+      return REGO_NODE_STRING;
+    }
+
+    if (node->type() == rego::JSONTrue)
+    {
+      return REGO_NODE_TRUE;
+    }
+
+    if (node->type() == rego::JSONFalse)
+    {
+      return REGO_NODE_FALSE;
+    }
+
+    if (node->type() == rego::JSONNull)
+    {
+      return REGO_NODE_NULL;
+    }
+
+    if (node->type() == rego::Undefined)
+    {
+      return REGO_NODE_UNDEFINED;
+    }
+
+    if (node->type() == rego::Error)
+    {
+      return REGO_NODE_ERROR;
+    }
+
+    if (node->type() == rego::ErrorMsg)
+    {
+      return REGO_NODE_ERROR_MESSAGE;
+    }
+
+    if (node->type() == rego::ErrorAst)
+    {
+      return REGO_NODE_ERROR_AST;
+    }
+
+    if (node->type() == rego::ErrorCode)
+    {
+      return REGO_NODE_ERROR_CODE;
+    }
+
+    return REGO_NODE_INTERNAL;
+  }
+
+  const char* regoNodeTypeName(regoNode* node)
+  {
+    return reinterpret_cast<trieste::NodeDef*>(node)->type().str();
+  }
+
+  regoSize regoNodeValueSize(regoNode* node)
+  {
+    std::size_t size =
+      reinterpret_cast<trieste::NodeDef*>(node)->location().view().size();
+    return static_cast<regoSize>(size + 1);
+  }
+
+  regoEnum regoNodeValue(regoNode* node, char* buffer, regoSize size)
+  {
+    std::string_view view =
+      reinterpret_cast<trieste::NodeDef*>(node)->location().view();
+    if (size < view.size() + 1)
+    {
+      return REGO_ERROR_BUFFER_TOO_SMALL;
+    }
+
+    view.copy(buffer, size);
+    buffer[view.size()] = '\0';
+    return REGO_OK;
+  }
+
+  regoSize regoNodeSize(regoNode* node)
+  {
+    std::size_t size = reinterpret_cast<trieste::NodeDef*>(node)->size();
+    return static_cast<regoSize>(size);
+  }
+
+  regoNode* regoNodeGet(regoNode* node, regoSize index)
+  {
+    trieste::NodeDef* child =
+      reinterpret_cast<trieste::NodeDef*>(node)->at(index).get();
+    return reinterpret_cast<regoNode*>(child);
+  }
+
+  regoSize regoNodeJSONSize(regoNode* node)
+  {
+    auto node_ptr = reinterpret_cast<trieste::NodeDef*>(node);
+    std::string json = rego::to_json(node_ptr->shared_from_this());
+    return static_cast<regoSize>(json.size() + 1);
+  }
+
+  regoEnum regoNodeJSON(regoNode* node, char* buffer, regoSize size)
+  {
+    auto node_ptr = reinterpret_cast<trieste::NodeDef*>(node);
+    std::string json = rego::to_json(node_ptr->shared_from_this());
+    if (size < json.size() + 1)
+    {
+      return REGO_ERROR_BUFFER_TOO_SMALL;
+    }
+
+    json.copy(buffer, size);
+    return REGO_OK;
+  }
+}
\ No newline at end of file
diff --git a/src/version.h.in b/src/version.h.in
index 9f3dc05d..aa5ee276 100644
--- a/src/version.h.in
+++ b/src/version.h.in
@@ -1,7 +1,8 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT License.
 
-#pragma once
+#ifndef _REGO_VERSION_H_
+#define _REGO_VERSION_H_
 
 #define REGOCPP_VERSION_MAJOR @REGOCPP_VERSION_MAJOR@
 #define REGOCPP_VERSION_MINOR @REGOCPP_VERSION_MINOR@
@@ -9,3 +10,18 @@
 #define REGOCPP_OPA_VERSION "@REGOCPP_OPA_VERSION@"
 #define REGOCPP_VERSION "@REGOCPP_VERSION@"
 #define REGOCPP_GIT_HASH "@REGOCPP_GIT_HASH@"
+#define REGOCPP_BUILD_NAME "@REGOCPP_GIT_BRANCH@:@REGOCPP_GIT_SHORT_HASH@"
+#define REGOCPP_BUILD_DATE "@REGOCPP_BUILD_DATE@"
+#define REGOCPP_BUILD_TOOLCHAIN "@REGOCPP_BUILD_TOOLCHAIN@"
+
+#ifdef _WIN32
+#define REGOCPP_PLATFORM "windows"
+#elif __APPLE__
+#define REGOCPP_PLATFORM "macos"
+#elif __linux__
+#define REGOCPP_PLATFORM "linux"
+#else
+#define REGOCPP_PLATFORM "other"
+#endif
+
+#endif
\ No newline at end of file
diff --git a/tools/driver.cc b/tools/driver.cc
index fbef5759..954bd5c7 100644
--- a/tools/driver.cc
+++ b/tools/driver.cc
@@ -1,4 +1,4 @@
-#include "rego.h"
+#include <rego/rego.h>
 
 int main(int argc, char** argv)
 {
diff --git a/tools/main.cc b/tools/main.cc
index d1238bd7..90377344 100644
--- a/tools/main.cc
+++ b/tools/main.cc
@@ -1,9 +1,12 @@
-#include "rego.h"
-
 #include <CLI/CLI.hpp>
+#include <rego/rego.h>
 
 int main(int argc, char** argv)
 {
+  std::cout << "rego " << REGOCPP_VERSION << " (" << REGOCPP_BUILD_NAME << ", "
+            << REGOCPP_BUILD_DATE << ")"
+            << "[" << REGOCPP_BUILD_TOOLCHAIN << "] on " << REGOCPP_PLATFORM
+            << std::endl;
   CLI::App app;
 
   app.set_help_all_flag("--help-all", "Expand all help");


Test patch:
diff --git a/tests/rego_test.h b/tests/rego_test.h
index 5831042a..792d2873 100644
--- a/tests/rego_test.h
+++ b/tests/rego_test.h
@@ -2,10 +2,10 @@
 
 // Trieste-based rewriter for Rego YAML test cases
 
-#include "rego.h"
-#include "trieste/driver.h"
-#include "trieste/token.h"
-#include "wf.h"
+#include <rego/rego.h>
+#include <rego/wf.h>
+#include <trieste/driver.h>
+#include <trieste/token.h>
 
 namespace rego_test
 {
diff --git a/tests/test_case.h b/tests/test_case.h
index 38f1bab1..7e183561 100644
--- a/tests/test_case.h
+++ b/tests/test_case.h
@@ -1,8 +1,7 @@
 #pragma once
 
-#include "rego.h"
-
 #include <optional>
+#include <rego/rego.h>
 #include <string>
 #include <vector>