mirror of
https://github.com/google/go-jsonnet.git
synced 2025-09-28 17:01:02 +02:00
Internal refactor to improve performance (#225)
* Internal refactor to improve performance
This commit is contained in:
parent
530cf7b07c
commit
2973e24152
535
builtins.go
535
builtins.go
File diff suppressed because it is too large
Load Diff
227
evaluator.go
227
evaluator.go
@ -1,227 +0,0 @@
|
||||
/*
|
||||
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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/google/go-jsonnet/ast"
|
||||
)
|
||||
|
||||
// evaluator is a convenience wrapper for interpreter
|
||||
// Most importantly it keeps the context for traces and handles details
|
||||
// of error handling.
|
||||
type evaluator struct {
|
||||
i *interpreter
|
||||
trace *TraceElement
|
||||
}
|
||||
|
||||
func makeEvaluator(i *interpreter, trace *TraceElement) *evaluator {
|
||||
return &evaluator{i: i, trace: trace}
|
||||
}
|
||||
|
||||
func (e *evaluator) inNewContext(trace *TraceElement) *evaluator {
|
||||
return makeEvaluator(e.i, trace)
|
||||
}
|
||||
|
||||
func (e *evaluator) evaluate(ph potentialValue) (value, error) {
|
||||
return ph.getValue(e.i, e.trace)
|
||||
}
|
||||
|
||||
func (e *evaluator) evaluateTailCall(ph potentialValue, tc tailCallStatus) (value, error) {
|
||||
if tc == tailCall {
|
||||
e.i.stack.tailCallTrimStack()
|
||||
}
|
||||
return ph.getValue(e.i, e.trace)
|
||||
}
|
||||
|
||||
func (e *evaluator) Error(s string) error {
|
||||
err := makeRuntimeError(s, e.i.getCurrentStackTrace(e.trace))
|
||||
return err
|
||||
}
|
||||
|
||||
func (e *evaluator) typeErrorSpecific(bad value, good value) error {
|
||||
return e.Error(
|
||||
fmt.Sprintf("Unexpected type %v, expected %v", bad.getType().name, good.getType().name),
|
||||
)
|
||||
}
|
||||
|
||||
func (e *evaluator) typeErrorGeneral(bad value) error {
|
||||
return e.Error(
|
||||
fmt.Sprintf("Unexpected type %v", bad.getType().name),
|
||||
)
|
||||
}
|
||||
|
||||
func (e *evaluator) getNumber(val value) (*valueNumber, error) {
|
||||
switch v := val.(type) {
|
||||
case *valueNumber:
|
||||
return v, nil
|
||||
default:
|
||||
return nil, e.typeErrorSpecific(val, &valueNumber{})
|
||||
}
|
||||
}
|
||||
|
||||
func (e *evaluator) evaluateNumber(pv potentialValue) (*valueNumber, error) {
|
||||
v, err := e.evaluate(pv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return e.getNumber(v)
|
||||
}
|
||||
|
||||
func (e *evaluator) getInt(val value) (int, error) {
|
||||
num, err := e.getNumber(val)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// We conservatively convert ot int32, so that it can be machine-sized int
|
||||
// on any machine. And it's used only for indexing anyway.
|
||||
intNum := int(int32(num.value))
|
||||
if float64(intNum) != num.value {
|
||||
return 0, e.Error(fmt.Sprintf("Expected an integer, but got %v", num.value))
|
||||
}
|
||||
return intNum, nil
|
||||
}
|
||||
|
||||
func (e *evaluator) evaluateInt(pv potentialValue) (int, error) {
|
||||
v, err := e.evaluate(pv)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return e.getInt(v)
|
||||
}
|
||||
|
||||
func (e *evaluator) getInt64(val value) (int64, error) {
|
||||
num, err := e.getNumber(val)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
intNum := int64(num.value)
|
||||
if float64(intNum) != num.value {
|
||||
return 0, e.Error(fmt.Sprintf("Expected an integer, but got %v", num.value))
|
||||
}
|
||||
return intNum, nil
|
||||
}
|
||||
|
||||
func (e *evaluator) evaluateInt64(pv potentialValue) (int64, error) {
|
||||
v, err := e.evaluate(pv)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return e.getInt64(v)
|
||||
}
|
||||
|
||||
func (e *evaluator) getString(val value) (*valueString, error) {
|
||||
switch v := val.(type) {
|
||||
case *valueString:
|
||||
return v, nil
|
||||
default:
|
||||
return nil, e.typeErrorSpecific(val, &valueString{})
|
||||
}
|
||||
}
|
||||
|
||||
func (e *evaluator) evaluateString(pv potentialValue) (*valueString, error) {
|
||||
v, err := e.evaluate(pv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return e.getString(v)
|
||||
}
|
||||
|
||||
func (e *evaluator) getBoolean(val value) (*valueBoolean, error) {
|
||||
switch v := val.(type) {
|
||||
case *valueBoolean:
|
||||
return v, nil
|
||||
default:
|
||||
return nil, e.typeErrorSpecific(val, &valueBoolean{})
|
||||
}
|
||||
}
|
||||
|
||||
func (e *evaluator) evaluateBoolean(pv potentialValue) (*valueBoolean, error) {
|
||||
v, err := e.evaluate(pv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return e.getBoolean(v)
|
||||
}
|
||||
|
||||
func (e *evaluator) getArray(val value) (*valueArray, error) {
|
||||
switch v := val.(type) {
|
||||
case *valueArray:
|
||||
return v, nil
|
||||
default:
|
||||
return nil, e.typeErrorSpecific(val, &valueArray{})
|
||||
}
|
||||
}
|
||||
|
||||
func (e *evaluator) evaluateArray(pv potentialValue) (*valueArray, error) {
|
||||
v, err := e.evaluate(pv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return e.getArray(v)
|
||||
}
|
||||
|
||||
func (e *evaluator) getFunction(val value) (*valueFunction, error) {
|
||||
switch v := val.(type) {
|
||||
case *valueFunction:
|
||||
return v, nil
|
||||
default:
|
||||
return nil, e.typeErrorSpecific(val, &valueFunction{})
|
||||
}
|
||||
}
|
||||
|
||||
func (e *evaluator) evaluateFunction(pv potentialValue) (*valueFunction, error) {
|
||||
v, err := e.evaluate(pv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return e.getFunction(v)
|
||||
}
|
||||
|
||||
func (e *evaluator) getObject(val value) (valueObject, error) {
|
||||
switch v := val.(type) {
|
||||
case valueObject:
|
||||
return v, nil
|
||||
default:
|
||||
return nil, e.typeErrorSpecific(val, &valueSimpleObject{})
|
||||
}
|
||||
}
|
||||
|
||||
func (e *evaluator) evaluateObject(pv potentialValue) (valueObject, error) {
|
||||
v, err := e.evaluate(pv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return e.getObject(v)
|
||||
}
|
||||
|
||||
func (e *evaluator) evalInCurrentContext(a ast.Node, tc tailCallStatus) (value, error) {
|
||||
return e.i.evaluate(a, tc)
|
||||
}
|
||||
|
||||
func (e *evaluator) evalInCleanEnv(env *environment, ast ast.Node, trimmable bool) (value, error) {
|
||||
return e.i.EvalInCleanEnv(e.trace, env, ast, trimmable)
|
||||
}
|
||||
|
||||
func (e *evaluator) lookUpVar(ident ast.Identifier) potentialValue {
|
||||
th := e.i.stack.lookUpVar(ident)
|
||||
if th == nil {
|
||||
panic(fmt.Sprintf("RUNTIME: Unknown variable: %v (we should have caught this statically)", ident))
|
||||
}
|
||||
return th
|
||||
}
|
23
imports.go
23
imports.go
@ -98,15 +98,15 @@ func (cache *ImportCache) importData(importedFrom, importedPath string) (content
|
||||
}
|
||||
|
||||
// ImportString imports a string, caches it and then returns it.
|
||||
func (cache *ImportCache) ImportString(importedFrom, importedPath string, e *evaluator) (*valueString, error) {
|
||||
func (cache *ImportCache) ImportString(importedFrom, importedPath string, i *interpreter, trace TraceElement) (*valueString, error) {
|
||||
data, _, err := cache.importData(importedFrom, importedPath)
|
||||
if err != nil {
|
||||
return nil, e.Error(err.Error())
|
||||
return nil, i.Error(err.Error(), trace)
|
||||
}
|
||||
return makeValueString(data.String()), nil
|
||||
}
|
||||
|
||||
func codeToPV(e *evaluator, filename string, code string) potentialValue {
|
||||
func codeToPV(i *interpreter, filename string, code string) *cachedThunk {
|
||||
node, err := snippetToAST(filename, code)
|
||||
if err != nil {
|
||||
// TODO(sbarzowski) we should wrap (static) error here
|
||||
@ -114,26 +114,31 @@ func codeToPV(e *evaluator, filename string, code string) potentialValue {
|
||||
// actually depends on what happens in Runtime (whether import gets
|
||||
// evaluated).
|
||||
// The same thinking applies to external variables.
|
||||
return makeErrorThunk(err)
|
||||
return &cachedThunk{err: err}
|
||||
}
|
||||
env := makeInitialEnv(filename, i.baseStd)
|
||||
return &cachedThunk{
|
||||
env: &env,
|
||||
body: node,
|
||||
content: nil,
|
||||
}
|
||||
return makeThunk(makeInitialEnv(filename, e.i.baseStd), node)
|
||||
}
|
||||
|
||||
// ImportCode imports code from a path.
|
||||
func (cache *ImportCache) ImportCode(importedFrom, importedPath string, e *evaluator) (value, error) {
|
||||
func (cache *ImportCache) ImportCode(importedFrom, importedPath string, i *interpreter, trace TraceElement) (value, error) {
|
||||
contents, foundAt, err := cache.importData(importedFrom, importedPath)
|
||||
if err != nil {
|
||||
return nil, e.Error(err.Error())
|
||||
return nil, i.Error(err.Error(), trace)
|
||||
}
|
||||
var pv potentialValue
|
||||
if cachedPV, isCached := cache.codeCache[foundAt]; !isCached {
|
||||
// File hasn't been parsed and analyzed before, update the cache record.
|
||||
pv = codeToPV(e, foundAt, contents.String())
|
||||
pv = codeToPV(i, foundAt, contents.String())
|
||||
cache.codeCache[foundAt] = pv
|
||||
} else {
|
||||
pv = cachedPV
|
||||
}
|
||||
return e.evaluate(pv)
|
||||
return i.evaluatePV(pv, trace)
|
||||
}
|
||||
|
||||
// Concrete importers
|
||||
|
512
interpreter.go
512
interpreter.go
@ -29,7 +29,7 @@ import (
|
||||
// TODO(sbarzowski) use it as a pointer in most places b/c it can sometimes be shared
|
||||
// for example it can be shared between array elements and function arguments
|
||||
type environment struct {
|
||||
sb selfBinding
|
||||
selfBinding selfBinding
|
||||
|
||||
// Bindings introduced in this frame. The way previous bindings are treated
|
||||
// depends on the type of a frame.
|
||||
@ -43,22 +43,18 @@ type environment struct {
|
||||
func makeEnvironment(upValues bindingFrame, sb selfBinding) environment {
|
||||
return environment{
|
||||
upValues: upValues,
|
||||
sb: sb,
|
||||
selfBinding: sb,
|
||||
}
|
||||
}
|
||||
|
||||
func callFrameToTraceFrame(frame *callFrame) TraceFrame {
|
||||
return traceElementToTraceFrame(frame.trace)
|
||||
}
|
||||
|
||||
func (i *interpreter) getCurrentStackTrace(additional *TraceElement) []TraceFrame {
|
||||
func (i *interpreter) getCurrentStackTrace(additional TraceElement) []TraceFrame {
|
||||
var result []TraceFrame
|
||||
for _, f := range i.stack.stack {
|
||||
if f.isCall {
|
||||
result = append(result, callFrameToTraceFrame(f))
|
||||
result = append(result, traceElementToTraceFrame(f.trace))
|
||||
}
|
||||
}
|
||||
if additional != nil {
|
||||
if additional.loc != nil {
|
||||
result = append(result, traceElementToTraceFrame(additional))
|
||||
}
|
||||
return result
|
||||
@ -71,7 +67,7 @@ type callFrame struct {
|
||||
isCall bool
|
||||
|
||||
// Tracing information about the place where it was called from.
|
||||
trace *TraceElement
|
||||
trace TraceElement
|
||||
|
||||
// Whether this frame can be removed from the stack when it doesn't affect
|
||||
// the evaluation result, but in case of an error, it won't appear on the
|
||||
@ -84,7 +80,7 @@ type callFrame struct {
|
||||
|
||||
func dumpCallFrame(c *callFrame) string {
|
||||
var loc ast.LocationRange
|
||||
if c.trace == nil || c.trace.loc == nil {
|
||||
if c.trace.loc == nil {
|
||||
loc = ast.MakeLocationRangeMessage("?")
|
||||
} else {
|
||||
loc = *c.trace.loc
|
||||
@ -151,11 +147,7 @@ const (
|
||||
tailCall
|
||||
)
|
||||
|
||||
func (i *interpreter) newCall(trace *TraceElement, env environment, trimmable bool) error {
|
||||
s := &i.stack
|
||||
if s.calls >= s.limit {
|
||||
return makeRuntimeError("max stack frames exceeded.", i.getCurrentStackTrace(trace))
|
||||
}
|
||||
func (s *callStack) newCall(trace TraceElement, env environment, trimmable bool) {
|
||||
s.stack = append(s.stack, &callFrame{
|
||||
isCall: true,
|
||||
trace: trace,
|
||||
@ -163,11 +155,9 @@ func (i *interpreter) newCall(trace *TraceElement, env environment, trimmable bo
|
||||
trimmable: trimmable,
|
||||
})
|
||||
s.calls++
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *interpreter) newLocal(vars bindingFrame) {
|
||||
s := &i.stack
|
||||
func (s *callStack) newLocal(vars bindingFrame) {
|
||||
s.stack = append(s.stack, &callFrame{
|
||||
env: makeEnvironment(vars, selfBinding{}),
|
||||
})
|
||||
@ -177,17 +167,17 @@ func (i *interpreter) newLocal(vars bindingFrame) {
|
||||
func (s *callStack) getSelfBinding() selfBinding {
|
||||
for i := len(s.stack) - 1; i >= 0; i-- {
|
||||
if s.stack[i].isCall {
|
||||
return s.stack[i].env.sb
|
||||
return s.stack[i].env.selfBinding
|
||||
}
|
||||
}
|
||||
panic(fmt.Sprintf("malformed stack %v", dumpCallStack(s)))
|
||||
}
|
||||
|
||||
// lookUpVar finds for the closest variable in scope that matches the given name.
|
||||
func (s *callStack) lookUpVar(id ast.Identifier) potentialValue {
|
||||
func (s *callStack) lookUpVar(id ast.Identifier) *cachedThunk {
|
||||
for i := len(s.stack) - 1; i >= 0; i-- {
|
||||
bind := s.stack[i].env.upValues[id]
|
||||
if bind != nil {
|
||||
bind, present := s.stack[i].env.upValues[id]
|
||||
if present {
|
||||
return bind
|
||||
}
|
||||
if s.stack[i].isCall {
|
||||
@ -198,6 +188,30 @@ func (s *callStack) lookUpVar(id ast.Identifier) potentialValue {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *callStack) lookUpVarOrPanic(id ast.Identifier) *cachedThunk {
|
||||
th := s.lookUpVar(id)
|
||||
if th == nil {
|
||||
panic(fmt.Sprintf("RUNTIME: Unknown variable: %v (we should have caught this statically)", id))
|
||||
}
|
||||
return th
|
||||
}
|
||||
|
||||
func (s *callStack) getCurrentEnv(ast ast.Node) environment {
|
||||
return makeEnvironment(
|
||||
s.capture(ast.FreeVariables()),
|
||||
s.getSelfBinding(),
|
||||
)
|
||||
}
|
||||
|
||||
// Build a binding frame containing specified variables.
|
||||
func (s *callStack) capture(freeVars ast.Identifiers) bindingFrame {
|
||||
env := make(bindingFrame)
|
||||
for _, fv := range freeVars {
|
||||
env[fv] = s.lookUpVarOrPanic(fv)
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
func makeCallStack(limit int) callStack {
|
||||
return callStack{
|
||||
calls: 0,
|
||||
@ -213,7 +227,7 @@ type interpreter struct {
|
||||
stack callStack
|
||||
|
||||
// External variables
|
||||
extVars map[string]potentialValue
|
||||
extVars map[string]*cachedThunk
|
||||
|
||||
// Native functions
|
||||
nativeFuncs map[string]*NativeFunction
|
||||
@ -225,18 +239,7 @@ type interpreter struct {
|
||||
importCache *ImportCache
|
||||
}
|
||||
|
||||
// Build a binding frame containing specified variables.
|
||||
func (i *interpreter) capture(freeVars ast.Identifiers) bindingFrame {
|
||||
env := make(bindingFrame)
|
||||
for _, fv := range freeVars {
|
||||
env[fv] = i.stack.lookUpVar(fv)
|
||||
if env[fv] == nil {
|
||||
panic(fmt.Sprintf("Variable %v vanished", fv))
|
||||
}
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
// Map union, b takes precedence when keys collide.
|
||||
func addBindings(a, b bindingFrame) bindingFrame {
|
||||
result := make(bindingFrame)
|
||||
|
||||
@ -251,82 +254,117 @@ func addBindings(a, b bindingFrame) bindingFrame {
|
||||
return result
|
||||
}
|
||||
|
||||
func (i *interpreter) getCurrentEnv(ast ast.Node) environment {
|
||||
return makeEnvironment(
|
||||
i.capture(ast.FreeVariables()),
|
||||
i.stack.getSelfBinding(),
|
||||
)
|
||||
func (i *interpreter) newCall(trace TraceElement, env environment, trimmable bool) error {
|
||||
s := &i.stack
|
||||
if s.calls >= s.limit {
|
||||
return makeRuntimeError("max stack frames exceeded.", i.getCurrentStackTrace(trace))
|
||||
}
|
||||
s.newCall(trace, env, trimmable)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *interpreter) evaluate(a ast.Node, tc tailCallStatus) (value, error) {
|
||||
e := &evaluator{
|
||||
trace: &TraceElement{
|
||||
trace := TraceElement{
|
||||
loc: a.Loc(),
|
||||
context: a.Context(),
|
||||
},
|
||||
i: i,
|
||||
}
|
||||
|
||||
switch node := a.(type) {
|
||||
case *ast.Array:
|
||||
sb := i.stack.getSelfBinding()
|
||||
var elements []potentialValue
|
||||
var elements []*cachedThunk
|
||||
for _, el := range node.Elements {
|
||||
env := makeEnvironment(i.capture(el.FreeVariables()), sb)
|
||||
elThunk := makeThunk(env, el)
|
||||
elements = append(elements, elThunk)
|
||||
env := makeEnvironment(i.stack.capture(el.FreeVariables()), sb)
|
||||
elThunk := cachedThunk{env: &env, body: el}
|
||||
elements = append(elements, &elThunk)
|
||||
}
|
||||
return makeValueArray(elements), nil
|
||||
|
||||
case *ast.Binary:
|
||||
// Some binary operators are lazy, so thunks are needed in general
|
||||
env := i.getCurrentEnv(node)
|
||||
// TODO(sbarzowski) make sure it displays nicely in stack trace (thunk names etc.)
|
||||
// TODO(sbarzowski) it may make sense not to show a line in stack trace for operators
|
||||
// at all in many cases. 1 + 2 + 3 + 4 + error "x" will show 5 lines
|
||||
// of stack trace now, and it's not that nice.
|
||||
left := makeThunk(env, node.Left)
|
||||
right := makeThunk(env, node.Right)
|
||||
|
||||
builtin := bopBuiltins[node.Op]
|
||||
|
||||
result, err := builtin.function(e, left, right)
|
||||
if node.Op == ast.BopAnd {
|
||||
// Special case for shortcut semantics.
|
||||
xv, err := i.evaluate(node.Left, tc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
x, err := i.getBoolean(xv, trace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !x.value {
|
||||
return x, nil
|
||||
}
|
||||
yv, err := i.evaluate(node.Right, tc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return i.getBoolean(yv, trace)
|
||||
} else if node.Op == ast.BopOr {
|
||||
// Special case for shortcut semantics.
|
||||
xv, err := i.evaluate(node.Left, tc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x, err := i.getBoolean(xv, trace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if x.value {
|
||||
return x, nil
|
||||
}
|
||||
yv, err := i.evaluate(node.Right, tc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return i.getBoolean(yv, trace)
|
||||
|
||||
} else {
|
||||
left, err := i.evaluate(node.Left, tc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
right, err := i.evaluate(node.Right, tc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO(dcunnin): The double dereference here is probably not necessary.
|
||||
builtin := bopBuiltins[node.Op]
|
||||
return builtin.function(i, trace, left, right)
|
||||
}
|
||||
|
||||
case *ast.Unary:
|
||||
env := i.getCurrentEnv(node)
|
||||
arg := makeThunk(env, node.Expr)
|
||||
value, err := i.evaluate(node.Expr, tc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
builtin := uopBuiltins[node.Op]
|
||||
|
||||
result, err := builtin.function(e, arg)
|
||||
result, err := builtin.function(i, trace, value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
|
||||
case *ast.Conditional:
|
||||
cond, err := e.evalInCurrentContext(node.Cond, nonTailCall)
|
||||
cond, err := i.evaluate(node.Cond, nonTailCall)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
condBool, err := e.getBoolean(cond)
|
||||
condBool, err := i.getBoolean(cond, trace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if condBool.value {
|
||||
return e.evalInCurrentContext(node.BranchTrue, tc)
|
||||
return i.evaluate(node.BranchTrue, tc)
|
||||
}
|
||||
return e.evalInCurrentContext(node.BranchFalse, tc)
|
||||
return i.evaluate(node.BranchFalse, tc)
|
||||
|
||||
case *ast.DesugaredObject:
|
||||
// Evaluate all the field names. Check for null, dups, etc.
|
||||
fields := make(simpleObjectFieldMap)
|
||||
for _, field := range node.Fields {
|
||||
fieldNameValue, err := e.evalInCurrentContext(field.Name, nonTailCall)
|
||||
fieldNameValue, err := i.evaluate(field.Name, nonTailCall)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -338,11 +376,11 @@ func (i *interpreter) evaluate(a ast.Node, tc tailCallStatus) (value, error) {
|
||||
// Omitted field.
|
||||
continue
|
||||
default:
|
||||
return nil, e.Error(fmt.Sprintf("Field name must be string, got %v", fieldNameValue.getType().name))
|
||||
return nil, i.Error(fmt.Sprintf("Field name must be string, got %v", fieldNameValue.getType().name), trace)
|
||||
}
|
||||
|
||||
if _, ok := fields[fieldName]; ok {
|
||||
return nil, e.Error(duplicateFieldNameErrMsg(fieldName))
|
||||
return nil, i.Error(duplicateFieldNameErrMsg(fieldName), trace)
|
||||
}
|
||||
var f unboundField = &codeUnboundField{field.Body}
|
||||
if field.PlusSuper {
|
||||
@ -354,69 +392,69 @@ func (i *interpreter) evaluate(a ast.Node, tc tailCallStatus) (value, error) {
|
||||
for _, assert := range node.Asserts {
|
||||
asserts = append(asserts, &codeUnboundField{assert})
|
||||
}
|
||||
upValues := i.capture(node.FreeVariables())
|
||||
upValues := i.stack.capture(node.FreeVariables())
|
||||
return makeValueSimpleObject(upValues, fields, asserts), nil
|
||||
|
||||
case *ast.Error:
|
||||
msgVal, err := e.evalInCurrentContext(node.Expr, nonTailCall)
|
||||
msgVal, err := i.evaluate(node.Expr, nonTailCall)
|
||||
if err != nil {
|
||||
// error when evaluating error message
|
||||
return nil, err
|
||||
}
|
||||
if msgVal.getType() != stringType {
|
||||
msgVal, err = builtinToString(e, &readyValue{msgVal})
|
||||
msgVal, err = builtinToString(i, trace, msgVal)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
msg, err := e.getString(msgVal)
|
||||
msg, err := i.getString(msgVal, trace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, e.Error(msg.getString())
|
||||
return nil, i.Error(msg.getString(), trace)
|
||||
|
||||
case *ast.Index:
|
||||
targetValue, err := e.evalInCurrentContext(node.Target, nonTailCall)
|
||||
targetValue, err := i.evaluate(node.Target, nonTailCall)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
index, err := e.evalInCurrentContext(node.Index, nonTailCall)
|
||||
index, err := i.evaluate(node.Index, nonTailCall)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch target := targetValue.(type) {
|
||||
case valueObject:
|
||||
indexString, err := e.getString(index)
|
||||
indexString, err := i.getString(index, trace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return target.index(e, indexString.getString())
|
||||
return target.index(i, trace, indexString.getString())
|
||||
case *valueArray:
|
||||
indexInt, err := e.getNumber(index)
|
||||
indexInt, err := i.getNumber(index, trace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO(https://github.com/google/jsonnet/issues/377): non-integer indexes should be an error
|
||||
return target.index(e, int(indexInt.value), tc)
|
||||
return target.index(i, trace, int(indexInt.value))
|
||||
|
||||
case *valueString:
|
||||
indexInt, err := e.getNumber(index)
|
||||
indexInt, err := i.getNumber(index, trace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO(https://github.com/google/jsonnet/issues/377): non-integer indexes should be an error
|
||||
return target.index(e, int(indexInt.value))
|
||||
return target.index(i, trace, int(indexInt.value))
|
||||
}
|
||||
|
||||
return nil, e.Error(fmt.Sprintf("Value non indexable: %v", reflect.TypeOf(targetValue)))
|
||||
return nil, i.Error(fmt.Sprintf("Value non indexable: %v", reflect.TypeOf(targetValue)), trace)
|
||||
|
||||
case *ast.Import:
|
||||
codePath := node.Loc().FileName
|
||||
return i.importCache.ImportCode(codePath, node.File.Value, e)
|
||||
return i.importCache.ImportCode(codePath, node.File.Value, i, trace)
|
||||
|
||||
case *ast.ImportStr:
|
||||
codePath := node.Loc().FileName
|
||||
return i.importCache.ImportString(codePath, node.File.Value, e)
|
||||
return i.importCache.ImportString(codePath, node.File.Value, i, trace)
|
||||
|
||||
case *ast.LiteralBoolean:
|
||||
return makeValueBoolean(node.Value), nil
|
||||
@ -432,19 +470,19 @@ func (i *interpreter) evaluate(a ast.Node, tc tailCallStatus) (value, error) {
|
||||
|
||||
case *ast.Local:
|
||||
vars := make(bindingFrame)
|
||||
bindEnv := i.getCurrentEnv(a)
|
||||
bindEnv := i.stack.getCurrentEnv(a)
|
||||
for _, bind := range node.Binds {
|
||||
th := makeThunk(bindEnv, bind.Body)
|
||||
th := cachedThunk{env: &bindEnv, body: bind.Body}
|
||||
|
||||
// recursive locals
|
||||
vars[bind.Variable] = th
|
||||
bindEnv.upValues[bind.Variable] = th
|
||||
vars[bind.Variable] = &th
|
||||
bindEnv.upValues[bind.Variable] = &th
|
||||
}
|
||||
i.newLocal(vars)
|
||||
i.stack.newLocal(vars)
|
||||
sz := len(i.stack.stack)
|
||||
// Add new stack frame, with new thunk for this variable
|
||||
// execute body WRT stack frame.
|
||||
v, err := e.evalInCurrentContext(node.Body, tc)
|
||||
v, err := i.evaluate(node.Body, tc)
|
||||
i.stack.popIfExists(sz)
|
||||
|
||||
return v, err
|
||||
@ -454,65 +492,76 @@ func (i *interpreter) evaluate(a ast.Node, tc tailCallStatus) (value, error) {
|
||||
return sb.self, nil
|
||||
|
||||
case *ast.Var:
|
||||
return e.evaluateTailCall(e.lookUpVar(node.Id), tc)
|
||||
foo := i.stack.lookUpVarOrPanic(node.Id)
|
||||
return foo.getValue(i, trace)
|
||||
|
||||
case *ast.SuperIndex:
|
||||
index, err := e.evalInCurrentContext(node.Index, nonTailCall)
|
||||
index, err := i.evaluate(node.Index, nonTailCall)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
indexStr, err := e.getString(index)
|
||||
indexStr, err := i.getString(index, trace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return objectIndex(e, i.stack.getSelfBinding().super(), indexStr.getString())
|
||||
return objectIndex(i, trace, i.stack.getSelfBinding().super(), indexStr.getString())
|
||||
|
||||
case *ast.InSuper:
|
||||
index, err := e.evalInCurrentContext(node.Index, nonTailCall)
|
||||
index, err := i.evaluate(node.Index, nonTailCall)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
indexStr, err := e.getString(index)
|
||||
indexStr, err := i.getString(index, trace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
field := tryObjectIndex(i.stack.getSelfBinding().super(), indexStr.getString(), withHidden)
|
||||
return makeValueBoolean(field != nil), nil
|
||||
hasField := objectHasField(i.stack.getSelfBinding().super(), indexStr.getString(), withHidden)
|
||||
return makeValueBoolean(hasField), nil
|
||||
|
||||
case *ast.Function:
|
||||
return &valueFunction{
|
||||
ec: makeClosure(i.getCurrentEnv(a), node),
|
||||
ec: makeClosure(i.stack.getCurrentEnv(a), node),
|
||||
}, nil
|
||||
|
||||
case *ast.Apply:
|
||||
// Eval target
|
||||
target, err := e.evalInCurrentContext(node.Target, nonTailCall)
|
||||
target, err := i.evaluate(node.Target, nonTailCall)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
function, err := e.getFunction(target)
|
||||
function, err := i.getFunction(target, trace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// environment in which we can evaluate arguments
|
||||
argEnv := i.getCurrentEnv(a)
|
||||
argEnv := i.stack.getCurrentEnv(a)
|
||||
arguments := callArguments{
|
||||
positional: make([]potentialValue, len(node.Arguments.Positional)),
|
||||
positional: make([]*cachedThunk, len(node.Arguments.Positional)),
|
||||
named: make([]namedCallArgument, len(node.Arguments.Named)),
|
||||
tailstrict: node.TailStrict,
|
||||
}
|
||||
for i, arg := range node.Arguments.Positional {
|
||||
arguments.positional[i] = makeThunk(argEnv, arg)
|
||||
arguments.positional[i] = &cachedThunk{env: &argEnv, body: arg}
|
||||
}
|
||||
|
||||
for i, arg := range node.Arguments.Named {
|
||||
arguments.named[i] = namedCallArgument{name: arg.Name, pv: makeThunk(argEnv, arg.Arg)}
|
||||
arguments.named[i] = namedCallArgument{name: arg.Name, pv: &cachedThunk{env: &argEnv, body: arg.Arg}}
|
||||
}
|
||||
return e.evaluateTailCall(function.call(arguments), tc)
|
||||
return i.evaluateTailCall(function, arguments, tc, trace)
|
||||
|
||||
case *astMakeArrayElement:
|
||||
arguments := callArguments{
|
||||
positional: []*cachedThunk{
|
||||
&cachedThunk{
|
||||
content: intToValue(node.index),
|
||||
},
|
||||
},
|
||||
}
|
||||
return i.evaluateTailCall(node.function, arguments, tc, trace)
|
||||
|
||||
default:
|
||||
return nil, e.Error(fmt.Sprintf("Executing this AST type not implemented yet: %v", reflect.TypeOf(a)))
|
||||
return nil, i.Error(fmt.Sprintf("Executing this AST type not implemented: %v", reflect.TypeOf(a)), trace)
|
||||
}
|
||||
}
|
||||
|
||||
@ -562,8 +611,7 @@ func unparseNumber(v float64) string {
|
||||
}
|
||||
|
||||
// manifestJSON converts to standard JSON representation as in "encoding/json" package
|
||||
func (i *interpreter) manifestJSON(trace *TraceElement, v value) (interface{}, error) {
|
||||
e := &evaluator{i: i, trace: trace}
|
||||
func (i *interpreter) manifestJSON(trace TraceElement, v value) (interface{}, error) {
|
||||
switch v := v.(type) {
|
||||
|
||||
case *valueBoolean:
|
||||
@ -584,7 +632,7 @@ func (i *interpreter) manifestJSON(trace *TraceElement, v value) (interface{}, e
|
||||
case *valueArray:
|
||||
result := make([]interface{}, 0, len(v.elements))
|
||||
for _, th := range v.elements {
|
||||
elVal, err := e.evaluate(th)
|
||||
elVal, err := i.evaluatePV(th, trace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -600,7 +648,7 @@ func (i *interpreter) manifestJSON(trace *TraceElement, v value) (interface{}, e
|
||||
fieldNames := objectFields(v, withoutHidden)
|
||||
sort.Strings(fieldNames)
|
||||
|
||||
err := checkAssertions(e, v)
|
||||
err := checkAssertions(i, trace, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -608,7 +656,7 @@ func (i *interpreter) manifestJSON(trace *TraceElement, v value) (interface{}, e
|
||||
result := make(map[string]interface{})
|
||||
|
||||
for _, fieldName := range fieldNames {
|
||||
fieldVal, err := v.index(e, fieldName)
|
||||
fieldVal, err := v.index(i, trace, fieldName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -729,7 +777,7 @@ func serializeJSON(v interface{}, multiline bool, indent string, buf *bytes.Buff
|
||||
}
|
||||
|
||||
func (i *interpreter) manifestAndSerializeJSON(
|
||||
buf *bytes.Buffer, trace *TraceElement, v value, multiline bool, indent string) error {
|
||||
buf *bytes.Buffer, trace TraceElement, v value, multiline bool, indent string) error {
|
||||
manifested, err := i.manifestJSON(trace, v)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -739,7 +787,7 @@ func (i *interpreter) manifestAndSerializeJSON(
|
||||
}
|
||||
|
||||
// manifestString expects the value to be a string and returns it.
|
||||
func (i *interpreter) manifestString(buf *bytes.Buffer, trace *TraceElement, v value) error {
|
||||
func (i *interpreter) manifestString(buf *bytes.Buffer, trace TraceElement, v value) error {
|
||||
switch v := v.(type) {
|
||||
case *valueString:
|
||||
buf.WriteString(v.getString())
|
||||
@ -749,7 +797,7 @@ func (i *interpreter) manifestString(buf *bytes.Buffer, trace *TraceElement, v v
|
||||
}
|
||||
}
|
||||
|
||||
func (i *interpreter) manifestAndSerializeMulti(trace *TraceElement, v value, stringOutputMode bool) (r map[string]string, err error) {
|
||||
func (i *interpreter) manifestAndSerializeMulti(trace TraceElement, v value, stringOutputMode bool) (r map[string]string, err error) {
|
||||
r = make(map[string]string)
|
||||
json, err := i.manifestJSON(trace, v)
|
||||
if err != nil {
|
||||
@ -783,7 +831,7 @@ func (i *interpreter) manifestAndSerializeMulti(trace *TraceElement, v value, st
|
||||
return
|
||||
}
|
||||
|
||||
func (i *interpreter) manifestAndSerializeYAMLStream(trace *TraceElement, v value) (r []string, err error) {
|
||||
func (i *interpreter) manifestAndSerializeYAMLStream(trace TraceElement, v value) (r []string, err error) {
|
||||
r = make([]string, 0)
|
||||
json, err := i.manifestJSON(trace, v)
|
||||
if err != nil {
|
||||
@ -806,19 +854,19 @@ func (i *interpreter) manifestAndSerializeYAMLStream(trace *TraceElement, v valu
|
||||
return
|
||||
}
|
||||
|
||||
func jsonToValue(e *evaluator, v interface{}) (value, error) {
|
||||
func jsonToValue(i *interpreter, trace TraceElement, v interface{}) (value, error) {
|
||||
switch v := v.(type) {
|
||||
case nil:
|
||||
return &nullValue, nil
|
||||
|
||||
case []interface{}:
|
||||
elems := make([]potentialValue, len(v))
|
||||
for i, elem := range v {
|
||||
val, err := jsonToValue(e, elem)
|
||||
elems := make([]*cachedThunk, len(v))
|
||||
for counter, elem := range v {
|
||||
val, err := jsonToValue(i, trace, elem)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
elems[i] = &readyValue{val}
|
||||
elems[counter] = readyThunk(val)
|
||||
}
|
||||
return makeValueArray(elems), nil
|
||||
|
||||
@ -830,7 +878,7 @@ func jsonToValue(e *evaluator, v interface{}) (value, error) {
|
||||
case map[string]interface{}:
|
||||
fieldMap := map[string]value{}
|
||||
for name, f := range v {
|
||||
val, err := jsonToValue(e, f)
|
||||
val, err := jsonToValue(i, trace, f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -842,11 +890,11 @@ func jsonToValue(e *evaluator, v interface{}) (value, error) {
|
||||
return makeValueString(v), nil
|
||||
|
||||
default:
|
||||
return nil, e.Error(fmt.Sprintf("Not a json type: %#+v", v))
|
||||
return nil, i.Error(fmt.Sprintf("Not a json type: %#+v", v), trace)
|
||||
}
|
||||
}
|
||||
|
||||
func (i *interpreter) EvalInCleanEnv(fromWhere *TraceElement, env *environment, ast ast.Node, trimmable bool) (value, error) {
|
||||
func (i *interpreter) EvalInCleanEnv(fromWhere TraceElement, env *environment, ast ast.Node, trimmable bool) (value, error) {
|
||||
err := i.newCall(fromWhere, *env, trimmable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -860,6 +908,180 @@ func (i *interpreter) EvalInCleanEnv(fromWhere *TraceElement, env *environment,
|
||||
return val, err
|
||||
}
|
||||
|
||||
func (i *interpreter) evaluatePV(ph potentialValue, trace TraceElement) (value, error) {
|
||||
return ph.getValue(i, trace)
|
||||
}
|
||||
|
||||
func (i *interpreter) evaluateTailCall(function *valueFunction, args callArguments, tc tailCallStatus, trace TraceElement) (value, error) {
|
||||
if tc == tailCall {
|
||||
i.stack.tailCallTrimStack()
|
||||
}
|
||||
return function.call(i, trace, args)
|
||||
}
|
||||
|
||||
func (i *interpreter) Error(s string, trace TraceElement) error {
|
||||
err := makeRuntimeError(s, i.getCurrentStackTrace(trace))
|
||||
return err
|
||||
}
|
||||
|
||||
func (i *interpreter) typeErrorSpecific(bad value, good value, trace TraceElement) error {
|
||||
return i.Error(
|
||||
fmt.Sprintf("Unexpected type %v, expected %v", bad.getType().name, good.getType().name),
|
||||
trace,
|
||||
)
|
||||
}
|
||||
|
||||
func (i *interpreter) typeErrorGeneral(bad value, trace TraceElement) error {
|
||||
return i.Error(
|
||||
fmt.Sprintf("Unexpected type %v", bad.getType().name),
|
||||
trace,
|
||||
)
|
||||
}
|
||||
|
||||
func (i *interpreter) getNumber(val value, trace TraceElement) (*valueNumber, error) {
|
||||
switch v := val.(type) {
|
||||
case *valueNumber:
|
||||
return v, nil
|
||||
default:
|
||||
return nil, i.typeErrorSpecific(val, &valueNumber{}, trace)
|
||||
}
|
||||
}
|
||||
|
||||
func (i *interpreter) evaluateNumber(pv potentialValue, trace TraceElement) (*valueNumber, error) {
|
||||
v, err := i.evaluatePV(pv, trace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return i.getNumber(v, trace)
|
||||
}
|
||||
|
||||
func (i *interpreter) getInt(val value, trace TraceElement) (int, error) {
|
||||
num, err := i.getNumber(val, trace)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// We conservatively convert ot int32, so that it can be machine-sized int
|
||||
// on any machine. And it's used only for indexing anyway.
|
||||
intNum := int(int32(num.value))
|
||||
if float64(intNum) != num.value {
|
||||
return 0, i.Error(fmt.Sprintf("Expected an integer, but got %v", num.value), trace)
|
||||
}
|
||||
return intNum, nil
|
||||
}
|
||||
|
||||
func (i *interpreter) evaluateInt(pv potentialValue, trace TraceElement) (int, error) {
|
||||
v, err := i.evaluatePV(pv, trace)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return i.getInt(v, trace)
|
||||
}
|
||||
|
||||
func (i *interpreter) getInt64(val value, trace TraceElement) (int64, error) {
|
||||
num, err := i.getNumber(val, trace)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
intNum := int64(num.value)
|
||||
if float64(intNum) != num.value {
|
||||
return 0, i.Error(fmt.Sprintf("Expected an integer, but got %v", num.value), trace)
|
||||
}
|
||||
return intNum, nil
|
||||
}
|
||||
|
||||
func (i *interpreter) evaluateInt64(pv potentialValue, trace TraceElement) (int64, error) {
|
||||
v, err := i.evaluatePV(pv, trace)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return i.getInt64(v, trace)
|
||||
}
|
||||
|
||||
func (i *interpreter) getString(val value, trace TraceElement) (*valueString, error) {
|
||||
switch v := val.(type) {
|
||||
case *valueString:
|
||||
return v, nil
|
||||
default:
|
||||
return nil, i.typeErrorSpecific(val, &valueString{}, trace)
|
||||
}
|
||||
}
|
||||
|
||||
func (i *interpreter) evaluateString(pv potentialValue, trace TraceElement) (*valueString, error) {
|
||||
v, err := i.evaluatePV(pv, trace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return i.getString(v, trace)
|
||||
}
|
||||
|
||||
func (i *interpreter) getBoolean(val value, trace TraceElement) (*valueBoolean, error) {
|
||||
switch v := val.(type) {
|
||||
case *valueBoolean:
|
||||
return v, nil
|
||||
default:
|
||||
return nil, i.typeErrorSpecific(val, &valueBoolean{}, trace)
|
||||
}
|
||||
}
|
||||
|
||||
func (i *interpreter) evaluateBoolean(pv potentialValue, trace TraceElement) (*valueBoolean, error) {
|
||||
v, err := i.evaluatePV(pv, trace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return i.getBoolean(v, trace)
|
||||
}
|
||||
|
||||
func (i *interpreter) getArray(val value, trace TraceElement) (*valueArray, error) {
|
||||
switch v := val.(type) {
|
||||
case *valueArray:
|
||||
return v, nil
|
||||
default:
|
||||
return nil, i.typeErrorSpecific(val, &valueArray{}, trace)
|
||||
}
|
||||
}
|
||||
|
||||
func (i *interpreter) evaluateArray(pv potentialValue, trace TraceElement) (*valueArray, error) {
|
||||
v, err := i.evaluatePV(pv, trace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return i.getArray(v, trace)
|
||||
}
|
||||
|
||||
func (i *interpreter) getFunction(val value, trace TraceElement) (*valueFunction, error) {
|
||||
switch v := val.(type) {
|
||||
case *valueFunction:
|
||||
return v, nil
|
||||
default:
|
||||
return nil, i.typeErrorSpecific(val, &valueFunction{}, trace)
|
||||
}
|
||||
}
|
||||
|
||||
func (i *interpreter) evaluateFunction(pv potentialValue, trace TraceElement) (*valueFunction, error) {
|
||||
v, err := i.evaluatePV(pv, trace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return i.getFunction(v, trace)
|
||||
}
|
||||
|
||||
func (i *interpreter) getObject(val value, trace TraceElement) (valueObject, error) {
|
||||
switch v := val.(type) {
|
||||
case valueObject:
|
||||
return v, nil
|
||||
default:
|
||||
return nil, i.typeErrorSpecific(val, &valueSimpleObject{}, trace)
|
||||
}
|
||||
}
|
||||
|
||||
func (i *interpreter) evaluateObject(pv potentialValue, trace TraceElement) (valueObject, error) {
|
||||
v, err := i.evaluatePV(pv, trace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return i.getObject(v, trace)
|
||||
}
|
||||
|
||||
func buildStdObject(i *interpreter) (valueObject, error) {
|
||||
objVal, err := evaluateStd(i)
|
||||
if err != nil {
|
||||
@ -884,26 +1106,18 @@ func evaluateStd(i *interpreter) (value, error) {
|
||||
makeUnboundSelfBinding(),
|
||||
)
|
||||
evalLoc := ast.MakeLocationRangeMessage("During evaluation of std")
|
||||
evalTrace := &TraceElement{loc: &evalLoc}
|
||||
evalTrace := TraceElement{loc: &evalLoc}
|
||||
node := ast.StdAst
|
||||
return i.EvalInCleanEnv(evalTrace, &beforeStdEnv, node, false)
|
||||
}
|
||||
|
||||
func prepareExtVars(i *interpreter, ext vmExtMap, kind string) map[string]potentialValue {
|
||||
result := make(map[string]potentialValue)
|
||||
func prepareExtVars(i *interpreter, ext vmExtMap, kind string) map[string]*cachedThunk {
|
||||
result := make(map[string]*cachedThunk)
|
||||
for name, content := range ext {
|
||||
if content.isCode {
|
||||
varLoc := ast.MakeLocationRangeMessage("During evaluation")
|
||||
varTrace := &TraceElement{
|
||||
loc: &varLoc,
|
||||
}
|
||||
e := &evaluator{
|
||||
i: i,
|
||||
trace: varTrace,
|
||||
}
|
||||
result[name] = codeToPV(e, "<"+kind+":"+name+">", content.value)
|
||||
result[name] = codeToPV(i, "<"+kind+":"+name+">", content.value)
|
||||
} else {
|
||||
result[name] = &readyValue{makeValueString(content.value)}
|
||||
result[name] = readyThunk(makeValueString(content.value))
|
||||
}
|
||||
}
|
||||
return result
|
||||
@ -942,21 +1156,21 @@ func makeInitialEnv(filename string, baseStd valueObject) environment {
|
||||
})
|
||||
return makeEnvironment(
|
||||
bindingFrame{
|
||||
"std": &readyValue{makeValueExtendedObject(baseStd, fileSpecific)},
|
||||
"std": readyThunk(makeValueExtendedObject(baseStd, fileSpecific)),
|
||||
},
|
||||
makeUnboundSelfBinding(),
|
||||
)
|
||||
}
|
||||
|
||||
func evaluateAux(i *interpreter, node ast.Node, tla vmExtMap) (value, *TraceElement, error) {
|
||||
func evaluateAux(i *interpreter, node ast.Node, tla vmExtMap) (value, TraceElement, error) {
|
||||
evalLoc := ast.MakeLocationRangeMessage("During evaluation")
|
||||
evalTrace := &TraceElement{
|
||||
evalTrace := TraceElement{
|
||||
loc: &evalLoc,
|
||||
}
|
||||
env := makeInitialEnv(node.Loc().FileName, i.baseStd)
|
||||
result, err := i.EvalInCleanEnv(evalTrace, &env, node, false)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, TraceElement{}, err
|
||||
}
|
||||
// If it's not a function, ignore TLA
|
||||
if f, ok := result.(*valueFunction); ok {
|
||||
@ -966,16 +1180,16 @@ func evaluateAux(i *interpreter, node ast.Node, tla vmExtMap) (value, *TraceElem
|
||||
args.named = append(args.named, namedCallArgument{name: ast.Identifier(argName), pv: pv})
|
||||
}
|
||||
funcLoc := ast.MakeLocationRangeMessage("Top-level function")
|
||||
funcTrace := &TraceElement{
|
||||
funcTrace := TraceElement{
|
||||
loc: &funcLoc,
|
||||
}
|
||||
result, err = f.call(args).getValue(i, funcTrace)
|
||||
result, err = f.call(i, funcTrace, args)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, TraceElement{}, err
|
||||
}
|
||||
}
|
||||
manifestationLoc := ast.MakeLocationRangeMessage("During manifestation")
|
||||
manifestationTrace := &TraceElement{
|
||||
manifestationTrace := TraceElement{
|
||||
loc: &manifestationLoc,
|
||||
}
|
||||
return result, manifestationTrace, nil
|
||||
|
@ -44,7 +44,7 @@ type TraceFrame struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func traceElementToTraceFrame(trace *TraceElement) TraceFrame {
|
||||
func traceElementToTraceFrame(trace TraceElement) TraceFrame {
|
||||
tf := TraceFrame{Loc: *trace.loc}
|
||||
if trace.context != nil {
|
||||
// TODO(sbarzowski) maybe it should never be nil
|
||||
|
5
testdata/bitwise_and4.golden
vendored
5
testdata/bitwise_and4.golden
vendored
@ -4,11 +4,6 @@ RUNTIME ERROR: x
|
||||
|
||||
1 & error "x"
|
||||
|
||||
-------------------------------------------------
|
||||
testdata/bitwise_and4:1:1-14 $
|
||||
|
||||
1 & error "x"
|
||||
|
||||
-------------------------------------------------
|
||||
During evaluation
|
||||
|
||||
|
5
testdata/bitwise_xor7.golden
vendored
5
testdata/bitwise_xor7.golden
vendored
@ -4,11 +4,6 @@ RUNTIME ERROR: x
|
||||
|
||||
1 ^ error "x"
|
||||
|
||||
-------------------------------------------------
|
||||
testdata/bitwise_xor7:1:1-14 $
|
||||
|
||||
1 ^ error "x"
|
||||
|
||||
-------------------------------------------------
|
||||
During evaluation
|
||||
|
||||
|
5
testdata/lazy_operator2.golden
vendored
5
testdata/lazy_operator2.golden
vendored
@ -4,11 +4,6 @@ RUNTIME ERROR: should happen
|
||||
|
||||
true && error "should happen"
|
||||
|
||||
-------------------------------------------------
|
||||
testdata/lazy_operator2:1:1-30 $
|
||||
|
||||
true && error "should happen"
|
||||
|
||||
-------------------------------------------------
|
||||
During evaluation
|
||||
|
||||
|
5
testdata/object_invariant7.golden
vendored
5
testdata/object_invariant7.golden
vendored
@ -4,11 +4,6 @@ RUNTIME ERROR: Attempt to use super when there is no super class.
|
||||
|
||||
{ x: 5, assert super.x == 5 }
|
||||
|
||||
-------------------------------------------------
|
||||
testdata/object_invariant7:1:16-28 object <anonymous>
|
||||
|
||||
{ x: 5, assert super.x == 5 }
|
||||
|
||||
-------------------------------------------------
|
||||
During manifestation
|
||||
|
||||
|
5
testdata/or4.golden
vendored
5
testdata/or4.golden
vendored
@ -4,11 +4,6 @@ RUNTIME ERROR: xxx
|
||||
|
||||
false || error "xxx"
|
||||
|
||||
-------------------------------------------------
|
||||
testdata/or4:1:1-21 $
|
||||
|
||||
false || error "xxx"
|
||||
|
||||
-------------------------------------------------
|
||||
During evaluation
|
||||
|
||||
|
4
testdata/percent_format_str5.golden
vendored
4
testdata/percent_format_str5.golden
vendored
@ -15,12 +15,12 @@ RUNTIME ERROR: Not enough values to format, got 1
|
||||
std.toString(val)
|
||||
|
||||
-------------------------------------------------
|
||||
<std>:558:9-26 builtin function <toString>
|
||||
<std>:558:9-26 function <format_code>
|
||||
|
||||
std.toString(val)
|
||||
|
||||
-------------------------------------------------
|
||||
... (skipped 14 frames)
|
||||
... (skipped 10 frames)
|
||||
-------------------------------------------------
|
||||
<std>:699:11-64 function <format_codes_arr>
|
||||
|
||||
|
4
testdata/percent_format_str6.golden
vendored
4
testdata/percent_format_str6.golden
vendored
@ -15,12 +15,12 @@ RUNTIME ERROR: Not enough values to format, got 1
|
||||
std.toString(val)
|
||||
|
||||
-------------------------------------------------
|
||||
<std>:558:9-26 builtin function <toString>
|
||||
<std>:558:9-26 function <format_code>
|
||||
|
||||
std.toString(val)
|
||||
|
||||
-------------------------------------------------
|
||||
... (skipped 14 frames)
|
||||
... (skipped 10 frames)
|
||||
-------------------------------------------------
|
||||
<std>:699:11-64 function <format_codes_arr>
|
||||
|
||||
|
2
testdata/percent_format_str7.golden
vendored
2
testdata/percent_format_str7.golden
vendored
@ -21,7 +21,7 @@ RUNTIME ERROR: Format required number at 0, got string
|
||||
padding(w - std.length(str), s) + str;
|
||||
|
||||
-------------------------------------------------
|
||||
... (skipped 11 frames)
|
||||
... (skipped 7 frames)
|
||||
-------------------------------------------------
|
||||
<std>:699:11-64 function <format_codes_arr>
|
||||
|
||||
|
2
testdata/std.filter2.golden
vendored
2
testdata/std.filter2.golden
vendored
@ -5,7 +5,7 @@ RUNTIME ERROR: x
|
||||
std.filter(error "x", [])
|
||||
|
||||
-------------------------------------------------
|
||||
testdata/std.filter2:1:1-26 builtin function <filter>
|
||||
testdata/std.filter2:1:1-26 $
|
||||
|
||||
std.filter(error "x", [])
|
||||
|
||||
|
2
testdata/std.flatmap5.golden
vendored
2
testdata/std.flatmap5.golden
vendored
@ -10,7 +10,7 @@ local failWith(x) = error x;
|
||||
std.type(std.flatMap(failWith, ["a", "b", "c"]))
|
||||
|
||||
-------------------------------------------------
|
||||
testdata/std.flatmap5:2:1-49 builtin function <type>
|
||||
testdata/std.flatmap5:2:1-49 $
|
||||
|
||||
std.type(std.flatMap(failWith, ["a", "b", "c"]))
|
||||
|
||||
|
4
testdata/std.makeArray_noninteger_big.golden
vendored
4
testdata/std.makeArray_noninteger_big.golden
vendored
@ -1,8 +1,8 @@
|
||||
RUNTIME ERROR: Expected an integer, but got 1e+100
|
||||
-------------------------------------------------
|
||||
testdata/std.makeArray_noninteger_big:1:1-47 builtin function <makeArray>
|
||||
testdata/std.makeArray_noninteger_big:1:1-36 builtin function <makeArray>
|
||||
|
||||
std.makeArray(1e100, error "shouldn't happen")
|
||||
std.makeArray(1e100, function(i) i)
|
||||
|
||||
-------------------------------------------------
|
||||
During evaluation
|
||||
|
@ -1 +1 @@
|
||||
std.makeArray(1e100, error "shouldn't happen")
|
||||
std.makeArray(1e100, function(i) i)
|
||||
|
@ -1,8 +1,6 @@
|
||||
RUNTIME ERROR: max stack frames exceeded.
|
||||
-------------------------------------------------
|
||||
testdata/std.makeArray_recursive_evalutation_order_matters:1:51-61 function <anonymous>
|
||||
|
||||
local arr = [0] + std.makeArray(2000, function(i) arr[i] + 1); arr[500]
|
||||
|
||||
-------------------------------------------------
|
||||
testdata/std.makeArray_recursive_evalutation_order_matters:1:51-57 function <anonymous>
|
||||
@ -10,9 +8,7 @@ local arr = [0] + std.makeArray(2000, function(i) arr[i] + 1); arr[500]
|
||||
local arr = [0] + std.makeArray(2000, function(i) arr[i] + 1); arr[500]
|
||||
|
||||
-------------------------------------------------
|
||||
testdata/std.makeArray_recursive_evalutation_order_matters:1:51-61 function <anonymous>
|
||||
|
||||
local arr = [0] + std.makeArray(2000, function(i) arr[i] + 1); arr[500]
|
||||
|
||||
-------------------------------------------------
|
||||
testdata/std.makeArray_recursive_evalutation_order_matters:1:51-57 function <anonymous>
|
||||
@ -22,9 +18,7 @@ local arr = [0] + std.makeArray(2000, function(i) arr[i] + 1); arr[500]
|
||||
-------------------------------------------------
|
||||
... (skipped 492 frames)
|
||||
-------------------------------------------------
|
||||
testdata/std.makeArray_recursive_evalutation_order_matters:1:51-61 function <anonymous>
|
||||
|
||||
local arr = [0] + std.makeArray(2000, function(i) arr[i] + 1); arr[500]
|
||||
|
||||
-------------------------------------------------
|
||||
testdata/std.makeArray_recursive_evalutation_order_matters:1:51-57 function <anonymous>
|
||||
@ -32,9 +26,7 @@ local arr = [0] + std.makeArray(2000, function(i) arr[i] + 1); arr[500]
|
||||
local arr = [0] + std.makeArray(2000, function(i) arr[i] + 1); arr[500]
|
||||
|
||||
-------------------------------------------------
|
||||
testdata/std.makeArray_recursive_evalutation_order_matters:1:51-61 function <anonymous>
|
||||
|
||||
local arr = [0] + std.makeArray(2000, function(i) arr[i] + 1); arr[500]
|
||||
|
||||
-------------------------------------------------
|
||||
testdata/std.makeArray_recursive_evalutation_order_matters:1:64-72 $
|
||||
|
2
testdata/std.primitiveEquals10.golden
vendored
2
testdata/std.primitiveEquals10.golden
vendored
@ -5,7 +5,7 @@ RUNTIME ERROR: x
|
||||
std.primitiveEquals(error "x", 42)
|
||||
|
||||
-------------------------------------------------
|
||||
testdata/std.primitiveEquals10:1:1-35 builtin function <primitiveEquals>
|
||||
testdata/std.primitiveEquals10:1:1-35 $
|
||||
|
||||
std.primitiveEquals(error "x", 42)
|
||||
|
||||
|
2
testdata/std.primitiveEquals9.golden
vendored
2
testdata/std.primitiveEquals9.golden
vendored
@ -5,7 +5,7 @@ RUNTIME ERROR: x
|
||||
std.primitiveEquals(42, error "x")
|
||||
|
||||
-------------------------------------------------
|
||||
testdata/std.primitiveEquals9:1:1-35 builtin function <primitiveEquals>
|
||||
testdata/std.primitiveEquals9:1:1-35 $
|
||||
|
||||
std.primitiveEquals(42, error "x")
|
||||
|
||||
|
2
testdata/std.toString5.golden
vendored
2
testdata/std.toString5.golden
vendored
@ -5,7 +5,7 @@ RUNTIME ERROR: x
|
||||
std.toString(error "x")
|
||||
|
||||
-------------------------------------------------
|
||||
testdata/std.toString5:1:1-24 builtin function <toString>
|
||||
testdata/std.toString5:1:1-24 $
|
||||
|
||||
std.toString(error "x")
|
||||
|
||||
|
2
testdata/type_error.golden
vendored
2
testdata/type_error.golden
vendored
@ -5,7 +5,7 @@ RUNTIME ERROR: xxx
|
||||
std.type(error "xxx")
|
||||
|
||||
-------------------------------------------------
|
||||
testdata/type_error:1:1-22 builtin function <type>
|
||||
testdata/type_error:1:1-22 $
|
||||
|
||||
std.type(error "xxx")
|
||||
|
||||
|
176
thunks.go
176
thunks.go
@ -30,14 +30,10 @@ type readyValue struct {
|
||||
content value
|
||||
}
|
||||
|
||||
func (rv *readyValue) getValue(i *interpreter, t *TraceElement) (value, error) {
|
||||
func (rv *readyValue) evaluate(i *interpreter, trace TraceElement, sb selfBinding, origBinding bindingFrame, fieldName string) (value, error) {
|
||||
return rv.content, nil
|
||||
}
|
||||
|
||||
func (rv *readyValue) bindToObject(sb selfBinding, origBinding bindingFrame, fieldName string) potentialValue {
|
||||
return rv
|
||||
}
|
||||
|
||||
func (rv *readyValue) aPotentialValue() {}
|
||||
|
||||
// potentialValues
|
||||
@ -48,108 +44,50 @@ func (rv *readyValue) aPotentialValue() {}
|
||||
// potentialValue which guarantees that computation happens at most once).
|
||||
type evaluable interface {
|
||||
// fromWhere keeps the information from where the evaluation was requested.
|
||||
getValue(i *interpreter, fromWhere *TraceElement) (value, error)
|
||||
}
|
||||
|
||||
// thunk holds code and environment in which the code is supposed to be evaluated
|
||||
type thunk struct {
|
||||
env environment
|
||||
body ast.Node
|
||||
}
|
||||
|
||||
// TODO(sbarzowski) feedback from dcunnin:
|
||||
// makeThunk returning a cachedThunk is weird.
|
||||
// Maybe call thunk 'exprThunk' (or astThunk but then it looks like an AST node).
|
||||
// Then call cachedThunk just thunk?
|
||||
// Or, call this makeCachedExprThunk because that's what it really is.
|
||||
func makeThunk(env environment, body ast.Node) *cachedThunk {
|
||||
return makeCachedThunk(&thunk{
|
||||
env: env,
|
||||
body: body,
|
||||
})
|
||||
}
|
||||
|
||||
func (t *thunk) getValue(i *interpreter, trace *TraceElement) (value, error) {
|
||||
return i.EvalInCleanEnv(trace, &t.env, t.body, false)
|
||||
}
|
||||
|
||||
// callThunk represents a concrete, but not yet evaluated call to a function
|
||||
type callThunk struct {
|
||||
function evalCallable
|
||||
args callArguments
|
||||
}
|
||||
|
||||
func makeCallThunk(ec evalCallable, args callArguments) potentialValue {
|
||||
return makeCachedThunk(&callThunk{function: ec, args: args})
|
||||
}
|
||||
|
||||
func call(ec evalCallable, arguments ...potentialValue) potentialValue {
|
||||
return makeCallThunk(ec, args(arguments...))
|
||||
}
|
||||
|
||||
func (th *callThunk) getValue(i *interpreter, trace *TraceElement) (value, error) {
|
||||
evaluator := makeEvaluator(i, trace)
|
||||
err := checkArguments(evaluator, th.args, th.function.Parameters())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return th.function.EvalCall(th.args, evaluator)
|
||||
}
|
||||
|
||||
// deferredThunk allows deferring creation of evaluable until it's actually needed.
|
||||
// It's useful for self-recursive structures.
|
||||
type deferredThunk func() evaluable
|
||||
|
||||
func (th deferredThunk) getValue(i *interpreter, trace *TraceElement) (value, error) {
|
||||
return th().getValue(i, trace)
|
||||
}
|
||||
|
||||
func makeDeferredThunk(th deferredThunk) potentialValue {
|
||||
return makeCachedThunk(th)
|
||||
getValue(i *interpreter, fromWhere TraceElement) (value, error)
|
||||
}
|
||||
|
||||
// cachedThunk is a wrapper that caches the value of a potentialValue after
|
||||
// the first evaluation.
|
||||
// Note: All potentialValues are required to provide the same value every time,
|
||||
// so it's only there for efficiency.
|
||||
// TODO(sbarzowski) investigate efficiency of various representations
|
||||
type cachedThunk struct {
|
||||
pv evaluable
|
||||
// The environment is a pointer because it may be a cyclic structure. A thunk
|
||||
// may refer to itself, so inside `env` there will be a variable bound back to us.
|
||||
env *environment
|
||||
body ast.Node
|
||||
// If nil, use err.
|
||||
content value
|
||||
// If also nil, content is not cached yet.
|
||||
err error
|
||||
}
|
||||
|
||||
func makeCachedThunk(pv evaluable) *cachedThunk {
|
||||
return &cachedThunk{pv}
|
||||
func readyThunk(content value) *cachedThunk {
|
||||
return &cachedThunk{content: content}
|
||||
}
|
||||
|
||||
func (t *cachedThunk) getValue(i *interpreter, trace *TraceElement) (value, error) {
|
||||
v, err := t.pv.getValue(i, trace)
|
||||
func (t *cachedThunk) getValue(i *interpreter, trace TraceElement) (value, error) {
|
||||
if t.content != nil {
|
||||
return t.content, nil
|
||||
}
|
||||
if t.err != nil {
|
||||
return nil, t.err
|
||||
}
|
||||
v, err := i.EvalInCleanEnv(trace, t.env, t.body, false)
|
||||
if err != nil {
|
||||
// TODO(sbarzowski) perhaps cache errors as well
|
||||
// may be necessary if we allow handling them in any way
|
||||
return nil, err
|
||||
}
|
||||
t.pv = &readyValue{v}
|
||||
t.content = v
|
||||
// No need to keep the environment around anymore.
|
||||
// So, this might reduce memory pressure:
|
||||
t.env = nil
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (t *cachedThunk) aPotentialValue() {}
|
||||
|
||||
// errorThunk can be used when potentialValue is expected, but we already
|
||||
// know that something went wrong
|
||||
type errorThunk struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (th *errorThunk) getValue(i *interpreter, trace *TraceElement) (value, error) {
|
||||
return nil, th.err
|
||||
}
|
||||
|
||||
func makeErrorThunk(err error) *errorThunk {
|
||||
return &errorThunk{err}
|
||||
}
|
||||
|
||||
func (th *errorThunk) aPotentialValue() {}
|
||||
|
||||
// unboundFields
|
||||
// -------------------------------------
|
||||
|
||||
@ -157,9 +95,9 @@ type codeUnboundField struct {
|
||||
body ast.Node
|
||||
}
|
||||
|
||||
func (f *codeUnboundField) bindToObject(sb selfBinding, origBindings bindingFrame, fieldName string) potentialValue {
|
||||
// TODO(sbarzowski) better object names (perhaps include a field name too?)
|
||||
return makeThunk(makeEnvironment(origBindings, sb), f.body)
|
||||
func (f *codeUnboundField) evaluate(i *interpreter, trace TraceElement, sb selfBinding, origBindings bindingFrame, fieldName string) (value, error) {
|
||||
env := makeEnvironment(origBindings, sb)
|
||||
return i.EvalInCleanEnv(trace, &env, f.body, false)
|
||||
}
|
||||
|
||||
// Provide additional bindings for a field. It shadows bindings from the object.
|
||||
@ -169,7 +107,7 @@ type bindingsUnboundField struct {
|
||||
bindings bindingFrame
|
||||
}
|
||||
|
||||
func (f *bindingsUnboundField) bindToObject(sb selfBinding, origBindings bindingFrame, fieldName string) potentialValue {
|
||||
func (f *bindingsUnboundField) evaluate(i *interpreter, trace TraceElement, sb selfBinding, origBindings bindingFrame, fieldName string) (value, error) {
|
||||
var upValues bindingFrame
|
||||
upValues = make(bindingFrame)
|
||||
for variable, pvalue := range origBindings {
|
||||
@ -178,7 +116,7 @@ func (f *bindingsUnboundField) bindToObject(sb selfBinding, origBindings binding
|
||||
for variable, pvalue := range f.bindings {
|
||||
upValues[variable] = pvalue
|
||||
}
|
||||
return f.inner.bindToObject(sb, upValues, fieldName)
|
||||
return f.inner.evaluate(i, trace, sb, upValues, fieldName)
|
||||
}
|
||||
|
||||
// PlusSuperUnboundField represents a `field+: ...` that hasn't been bound to an object.
|
||||
@ -186,13 +124,19 @@ type PlusSuperUnboundField struct {
|
||||
inner unboundField
|
||||
}
|
||||
|
||||
func (f *PlusSuperUnboundField) bindToObject(sb selfBinding, origBinding bindingFrame, fieldName string) potentialValue {
|
||||
left := tryObjectIndex(sb.super(), fieldName, withHidden)
|
||||
right := f.inner.bindToObject(sb, origBinding, fieldName)
|
||||
if left != nil {
|
||||
return call(bopBuiltins[ast.BopPlus], left, right)
|
||||
func (f *PlusSuperUnboundField) evaluate(i *interpreter, trace TraceElement, sb selfBinding, origBinding bindingFrame, fieldName string) (value, error) {
|
||||
right, err := f.inner.evaluate(i, trace, sb, origBinding, fieldName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return right
|
||||
if !objectHasField(sb.super(), fieldName, withHidden) {
|
||||
return right, nil
|
||||
}
|
||||
left, err := objectIndex(i, trace, sb.super(), fieldName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return builtinPlus(i, trace, left, right)
|
||||
}
|
||||
|
||||
// evalCallables
|
||||
@ -206,9 +150,9 @@ type closure struct {
|
||||
params Parameters
|
||||
}
|
||||
|
||||
func forceThunks(e *evaluator, args bindingFrame) error {
|
||||
for _, arg := range args {
|
||||
_, err := e.evaluate(arg)
|
||||
func forceThunks(i *interpreter, trace TraceElement, args *bindingFrame) error {
|
||||
for _, arg := range *args {
|
||||
_, err := arg.getValue(i, trace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -216,7 +160,7 @@ func forceThunks(e *evaluator, args bindingFrame) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (closure *closure) EvalCall(arguments callArguments, e *evaluator) (value, error) {
|
||||
func (closure *closure) EvalCall(arguments callArguments, i *interpreter, trace TraceElement) (value, error) {
|
||||
argThunks := make(bindingFrame)
|
||||
parameters := closure.Parameters()
|
||||
for i, arg := range arguments.positional {
|
||||
@ -238,15 +182,16 @@ func (closure *closure) EvalCall(arguments callArguments, e *evaluator) (value,
|
||||
for i := range parameters.optional {
|
||||
param := ¶meters.optional[i]
|
||||
if _, exists := argThunks[param.name]; !exists {
|
||||
argThunks[param.name] = makeDeferredThunk(func() evaluable {
|
||||
argThunks[param.name] = &cachedThunk{
|
||||
// Default arguments are evaluated in the same environment as function body
|
||||
return param.defaultArg.inEnv(&calledEnvironment)
|
||||
})
|
||||
env: &calledEnvironment,
|
||||
body: param.defaultArg,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if arguments.tailstrict {
|
||||
err := forceThunks(e, argThunks)
|
||||
err := forceThunks(i, trace, &argThunks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -254,9 +199,9 @@ func (closure *closure) EvalCall(arguments callArguments, e *evaluator) (value,
|
||||
|
||||
calledEnvironment = makeEnvironment(
|
||||
addBindings(closure.env.upValues, argThunks),
|
||||
closure.env.sb,
|
||||
closure.env.selfBinding,
|
||||
)
|
||||
return e.evalInCleanEnv(&calledEnvironment, closure.function.Body, arguments.tailstrict)
|
||||
return i.EvalInCleanEnv(trace, &calledEnvironment, closure.function.Body, arguments.tailstrict)
|
||||
}
|
||||
|
||||
func (closure *closure) Parameters() Parameters {
|
||||
@ -269,9 +214,7 @@ func prepareClosureParameters(parameters ast.Parameters, env environment) Parame
|
||||
for _, named := range parameters.Optional {
|
||||
optionalParameters = append(optionalParameters, namedParameter{
|
||||
name: named.Name,
|
||||
defaultArg: &defaultArgument{
|
||||
body: named.DefaultArg,
|
||||
},
|
||||
defaultArg: named.DefaultArg,
|
||||
})
|
||||
}
|
||||
return Parameters{
|
||||
@ -296,15 +239,15 @@ type NativeFunction struct {
|
||||
}
|
||||
|
||||
// EvalCall evaluates a call to a NativeFunction and returns the result.
|
||||
func (native *NativeFunction) EvalCall(arguments callArguments, e *evaluator) (value, error) {
|
||||
func (native *NativeFunction) EvalCall(arguments callArguments, i *interpreter, trace TraceElement) (value, error) {
|
||||
flatArgs := flattenArgs(arguments, native.Parameters())
|
||||
nativeArgs := make([]interface{}, 0, len(flatArgs))
|
||||
for _, arg := range flatArgs {
|
||||
v, err := e.evaluate(arg)
|
||||
v, err := i.evaluatePV(arg, trace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
json, err := e.i.manifestJSON(e.trace, v)
|
||||
json, err := i.manifestJSON(trace, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -312,9 +255,9 @@ func (native *NativeFunction) EvalCall(arguments callArguments, e *evaluator) (v
|
||||
}
|
||||
resultJSON, err := native.Func(nativeArgs)
|
||||
if err != nil {
|
||||
return nil, e.Error(err.Error())
|
||||
return nil, i.Error(err.Error(), trace)
|
||||
}
|
||||
return jsonToValue(e, resultJSON)
|
||||
return jsonToValue(i, trace, resultJSON)
|
||||
}
|
||||
|
||||
// Parameters returns a NativeFunction's parameters.
|
||||
@ -322,7 +265,6 @@ func (native *NativeFunction) Parameters() Parameters {
|
||||
return Parameters{required: native.Params}
|
||||
}
|
||||
|
||||
// partialPotentialValue
|
||||
// -------------------------------------
|
||||
|
||||
type defaultArgument struct {
|
||||
@ -330,5 +272,5 @@ type defaultArgument struct {
|
||||
}
|
||||
|
||||
func (da *defaultArgument) inEnv(env *environment) potentialValue {
|
||||
return makeThunk(*env, da.body)
|
||||
return &cachedThunk{env: env, body: da.body}
|
||||
}
|
||||
|
128
value.go
128
value.go
@ -58,13 +58,13 @@ var arrayType = &valueType{"array"}
|
||||
// TODO(sbarzowski) perhaps call it just "Thunk"?
|
||||
type potentialValue interface {
|
||||
// fromWhere keeps the information from where the evaluation was requested.
|
||||
getValue(i *interpreter, fromWhere *TraceElement) (value, error)
|
||||
getValue(i *interpreter, fromWhere TraceElement) (value, error)
|
||||
|
||||
aPotentialValue()
|
||||
}
|
||||
|
||||
// A set of variables with associated potentialValues.
|
||||
type bindingFrame map[ast.Identifier]potentialValue
|
||||
// A set of variables with associated thunks.
|
||||
type bindingFrame map[ast.Identifier]*cachedThunk
|
||||
|
||||
type valueBase struct{}
|
||||
|
||||
@ -81,11 +81,11 @@ type valueString struct {
|
||||
value []rune
|
||||
}
|
||||
|
||||
func (s *valueString) index(e *evaluator, index int) (value, error) {
|
||||
func (s *valueString) index(i *interpreter, trace TraceElement, index int) (value, error) {
|
||||
if 0 <= index && index < s.length() {
|
||||
return makeValueString(string(s.value[index])), nil
|
||||
}
|
||||
return nil, e.Error(fmt.Sprintf("Index %d out of bounds, not within [0, %v)", index, s.length()))
|
||||
return nil, i.Error(fmt.Sprintf("Index %d out of bounds, not within [0, %v)", index, s.length()), trace)
|
||||
}
|
||||
|
||||
func concatStrings(a, b *valueString) *valueString {
|
||||
@ -199,28 +199,28 @@ func (*valueNull) getType() *valueType {
|
||||
|
||||
type valueArray struct {
|
||||
valueBase
|
||||
elements []potentialValue
|
||||
elements []*cachedThunk
|
||||
}
|
||||
|
||||
func (arr *valueArray) index(e *evaluator, index int, tc tailCallStatus) (value, error) {
|
||||
func (arr *valueArray) index(i *interpreter, trace TraceElement, index int) (value, error) {
|
||||
if 0 <= index && index < arr.length() {
|
||||
return e.evaluateTailCall(arr.elements[index], tc)
|
||||
return i.evaluatePV(arr.elements[index], trace)
|
||||
}
|
||||
return nil, e.Error(fmt.Sprintf("Index %d out of bounds, not within [0, %v)", index, arr.length()))
|
||||
return nil, i.Error(fmt.Sprintf("Index %d out of bounds, not within [0, %v)", index, arr.length()), trace)
|
||||
}
|
||||
|
||||
func (arr *valueArray) length() int {
|
||||
return len(arr.elements)
|
||||
}
|
||||
|
||||
func makeValueArray(elements []potentialValue) *valueArray {
|
||||
func makeValueArray(elements []*cachedThunk) *valueArray {
|
||||
// We don't want to keep a bigger array than necessary
|
||||
// so we create a new one with minimal capacity
|
||||
var arrayElems []potentialValue
|
||||
var arrayElems []*cachedThunk
|
||||
if len(elements) == cap(elements) {
|
||||
arrayElems = elements
|
||||
} else {
|
||||
arrayElems = make([]potentialValue, len(elements))
|
||||
arrayElems = make([]*cachedThunk, len(elements))
|
||||
for i := range elements {
|
||||
arrayElems[i] = elements[i]
|
||||
}
|
||||
@ -231,7 +231,7 @@ func makeValueArray(elements []potentialValue) *valueArray {
|
||||
}
|
||||
|
||||
func concatArrays(a, b *valueArray) *valueArray {
|
||||
result := make([]potentialValue, 0, len(a.elements)+len(b.elements))
|
||||
result := make([]*cachedThunk, 0, len(a.elements)+len(b.elements))
|
||||
for _, r := range a.elements {
|
||||
result = append(result, r)
|
||||
}
|
||||
@ -255,23 +255,23 @@ type valueFunction struct {
|
||||
|
||||
// TODO(sbarzowski) better name?
|
||||
type evalCallable interface {
|
||||
EvalCall(args callArguments, e *evaluator) (value, error)
|
||||
EvalCall(args callArguments, i *interpreter, trace TraceElement) (value, error)
|
||||
Parameters() Parameters
|
||||
}
|
||||
|
||||
type partialPotentialValue interface {
|
||||
inEnv(env *environment) potentialValue
|
||||
func (f *valueFunction) call(i *interpreter, trace TraceElement, args callArguments) (value, error) {
|
||||
err := checkArguments(i, trace, args, f.parameters())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (f *valueFunction) call(args callArguments) potentialValue {
|
||||
return makeCallThunk(f.ec, args)
|
||||
return f.ec.EvalCall(args, i, trace)
|
||||
}
|
||||
|
||||
func (f *valueFunction) parameters() Parameters {
|
||||
return f.ec.Parameters()
|
||||
}
|
||||
|
||||
func checkArguments(e *evaluator, args callArguments, params Parameters) error {
|
||||
func checkArguments(i *interpreter, trace TraceElement, args callArguments, params Parameters) error {
|
||||
received := make(map[ast.Identifier]bool)
|
||||
accepted := make(map[ast.Identifier]bool)
|
||||
|
||||
@ -279,7 +279,7 @@ func checkArguments(e *evaluator, args callArguments, params Parameters) error {
|
||||
numExpected := len(params.required) + len(params.optional)
|
||||
|
||||
if numPassed > numExpected {
|
||||
return e.Error(fmt.Sprintf("function expected %v positional argument(s), but got %v", numExpected, numPassed))
|
||||
return i.Error(fmt.Sprintf("function expected %v positional argument(s), but got %v", numExpected, numPassed), trace)
|
||||
}
|
||||
|
||||
for _, param := range params.required {
|
||||
@ -300,17 +300,17 @@ func checkArguments(e *evaluator, args callArguments, params Parameters) error {
|
||||
|
||||
for _, arg := range args.named {
|
||||
if _, present := received[arg.name]; present {
|
||||
return e.Error(fmt.Sprintf("Argument %v already provided", arg.name))
|
||||
return i.Error(fmt.Sprintf("Argument %v already provided", arg.name), trace)
|
||||
}
|
||||
if _, present := accepted[arg.name]; !present {
|
||||
return e.Error(fmt.Sprintf("function has no parameter %v", arg.name))
|
||||
return i.Error(fmt.Sprintf("function has no parameter %v", arg.name), trace)
|
||||
}
|
||||
received[arg.name] = true
|
||||
}
|
||||
|
||||
for _, param := range params.required {
|
||||
if _, present := received[param]; !present {
|
||||
return e.Error(fmt.Sprintf("Missing argument: %v", param))
|
||||
return i.Error(fmt.Sprintf("Missing argument: %v", param), trace)
|
||||
}
|
||||
}
|
||||
|
||||
@ -330,25 +330,25 @@ type Parameters struct {
|
||||
|
||||
type namedParameter struct {
|
||||
name ast.Identifier
|
||||
defaultArg potentialValueInEnv
|
||||
defaultArg ast.Node
|
||||
}
|
||||
|
||||
type potentialValueInEnv interface {
|
||||
inEnv(env *environment) potentialValue
|
||||
inEnv(env *environment) *cachedThunk
|
||||
}
|
||||
|
||||
type callArguments struct {
|
||||
positional []potentialValue
|
||||
positional []*cachedThunk
|
||||
named []namedCallArgument
|
||||
tailstrict bool
|
||||
}
|
||||
|
||||
type namedCallArgument struct {
|
||||
name ast.Identifier
|
||||
pv potentialValue
|
||||
pv *cachedThunk
|
||||
}
|
||||
|
||||
func args(xs ...potentialValue) callArguments {
|
||||
func args(xs ...*cachedThunk) callArguments {
|
||||
return callArguments{positional: xs}
|
||||
}
|
||||
|
||||
@ -363,7 +363,7 @@ func args(xs ...potentialValue) callArguments {
|
||||
type valueObject interface {
|
||||
value
|
||||
inheritanceSize() int
|
||||
index(e *evaluator, field string) (value, error)
|
||||
index(i *interpreter, trace TraceElement, field string) (value, error)
|
||||
assertionsChecked() bool
|
||||
setAssertionsCheckResult(err error)
|
||||
getAssertionsCheckResult() error
|
||||
@ -471,21 +471,22 @@ type valueSimpleObject struct {
|
||||
asserts []unboundField
|
||||
}
|
||||
|
||||
func checkAssertionsHelper(e *evaluator, obj valueObject, curr valueObject, superDepth int) error {
|
||||
func checkAssertionsHelper(i *interpreter, trace TraceElement, obj valueObject, curr valueObject, superDepth int) error {
|
||||
switch curr := curr.(type) {
|
||||
case *valueExtendedObject:
|
||||
err := checkAssertionsHelper(e, obj, curr.right, superDepth)
|
||||
err := checkAssertionsHelper(i, trace, obj, curr.right, superDepth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = checkAssertionsHelper(e, obj, curr.left, superDepth+curr.right.inheritanceSize())
|
||||
err = checkAssertionsHelper(i, trace, obj, curr.left, superDepth+curr.right.inheritanceSize())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
case *valueSimpleObject:
|
||||
for _, assert := range curr.asserts {
|
||||
_, err := e.evaluate(assert.bindToObject(selfBinding{self: obj, superDepth: superDepth}, curr.upValues, ""))
|
||||
sb := selfBinding{self: obj, superDepth: superDepth}
|
||||
_, err := assert.evaluate(i, trace, sb, curr.upValues, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -496,19 +497,19 @@ func checkAssertionsHelper(e *evaluator, obj valueObject, curr valueObject, supe
|
||||
}
|
||||
}
|
||||
|
||||
func checkAssertions(e *evaluator, obj valueObject) error {
|
||||
func checkAssertions(i *interpreter, trace TraceElement, obj valueObject) error {
|
||||
if !obj.assertionsChecked() {
|
||||
// Assertions may refer to the object that will normally
|
||||
// trigger checking of assertions, resulting in an endless recursion.
|
||||
// To avoid that, while we check them, we treat them as already passed.
|
||||
obj.setAssertionsCheckResult(errNoErrorInObjectInvariants)
|
||||
obj.setAssertionsCheckResult(checkAssertionsHelper(e, obj, obj, 0))
|
||||
obj.setAssertionsCheckResult(checkAssertionsHelper(i, trace, obj, obj, 0))
|
||||
}
|
||||
return obj.getAssertionsCheckResult()
|
||||
}
|
||||
|
||||
func (o *valueSimpleObject) index(e *evaluator, field string) (value, error) {
|
||||
return objectIndex(e, objectBinding(o), field)
|
||||
func (o *valueSimpleObject) index(i *interpreter, trace TraceElement, field string) (value, error) {
|
||||
return objectIndex(i, trace, objectBinding(o), field)
|
||||
}
|
||||
|
||||
func (*valueSimpleObject) inheritanceSize() int {
|
||||
@ -532,7 +533,7 @@ type simpleObjectField struct {
|
||||
|
||||
// unboundField is a field that doesn't know yet in which object it is.
|
||||
type unboundField interface {
|
||||
bindToObject(sb selfBinding, origBinding bindingFrame, fieldName string) potentialValue
|
||||
evaluate(i *interpreter, trace TraceElement, sb selfBinding, origBinding bindingFrame, fieldName string) (value, error)
|
||||
}
|
||||
|
||||
// valueExtendedObject represents an object created through inheritence (left + right).
|
||||
@ -559,8 +560,8 @@ type valueExtendedObject struct {
|
||||
totalInheritanceSize int
|
||||
}
|
||||
|
||||
func (o *valueExtendedObject) index(e *evaluator, field string) (value, error) {
|
||||
return objectIndex(e, objectBinding(o), field)
|
||||
func (o *valueExtendedObject) index(i *interpreter, trace TraceElement, field string) (value, error) {
|
||||
return objectIndex(i, trace, objectBinding(o), field)
|
||||
}
|
||||
|
||||
func (o *valueExtendedObject) inheritanceSize() int {
|
||||
@ -578,53 +579,54 @@ func makeValueExtendedObject(left, right valueObject) *valueExtendedObject {
|
||||
// findField returns a field in object curr, with superDepth at least minSuperDepth
|
||||
// It also returns an associated bindingFrame and actual superDepth that the field
|
||||
// was found at.
|
||||
func findField(curr value, minSuperDepth int, f string) (*simpleObjectField, bindingFrame, int) {
|
||||
func findField(curr value, minSuperDepth int, f string) (bool, simpleObjectField, bindingFrame, int) {
|
||||
switch curr := curr.(type) {
|
||||
case *valueExtendedObject:
|
||||
if curr.right.inheritanceSize() > minSuperDepth {
|
||||
field, frame, counter := findField(curr.right, minSuperDepth, f)
|
||||
if field != nil {
|
||||
return field, frame, counter
|
||||
found, field, frame, counter := findField(curr.right, minSuperDepth, f)
|
||||
if found {
|
||||
return true, field, frame, counter
|
||||
}
|
||||
}
|
||||
field, frame, counter := findField(curr.left, minSuperDepth-curr.right.inheritanceSize(), f)
|
||||
return field, frame, counter + curr.right.inheritanceSize()
|
||||
found, field, frame, counter := findField(curr.left, minSuperDepth-curr.right.inheritanceSize(), f)
|
||||
return found, field, frame, counter + curr.right.inheritanceSize()
|
||||
|
||||
case *valueSimpleObject:
|
||||
if minSuperDepth <= 0 {
|
||||
if field, ok := curr.fields[f]; ok {
|
||||
return &field, curr.upValues, 0
|
||||
return true, field, curr.upValues, 0
|
||||
}
|
||||
}
|
||||
return nil, nil, 0
|
||||
return false, simpleObjectField{}, nil, 0
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown object type %#v", curr))
|
||||
}
|
||||
}
|
||||
|
||||
func objectIndex(e *evaluator, sb selfBinding, fieldName string) (value, error) {
|
||||
err := checkAssertions(e, sb.self)
|
||||
func objectIndex(i *interpreter, trace TraceElement, sb selfBinding, fieldName string) (value, error) {
|
||||
err := checkAssertions(i, trace, sb.self)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sb.superDepth >= sb.self.inheritanceSize() {
|
||||
return nil, e.Error("Attempt to use super when there is no super class.")
|
||||
}
|
||||
objp := tryObjectIndex(sb, fieldName, withHidden)
|
||||
if objp == nil {
|
||||
return nil, e.Error(fmt.Sprintf("Field does not exist: %s", fieldName))
|
||||
}
|
||||
return e.evaluate(objp)
|
||||
return nil, i.Error("Attempt to use super when there is no super class.", trace)
|
||||
}
|
||||
|
||||
func tryObjectIndex(sb selfBinding, fieldName string, h Hidden) potentialValue {
|
||||
field, upValues, foundAt := findField(sb.self, sb.superDepth, fieldName)
|
||||
if field == nil || (h == withoutHidden && field.hide == ast.ObjectFieldHidden) {
|
||||
return nil
|
||||
found, field, upValues, foundAt := findField(sb.self, sb.superDepth, fieldName)
|
||||
if !found {
|
||||
return nil, i.Error(fmt.Sprintf("Field does not exist: %s", fieldName), trace)
|
||||
}
|
||||
|
||||
fieldSelfBinding := selfBinding{self: sb.self, superDepth: foundAt}
|
||||
return field.field.evaluate(i, trace, fieldSelfBinding, upValues, fieldName)
|
||||
}
|
||||
|
||||
return field.field.bindToObject(fieldSelfBinding, upValues, fieldName)
|
||||
func objectHasField(sb selfBinding, fieldName string, h Hidden) bool {
|
||||
found, field, _, _ := findField(sb.self, sb.superDepth, fieldName)
|
||||
if !found || (h == withoutHidden && field.hide == ast.ObjectFieldHidden) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type fieldHideMap map[string]ast.ObjectFieldHide
|
||||
|
Loading…
x
Reference in New Issue
Block a user