diff --git a/core/BUILD b/core/BUILD index f429bd973..addbd3938 100644 --- a/core/BUILD +++ b/core/BUILD @@ -1,22 +1,36 @@ package(default_visibility = ["//visibility:public"]) cc_library( - name = "common", + name = "libjsonnet", + srcs = [ + "desugarer.cpp", + "formatter.cpp", + "lexer.cpp", + "libjsonnet.cpp", + "parser.cpp", + "static_analysis.cpp", + "string_utils.cpp", + "vm.cpp", + ], hdrs = [ "ast.h", + "desugarer.h", + "formatter.h", + "json.h", + "lexer.h", + "parser.h", + "state.h", + "static_analysis.h", "static_error.h", + "string_utils.h", "unicode.h", + "vm.h", ], - includes = ["."], -) - -cc_library( - name = "lexer", - srcs = ["lexer.cpp"], - hdrs = ["lexer.h"], deps = [ - ":common", + "//include:libjsonnet", + "//stdlib:std", ], + linkopts = ["-lm"], includes = ["."], ) @@ -24,81 +38,26 @@ cc_test( name = "lexer_test", srcs = ["lexer_test.cpp"], deps = [ - ":lexer", + ":libjsonnet", + # Note: On Ubuntu, apt-get install libgtest-dev google-mock "//external:gtest_main", ], ) -cc_library( - name = "parser", - srcs = [ - "desugarer.cpp", - "parser.cpp", - "string_utils.cpp", - ], - hdrs = [ - "desugarer.h", - "parser.h", - "string_utils.h", - ], - deps = [ - ":common", - ":lexer", - "//stdlib:std", - ], - includes = [".", "../include"], -) - cc_test( name = "parser_test", srcs = ["parser_test.cpp"], deps = [ - ":parser", + ":libjsonnet", "//external:gtest_main", ], ) -cc_library( - name = "jsonnet-common", - srcs = [ - "desugarer.cpp", - "formatter.cpp", - "libjsonnet.cpp", - "static_analysis.cpp", - "vm.cpp", - ], - hdrs = [ - "desugarer.h", - "formatter.h", - "state.h", - "static_analysis.h", - "vm.h", - ], - deps = [ - ":common", - ":lexer", - ":parser", - "//include:libjsonnet", - ], - linkopts = ["-lm"], - includes = [".", "../include"], -) - -cc_library( - name = "libjsonnet", - srcs = ["libjsonnet.cpp"], - deps = [ - ":jsonnet-common", - "//include:libjsonnet", - ], - includes = [".", "../include"], -) - cc_test( name = "libjsonnet_test", srcs = ["libjsonnet_test.cpp"], deps = [ - ":jsonnet-common", + ":libjsonnet", "//external:gtest_main", ], ) diff --git a/core/json.h b/core/json.h index f7d06370e..e99d310da 100644 --- a/core/json.h +++ b/core/json.h @@ -23,10 +23,16 @@ limitations under the License. struct JsonnetJsonValue { enum Kind { - STRING + ARRAY, // Not implemented yet. + BOOL, + NULL_KIND, + NUMBER, + OBJECT, // Not implemented yet. + STRING, }; Kind kind; std::string string; + double number; // Also used for bool (0 and 1) }; #endif diff --git a/core/libjsonnet.cpp b/core/libjsonnet.cpp index e45df15ff..972c0402d 100644 --- a/core/libjsonnet.cpp +++ b/core/libjsonnet.cpp @@ -59,8 +59,28 @@ const char *jsonnet_json_extract_string(JsonnetVm *vm, const struct JsonnetJsonV return v->string.c_str(); } -/** Convert the given UTF8 string to a JsonnetJsonValue. - */ +int jsonnet_json_extract_number(struct JsonnetVm *vm, const struct JsonnetJsonValue *v, double *out) +{ + (void) vm; + if (v->kind != JsonnetJsonValue::NUMBER) + return 0; + *out = v->number; + return 1; +} + +int jsonnet_json_extract_bool(struct JsonnetVm *vm, const struct JsonnetJsonValue *v) +{ + (void) vm; + if (v->kind != JsonnetJsonValue::BOOL) return 2; + return v->number != 0; +} + +int jsonnet_json_extract_null(struct JsonnetVm *vm, const struct JsonnetJsonValue *v) +{ + (void) vm; + return v->kind == JsonnetJsonValue::NULL_KIND; +} + JsonnetJsonValue *jsonnet_json_make_string(JsonnetVm *vm, const char *v) { (void) vm; @@ -70,6 +90,32 @@ JsonnetJsonValue *jsonnet_json_make_string(JsonnetVm *vm, const char *v) return r; } +JsonnetJsonValue *jsonnet_json_make_number(struct JsonnetVm *vm, double v) +{ + (void) vm; + JsonnetJsonValue *r = new JsonnetJsonValue(); + r->kind = JsonnetJsonValue::NUMBER; + r->number = v; + return r; +} + +JsonnetJsonValue *jsonnet_json_make_bool(struct JsonnetVm *vm, int v) +{ + (void) vm; + JsonnetJsonValue *r = new JsonnetJsonValue(); + r->kind = JsonnetJsonValue::BOOL; + r->number = v != 0; + return r; +} + +JsonnetJsonValue *jsonnet_json_make_null(struct JsonnetVm *vm) +{ + (void) vm; + JsonnetJsonValue *r = new JsonnetJsonValue(); + r->kind = JsonnetJsonValue::NULL_KIND; + return r; +} + struct JsonnetVm { double gcGrowthTrigger; unsigned maxStack; @@ -497,6 +543,7 @@ static char *jsonnet_evaluate_snippet_aux(JsonnetVm *vm, const char *filename, return from_string(vm, ss.str()); } + return nullptr; // Quiet, compiler. } static char *jsonnet_evaluate_file_aux(JsonnetVm *vm, const char *filename, int *error, EvalKind kind) diff --git a/core/libjsonnet_test_snippet.c b/core/libjsonnet_test_snippet.c index 81d7d0219..ba9e989e7 100644 --- a/core/libjsonnet_test_snippet.c +++ b/core/libjsonnet_test_snippet.c @@ -17,18 +17,18 @@ limitations under the License. #include #include #include +#include #include -struct JsonnetJsonValue *concat(void *ctx, const struct JsonnetJsonValue * const *argv, int *succ) +struct JsonnetJsonValue *native_concat(void *ctx, const struct JsonnetJsonValue * const *argv, int *succ) { struct JsonnetVm *vm = (struct JsonnetVm *)ctx; const char *a = jsonnet_json_extract_string(vm, argv[0]); const char *b = jsonnet_json_extract_string(vm, argv[1]); if (a == NULL || b == NULL) { - struct JsonnetJsonValue *r = jsonnet_json_make_string(vm, "Bad params."); *succ = 0; - return r; + return jsonnet_json_make_string(vm, "Bad params."); } char *str = malloc(strlen(a) + strlen(b) + 1); sprintf(str, "%s%s", a, b); @@ -38,18 +38,32 @@ struct JsonnetJsonValue *concat(void *ctx, const struct JsonnetJsonValue * const return r; } +struct JsonnetJsonValue *native_square(void *ctx, const struct JsonnetJsonValue * const *argv, int *succ) +{ + struct JsonnetVm *vm = (struct JsonnetVm *)ctx; + double a; + if (!jsonnet_json_extract_number(vm, argv[0], &a)) { + *succ = 0; + return jsonnet_json_make_string(vm, "Bad param 'a'."); + } + *succ = 1; + return jsonnet_json_make_number(vm, a * a); +} + int main(int argc, const char **argv) { int error; char *output; struct JsonnetVm *vm; - const char *params[] = {"a", "b", NULL}; + const char *params1[] = {"a", NULL}; + const char *params2[] = {"a", "b", NULL}; if (argc != 2) { fprintf(stderr, "libjsonnet_test_snippet \n"); return EXIT_FAILURE; } vm = jsonnet_make(); - jsonnet_native_callback(vm, "concat", concat, vm, params); + jsonnet_native_callback(vm, "concat", native_concat, vm, params2); + jsonnet_native_callback(vm, "square", native_square, vm, params1); output = jsonnet_evaluate_snippet(vm, "snippet", argv[1], &error); if (error) { fprintf(stderr, "%s", output); diff --git a/core/vm.cpp b/core/vm.cpp index bc847476e..d823ded1c 100644 --- a/core/vm.cpp +++ b/core/vm.cpp @@ -16,6 +16,8 @@ limitations under the License. #include #include + +#include #include #include @@ -1971,53 +1973,98 @@ class Interpreter { } VmNativeCallbackMap::const_iterator nit = nativeCallbacks.find(builtin_name); - // TODO(dcunnin): Support more than just strings. + // TODO(dcunnin): Support arrays. + // TODO(dcunnin): Support objects. std::vector args2; for (const Value &arg : args) { - if (arg.t != Value::STRING) { + switch (arg.t) { + case Value::STRING: + args2.push_back(JsonnetJsonValue{ + JsonnetJsonValue::STRING, + encode_utf8(static_cast(arg.v.h)->value), + 0, + }); + break; + + case Value::BOOLEAN: + args2.push_back(JsonnetJsonValue{ + JsonnetJsonValue::BOOL, + "", + arg.v.b ? 0.0 : 1.0, + }); + break; + + case Value::DOUBLE: + args2.push_back(JsonnetJsonValue{ + JsonnetJsonValue::NUMBER, + "", + arg.v.d, + }); + break; + + case Value::NULL_TYPE: + args2.push_back(JsonnetJsonValue{ + JsonnetJsonValue::NULL_KIND, + "", + 0, + }); + break; + + default: throw makeError(ast.location, - "Native extensions can only take strings."); + "Native extensions can only take primitives."); } - args2.push_back(JsonnetJsonValue{ - JsonnetJsonValue::STRING, - encode_utf8(static_cast(arg.v.h)->value) - }); + } std::vector args3; for (size_t i = 0; i < args2.size() ; ++i) { args3.push_back(&args2[i]); } - if (nit != nativeCallbacks.end()) { - const VmNativeCallback &cb = nit->second; - int succ; - JsonnetJsonValue *r = cb.cb(cb.ctx, &args3[0], &succ); - if (succ) { - // TODO(dcunnin): Support more than just strings. - if (r->kind != JsonnetJsonValue::STRING) { - delete r; - throw makeError(ast.location, - "Native extensions can only return a string."); - } - std::string rs = r->string; - delete r; - scratch = makeString(decode_utf8(rs)); + if (nit == nativeCallbacks.end()) { + throw makeError(ast.location, + "Unrecognized builtin name: " + builtin_name); + } + const VmNativeCallback &cb = nit->second; + + int succ; + std::unique_ptr r(cb.cb(cb.ctx, &args3[0], &succ)); + if (succ) { + // TODO(dcunnin): Support arrays. + // TODO(dcunnin): Support objects. + switch (r->kind) { + case JsonnetJsonValue::STRING: + scratch = makeString(decode_utf8(r->string)); break; - } else { - if (r->kind != JsonnetJsonValue::STRING) { - delete r; - throw makeError( - ast.location, - "Native extension returned an error that was not a string."); - } - std::string rs = r->string; - delete r; - throw makeError(ast.location, rs); + + case JsonnetJsonValue::BOOL: + scratch = makeBoolean(r->number != 0.0); + break; + + case JsonnetJsonValue::NUMBER: + scratch = makeDouble(r->number); + break; + + case JsonnetJsonValue::NULL_KIND: + scratch = makeNull(); + break; + + default: + throw makeError(ast.location, + "Native extensions can only return primitives."); } + + } else { + if (r->kind != JsonnetJsonValue::STRING) { + throw makeError( + ast.location, + "Native extension returned an error that was not a string."); + } + std::string rs = r->string; + throw makeError(ast.location, rs); } - throw makeError(ast.location, - "Unrecognized builtin name: " + builtin_name); } else { + // Not all arguments forced yet. HeapThunk *th = f.thunks[f.elementId++]; if (!th->filled) { stack.newCall(ast.location, th, th->self, th->offset, th->upValues); diff --git a/include/libjsonnet.h b/include/libjsonnet.h index 1e8fe596e..978d2fb4f 100644 --- a/include/libjsonnet.h +++ b/include/libjsonnet.h @@ -76,10 +76,34 @@ struct JsonnetJsonValue; */ const char *jsonnet_json_extract_string(struct JsonnetVm *vm, const struct JsonnetJsonValue *v); +/** If the value is a number, return 1 and store the number in out, otherwise return 0. + */ +int jsonnet_json_extract_number(struct JsonnetVm *vm, const struct JsonnetJsonValue *v, double *out); + +/** Return 0 if the value is false, 1 if it is true, and 2 if it is not a bool. + */ +int jsonnet_json_extract_bool(struct JsonnetVm *vm, const struct JsonnetJsonValue *v); + +/** Return 1 if the value is null, else 0. + */ +int jsonnet_json_extract_null(struct JsonnetVm *vm, const struct JsonnetJsonValue *v); + /** Convert the given UTF8 string to a JsonnetJsonValue. */ struct JsonnetJsonValue *jsonnet_json_make_string(struct JsonnetVm *vm, const char *v); +/** Convert the given double to a JsonnetJsonValue. + */ +struct JsonnetJsonValue *jsonnet_json_make_number(struct JsonnetVm *vm, double v); + +/** Convert the given bool (1 or 0) to a JsonnetJsonValue. + */ +struct JsonnetJsonValue *jsonnet_json_make_bool(struct JsonnetVm *vm, int v); + +/** Make a JsonnetJsonValue representing null. + */ +struct JsonnetJsonValue *jsonnet_json_make_null(struct JsonnetVm *vm); + /** Callback to provide native extensions to Jsonnet. * * The returned JsonnetJsonValue* should be allocated with jsonnet_realloc. It will be cleaned up diff --git a/python/_jsonnet.c b/python/_jsonnet.c index 08046dad2..e6ef75d05 100644 --- a/python/_jsonnet.c +++ b/python/_jsonnet.c @@ -57,13 +57,26 @@ static struct JsonnetJsonValue *cpython_native_callback( // Populate python function args. arglist = PyTuple_New(ctx->argc); for (i = 0; i < ctx->argc; ++i) { - const char *param = jsonnet_json_extract_string(ctx->vm, argv[i]); - if (param == NULL) { + double d; + const char *param_str = jsonnet_json_extract_string(ctx->vm, argv[i]); + int param_null = jsonnet_json_extract_null(ctx->vm, argv[i]); + int param_bool = jsonnet_json_extract_bool(ctx->vm, argv[i]); + int param_num = jsonnet_json_extract_number(ctx->vm, argv[i], &d); + if (param_str != NULL) { + PyTuple_SetItem(arglist, i, PyString_FromString(param_str)); + } else if (param_null) { + PyTuple_SetItem(arglist, i, Py_None); + } else if (param_bool != 2) { + PyTuple_SetItem(arglist, i, PyBool_FromLong(param_bool)); + } else if (param_num) { + PyTuple_SetItem(arglist, i, PyFloat_FromDouble(d)); + } else { + // TODO(dcunnin): Support arrays (to tuples). + // TODO(dcunnin): Support objects (to dicts). Py_DECREF(arglist); *succ = 0; - return jsonnet_json_make_string(ctx->vm, "Non-string param."); + return jsonnet_json_make_string(ctx->vm, "Non-primitive param."); } - PyTuple_SetItem(arglist, i, PyString_FromString(param)); } // Call python function. @@ -78,16 +91,23 @@ static struct JsonnetJsonValue *cpython_native_callback( return r; } - if (!PyString_Check(result)) { - struct JsonnetJsonValue *r = - jsonnet_json_make_string(ctx->vm, "Python function did not return string"); + // TODO(dcunnin): Support arrays (from tuples). + // TODO(dcunnin): Support objects (from dicts). + struct JsonnetJsonValue *r; + *succ = 1; + if (PyString_Check(result)) { + r = jsonnet_json_make_string(ctx->vm, PyString_AsString(result)); + } else if (PyFloat_Check(result)) { + r = jsonnet_json_make_number(ctx->vm, PyFloat_AsDouble(result)); + } else if (PyBool_Check(result)) { + r = jsonnet_json_make_bool(ctx->vm, PyObject_IsTrue(result)); + } else if (result == Py_None) { + r = jsonnet_json_make_null(ctx->vm); + } else { + r = jsonnet_json_make_string(ctx->vm, "Python function did not return primitive"); *succ = 0; - return r; } - struct JsonnetJsonValue *r = - jsonnet_json_make_string(ctx->vm, PyString_AsString(result)); - *succ = 1; return r; }