Internal refactor to improve performance (#225)

* Internal refactor to improve performance
This commit is contained in:
Dave Cunningham 2018-06-01 10:52:20 -04:00 committed by GitHub
parent 530cf7b07c
commit 2973e24152
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 774 additions and 910 deletions

File diff suppressed because it is too large Load Diff

View File

@ -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
}

View File

@ -98,15 +98,15 @@ func (cache *ImportCache) importData(importedFrom, importedPath string) (content
} }
// ImportString imports a string, caches it and then returns it. // 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) data, _, err := cache.importData(importedFrom, importedPath)
if err != nil { if err != nil {
return nil, e.Error(err.Error()) return nil, i.Error(err.Error(), trace)
} }
return makeValueString(data.String()), nil 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) node, err := snippetToAST(filename, code)
if err != nil { if err != nil {
// TODO(sbarzowski) we should wrap (static) error here // 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 // actually depends on what happens in Runtime (whether import gets
// evaluated). // evaluated).
// The same thinking applies to external variables. // 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. // 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) contents, foundAt, err := cache.importData(importedFrom, importedPath)
if err != nil { if err != nil {
return nil, e.Error(err.Error()) return nil, i.Error(err.Error(), trace)
} }
var pv potentialValue var pv potentialValue
if cachedPV, isCached := cache.codeCache[foundAt]; !isCached { if cachedPV, isCached := cache.codeCache[foundAt]; !isCached {
// File hasn't been parsed and analyzed before, update the cache record. // 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 cache.codeCache[foundAt] = pv
} else { } else {
pv = cachedPV pv = cachedPV
} }
return e.evaluate(pv) return i.evaluatePV(pv, trace)
} }
// Concrete importers // Concrete importers

View File

