Unverified Commit bc02b27a authored by Angus Lees's avatar Angus Lees
Browse files

vendor: New version of jsonnet_cgo

Adds support for native functions
parent c4e5813a
......@@ -6,17 +6,19 @@
# See "Usage:" a few lines below.
case "$#/$1" in
0/ )
set ../../google/jsonnet/
;;
1/*/jsonnet/ )
: ok
;;
* )
echo >&2 '
Usage:
sh $0 /path/to/jsonnet/
sh $0 ?/path/to/jsonnet/?
This command requires one argument,
the jsonnet repository directory,
ending in /jsonnet/
This command takes one argument, the jsonnet repository directory,
ending in /jsonnet/. The default is ../../google/jsonnet/.
'
exit 13
;;
......
......@@ -2,18 +2,14 @@
#include <stdio.h>
#include <string.h>
#include <libjsonnet.h>
#include "bridge.h"
#include "_cgo_export.h"
char* CallImport_cgo(void *ctx, const char *base, const char *rel, char **found_here, int *success) {
struct JsonnetVm* vm = ctx;
char *path = NULL;
char* result = go_call_import(vm, (char*)base, (char*)rel, &path, success);
if (*success) {
char* found_here_buf = jsonnet_realloc(vm, NULL, strlen(path)+1);
strcpy(found_here_buf, path);
*found_here = found_here_buf;
}
char* buf = jsonnet_realloc(vm, NULL, strlen(result)+1);
strcpy(buf, result);
return buf;
return go_call_import(vm, (char*)base, (char*)rel, found_here, success);
}
struct JsonnetJsonValue* CallNative_cgo(void* ctx, const struct JsonnetJsonValue* const* argv, int* success) {
GoUintptr key = (GoUintptr)ctx;
return go_call_native(key, (struct JsonnetJsonValue**)argv, success);
}
#ifndef LIBJSONNET_BRIDGE_H
#define LIBJSONNET_BRIDGE_H
#include <libjsonnet.h>
typedef JsonnetImportCallback* JsonnetImportCallbackPtr;
struct JsonnetVm* go_get_guts(void* ctx);
char* CallImport_cgo(void *ctx, const char *base, const char *rel, char **found_here, int *success);
char* go_call_import(void* vm, char *base, char *rel, char **path, int *success);
#endif
......@@ -324,7 +324,7 @@ class Desugarer {
};
SuperVars super_vars;
unsigned counter;
unsigned counter = 0;
// Remove +:
for (auto &field : fields) {
......
......@@ -12,34 +12,122 @@ package jsonnet
#include <memory.h>
#include <string.h>
#include <stdio.h>
#include "bridge.h"
#include <stdlib.h>
#include <libjsonnet.h>
char *CallImport_cgo(void *ctx, const char *base, const char *rel, char **found_here, int *success);
struct JsonnetJsonValue *CallNative_cgo(void *ctx, const struct JsonnetJsonValue *const *argv, int *success);
#cgo CXXFLAGS: -std=c++0x -O3
*/
import "C"
import (
"errors"
"fmt"
"reflect"
"runtime"
"sync"
"unsafe"
)
type ImportCallback func(base, rel string) (result string, path string, err error)
type NativeCallback func(args ...*JsonValue) (result *JsonValue, err error)
type nativeFunc struct {
vm *VM
argc int
callback NativeCallback
}
// Global registry of native functions. Cgo pointer rules don't allow
// us to pass go pointers directly (may not be stable), so pass uintptr
// keys into this indirect map instead.
var nativeFuncsMu sync.Mutex
var nativeFuncsIdx uintptr
var nativeFuncs = make(map[uintptr]*nativeFunc)
func registerFunc(vm *VM, arity int, callback NativeCallback) uintptr {
f := nativeFunc{vm: vm, argc: arity, callback: callback}
nativeFuncsMu.Lock()
defer nativeFuncsMu.Unlock()
nativeFuncsIdx++
for nativeFuncs[nativeFuncsIdx] != nil {
nativeFuncsIdx++
}
nativeFuncs[nativeFuncsIdx] = &f
return nativeFuncsIdx
}
func getFunc(key uintptr) *nativeFunc {
nativeFuncsMu.Lock()
defer nativeFuncsMu.Unlock()
return nativeFuncs[key]
}
func unregisterFuncs(vm *VM) {
nativeFuncsMu.Lock()
defer nativeFuncsMu.Unlock()
// This is inefficient if there are many
// simultaneously-existing VMs...
for idx, f := range nativeFuncs {
if f.vm == vm {
delete(nativeFuncs, idx)
}
}
}
type VM struct {
guts *C.struct_JsonnetVm
importCallback ImportCallback
}
//export go_call_native
func go_call_native(key uintptr, argv **C.struct_JsonnetJsonValue, okPtr *C.int) *C.struct_JsonnetJsonValue {
f := getFunc(key)
vm := f.vm
goArgv := make([]*JsonValue, f.argc)
for i := 0; i < f.argc; i++ {
p := unsafe.Pointer(uintptr(unsafe.Pointer(argv)) + unsafe.Sizeof(*argv)*uintptr(i))
argptr := (**C.struct_JsonnetJsonValue)(p)
// NB: argv will be freed by jsonnet after this
// function exits, so don't want (*JsonValue).destroy
// finalizer.
goArgv[i] = &JsonValue{
vm: vm,
guts: *argptr,
}
}
ret, err := f.callback(goArgv...)
if err != nil {
*okPtr = C.int(0)
ret = vm.NewString(err.Error())
} else {
*okPtr = C.int(1)
}
return ret.take()
}
//export go_call_import
func go_call_import(vmPtr unsafe.Pointer, base, rel *C.char, pathPtr **C.char, okPtr *C.int) *C.char {
vm := (*VM)(vmPtr)
result, path, err := vm.importCallback(C.GoString(base), C.GoString(rel))
if err != nil {
*okPtr = C.int(0)
return C.CString(err.Error())
return jsonnetString(vm, err.Error())
}
*pathPtr = C.CString(path)
*pathPtr = jsonnetString(vm, path)
*okPtr = C.int(1)
return C.CString(result)
return jsonnetString(vm, result)
}
// Evaluate a file containing Jsonnet code, return a JSON string.
......@@ -55,10 +143,26 @@ func Make() *VM {
// Complement of Make().
func (vm *VM) Destroy() {
unregisterFuncs(vm)
C.jsonnet_destroy(vm.guts)
vm.guts = nil
}
// jsonnet often wants char* strings that were allocated via
// jsonnet_realloc. This function does that.
func jsonnetString(vm *VM, s string) *C.char {
clen := C.size_t(len(s)) + 1 // num bytes including trailing \0
// TODO: remove additional copy
cstr := C.CString(s)
defer C.free(unsafe.Pointer(cstr))
ret := C.jsonnet_realloc(vm.guts, nil, clen)
C.memcpy(unsafe.Pointer(ret), unsafe.Pointer(cstr), clen)
return ret
}
// Evaluate a file containing Jsonnet code, return a JSON string.
func (vm *VM) EvaluateFile(filename string) (string, error) {
var e C.int
......@@ -79,10 +183,88 @@ func (vm *VM) EvaluateSnippet(filename, snippet string) (string, error) {
return z, nil
}
// Format a file containing Jsonnet code, return a JSON string.
func (vm *VM) FormatFile(filename string) (string, error) {
var e C.int
z := C.GoString(C.jsonnet_fmt_file(vm.guts, C.CString(filename), &e))
if e != 0 {
return "", errors.New(z)
}
return z, nil
}
// Indentation level when reformatting (number of spaces)
func (vm *VM) FormatIndent(n int) {
C.jsonnet_fmt_indent(vm.guts, C.int(n))
}
// Format a string containing Jsonnet code, return a JSON string.
func (vm *VM) FormatSnippet(filename, snippet string) (string, error) {
var e C.int
z := C.GoString(C.jsonnet_fmt_snippet(vm.guts, C.CString(filename), C.CString(snippet), &e))
if e != 0 {
return "", errors.New(z)
}
return z, nil
}
// Override the callback used to locate imports.
func (vm *VM) ImportCallback(f ImportCallback) {
vm.importCallback = f
C.jsonnet_import_callback(vm.guts, C.JsonnetImportCallbackPtr(C.CallImport_cgo), unsafe.Pointer(vm))
C.jsonnet_import_callback(vm.guts, (*C.JsonnetImportCallback)(unsafe.Pointer(C.CallImport_cgo)), unsafe.Pointer(vm))
}
// NativeCallback is a helper around NativeCallbackRaw that uses
// `reflect` to convert argument and result types to/from JsonValue.
// `f` is expected to be a function that takes argument types
// supported by `(*JsonValue).Extract` and returns `(x, error)` where
// `x` is a type supported by `NewJson`.
func (vm *VM) NativeCallback(name string, params []string, f interface{}) {
ty := reflect.TypeOf(f)
if ty.NumIn() != len(params) {
panic("Wrong number of parameters")
}
if ty.NumOut() != 2 {
panic("Wrong number of output parameters")
}
wrapper := func(args ...*JsonValue) (*JsonValue, error) {
in := make([]reflect.Value, len(args))
for i, arg := range args {
value := reflect.ValueOf(arg.Extract())
if vty := value.Type(); !vty.ConvertibleTo(ty.In(i)) {
return nil, fmt.Errorf("parameter %d (type %s) cannot be converted to type %s", i, vty, ty.In(i))
}
in[i] = value.Convert(ty.In(i))
}
out := reflect.ValueOf(f).Call(in)
result := vm.NewJson(out[0].Interface())
var err error
if out[1].IsValid() && !out[1].IsNil() {
err = out[1].Interface().(error)
}
return result, err
}
vm.NativeCallbackRaw(name, params, wrapper)
}
func (vm *VM) NativeCallbackRaw(name string, params []string, f NativeCallback) {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
// jsonnet expects this to be NULL-terminated, so the last
// element is left as nil
cparams := make([]*C.char, len(params)+1)
for i, param := range params {
cparams[i] = C.CString(param)
defer C.free(unsafe.Pointer(cparams[i]))
}
key := registerFunc(vm, len(params), f)
C.jsonnet_native_callback(vm.guts, cname, (*C.JsonnetNativeCallback)(C.CallNative_cgo), unsafe.Pointer(key), (**C.char)(unsafe.Pointer(&cparams[0])))
}
// Bind a Jsonnet external var to the given value.
......@@ -147,3 +329,165 @@ func (vm *VM) JpathAdd(path string) {
* jsonnet_evaluate_file_stream
* jsonnet_evaluate_snippet_stream
*/
// JsonValue represents a jsonnet JSON object.
type JsonValue struct {
vm *VM
guts *C.struct_JsonnetJsonValue
}
func (v *JsonValue) Extract() interface{} {
if x, ok := v.ExtractString(); ok {
return x
}
if x, ok := v.ExtractNumber(); ok {
return x
}
if x, ok := v.ExtractBool(); ok {
return x
}
if ok := v.ExtractNull(); ok {
return nil
}
panic("Unable to extract value")
}
// ExtractString returns the string value and true if the value was a string
func (v *JsonValue) ExtractString() (string, bool) {
cstr := C.jsonnet_json_extract_string(v.vm.guts, v.guts)
if cstr == nil {
return "", false
}
return C.GoString(cstr), true
}
func (v *JsonValue) ExtractNumber() (float64, bool) {
var ret C.double
ok := C.jsonnet_json_extract_number(v.vm.guts, v.guts, &ret)
return float64(ret), ok != 0
}
func (v *JsonValue) ExtractBool() (bool, bool) {
ret := C.jsonnet_json_extract_bool(v.vm.guts, v.guts)
switch ret {
case 0:
return false, true
case 1:
return true, true
case 2:
// Not a bool
return false, false
default:
panic("jsonnet_json_extract_number returned unexpected value")
}
}
// ExtractNull returns true iff the value is null
func (v *JsonValue) ExtractNull() bool {
ret := C.jsonnet_json_extract_null(v.vm.guts, v.guts)
return ret != 0
}
func (vm *VM) newjson(ptr *C.struct_JsonnetJsonValue) *JsonValue {
v := &JsonValue{vm: vm, guts: ptr}
runtime.SetFinalizer(v, (*JsonValue).destroy)
return v
}
func (v *JsonValue) destroy() {
if v.guts == nil {
return
}
C.jsonnet_json_destroy(v.vm.guts, v.guts)
v.guts = nil
runtime.SetFinalizer(v, nil)
}
// Take ownership of the embedded ptr, effectively consuming the JsonValue
func (v *JsonValue) take() *C.struct_JsonnetJsonValue {
ptr := v.guts
if ptr == nil {
panic("taking nil pointer from JsonValue")
}
v.guts = nil
runtime.SetFinalizer(v, nil)
return ptr
}
func (vm *VM) NewJson(value interface{}) *JsonValue {
switch val := value.(type) {
case string:
return vm.NewString(val)
case int:
return vm.NewNumber(float64(val))
case float64:
return vm.NewNumber(val)
case bool:
return vm.NewBool(val)
case nil:
return vm.NewNull()
case []interface{}:
a := vm.NewArray()
for _, v := range val {
a.ArrayAppend(vm.NewJson(v))
}
return a
case map[string]interface{}:
o := vm.NewObject()
for k, v := range val {
o.ObjectAppend(k, vm.NewJson(v))
}
return o
default:
panic(fmt.Sprintf("NewJson can't handle type: %T", value))
}
}
func (vm *VM) NewString(v string) *JsonValue {
cstr := C.CString(v)
defer C.free(unsafe.Pointer(cstr))
ptr := C.jsonnet_json_make_string(vm.guts, cstr)
return vm.newjson(ptr)
}
func (vm *VM) NewNumber(v float64) *JsonValue {
ptr := C.jsonnet_json_make_number(vm.guts, C.double(v))
return vm.newjson(ptr)
}
func (vm *VM) NewBool(v bool) *JsonValue {
var i C.int
if v {
i = 1
} else {
i = 0
}
ptr := C.jsonnet_json_make_bool(vm.guts, i)
return vm.newjson(ptr)
}
func (vm *VM) NewNull() *JsonValue {
ptr := C.jsonnet_json_make_null(vm.guts)
return vm.newjson(ptr)
}
func (vm *VM) NewArray() *JsonValue {
ptr := C.jsonnet_json_make_array(vm.guts)
return vm.newjson(ptr)
}
func (v *JsonValue) ArrayAppend(item *JsonValue) {
C.jsonnet_json_array_append(v.vm.guts, v.guts, item.take())
}
func (vm *VM) NewObject() *JsonValue {
ptr := C.jsonnet_json_make_object(vm.guts)
return vm.newjson(ptr)
}
func (v *JsonValue) ObjectAppend(key string, value *JsonValue) {
ckey := C.CString(key)
defer C.free(unsafe.Pointer(ckey))
C.jsonnet_json_object_append(v.vm.guts, v.guts, ckey, value.take())
}
......@@ -179,7 +179,7 @@ struct HeapSimpleObject : public HeapLeafObject {
{ }
};
/** Objects created by the extendby construct. */
/** Objects created by the + construct. */
struct HeapExtendedObject : public HeapObject {
/** The left hand side of the construct. */
HeapObject *left;
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -776,7 +776,7 @@ class Interpreter {
/** Count the number of leaves in the tree.
*
* \param obj The root of the tree.
* \param counter Initialize to 0, returns the number of leaves.
* \returns The number of leaves.
*/
unsigned countLeaves(HeapObject *obj)
{
......
......@@ -269,10 +269,10 @@
"revisionTime": "2017-05-08T18:43:26Z"
},
{
"checksumSHA1": "BXgH3TNmg26Ql/fH29Yb3dR3u2g=",
"checksumSHA1": "Ig39xerEZknDRBUmktZnC6GdkFs=",
"path": "github.com/strickyak/jsonnet_cgo",
"revision": "f22f0c43815490049135532023c27392536b057b",
"revisionTime": "2017-04-18T07:42:43Z"
"revision": "764010f55a278d2abc7be38b439929824a66aee4",
"revisionTime": "2017-06-16T09:44:14Z"
},
{
"checksumSHA1": "MxLnUmfrP+r5HfCZM29+WPKebn8=",
......
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