mirror of
https://github.com/google/go-jsonnet.git
synced 2025-08-07 23:07:14 +02:00
* Interpreter & runtime - minimal usable version Accomplishments in this commit: * Majority of language features implemented: * Unary operators * Binary operators * Conditionals * Errors * Indexing arrays * Indexing objects * Object inheritance * Imports * Functions * Function calls * There is a quite nice way for creating builtins * Static analyzer is there with most of the functionality * Standard library is included and parts unaffected by missing features work * Some bugs in existing parts fixed * Most positive tests from C++ version pass, the rest is failing mostly due to missing builtins and comprehensions. * Some initial structure was created that should allow more incremental and focused changes in the future PRs. * Some comments/explanations added * Panics translated to a little bit more gentle internal errors (with a link to issues on github). What still sucks: * Stack traces & error messages (there's some stuff in place) * Almost everything is in the same package * Stuff is exported or unexporeted randomly (see above) * Missing a few lexing/parsing features * Missing builtins * Missing support for extvars and top-level-args * Checking function arguments is missing * No clean Go API that commandline and compatibility layer to C can use * No compatibility layer to C * Assertions don't work (desugaring level, assertEquals works). * Manifestation stack traces (and generally it could use some work). * The way environments are constructed is sometimes suboptimal/clumsy.
344 lines
9.4 KiB
Go
344 lines
9.4 KiB
Go
/*
|
|
Copyright 2017 Google Inc. All rights reserved.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package jsonnet
|
|
|
|
// TODO(sbarzowski) Is this the best option? It's the first one that worked for me...
|
|
//go:generate esc -o std.go -pkg=jsonnet std/std.jsonnet
|
|
|
|
func getStdCode() string {
|
|
return FSMustString(false, "/std/std.jsonnet")
|
|
}
|
|
|
|
func builtinPlus(e *evaluator, xp, yp potentialValue) (value, error) {
|
|
// TODO(sbarzowski) more types, mixing types
|
|
// TODO(sbarzowski) perhaps a more elegant way to dispatch
|
|
x, err := e.evaluate(xp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
switch left := x.(type) {
|
|
case *valueNumber:
|
|
right, err := e.evaluateNumber(yp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return makeValueNumber(left.value + right.value), nil
|
|
case *valueString:
|
|
right, err := e.evaluateString(yp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return makeValueString(left.value + right.value), nil
|
|
case valueObject:
|
|
right, err := e.evaluateObject(yp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return makeValueExtendedObject(left, right), nil
|
|
default:
|
|
return nil, e.typeErrorGeneral(x)
|
|
}
|
|
}
|
|
|
|
func builtinMinus(e *evaluator, xp, yp potentialValue) (value, error) {
|
|
x, err := e.evaluateNumber(xp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
y, err := e.evaluateNumber(yp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return makeValueNumber(x.value - y.value), nil
|
|
}
|
|
|
|
func builtinGreater(e *evaluator, xp, yp potentialValue) (value, error) {
|
|
x, err := e.evaluate(xp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
switch left := x.(type) {
|
|
case *valueNumber:
|
|
right, err := e.evaluateNumber(yp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return makeValueBoolean(left.value > right.value), nil
|
|
case *valueString:
|
|
right, err := e.evaluateString(yp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return makeValueBoolean(left.value > right.value), nil
|
|
default:
|
|
return nil, e.typeErrorGeneral(x)
|
|
}
|
|
}
|
|
|
|
func builtinLess(e *evaluator, xp, yp potentialValue) (value, error) {
|
|
return builtinGreater(e, yp, xp)
|
|
}
|
|
|
|
func builtinGreaterEq(e *evaluator, xp, yp potentialValue) (value, error) {
|
|
res, err := builtinLess(e, xp, yp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return res.(*valueBoolean).not(), nil
|
|
}
|
|
|
|
func builtinLessEq(e *evaluator, xp, yp potentialValue) (value, error) {
|
|
res, err := builtinGreater(e, xp, yp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return res.(*valueBoolean).not(), nil
|
|
}
|
|
|
|
func builtinAnd(e *evaluator, xp, yp potentialValue) (value, error) {
|
|
x, err := e.evaluateBoolean(xp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !x.value {
|
|
return x, nil
|
|
}
|
|
y, err := e.evaluateBoolean(yp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return y, nil
|
|
}
|
|
|
|
func builtinLength(e *evaluator, xp potentialValue) (value, error) {
|
|
x, err := e.evaluate(xp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var num int
|
|
switch x := x.(type) {
|
|
case *valueSimpleObject:
|
|
panic("TODO getting all the fields")
|
|
case *valueArray:
|
|
num = len(x.elements)
|
|
case *valueString:
|
|
num = len(x.value)
|
|
case *valueFunction:
|
|
num = len(x.parameters())
|
|
default:
|
|
return nil, e.typeErrorGeneral(x)
|
|
}
|
|
return makeValueNumber(float64(num)), nil
|
|
}
|
|
|
|
func builtinMakeArray(e *evaluator, szp potentialValue, funcp potentialValue) (value, error) {
|
|
sz, err := e.evaluateNumber(szp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fun, err := e.evaluateFunction(funcp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
num := int(sz.value)
|
|
var elems []potentialValue
|
|
for i := 0; i < num; i++ {
|
|
elem := fun.call(args(&readyValue{intToValue(i)}))
|
|
elems = append(elems, elem)
|
|
}
|
|
return makeValueArray(elems), nil
|
|
}
|
|
|
|
func builtinNegation(e *evaluator, xp potentialValue) (value, error) {
|
|
x, err := e.evaluateBoolean(xp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return makeValueBoolean(!x.value), nil
|
|
}
|
|
|
|
func builtinBitNeg(e *evaluator, xp potentialValue) (value, error) {
|
|
x, err := e.evaluateNumber(xp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
i := int64(x.value)
|
|
return int64ToValue(^i), nil
|
|
}
|
|
|
|
func builtinIdentity(e *evaluator, xp potentialValue) (value, error) {
|
|
x, err := e.evaluate(xp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return x, nil
|
|
}
|
|
|
|
func builtinUnaryMinus(e *evaluator, xp potentialValue) (value, error) {
|
|
x, err := e.evaluateNumber(xp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return makeValueNumber(-x.value), nil
|
|
}
|
|
|
|
func primitiveEquals(e *evaluator, xp potentialValue, yp potentialValue) (value, error) {
|
|
x, err := e.evaluate(xp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
y, err := e.evaluate(yp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if x.typename() != y.typename() { // TODO(sbarzowski) ugh, string comparison
|
|
return makeValueBoolean(false), nil
|
|
}
|
|
switch left := x.(type) {
|
|
case *valueBoolean:
|
|
right, err := e.getBoolean(y)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return makeValueBoolean(left.value == right.value), nil
|
|
case *valueNumber:
|
|
right, err := e.getNumber(y)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return makeValueBoolean(left.value == right.value), nil
|
|
case *valueString:
|
|
right, err := e.getString(y)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return makeValueBoolean(left.value == right.value), nil
|
|
case *valueNull:
|
|
return makeValueBoolean(true), nil
|
|
case *valueFunction:
|
|
return nil, e.Error("Cannot test equality of functions")
|
|
default:
|
|
return nil, e.Error(
|
|
"primitiveEquals operates on primitive types, got " + x.typename(),
|
|
)
|
|
}
|
|
}
|
|
|
|
func builtinType(e *evaluator, xp potentialValue) (value, error) {
|
|
x, err := e.evaluate(xp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return makeValueString(x.typename()), nil
|
|
}
|
|
|
|
type unaryBuiltin func(*evaluator, potentialValue) (value, error)
|
|
type binaryBuiltin func(*evaluator, potentialValue, potentialValue) (value, error)
|
|
|
|
type UnaryBuiltin struct {
|
|
name identifier
|
|
function unaryBuiltin
|
|
parameters identifiers
|
|
}
|
|
|
|
func getBuiltinEvaluator(e *evaluator, name identifier) *evaluator {
|
|
loc := makeLocationRangeMessage("<builtin>")
|
|
context := TraceContext{Name: "builtin function <" + string(name) + ">"}
|
|
trace := TraceElement{loc: &loc, context: &context}
|
|
return &evaluator{i: e.i, trace: &trace}
|
|
}
|
|
|
|
func (b *UnaryBuiltin) EvalCall(args callArguments, e *evaluator) (value, error) {
|
|
|
|
// TODO check args
|
|
return b.function(getBuiltinEvaluator(e, b.name), args.positional[0])
|
|
}
|
|
|
|
func (b *UnaryBuiltin) Parameters() identifiers {
|
|
return b.parameters
|
|
}
|
|
|
|
type BinaryBuiltin struct {
|
|
name identifier
|
|
function binaryBuiltin
|
|
parameters identifiers
|
|
}
|
|
|
|
func (b *BinaryBuiltin) EvalCall(args callArguments, e *evaluator) (value, error) {
|
|
// TODO check args
|
|
return b.function(getBuiltinEvaluator(e, b.name), args.positional[0], args.positional[1])
|
|
}
|
|
|
|
func (b *BinaryBuiltin) Parameters() identifiers {
|
|
return b.parameters
|
|
}
|
|
|
|
func todoFunc(e *evaluator, x, y potentialValue) (value, error) {
|
|
return nil, e.Error("not implemented yet")
|
|
}
|
|
|
|
// so that we don't get segfaults
|
|
var todo = &BinaryBuiltin{function: todoFunc, parameters: identifiers{"x", "y"}}
|
|
|
|
var desugaredBop = map[binaryOp]identifier{
|
|
//bopPercent,
|
|
bopManifestEqual: "equals",
|
|
bopManifestUnequal: "notEquals", // Special case
|
|
}
|
|
|
|
var bopBuiltins = []*BinaryBuiltin{
|
|
bopMult: todo,
|
|
bopDiv: todo,
|
|
bopPercent: todo,
|
|
|
|
bopPlus: &BinaryBuiltin{name: "operator+", function: builtinPlus, parameters: identifiers{"x", "y"}},
|
|
bopMinus: &BinaryBuiltin{name: "operator-", function: builtinMinus, parameters: identifiers{"x", "y"}},
|
|
|
|
bopShiftL: todo,
|
|
bopShiftR: todo,
|
|
|
|
bopGreater: &BinaryBuiltin{name: "operator>", function: builtinGreater, parameters: identifiers{"x", "y"}},
|
|
bopGreaterEq: &BinaryBuiltin{name: "operator>=", function: builtinGreaterEq, parameters: identifiers{"x", "y"}},
|
|
bopLess: &BinaryBuiltin{name: "operator<,", function: builtinLess, parameters: identifiers{"x", "y"}},
|
|
bopLessEq: &BinaryBuiltin{name: "operator<=", function: builtinLessEq, parameters: identifiers{"x", "y"}},
|
|
|
|
bopManifestEqual: todo,
|
|
bopManifestUnequal: todo,
|
|
|
|
bopBitwiseAnd: todo,
|
|
bopBitwiseXor: todo,
|
|
bopBitwiseOr: todo,
|
|
|
|
bopAnd: &BinaryBuiltin{name: "operator&&", function: builtinAnd, parameters: identifiers{"x", "y"}},
|
|
bopOr: todo,
|
|
}
|
|
|
|
var uopBuiltins = []*UnaryBuiltin{
|
|
uopNot: &UnaryBuiltin{name: "operator!", function: builtinNegation, parameters: identifiers{"x"}},
|
|
uopBitwiseNot: &UnaryBuiltin{name: "operator~", function: builtinBitNeg, parameters: identifiers{"x"}},
|
|
uopPlus: &UnaryBuiltin{name: "operator+ (unary)", function: builtinIdentity, parameters: identifiers{"x"}},
|
|
uopMinus: &UnaryBuiltin{name: "operator- (unary)", function: builtinUnaryMinus, parameters: identifiers{"x"}},
|
|
}
|
|
|
|
// TODO(sbarzowski) eliminate duplication in function names (e.g. build map from array or constants)
|
|
var funcBuiltins = map[string]evalCallable{
|
|
"length": &UnaryBuiltin{name: "length", function: builtinLength, parameters: identifiers{"x"}},
|
|
"makeArray": &BinaryBuiltin{name: "makeArray", function: builtinMakeArray, parameters: identifiers{"sz", "func"}},
|
|
"primitiveEquals": &BinaryBuiltin{name: "primitiveEquals", function: primitiveEquals, parameters: identifiers{"sz", "func"}},
|
|
"type": &UnaryBuiltin{name: "type", function: builtinType, parameters: identifiers{"x"}},
|
|
}
|