@ -29,7 +29,7 @@ import (
// TODO(sbarzowski) use it as a pointer in most places b/c it can sometimes be shared // 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 // for example it can be shared between array elements and function arguments
type environment struct { type environment struct {
sb selfBinding selfBinding selfBinding
// Bindings introduced in this frame. The way previous bindings are treated // Bindings introduced in this frame. The way previous bindings are treated
// depends on the type of a frame. // depends on the type of a frame.
@ -43,22 +43,18 @@ type environment struct {
func makeEnvironment(upValues bindingFrame, sb selfBinding) environment { func makeEnvironment(upValues bindingFrame, sb selfBinding) environment {
return environment{ return environment{
upValues: upValues, upValues: upValues,
sb: sb, selfBinding: sb,
} }
} }
func callFrameToTraceFrame(frame *callFrame) TraceFrame { func (i *interpreter) getCurrentStackTrace(additional TraceElement) []TraceFrame {
return traceElementToTraceFrame(frame.trace)
}
func (i *interpreter) getCurrentStackTrace(additional *TraceElement) []TraceFrame {
var result []TraceFrame var result []TraceFrame
for _, f := range i.stack.stack { for _, f := range i.stack.stack {
if f.isCall { 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)) result = append(result, traceElementToTraceFrame(additional))
} }
return result return result
@ -71,7 +67,7 @@ type callFrame struct {
isCall bool isCall bool
// Tracing information about the place where it was called from. // 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 // 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 // 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 { func dumpCallFrame(c *callFrame) string {
var loc ast.LocationRange var loc ast.LocationRange
if c.trace == nil || c.trace.loc == nil { if c.trace.loc == nil {
loc = ast.MakeLocationRangeMessage("?") loc = ast.MakeLocationRangeMessage("?")
} else { } else {
loc = *c.trace.loc loc = *c.trace.loc
@ -151,11 +147,7 @@ const (
tailCall tailCall
) )
func (i *interpreter) newCall(trace *TraceElement, env environment, trimmable bool) error { func (s *callStack) newCall(trace TraceElement, env environment, trimmable bool) {
s := &i.stack
if s.calls >= s.limit {
return makeRuntimeError("max stack frames exceeded.", i.getCurrentStackTrace(trace))
}
s.stack = append(s.stack, &callFrame{ s.stack = append(s.stack, &callFrame{
isCall: true, isCall: true,
trace: trace, trace: trace,
@ -163,11 +155,9 @@ func (i *interpreter) newCall(trace *TraceElement, env environment, trimmable bo
trimmable: trimmable, trimmable: trimmable,
}) })
s.calls++ s.calls++
return nil
} }
func (i *interpreter) newLocal(vars bindingFrame) { func (s *callStack) newLocal(vars bindingFrame) {
s := &i.stack
s.stack = append(s.stack, &callFrame{ s.stack = append(s.stack, &callFrame{
env: makeEnvironment(vars, selfBinding{}), env: makeEnvironment(vars, selfBinding{}),
}) })
@ -177,17 +167,17 @@ func (i *interpreter) newLocal(vars bindingFrame) {
func (s *callStack) getSelfBinding() selfBinding { func (s *callStack) getSelfBinding() selfBinding {
for i := len(s.stack) - 1; i >= 0; i-- { for i := len(s.stack) - 1; i >= 0; i-- {
if s.stack[i].isCall { 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))) panic(fmt.Sprintf("malformed stack %v", dumpCallStack(s)))
} }
// lookUpVar finds for the closest variable in scope that matches the given name. // 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-- { for i := len(s.stack) - 1; i >= 0; i-- {
bind := s.stack[i].env.upValues[id] bind, present := s.stack[i].env.upValues[id]
if bind != nil { if present {
return bind return bind
} }
if s.stack[i].isCall { if s.stack[i].isCall {
@ -198,6 +188,30 @@ func (s *callStack) lookUpVar(id ast.Identifier) potentialValue {
return nil 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 { func makeCallStack(limit int) callStack {
return callStack{ return callStack{
calls: 0, calls: 0,
@ -213,7 +227,7 @@ type interpreter struct {
stack callStack stack callStack
// External variables // External variables
extVars map[string]potentialValue extVars map[string]*cachedThunk
// Native functions // Native functions
nativeFuncs map[string]*NativeFunction nativeFuncs map[string]*NativeFunction
@ -225,18 +239,7 @@ type interpreter struct {
importCache *ImportCache importCache *ImportCache
} }
// Build a binding frame containing specified variables. // Map union, b takes precedence when keys collide.
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
}
func addBindings(a, b bindingFrame) bindingFrame { func addBindings(a, b bindingFrame) bindingFrame {
result := make(bindingFrame) result := make(bindingFrame)
@ -251,82 +254,117 @@ func addBindings(a, b bindingFrame) bindingFrame {
return result return result
} }
func (i *interpreter) getCurrentEnv(ast ast.Node) environment { func (i *interpreter) newCall(trace TraceElement, env environment, trimmable bool) error {
return makeEnvironment( s := &i.stack
i.capture(ast.FreeVariables()), if s.calls >= s.limit {
i.stack.getSelfBinding(), 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) { func (i *interpreter) evaluate(a ast.Node, tc tailCallStatus) (value, error) {
e := &evaluator{ trace := TraceElement{
trace: &TraceElement{
loc: a.Loc(), loc: a.Loc(),
context: a.Context(), context: a.Context(),
},
i: i,
} }
switch node := a.(type) { switch node := a.(type) {
case *ast.Array: case *ast.Array:
sb := i.stack.getSelfBinding() sb := i.stack.getSelfBinding()
var elements []potentialValue var elements []*cachedThunk
for _, el := range node.Elements { for _, el := range node.Elements {
env := makeEnvironment(i.capture(el.FreeVariables()), sb) env := makeEnvironment(i.stack.capture(el.FreeVariables()), sb)
elThunk := makeThunk(env, el) elThunk := cachedThunk{env: &env, body: el}
elements = append(elements, elThunk) elements = append(elements, &elThunk)
} }
return makeValueArray(elements), nil return makeValueArray(elements), nil
case *ast.Binary: case *ast.Binary:
// Some binary operators are lazy, so thunks are needed in general if node.Op == ast.BopAnd {
env := i.getCurrentEnv(node) // Special case for shortcut semantics.
// TODO(sbarzowski) make sure it displays nicely in stack trace (thunk names etc.) xv, err := i.evaluate(node.Left, tc)
// 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 err != nil { if err != nil {
return nil, err 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: case *ast.Unary:
env := i.getCurrentEnv(node) value, err := i.evaluate(node.Expr, tc)
arg := makeThunk(env, node.Expr) if err != nil {
return nil, err
}
builtin := uopBuiltins[node.Op] builtin := uopBuiltins[node.Op]
result, err := builtin.function(e, arg) result, err := builtin.function(i, trace, value)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return result, nil return result, nil
case *ast.Conditional: case *ast.Conditional:
cond, err := e.evalInCurrentContext(node.Cond, nonTailCall) cond, err := i.evaluate(node.Cond, nonTailCall)
if err != nil { if err != nil {
return nil, err return nil, err
} }
condBool, err := e.getBoolean(cond) condBool, err := i.getBoolean(cond, trace)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if condBool.value { 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: case *ast.DesugaredObject:
// Evaluate all the field names. Check for null, dups, etc. // Evaluate all the field names. Check for null, dups, etc.
fields := make(simpleObjectFieldMap) fields := make(simpleObjectFieldMap)
for _, field := range node.Fields { for _, field := range node.Fields {
fieldNameValue, err := e.evalInCurrentContext(field.Name, nonTailCall) fieldNameValue, err := i.evaluate(field.Name, nonTailCall)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -338,11 +376,11 @@ func (i *interpreter) evaluate(a ast.Node, tc tailCallStatus) (value, error) {
// Omitted field. // Omitted field.
continue continue
default: 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 { if _, ok := fields[fieldName]; ok {
return nil, e.Error(duplicateFieldNameErrMsg(fieldName)) return nil, i.Error(duplicateFieldNameErrMsg(fieldName), trace)
} }
var f unboundField = &codeUnboundField{field.Body} var f unboundField = &codeUnboundField{field.Body}
if field.PlusSuper { if field.PlusSuper {
@ -354,69 +392,69 @@ func (i *interpreter) evaluate(a ast.Node, tc tailCallStatus) (value, error) {
for _, assert := range node.Asserts { for _, assert := range node.Asserts {
asserts = append(asserts, &codeUnboundField{assert}) asserts = append(asserts, &codeUnboundField{assert})
} }
upValues := i.capture(node.FreeVariables()) upValues := i.stack.capture(node.FreeVariables())
return makeValueSimpleObject(upValues, fields, asserts), nil return makeValueSimpleObject(upValues, fields, asserts), nil
case *ast.Error: case *ast.Error:
msgVal, err := e.evalInCurrentContext(node.Expr, nonTailCall) msgVal, err := i.evaluate(node.Expr, nonTailCall)
if err != nil { if err != nil {
// error when evaluating error message // error when evaluating error message
return nil, err return nil, err
} }
if msgVal.getType() != stringType { if msgVal.getType() != stringType {
msgVal, err = builtinToString(e, &readyValue{msgVal}) msgVal, err = builtinToString(i, trace, msgVal)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
msg, err := e.getString(msgVal) msg, err := i.getString(msgVal, trace)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return nil, e.Error(msg.getString()) return nil, i.Error(msg.getString(), trace)
case *ast.Index: case *ast.Index:
targetValue, err := e.evalInCurrentContext(node.Target, nonTailCall) targetValue, err := i.evaluate(node.Target, nonTailCall)
if err != nil { if err != nil {
return nil, err return nil, err
} }
index, err := e.evalInCurrentContext(node.Index, nonTailCall) index, err := i.evaluate(node.Index, nonTailCall)
if err != nil { if err != nil {
return nil, err return nil, err
} }
switch target := targetValue.(type) { switch target := targetValue.(type) {
case valueObject: case valueObject:
indexString, err := e.getString(index) indexString, err := i.getString(index, trace)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return target.index(e, indexString.getString()) return target.index(i, trace, indexString.getString())
case *valueArray: case *valueArray:
indexInt, err := e.getNumber(index) indexInt, err := i.getNumber(index, trace)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// TODO(https://github.com/google/jsonnet/issues/377): non-integer indexes should be an error // 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: case *valueString:
indexInt, err := e.getNumber(index) indexInt, err := i.getNumber(index, trace)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// TODO(https://github.com/google/jsonnet/issues/377): non-integer indexes should be an error // 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: case *ast.Import:
codePath := node.Loc().FileName 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: case *ast.ImportStr:
codePath := node.Loc().FileName 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: case *ast.LiteralBoolean:
return makeValueBoolean(node.Value), nil return makeValueBoolean(node.Value), nil
@ -432,19 +470,19 @@ func (i *interpreter) evaluate(a ast.Node, tc tailCallStatus) (value, error) {
case *ast.Local: case *ast.Local:
vars := make(bindingFrame) vars := make(bindingFrame)
bindEnv := i.getCurrentEnv(a) bindEnv := i.stack.getCurrentEnv(a)
for _, bind := range node.Binds { for _, bind := range node.Binds {
th := makeThunk(bindEnv, bind.Body) th := cachedThunk{env: &bindEnv, body: bind.Body}
// recursive locals // recursive locals
vars[bind.Variable] = th vars[bind.Variable] = &th
bindEnv.upValues[bind.Variable] = th bindEnv.upValues[bind.Variable] = &th
} }
i.newLocal(vars) i.stack.newLocal(vars)
sz := len(i.stack.stack) sz := len(i.stack.stack)
// Add new stack frame, with new thunk for this variable // Add new stack frame, with new thunk for this variable
// execute body WRT stack frame. // execute body WRT stack frame.
v, err := e.evalInCurrentContext(node.Body, tc) v, err := i.evaluate(node.Body, tc)
i.stack.popIfExists(sz) i.stack.popIfExists(sz)
return v, err return v, err
@ -454,65 +492,76 @@ func (i *interpreter) evaluate(a ast.Node, tc tailCallStatus) (value, error) {
return sb.self, nil return sb.self, nil
case *ast.Var: 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: case *ast.SuperIndex:
index, err := e.evalInCurrentContext(node.Index, nonTailCall) index, err := i.evaluate(node.Index, nonTailCall)
if err != nil { if err != nil {
return nil, err return nil, err
} }
indexStr, err := e.getString(index) indexStr, err := i.getString(index, trace)
if err != nil { if err != nil {
return nil, err 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: case *ast.InSuper:
index, err := e.evalInCurrentContext(node.Index, nonTailCall) index, err := i.evaluate(node.Index, nonTailCall)
if err != nil { if err != nil {
return nil, err return nil, err
} }
indexStr, err := e.getString(index) indexStr, err := i.getString(index, trace)
if err != nil { if err != nil {
return nil, err return nil, err
} }
field := tryObjectIndex(i.stack.getSelfBinding().super(), indexStr.getString(), withHidden) hasField := objectHasField(i.stack.getSelfBinding().super(), indexStr.getString(), withHidden)
return makeValueBoolean(field != nil), nil return makeValueBoolean(hasField), nil
case *ast.Function: case *ast.Function:
return &valueFunction{ return &valueFunction{
ec: makeClosure(i.getCurrentEnv(a), node), ec: makeClosure(i.stack.getCurrentEnv(a), node),
}, nil }, nil
case *ast.Apply: case *ast.Apply:
// Eval target // Eval target
target, err := e.evalInCurrentContext(node.Target, nonTailCall) target, err := i.evaluate(node.Target, nonTailCall)
if err != nil { if err != nil {
return nil, err return nil, err
} }
function, err := e.getFunction(target) function, err := i.getFunction(target, trace)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// environment in which we can evaluate arguments // environment in which we can evaluate arguments
argEnv := i.getCurrentEnv(a) argEnv := i.stack.getCurrentEnv(a)
arguments := callArguments{ arguments := callArguments{
positional: make([]potentialValue, len(node.Arguments.Positional)), positional: make([]*cachedThunk, len(node.Arguments.Positional)),
named: make([]namedCallArgument, len(node.Arguments.Named)), named: make([]namedCallArgument, len(node.Arguments.Named)),
tailstrict: node.TailStrict, tailstrict: node.TailStrict,
} }
for i, arg := range node.Arguments.Positional { 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 { 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: 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 // manifestJSON converts to standard JSON representation as in "encoding/json" package
func (i *interpreter) manifestJSON(trace *TraceElement, v value) (interface{}, error) { func (i *interpreter) manifestJSON(trace TraceElement, v value) (interface{}, error) {
e := &evaluator{i: i, trace: trace}
switch v := v.(type) { switch v := v.(type) {
case *valueBoolean: case *valueBoolean:
@ -584,7 +632,7 @@ func (i *interpreter) manifestJSON(trace *TraceElement, v value) (interface{}, e
case *valueArray: case *valueArray:
result := make([]interface{}, 0, len(v.elements)) result := make([]interface{}, 0, len(v.elements))
for _, th := range v.elements { for _, th := range v.elements {
elVal, err := e.evaluate(th) elVal, err := i.evaluatePV(th, trace)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -600,7 +648,7 @@ func (i *interpreter) manifestJSON(trace *TraceElement, v value) (interface{}, e
fieldNames := objectFields(v, withoutHidden) fieldNames := objectFields(v, withoutHidden)
sort.Strings(fieldNames) sort.Strings(fieldNames)
err := checkAssertions(e, v) err := checkAssertions(i, trace, v)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -608,7 +656,7 @@ func (i *interpreter) manifestJSON(trace *TraceElement, v value) (interface{}, e
result := make(map[string]interface{}) result := make(map[string]interface{})
for _, fieldName := range fieldNames { for _, fieldName := range fieldNames {
fieldVal, err := v.index(e, fieldName) fieldVal, err := v.index(i, trace, fieldName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -729,7 +777,7 @@ func serializeJSON(v interface{}, multiline bool, indent string, buf *bytes.Buff
} }
func (i *interpreter) manifestAndSerializeJSON( 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) manifested, err := i.manifestJSON(trace, v)
if err != nil { if err != nil {
return err return err
@ -739,7 +787,7 @@ func (i *interpreter) manifestAndSerializeJSON(
} }
// manifestString expects the value to be a string and returns it. // 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) { switch v := v.(type) {
case *valueString: case *valueString:
buf.WriteString(v.getString()) 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) r = make(map[string]string)
json, err := i.manifestJSON(trace, v) json, err := i.manifestJSON(trace, v)
if err != nil { if err != nil {
@ -783,7 +831,7 @@ func (i *interpreter) manifestAndSerializeMulti(trace *TraceElement, v value, st
return 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) r = make([]string, 0)
json, err := i.manifestJSON(trace, v) json, err := i.manifestJSON(trace, v)
if err != nil { if err != nil {
@ -806,19 +854,19 @@ func (i *interpreter) manifestAndSerializeYAMLStream(trace *TraceElement, v valu
return return
} }
func jsonToValue(e *evaluator, v interface{}) (value, error) { func jsonToValue(i *interpreter, trace TraceElement, v interface{}) (value, error) {
switch v := v.(type) { switch v := v.(type) {
case nil: case nil:
return &nullValue, nil return &nullValue, nil
case []interface{}: case []interface{}:
elems := make([]potentialValue, len(v)) elems := make([]*cachedThunk, len(v))
for i, elem := range v { for counter, elem := range v {
val, err := jsonToValue(e, elem) val, err := jsonToValue(i, trace, elem)
if err != nil { if err != nil {
return nil, err return nil, err
} }
elems[i] = &readyValue{val} elems[counter] = readyThunk(val)
} }
return makeValueArray(elems), nil return makeValueArray(elems), nil
@ -830,7 +878,7 @@ func jsonToValue(e *evaluator, v interface{}) (value, error) {
case map[string]interface{}: case map[string]interface{}:
fieldMap := map[string]value{} fieldMap := map[string]value{}
for name, f := range v { for name, f := range v {
val, err := jsonToValue(e, f) val, err := jsonToValue(i, trace, f)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -842,11 +890,11 @@ func jsonToValue(e *evaluator, v interface{}) (value, error) {
return makeValueString(v), nil return makeValueString(v), nil
default: 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) err := i.newCall(fromWhere, *env, trimmable)
if err != nil { if err != nil {
return nil, err return nil, err
@ -860,6 +908,180 @@ func (i *interpreter) EvalInCleanEnv(fromWhere *TraceElement, env *environment,
return val, err 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) { func buildStdObject(i *interpreter) (valueObject, error) {
objVal, err := evaluateStd(i) objVal, err := evaluateStd(i)
if err != nil { if err != nil {
@ -884,26 +1106,18 @@ func evaluateStd(i *interpreter) (value, error) {
makeUnboundSelfBinding(), makeUnboundSelfBinding(),
) )
evalLoc := ast.MakeLocationRangeMessage("During evaluation of std") evalLoc := ast.MakeLocationRangeMessage("During evaluation of std")
evalTrace := &TraceElement{loc: &evalLoc} evalTrace := TraceElement{loc: &evalLoc}
node := ast.StdAst node := ast.StdAst
return i.EvalInCleanEnv(evalTrace, &beforeStdEnv, node, false) return i.EvalInCleanEnv(evalTrace, &beforeStdEnv, node, false)
} }
func prepareExtVars(i *interpreter, ext vmExtMap, kind string) map[string]potentialValue { func prepareExtVars(i *interpreter, ext vmExtMap, kind string) map[string]*cachedThunk {
result := make(map[string]potentialValue) result := make(map[string]*cachedThunk)
for name, content := range ext { for name, content := range ext {
if content.isCode { if content.isCode {
varLoc := ast.MakeLocationRangeMessage("During evaluation") result[name] = codeToPV(i, "<"+kind+":"+name+">", content.value)
varTrace := &TraceElement{
loc: &varLoc,
}
e := &evaluator{
i: i,
trace: varTrace,
}
result[name] = codeToPV(e, "<"+kind+":"+name+">", content.value)
} else { } else {
result[name] = &readyValue{makeValueString(content.value)} result[name] = readyThunk(makeValueString(content.value))
} }
} }
return result return result
@ -942,21 +1156,21 @@ func makeInitialEnv(filename string, baseStd valueObject) environment {
}) })
return makeEnvironment( return makeEnvironment(
bindingFrame{ bindingFrame{
"std": &readyValue{makeValueExtendedObject(baseStd, fileSpecific)}, "std": readyThunk(makeValueExtendedObject(baseStd, fileSpecific)),
}, },
makeUnboundSelfBinding(), 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") evalLoc := ast.MakeLocationRangeMessage("During evaluation")
evalTrace := &TraceElement{ evalTrace := TraceElement{
loc: &evalLoc, loc: &evalLoc,
} }
env := makeInitialEnv(node.Loc().FileName, i.baseStd) env := makeInitialEnv(node.Loc().FileName, i.baseStd)
result, err := i.EvalInCleanEnv(evalTrace, &env, node, false) result, err := i.EvalInCleanEnv(evalTrace, &env, node, false)
if err != nil { if err != nil {
return nil, nil, err return nil, TraceElement{}, err
} }
// If it's not a function, ignore TLA // If it's not a function, ignore TLA
if f, ok := result.(*valueFunction); ok { 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}) args.named = append(args.named, namedCallArgument{name: ast.Identifier(argName), pv: pv})
} }
funcLoc := ast.MakeLocationRangeMessage("Top-level function") funcLoc := ast.MakeLocationRangeMessage("Top-level function")
funcTrace := &TraceElement{ funcTrace := TraceElement{
loc: &funcLoc, loc: &funcLoc,
} }
result, err = f.call(args).getValue(i, funcTrace) result, err = f.call(i, funcTrace, args)
if err != nil { if err != nil {
return nil, nil, err return nil, TraceElement{}, err
} }
} }
manifestationLoc := ast.MakeLocationRangeMessage("During manifestation") manifestationLoc := ast.MakeLocationRangeMessage("During manifestation")
manifestationTrace := &TraceElement{ manifestationTrace := TraceElement{
loc: &manifestationLoc, loc: &manifestationLoc,
} }
return result, manifestationTrace, nil return result, manifestationTrace, nil

View File

@ -44,7 +44,7 @@ type TraceFrame struct {
Name string Name string
} }
func traceElementToTraceFrame(trace *TraceElement) TraceFrame { func traceElementToTraceFrame(trace TraceElement) TraceFrame {
tf := TraceFrame{Loc: *trace.loc} tf := TraceFrame{Loc: *trace.loc}
if trace.context != nil { if trace.context != nil {
// TODO(sbarzowski) maybe it should never be nil // TODO(sbarzowski) maybe it should never be nil

View File

@ -4,11 +4,6 @@ RUNTIME ERROR: x
1 & error "x" 1 & error "x"
-------------------------------------------------
testdata/bitwise_and4:1:1-14 $
1 & error "x"
------------------------------------------------- -------------------------------------------------
During evaluation During evaluation

View File

@ -4,11 +4,6 @@ RUNTIME ERROR: x
1 ^ error "x" 1 ^ error "x"
-------------------------------------------------
testdata/bitwise_xor7:1:1-14 $
1 ^ error "x"
------------------------------------------------- -------------------------------------------------
During evaluation During evaluation

View File

@ -4,11 +4,6 @@ RUNTIME ERROR: should happen
true && error "should happen" true && error "should happen"
-------------------------------------------------
testdata/lazy_operator2:1:1-30 $
true && error "should happen"
------------------------------------------------- -------------------------------------------------
During evaluation During evaluation

View File

@ -4,11 +4,6 @@ RUNTIME ERROR: Attempt to use super when there is no super class.
{ x: 5, assert super.x == 5 } { x: 5, assert super.x == 5 }
-------------------------------------------------
testdata/object_invariant7:1:16-28 object <anonymous>
{ x: 5, assert super.x == 5 }
------------------------------------------------- -------------------------------------------------
During manifestation During manifestation

5
testdata/or4.golden vendored
View File

@ -4,11 +4,6 @@ RUNTIME ERROR: xxx
false || error "xxx" false || error "xxx"
-------------------------------------------------
testdata/or4:1:1-21 $
false || error "xxx"
------------------------------------------------- -------------------------------------------------
During evaluation During evaluation

View File

@ -15,12 +15,12 @@ RUNTIME ERROR: Not enough values to format, got 1
std.toString(val) std.toString(val)
------------------------------------------------- -------------------------------------------------
<std>:558:9-26 builtin function <toString> <std>:558:9-26 function <format_code>
std.toString(val) std.toString(val)
------------------------------------------------- -------------------------------------------------
... (skipped 14 frames) ... (skipped 10 frames)
------------------------------------------------- -------------------------------------------------
<std>:699:11-64 function <format_codes_arr> <std>:699:11-64 function <format_codes_arr>

View File

@ -15,12 +15,12 @@ RUNTIME ERROR: Not enough values to format, got 1
std.toString(val) std.toString(val)
------------------------------------------------- -------------------------------------------------
<std>:558:9-26 builtin function <toString> <std>:558:9-26 function <format_code>
std.toString(val) std.toString(val)
------------------------------------------------- -------------------------------------------------
... (skipped 14 frames) ... (skipped 10 frames)
------------------------------------------------- -------------------------------------------------
<std>:699:11-64 function <format_codes_arr> <std>:699:11-64 function <format_codes_arr>

View File

@ -21,7 +21,7 @@ RUNTIME ERROR: Format required number at 0, got string
padding(w - std.length(str), s) + str; padding(w - std.length(str), s) + str;
------------------------------------------------- -------------------------------------------------
... (skipped 11 frames) ... (skipped 7 frames)
------------------------------------------------- -------------------------------------------------
<std>:699:11-64 function <format_codes_arr> <std>:699:11-64 function <format_codes_arr>

View File

@ -5,7 +5,7 @@ RUNTIME ERROR: x
std.filter(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", []) std.filter(error "x", [])

View File

@ -10,7 +10,7 @@ local failWith(x) = error x;
std.type(std.flatMap(failWith, ["a", "b", "c"])) 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"])) std.type(std.flatMap(failWith, ["a", "b", "c"]))

View File

@ -1,8 +1,8 @@
RUNTIME ERROR: Expected an integer, but got 1e+100 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 During evaluation

View File

@ -1 +1 @@
std.makeArray(1e100, error "shouldn't happen") std.makeArray(1e100, function(i) i)

View File

@ -1,8 +1,6 @@
RUNTIME ERROR: max stack frames exceeded. 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> 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] 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> 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) ... (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> 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] 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 $ testdata/std.makeArray_recursive_evalutation_order_matters:1:64-72 $

View File

@ -5,7 +5,7 @@ RUNTIME ERROR: x
std.primitiveEquals(error "x", 42) 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) std.primitiveEquals(error "x", 42)

View File

@ -5,7 +5,7 @@ RUNTIME ERROR: x
std.primitiveEquals(42, 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") std.primitiveEquals(42, error "x")

View File

@ -5,7 +5,7 @@ RUNTIME ERROR: x
std.toString(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") std.toString(error "x")

View File

@ -5,7 +5,7 @@ RUNTIME ERROR: xxx
std.type(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") std.type(error "xxx")

176
thunks.go
View File

@ -30,14 +30,10 @@ type readyValue struct {
content value 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 return rv.content, nil
} }
func (rv *readyValue) bindToObject(sb selfBinding, origBinding bindingFrame, fieldName string) potentialValue {
return rv
}
func (rv *readyValue) aPotentialValue() {} func (rv *readyValue) aPotentialValue() {}
// potentialValues // potentialValues
@ -48,108 +44,50 @@ func (rv *readyValue) aPotentialValue() {}
// potentialValue which guarantees that computation happens at most once). // potentialValue which guarantees that computation happens at most once).
type evaluable interface { type evaluable interface {
// fromWhere keeps the information from where the evaluation was requested. // fromWhere keeps the information from where the evaluation was requested.
getValue(i *interpreter, fromWhere *TraceElement) (value, error) 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)
} }
// cachedThunk is a wrapper that caches the value of a potentialValue after // cachedThunk is a wrapper that caches the value of a potentialValue after
// the first evaluation. // the first evaluation.
// Note: All potentialValues are required to provide the same value every time, // Note: All potentialValues are required to provide the same value every time,
// so it's only there for efficiency. // so it's only there for efficiency.
// TODO(sbarzowski) investigate efficiency of various representations
type cachedThunk struct { 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 { func readyThunk(content value) *cachedThunk {
return &cachedThunk{pv} return &cachedThunk{content: content}
} }
func (t *cachedThunk) getValue(i *interpreter, trace *TraceElement) (value, error) { func (t *cachedThunk) getValue(i *interpreter, trace TraceElement) (value, error) {
v, err := t.pv.getValue(i, trace) 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 { if err != nil {
// TODO(sbarzowski) perhaps cache errors as well // TODO(sbarzowski) perhaps cache errors as well
// may be necessary if we allow handling them in any way // may be necessary if we allow handling them in any way
return nil, err 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 return v, nil
} }
func (t *cachedThunk) aPotentialValue() {} 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 // unboundFields
// ------------------------------------- // -------------------------------------
@ -157,9 +95,9 @@ type codeUnboundField struct {
body ast.Node body ast.Node
} }
func (f *codeUnboundField) bindToObject(sb selfBinding, origBindings bindingFrame, fieldName string) potentialValue { func (f *codeUnboundField) evaluate(i *interpreter, trace TraceElement, sb selfBinding, origBindings bindingFrame, fieldName string) (value, error) {
// TODO(sbarzowski) better object names (perhaps include a field name too?) env := makeEnvironment(origBindings, sb)
return makeThunk(makeEnvironment(origBindings, sb), f.body) return i.EvalInCleanEnv(trace, &env, f.body, false)
} }
// Provide additional bindings for a field. It shadows bindings from the object. // Provide additional bindings for a field. It shadows bindings from the object.
@ -169,7 +107,7 @@ type bindingsUnboundField struct {
bindings bindingFrame 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 var upValues bindingFrame
upValues = make(bindingFrame) upValues = make(bindingFrame)
for variable, pvalue := range origBindings { for variable, pvalue := range origBindings {
@ -178,7 +116,7 @@ func (f *bindingsUnboundField) bindToObject(sb selfBinding, origBindings binding
for variable, pvalue := range f.bindings { for variable, pvalue := range f.bindings {
upValues[variable] = pvalue 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. // PlusSuperUnboundField represents a `field+: ...` that hasn't been bound to an object.
@ -186,13 +124,19 @@ type PlusSuperUnboundField struct {
inner unboundField inner unboundField
} }
func (f *PlusSuperUnboundField) bindToObject(sb selfBinding, origBinding bindingFrame, fieldName string) potentialValue { func (f *PlusSuperUnboundField) evaluate(i *interpreter, trace TraceElement, sb selfBinding, origBinding bindingFrame, fieldName string) (value, error) {
left := tryObjectIndex(sb.super(), fieldName, withHidden) right, err := f.inner.evaluate(i, trace, sb, origBinding, fieldName)
right := f.inner.bindToObject(sb, origBinding, fieldName) if err != nil {
if left != nil { return nil, err
return call(bopBuiltins[ast.BopPlus], left, right)
} }
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 // evalCallables
@ -206,9 +150,9 @@ type closure struct {
params Parameters params Parameters
} }
func forceThunks(e *evaluator, args bindingFrame) error { func forceThunks(i *interpreter, trace TraceElement, args *bindingFrame) error {
for _, arg := range args { for _, arg := range *args {
_, err := e.evaluate(arg) _, err := arg.getValue(i, trace)
if err != nil { if err != nil {
return err return err
} }
@ -216,7 +160,7 @@ func forceThunks(e *evaluator, args bindingFrame) error {
return nil 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) argThunks := make(bindingFrame)
parameters := closure.Parameters() parameters := closure.Parameters()
for i, arg := range arguments.positional { for i, arg := range arguments.positional {
@ -238,15 +182,16 @@ func (closure *closure) EvalCall(arguments callArguments, e *evaluator) (value,
for i := range parameters.optional { for i := range parameters.optional {
param := &parameters.optional[i] param := &parameters.optional[i]
if _, exists := argThunks[param.name]; !exists { 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 // Default arguments are evaluated in the same environment as function body
return param.defaultArg.inEnv(&calledEnvironment) env: &calledEnvironment,
}) body: param.defaultArg,
}
} }
} }
if arguments.tailstrict { if arguments.tailstrict {
err := forceThunks(e, argThunks) err := forceThunks(i, trace, &argThunks)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -254,9 +199,9 @@ func (closure *closure) EvalCall(arguments callArguments, e *evaluator) (value,
calledEnvironment = makeEnvironment( calledEnvironment = makeEnvironment(
addBindings(closure.env.upValues, argThunks), 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 { func (closure *closure) Parameters() Parameters {
@ -269,9 +214,7 @@ func prepareClosureParameters(parameters ast.Parameters, env environment) Parame
for _, named := range parameters.Optional { for _, named := range parameters.Optional {
optionalParameters = append(optionalParameters, namedParameter{ optionalParameters = append(optionalParameters, namedParameter{
name: named.Name, name: named.Name,
defaultArg: &defaultArgument{ defaultArg: named.DefaultArg,
body: named.DefaultArg,
},
}) })
} }
return Parameters{ return Parameters{
@ -296,15 +239,15 @@ type NativeFunction struct {
} }
// EvalCall evaluates a call to a NativeFunction and returns the result. // 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()) flatArgs := flattenArgs(arguments, native.Parameters())
nativeArgs := make([]interface{}, 0, len(flatArgs)) nativeArgs := make([]interface{}, 0, len(flatArgs))
for _, arg := range flatArgs { for _, arg := range flatArgs {
v, err := e.evaluate(arg) v, err := i.evaluatePV(arg, trace)
if err != nil { if err != nil {
return nil, err return nil, err
} }
json, err := e.i.manifestJSON(e.trace, v) json, err := i.manifestJSON(trace, v)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -312,9 +255,9 @@ func (native *NativeFunction) EvalCall(arguments callArguments, e *evaluator) (v
} }
resultJSON, err := native.Func(nativeArgs) resultJSON, err := native.Func(nativeArgs)
if err != nil { 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. // Parameters returns a NativeFunction's parameters.
@ -322,7 +265,6 @@ func (native *NativeFunction) Parameters() Parameters {
return Parameters{required: native.Params} return Parameters{required: native.Params}
} }
// partialPotentialValue
// ------------------------------------- // -------------------------------------
type defaultArgument struct { type defaultArgument struct {
@ -330,5 +272,5 @@ type defaultArgument struct {
} }
func (da *defaultArgument) inEnv(env *environment) potentialValue { func (da *defaultArgument) inEnv(env *environment) potentialValue {
return makeThunk(*env, da.body) return &cachedThunk{env: env, body: da.body}
} }

128
value.go
View File

@ -58,13 +58,13 @@ var arrayType = &valueType{"array"}
// TODO(sbarzowski) perhaps call it just "Thunk"? // TODO(sbarzowski) perhaps call it just "Thunk"?
type potentialValue interface { type potentialValue interface {
// fromWhere keeps the information from where the evaluation was requested. // 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() aPotentialValue()
} }
// A set of variables with associated potentialValues. // A set of variables with associated thunks.
type bindingFrame map[ast.Identifier]potentialValue type bindingFrame map[ast.Identifier]*cachedThunk
type valueBase struct{} type valueBase struct{}
@ -81,11 +81,11 @@ type valueString struct {
value []rune 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() { if 0 <= index && index < s.length() {
return makeValueString(string(s.value[index])), nil 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 { func concatStrings(a, b *valueString) *valueString {
@ -199,28 +199,28 @@ func (*valueNull) getType() *valueType {
type valueArray struct { type valueArray struct {
valueBase 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() { 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 { func (arr *valueArray) length() int {
return len(arr.elements) 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 // We don't want to keep a bigger array than necessary
// so we create a new one with minimal capacity // so we create a new one with minimal capacity
var arrayElems []potentialValue var arrayElems []*cachedThunk
if len(elements) == cap(elements) { if len(elements) == cap(elements) {
arrayElems = elements arrayElems = elements
} else { } else {
arrayElems = make([]potentialValue, len(elements)) arrayElems = make([]*cachedThunk, len(elements))
for i := range elements { for i := range elements {
arrayElems[i] = elements[i] arrayElems[i] = elements[i]
} }
@ -231,7 +231,7 @@ func makeValueArray(elements []potentialValue) *valueArray {
} }
func concatArrays(a, b *valueArray) *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 { for _, r := range a.elements {
result = append(result, r) result = append(result, r)
} }
@ -255,23 +255,23 @@ type valueFunction struct {
// TODO(sbarzowski) better name? // TODO(sbarzowski) better name?
type evalCallable interface { type evalCallable interface {
EvalCall(args callArguments, e *evaluator) (value, error) EvalCall(args callArguments, i *interpreter, trace TraceElement) (value, error)
Parameters() Parameters Parameters() Parameters
} }
type partialPotentialValue interface { func (f *valueFunction) call(i *interpreter, trace TraceElement, args callArguments) (value, error) {
inEnv(env *environment) potentialValue err := checkArguments(i, trace, args, f.parameters())
if err != nil {
return nil, err
} }
return f.ec.EvalCall(args, i, trace)
func (f *valueFunction) call(args callArguments) potentialValue {
return makeCallThunk(f.ec, args)
} }
func (f *valueFunction) parameters() Parameters { func (f *valueFunction) parameters() Parameters {
return f.ec.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) received := make(map[ast.Identifier]bool)
accepted := 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) numExpected := len(params.required) + len(params.optional)
if numPassed > numExpected { 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 { for _, param := range params.required {
@ -300,17 +300,17 @@ func checkArguments(e *evaluator, args callArguments, params Parameters) error {
for _, arg := range args.named { for _, arg := range args.named {
if _, present := received[arg.name]; present { 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 { 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 received[arg.name] = true
} }
for _, param := range params.required { for _, param := range params.required {
if _, present := received[param]; !present { 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 { type namedParameter struct {
name ast.Identifier name ast.Identifier
defaultArg potentialValueInEnv defaultArg ast.Node
} }
type potentialValueInEnv interface { type potentialValueInEnv interface {
inEnv(env *environment) potentialValue inEnv(env *environment) *cachedThunk
} }
type callArguments struct { type callArguments struct {
positional []potentialValue positional []*cachedThunk
named []namedCallArgument named []namedCallArgument
tailstrict bool tailstrict bool
} }
type namedCallArgument struct { type namedCallArgument struct {
name ast.Identifier name ast.Identifier
pv potentialValue pv *cachedThunk
} }
func args(xs ...potentialValue) callArguments { func args(xs ...*cachedThunk) callArguments {
return callArguments{positional: xs} return callArguments{positional: xs}
} }
@ -363,7 +363,7 @@ func args(xs ...potentialValue) callArguments {
type valueObject interface { type valueObject interface {
value value
inheritanceSize() int inheritanceSize() int
index(e *evaluator, field string) (value, error) index(i *interpreter, trace TraceElement, field string) (value, error)
assertionsChecked() bool assertionsChecked() bool
setAssertionsCheckResult(err error) setAssertionsCheckResult(err error)
getAssertionsCheckResult() error getAssertionsCheckResult() error
@ -471,21 +471,22 @@ type valueSimpleObject struct {
asserts []unboundField 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) { switch curr := curr.(type) {
case *valueExtendedObject: case *valueExtendedObject:
err := checkAssertionsHelper(e, obj, curr.right, superDepth) err := checkAssertionsHelper(i, trace, obj, curr.right, superDepth)
if err != nil { if err != nil {
return err 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 { if err != nil {
return err return err
} }
return nil return nil
case *valueSimpleObject: case *valueSimpleObject:
for _, assert := range curr.asserts { 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 { if err != nil {
return err 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() { if !obj.assertionsChecked() {
// Assertions may refer to the object that will normally // Assertions may refer to the object that will normally
// trigger checking of assertions, resulting in an endless recursion. // trigger checking of assertions, resulting in an endless recursion.
// To avoid that, while we check them, we treat them as already passed. // To avoid that, while we check them, we treat them as already passed.
obj.setAssertionsCheckResult(errNoErrorInObjectInvariants) obj.setAssertionsCheckResult(errNoErrorInObjectInvariants)
obj.setAssertionsCheckResult(checkAssertionsHelper(e, obj, obj, 0)) obj.setAssertionsCheckResult(checkAssertionsHelper(i, trace, obj, obj, 0))
} }
return obj.getAssertionsCheckResult() return obj.getAssertionsCheckResult()
} }
func (o *valueSimpleObject) index(e *evaluator, field string) (value, error) { func (o *valueSimpleObject) index(i *interpreter, trace TraceElement, field string) (value, error) {
return objectIndex(e, objectBinding(o), field) return objectIndex(i, trace, objectBinding(o), field)
} }
func (*valueSimpleObject) inheritanceSize() int { 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. // unboundField is a field that doesn't know yet in which object it is.
type unboundField interface { 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). // valueExtendedObject represents an object created through inheritence (left + right).
@ -559,8 +560,8 @@ type valueExtendedObject struct {
totalInheritanceSize int totalInheritanceSize int
} }
func (o *valueExtendedObject) index(e *evaluator, field string) (value, error) { func (o *valueExtendedObject) index(i *interpreter, trace TraceElement, field string) (value, error) {
return objectIndex(e, objectBinding(o), field) return objectIndex(i, trace, objectBinding(o), field)
} }
func (o *valueExtendedObject) inheritanceSize() int { 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 // findField returns a field in object curr, with superDepth at least minSuperDepth
// It also returns an associated bindingFrame and actual superDepth that the field // It also returns an associated bindingFrame and actual superDepth that the field
// was found at. // 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) { switch curr := curr.(type) {
case *valueExtendedObject: case *valueExtendedObject:
if curr.right.inheritanceSize() > minSuperDepth { if curr.right.inheritanceSize() > minSuperDepth {
field, frame, counter := findField(curr.right, minSuperDepth, f) found, field, frame, counter := findField(curr.right, minSuperDepth, f)
if field != nil { if found {
return field, frame, counter return true, field, frame, counter
} }
} }
field, frame, counter := findField(curr.left, minSuperDepth-curr.right.inheritanceSize(), f) found, field, frame, counter := findField(curr.left, minSuperDepth-curr.right.inheritanceSize(), f)
return field, frame, counter + curr.right.inheritanceSize() return found, field, frame, counter + curr.right.inheritanceSize()
case *valueSimpleObject: case *valueSimpleObject:
if minSuperDepth <= 0 { if minSuperDepth <= 0 {
if field, ok := curr.fields[f]; ok { 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: default:
panic(fmt.Sprintf("Unknown object type %#v", curr)) panic(fmt.Sprintf("Unknown object type %#v", curr))
} }
} }
func objectIndex(e *evaluator, sb selfBinding, fieldName string) (value, error) { func objectIndex(i *interpreter, trace TraceElement, sb selfBinding, fieldName string) (value, error) {
err := checkAssertions(e, sb.self) err := checkAssertions(i, trace, sb.self)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if sb.superDepth >= sb.self.inheritanceSize() { if sb.superDepth >= sb.self.inheritanceSize() {
return nil, e.Error("Attempt to use super when there is no super class.") return nil, i.Error("Attempt to use super when there is no super class.", trace)
}
objp := tryObjectIndex(sb, fieldName, withHidden)
if objp == nil {
return nil, e.Error(fmt.Sprintf("Field does not exist: %s", fieldName))
}
return e.evaluate(objp)
} }
func tryObjectIndex(sb selfBinding, fieldName string, h Hidden) potentialValue { found, field, upValues, foundAt := findField(sb.self, sb.superDepth, fieldName)
field, upValues, foundAt := findField(sb.self, sb.superDepth, fieldName) if !found {
if field == nil || (h == withoutHidden && field.hide == ast.ObjectFieldHidden) { return nil, i.Error(fmt.Sprintf("Field does not exist: %s", fieldName), trace)
return nil
} }
fieldSelfBinding := selfBinding{self: sb.self, superDepth: foundAt} 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 type fieldHideMap map[string]ast.ObjectFieldHide