Location, error formatting and stack trace improvements (#59)

* Location, error formatting and stack trace improvements

* Static context for AST nodes
* Thunks no longer need `name`
* Prototype for showing snippets in error messages (old format still
available)
* Use ast.Function to represent methods and local function sugar.
* Change tests so that the error output is pretty
This commit is contained in:
Stanisław Barzowski 2017-10-03 14:27:44 -04:00 committed by Dave Cunningham
parent a1b8248e84
commit c3459153df
145 changed files with 4663 additions and 175 deletions

View File

@ -30,10 +30,14 @@ type Identifiers []Identifier
// ---------------------------------------------------------------------------
type Context *string
type Node interface {
Context() Context
Loc() *LocationRange
FreeVariables() Identifiers
SetFreeVariables(Identifiers)
SetContext(Context)
}
type Nodes []Node
@ -41,6 +45,7 @@ type Nodes []Node
type NodeBase struct {
loc LocationRange
context Context
freeVariables Identifiers
}
@ -70,6 +75,14 @@ func (n *NodeBase) SetFreeVariables(idents Identifiers) {
n.freeVariables = idents
}
func (n *NodeBase) Context() Context {
return n.context
}
func (n *NodeBase) SetContext(context Context) {
n.context = context
}
// ---------------------------------------------------------------------------
type IfSpec struct {
@ -356,11 +369,9 @@ type Slice struct {
// LocalBind is a helper struct for astLocal
type LocalBind struct {
Variable Identifier
Body Node
FunctionSugar bool
Params *Parameters // if functionSugar is true
TrailingComma bool
Variable Identifier
Body Node
Fun *Function
}
type LocalBinds []LocalBind
@ -439,7 +450,8 @@ type ObjectField struct {
Hide ObjectFieldHide // (ignore if kind != astObjectField*)
SuperSugar bool // +: (ignore if kind != astObjectField*)
MethodSugar bool // f(x, y, z): ... (ignore if kind == astObjectAssert)
Expr1 Node // Not in scope of the object
Method *Function
Expr1 Node // Not in scope of the object
Id *Identifier
Params *Parameters // If methodSugar == true then holds the params.
TrailingComma bool // If methodSugar == true then remembers the trailing comma
@ -448,12 +460,8 @@ type ObjectField struct {
// TODO(jbeda): Add the remaining constructor helpers here
func ObjectFieldLocal(methodSugar bool, id *Identifier, params *Parameters, trailingComma bool, body Node) ObjectField {
return ObjectField{ObjectLocal, ObjectFieldVisible, false, methodSugar, nil, id, params, trailingComma, body, nil}
}
func ObjectFieldLocalNoMethod(id *Identifier, body Node) ObjectField {
return ObjectField{ObjectLocal, ObjectFieldVisible, false, false, nil, id, nil, false, body, nil}
return ObjectField{ObjectLocal, ObjectFieldVisible, false, false, nil, nil, id, nil, false, body, nil}
}
type ObjectFields []ObjectField

View File

@ -16,14 +16,22 @@ limitations under the License.
package ast
import "fmt"
import (
"bytes"
"fmt"
)
type Source struct {
lines []string
}
//////////////////////////////////////////////////////////////////////////////
// Location
// Location represents a single location in an (unspecified) file.
type Location struct {
Line int
Line int
// Column is a byte offset from the beginning of the line
Column int
}
@ -36,6 +44,13 @@ func (l *Location) String() string {
return fmt.Sprintf("%v:%v", l.Line, l.Column)
}
func locationBefore(a Location, b Location) bool {
if a.Line != b.Line {
return a.Line < b.Line
}
return a.Column < b.Column
}
//////////////////////////////////////////////////////////////////////////////
// LocationRange
@ -43,7 +58,15 @@ func (l *Location) String() string {
type LocationRange struct {
FileName string
Begin Location
End Location
End Location // TODO(sbarzowski) inclusive? exclusive? a gap?
file *Source
}
func LocationRangeBetween(a, b *LocationRange) LocationRange {
if a.file != b.file {
panic("Cannot create a LocationRange between different files")
}
return MakeLocationRange(a.FileName, a.file, a.Begin, b.End)
}
// IsSet returns if this LocationRange has been set.
@ -70,11 +93,101 @@ func (lr *LocationRange) String() string {
return fmt.Sprintf("%s(%v)-(%v)", filePrefix, lr.Begin.String(), lr.End.String())
}
func (l *LocationRange) WithCode() bool {
return l.Begin.Line != 0
}
// This is useful for special locations, e.g. manifestation entry point.
func MakeLocationRangeMessage(msg string) LocationRange {
return LocationRange{FileName: msg}
}
func MakeLocationRange(fn string, begin Location, end Location) LocationRange {
return LocationRange{FileName: fn, Begin: begin, End: end}
func MakeLocationRange(fn string, fc *Source, begin Location, end Location) LocationRange {
return LocationRange{FileName: fn, file: fc, Begin: begin, End: end}
}
type SourceProvider struct {
}
func (sp *SourceProvider) GetSnippet(loc LocationRange) string {
var result bytes.Buffer
if loc.Begin.Line == 0 {
return ""
}
for i := loc.Begin.Line; i <= loc.End.Line; i++ {
inLineRange := trimToLine(loc, i)
for j := inLineRange.Begin.Column; j < inLineRange.End.Column; j++ {
result.WriteByte(loc.file.lines[i-1][j-1])
}
if i != loc.End.Line {
result.WriteByte('\n')
}
}
return result.String()
}
func BuildSource(s string) *Source {
var result []string
var lineBuf bytes.Buffer
for _, runeValue := range s {
lineBuf.WriteRune(runeValue)
if runeValue == '\n' {
result = append(result, lineBuf.String())
lineBuf.Reset()
}
}
rest := lineBuf.String()
// Stuff after last end-of-line (EOF or some more code)
result = append(result, rest+"\n")
return &Source{result}
}
func trimToLine(loc LocationRange, line int) LocationRange {
if loc.Begin.Line > line {
panic("invalid")
}
if loc.Begin.Line != line {
loc.Begin.Column = 1
}
loc.Begin.Line = line
if loc.End.Line < line {
panic("invalid")
}
if loc.End.Line != line {
loc.End.Column = len(loc.file.lines[line-1])
}
loc.End.Line = line
return loc
}
// lineBeginning returns a part of the line directly before LocationRange
// for example:
// local x = foo()
// ^^^^^ <- LocationRange loc
// then
// local x = foo()
// ^^^^^^^^^^ <- lineBeginning(loc)
func LineBeginning(loc *LocationRange) LocationRange {
return LocationRange{
Begin: Location{Line: loc.Begin.Line, Column: 1},
End: loc.Begin,
FileName: loc.FileName,
file: loc.file,
}
}
// lineEnding returns a part of the line directly after LocationRange
// for example:
// local x = foo() + test
// ^^^^^ <- LocationRange loc
// then
// local x = foo() + test
// ^^^^^^^ <- lineEnding(loc)
func LineEnding(loc *LocationRange) LocationRange {
return LocationRange{
Begin: loc.End,
End: Location{Line: loc.End.Line, Column: len(loc.file.lines[loc.End.Line-1])},
FileName: loc.FileName,
file: loc.file,
}
}

View File

@ -608,7 +608,7 @@ type UnaryBuiltin struct {
func getBuiltinEvaluator(e *evaluator, name ast.Identifier) *evaluator {
loc := ast.MakeLocationRangeMessage("<builtin>")
context := TraceContext{Name: "builtin function <" + string(name) + ">"}
context := "builtin function <" + string(name) + ">"
trace := TraceElement{loc: &loc, context: &context}
return &evaluator{i: e.i, trace: &trace}
}

View File

@ -128,22 +128,14 @@ func desugarFields(location ast.LocationRange, fields *ast.ObjectFields, objLeve
field.Expr2 = assertion
}
// Remove methods
for i := range *fields {
field := &((*fields)[i])
if !field.MethodSugar {
if field.Method == nil {
continue
}
origBody := field.Expr2
function := &ast.Function{
// TODO(sbarzowski) better location
NodeBase: ast.NewNodeBaseLoc(*origBody.Loc()),
Parameters: *field.Params,
Body: origBody,
}
field.MethodSugar = false
field.Params = nil
field.Expr2 = function
field.Expr2 = field.Method
field.Method = nil
// Body of the function already desugared through expr2
}
// Remove object-level locals
@ -483,19 +475,11 @@ func desugar(astPtr *ast.Node, objLevel int) (err error) {
case *ast.Local:
for i := range node.Binds {
if node.Binds[i].FunctionSugar {
origBody := node.Binds[i].Body
function := &ast.Function{
// TODO(sbarzowski) better location
NodeBase: ast.NewNodeBaseLoc(*origBody.Loc()),
Parameters: *node.Binds[i].Params,
Body: origBody,
}
if node.Binds[i].Fun != nil {
node.Binds[i] = ast.LocalBind{
Variable: node.Binds[i].Variable,
Body: function,
FunctionSugar: false,
Params: nil,
Variable: node.Binds[i].Variable,
Body: node.Binds[i].Fun,
Fun: nil,
}
}
err = desugar(&node.Binds[i].Body, objLevel)

View File

@ -19,6 +19,7 @@ import (
"bytes"
"fmt"
"github.com/fatih/color"
"github.com/google/go-jsonnet/ast"
"github.com/google/go-jsonnet/parser"
)
@ -27,10 +28,13 @@ type ErrorFormatter struct {
// TODO(sbarzowski) use this
// MaxStackTraceSize is the maximum length of stack trace before cropping
MaxStackTraceSize int
// TODO(sbarzowski) use these
// Examples of current state of the art.
// http://elm-lang.org/blog/compiler-errors-for-humans
// https://clang.llvm.org/diagnostics.html
pretty bool
colorful bool
SP SourceProvider
SP *ast.SourceProvider
}
func (ef *ErrorFormatter) format(err error) string {
@ -46,12 +50,13 @@ func (ef *ErrorFormatter) format(err error) string {
func (ef *ErrorFormatter) formatRuntime(err *RuntimeError) string {
return err.Error() + "\n" + ef.buildStackTrace(err.StackTrace)
// TODO(sbarzowski) pretty stuff
}
func (ef *ErrorFormatter) formatStatic(err *parser.StaticError) string {
return err.Error() + "\n"
// TODO(sbarzowski) pretty stuff
var buf bytes.Buffer
buf.WriteString(err.Error() + "\n")
ef.showCode(&buf, err.Loc)
return buf.String()
}
const bugURL = "https://github.com/google/go-jsonnet/issues"
@ -61,19 +66,42 @@ func (ef *ErrorFormatter) formatInternal(err error) string {
"Please report a bug here: " + bugURL + "\n"
}
func (ef *ErrorFormatter) showCode(buf *bytes.Buffer, loc ast.LocationRange) {
errFprintf := fmt.Fprintf
if ef.colorful {
errFprintf = color.New(color.FgRed).Fprintf
}
if loc.WithCode() {
// TODO(sbarzowski) include line numbers
// TODO(sbarzowski) underline errors instead of depending only on color
fmt.Fprintf(buf, "\n")
beginning := ast.LineBeginning(&loc)
ending := ast.LineEnding(&loc)
fmt.Fprintf(buf, "%v", ef.SP.GetSnippet(beginning))
errFprintf(buf, "%v", ef.SP.GetSnippet(loc))
fmt.Fprintf(buf, "%v", ef.SP.GetSnippet(ending))
buf.WriteByte('\n')
}
fmt.Fprintf(buf, "\n")
}
func (ef *ErrorFormatter) buildStackTrace(frames []TraceFrame) string {
// https://github.com/google/jsonnet/blob/master/core/libjsonnet.cpp#L594
var buf bytes.Buffer
for _, f := range frames {
for i := len(frames) - 1; i >= 0; i-- {
f := frames[i]
// TODO(sbarzowski) make pretty format more readable (it's already useful)
if ef.pretty {
fmt.Fprintf(&buf, "-------------------------------------------------\n")
}
// TODO(sbarzowski) tabs are probably a bad idea
fmt.Fprintf(&buf, "\t%v\t%v\n", &f.Loc, f.Name)
if ef.pretty {
ef.showCode(&buf, f.Loc)
}
// TODO(sbarzowski) handle max stack trace size
// TODO(sbarzowski) I think the order of frames is reversed
}
return buf.String()
}
type SourceProvider interface {
// TODO(sbarzowski) problem: locationRange.FileName may not necessarily
// uniquely identify a file. But this is the interface we want to have here.
getCode(ast.LocationRange) string
}

View File

@ -162,11 +162,11 @@ func (e *evaluator) evaluateObject(pv potentialValue) (valueObject, error) {
}
func (e *evaluator) evalInCurrentContext(a ast.Node) (value, error) {
return e.i.evaluate(a, e.trace.context)
return e.i.evaluate(a)
}
func (e *evaluator) evalInCleanEnv(newContext *TraceContext, env *environment, ast ast.Node) (value, error) {
return e.i.EvalInCleanEnv(e.trace, newContext, env, ast)
func (e *evaluator) evalInCleanEnv(env *environment, ast ast.Node) (value, error) {
return e.i.EvalInCleanEnv(e.trace, env, ast)
}
func (e *evaluator) lookUpVar(ident ast.Identifier) potentialValue {

View File

@ -86,7 +86,7 @@ func codeToPV(e *evaluator, filename string, code string) potentialValue {
// The same thinking applies to external variables.
return makeErrorThunk(err)
}
return makeThunk("_", e.i.initialEnv, node)
return makeThunk(e.i.initialEnv, node)
}
func (cache *ImportCache) ImportCode(codeDir, importedPath string, e *evaluator) (value, error) {

View File

@ -71,7 +71,7 @@ type callFrame struct {
// This makes callFrame a misnomer as it is technically not always a call...
isCall bool
// Tracing information about the place where (TODO)
// Tracing information about the place where it was called from.
trace *TraceElement
/** Reuse this stack frame for the purpose of tail call optimization. */
@ -242,11 +242,11 @@ func (i *interpreter) getCurrentEnv(ast ast.Node) environment {
)
}
func (i *interpreter) evaluate(a ast.Node, context *TraceContext) (value, error) {
func (i *interpreter) evaluate(a ast.Node) (value, error) {
e := &evaluator{
trace: &TraceElement{
loc: a.Loc(),
context: context,
context: a.Context(),
},
i: i,
}
@ -257,7 +257,7 @@ func (i *interpreter) evaluate(a ast.Node, context *TraceContext) (value, error)
var elements []potentialValue
for _, el := range ast.Elements {
env := makeEnvironment(i.capture(el.FreeVariables()), sb)
elThunk := makeThunk("array_element", env, el)
elThunk := makeThunk(env, el)
elements = append(elements, elThunk)
}
return makeValueArray(elements), nil
@ -269,8 +269,8 @@ func (i *interpreter) evaluate(a ast.Node, context *TraceContext) (value, error)
// 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("x", env, ast.Left)
right := makeThunk("y", env, ast.Right)
left := makeThunk(env, ast.Left)
right := makeThunk(env, ast.Right)
builtin := bopBuiltins[ast.Op]
@ -282,7 +282,7 @@ func (i *interpreter) evaluate(a ast.Node, context *TraceContext) (value, error)
case *ast.Unary:
env := i.getCurrentEnv(ast)
arg := makeThunk("x", env, ast.Expr)
arg := makeThunk(env, ast.Expr)
builtin := uopBuiltins[ast.Op]
@ -401,7 +401,7 @@ func (i *interpreter) evaluate(a ast.Node, context *TraceContext) (value, error)
vars := make(bindingFrame)
bindEnv := i.getCurrentEnv(a)
for _, bind := range ast.Binds {
th := makeThunk(bind.Variable, bindEnv, bind.Body)
th := makeThunk(bindEnv, bind.Body)
// recursive locals
vars[bind.Variable] = th
@ -467,8 +467,7 @@ func (i *interpreter) evaluate(a ast.Node, context *TraceContext) (value, error)
positional: make([]potentialValue, len(ast.Arguments.Positional)),
}
for i, arg := range ast.Arguments.Positional {
// TODO(sbarzowski) better thunk name
arguments.positional[i] = makeThunk("arg", argEnv, arg)
arguments.positional[i] = makeThunk(argEnv, arg)
}
return e.evaluate(function.call(arguments))
@ -700,13 +699,12 @@ func (i *interpreter) manifestAndSerializeJSON(trace *TraceElement, v value, mul
return buf.String(), nil
}
func (i *interpreter) EvalInCleanEnv(fromWhere *TraceElement, newContext *TraceContext,
env *environment, ast ast.Node) (value, error) {
func (i *interpreter) EvalInCleanEnv(fromWhere *TraceElement, env *environment, ast ast.Node) (value, error) {
err := i.newCall(fromWhere, *env)
if err != nil {
return nil, err
}
val, err := i.evaluate(ast, newContext)
val, err := i.evaluate(ast)
i.stack.pop()
return val, err
}
@ -736,12 +734,11 @@ func evaluateStd(i *interpreter) (value, error) {
)
evalLoc := ast.MakeLocationRangeMessage("During evaluation of std")
evalTrace := &TraceElement{loc: &evalLoc}
node, err := snippetToAST("std.jsonnet", getStdCode())
node, err := snippetToAST("<std>", getStdCode())
if err != nil {
return nil, err
}
context := TraceContext{Name: "<stdlib>"}
return i.EvalInCleanEnv(evalTrace, &context, &beforeStdEnv, node)
return i.EvalInCleanEnv(evalTrace, &beforeStdEnv, node)
}
func prepareExtVars(i *interpreter, ext vmExtMap) map[ast.Identifier]potentialValue {
@ -796,8 +793,7 @@ func evaluate(node ast.Node, ext vmExtMap, maxStack int, importer Importer) (str
evalTrace := &TraceElement{
loc: &evalLoc,
}
context := TraceContext{Name: "<main>"}
result, err := i.EvalInCleanEnv(evalTrace, &context, &i.initialEnv, node)
result, err := i.EvalInCleanEnv(evalTrace, &i.initialEnv, node)
if err != nil {
return "", err
}

View File

@ -92,6 +92,7 @@ func TestMain(t *testing.T) {
}
mainTests = append(mainTests, mainTest{name: name, input: input, golden: golden})
}
errFormatter := ErrorFormatter{pretty: true}
for _, test := range mainTests {
t.Run(test.name, func(t *testing.T) {
vm := MakeVM()
@ -109,7 +110,7 @@ func TestMain(t *testing.T) {
if err != nil {
// TODO(sbarzowski) perhaps somehow mark that we are processing
// an error. But for now we can treat them the same.
output = err.Error()
output = errFormatter.format(err)
}
output += "\n"
if *update {
@ -188,19 +189,20 @@ func TestOneLineError(t *testing.T) {
// TODO(sbarzowski) checking if the whitespace is right is quite unpleasant, what can we do about it?
var minimalErrorTests = []errorFormattingTest{
{"error", `error "x"`, "RUNTIME ERROR: x\n" +
" error:1:1-9 $\n" + // TODO(sbarzowski) if seems we have off-by-one in location
" During evaluation \n" +
" error:1:1-9 <main>\n"}, // TODO(sbarzowski) if seems we have off-by-one in location
""},
{"error_in_func", `local x(n) = if n == 0 then error "x" else x(n - 1); x(3)`, "RUNTIME ERROR: x\n" +
" error_in_func:1:29-37 function <x>\n" +
" error_in_func:1:44-52 function <x>\n" +
" error_in_func:1:44-52 function <x>\n" +
" error_in_func:1:44-52 function <x>\n" +
" error_in_func:1:54-58 $\n" +
" During evaluation \n" +
" error_in_func:1:54-58 <main>\n" +
" error_in_func:1:44-52 function <anonymous>\n" +
" error_in_func:1:44-52 function <anonymous>\n" +
" error_in_func:1:44-52 function <anonymous>\n" +
" error_in_func:1:29-37 function <anonymous>\n" +
""},
{"error_in_error", `error (error "x")`, "RUNTIME ERROR: x\n" +
" error_in_error:1:8-16 $\n" +
" During evaluation \n" +
" error_in_error:1:8-16 <main>\n" +
""},
}
@ -210,3 +212,6 @@ func TestMinimalError(t *testing.T) {
return formatter.format(r)
})
}
// TODO(sbarzowski) test pretty errors once they are stable-ish
// probably "golden" pattern is the right one for that

371
parser/context.go Normal file
View File

@ -0,0 +1,371 @@
/*
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 parser
import (
"fmt"
"github.com/google/go-jsonnet/ast"
)
var topLevelContext = "$"
const anonymous = "anonymous"
// TODO(sbarzowski) polish children functions and consider moving to AST
// and exporting
// directChildren are children of AST node that are executed in the same context
// and environment as their parent
//
// They must satisfy the following rules:
// * (no-delayed-evaluation) They are evaluated when their parent is evaluated or never.
// * (no-indirect-evaluation) They cannot be evaluated during evaluation of any non-direct children
// * (same-environment) They must be evaluated in the same environment as their parent
func directChildren(node ast.Node) []ast.Node {
switch node := node.(type) {
case *ast.Apply:
return []ast.Node{node.Target}
// TODO(sbarzowski) tailstrict call arguments (once we have tailstrict)
case *ast.ApplyBrace:
return []ast.Node{node.Left, node.Right}
case *ast.Array:
return nil
case *ast.Assert:
return []ast.Node{node.Cond, node.Message, node.Rest}
case *ast.Binary:
return []ast.Node{node.Left, node.Right}
case *ast.Conditional:
return []ast.Node{node.Cond, node.BranchTrue, node.BranchFalse}
case *ast.Dollar:
return nil
case *ast.Error:
return []ast.Node{node.Expr}
case *ast.Function:
return nil
case *ast.Import:
return nil
case *ast.ImportStr:
return nil
case *ast.Index:
return []ast.Node{node.Target, node.Index}
case *ast.Slice:
return []ast.Node{node.Target, node.BeginIndex, node.EndIndex, node.Step}
case *ast.Local:
return []ast.Node{node.Body}
case *ast.LiteralBoolean:
return nil
case *ast.LiteralNull:
return nil
case *ast.LiteralNumber:
return nil
case *ast.LiteralString:
return nil
case *ast.Object:
return objectFieldsDirectChildren(node.Fields)
case *ast.ArrayComp:
result := []ast.Node{}
spec := &node.Spec
for spec != nil {
result = append(result, spec.Expr)
for _, ifspec := range spec.Conditions {
result = append(result, ifspec.Expr)
}
spec = spec.Outer
}
return result
case *ast.ObjectComp:
result := objectFieldsDirectChildren(node.Fields)
spec := &node.Spec
for spec != nil {
result = append(result, spec.Expr)
for _, ifspec := range spec.Conditions {
result = append(result, ifspec.Expr)
}
spec = spec.Outer
}
return result
case *ast.Self:
return nil
case *ast.SuperIndex:
return []ast.Node{node.Index}
case *ast.InSuper:
return []ast.Node{node.Index}
case *ast.Unary:
return []ast.Node{node.Expr}
case *ast.Var:
return nil
}
panic(fmt.Sprintf("directChildren: Unknown node %#v", node))
}
// thunkChildren are children of AST node that are executed in a new context
// and capture environment from parent (thunked)
// TODO(sbarzowski) Make sure it works well with boundary cases like tailstrict arguments,
// make it more precise.
// Rules:
// * (same-environment) They must be evaluated in the same environment as their parent
// * (not-direct) If they can be direct children, they should (and cannot be thunked).
func thunkChildren(node ast.Node) []ast.Node {
switch node := node.(type) {
case *ast.Apply:
var nodes []ast.Node
for _, arg := range node.Arguments.Positional {
nodes = append(nodes, arg)
}
for _, arg := range node.Arguments.Named {
nodes = append(nodes, arg.Arg)
}
return nodes
case *ast.ApplyBrace:
return nil
case *ast.Array:
return node.Elements
case *ast.Assert:
return nil
case *ast.Binary:
return nil
case *ast.Conditional:
return nil
case *ast.Dollar:
return nil
case *ast.Error:
return nil
case *ast.Function:
return nil
case *ast.Import:
return nil
case *ast.ImportStr:
return nil
case *ast.Index:
return nil
case *ast.Slice:
return nil
case *ast.Local:
// TODO(sbarzowski) complicated
return nil
case *ast.LiteralBoolean:
return nil
case *ast.LiteralNull:
return nil
case *ast.LiteralNumber:
return nil
case *ast.LiteralString:
return nil
case *ast.Object:
return nil
case *ast.ArrayComp:
return []ast.Node{node.Body}
case *ast.ObjectComp:
return nil
case *ast.Self:
return nil
case *ast.SuperIndex:
return nil
case *ast.InSuper:
return nil
case *ast.Unary:
return nil
case *ast.Var:
return nil
}
panic(fmt.Sprintf("thunkChildren: Unknown node %#v", node))
}
func objectFieldsDirectChildren(fields ast.ObjectFields) ast.Nodes {
result := ast.Nodes{}
for _, field := range fields {
if field.Expr1 != nil {
result = append(result, field.Expr1)
}
}
return result
}
func inObjectFieldsChildren(fields ast.ObjectFields) ast.Nodes {
result := ast.Nodes{}
for _, field := range fields {
if field.MethodSugar {
result = append(result, field.Method)
} else {
if field.Expr2 != nil {
result = append(result, field.Expr2)
}
if field.Expr3 != nil {
result = append(result, field.Expr3)
}
}
}
return result
}
// children that are neither direct nor thunked, e.g. object field body
// They are evaluated in a different environment from their parent.
func specialChildren(node ast.Node) []ast.Node {
switch node := node.(type) {
case *ast.Apply:
return nil
case *ast.ApplyBrace:
return nil
case *ast.Array:
return nil
case *ast.Assert:
return nil
case *ast.Binary:
return nil
case *ast.Conditional:
return nil
case *ast.Dollar:
return nil
case *ast.Error:
return nil
case *ast.Function:
// TODO(sbarzowski) this
return nil
case *ast.Import:
return nil
case *ast.ImportStr:
return nil
case *ast.Index:
return nil
case *ast.Slice:
return nil
case *ast.Local:
return nil
case *ast.LiteralBoolean:
return nil
case *ast.LiteralNull:
return nil
case *ast.LiteralNumber:
return nil
case *ast.LiteralString:
return nil
case *ast.Object:
return inObjectFieldsChildren(node.Fields)
case *ast.ArrayComp:
return []ast.Node{node.Body}
case *ast.ObjectComp:
case *ast.Self:
return nil
case *ast.SuperIndex:
return nil
case *ast.InSuper:
return nil
case *ast.Unary:
return nil
case *ast.Var:
return nil
}
panic(fmt.Sprintf("specialChildren: Unknown node %#v", node))
}
func children(node ast.Node) []ast.Node {
var result []ast.Node
result = append(result, directChildren(node)...)
result = append(result, thunkChildren(node)...)
result = append(result, specialChildren(node)...)
return result
}
func functionContext(funcName string) *string {
r := "function <" + funcName + ">"
return &r
}
func objectContext(objName string) *string {
r := "object <" + objName + ">"
return &r
}
// addContext adds context to a node and its whole subtree.
//
// context is the surrounding context of a node (e.g. a function it's in)
//
// bind is a name that the node is bound to, i.e. if node is a local bind body
// then bind is its name. For nodes that are not bound to variables `anonymous`
// should be passed. For example:
// local x = 2 + 2; x
// In such case bind for binary node 2 + 2 is "x" and for every other node,
// including its children, its anonymous.
func addContext(node ast.Node, context *string, bind string) {
if node == nil {
return
}
node.SetContext(context)
switch node := node.(type) {
case *ast.Function:
funContext := functionContext(bind)
addContext(node.Body, funContext, anonymous)
for i := range node.Parameters.Named {
// Default arguments have the same context as the function body.
addContext(node.Parameters.Named[i].DefaultArg, funContext, anonymous)
}
case *ast.Object:
// TODO(sbarzowski) include fieldname, maybe even chains
outOfObject := directChildren(node)
for _, f := range outOfObject {
// This actually is evaluated outside of object
addContext(f, context, anonymous)
}
objContext := objectContext(bind)
inObject := inObjectFieldsChildren(node.Fields)
for _, f := range inObject {
// This actually is evaluated outside of object
addContext(f, objContext, anonymous)
}
case *ast.ObjectComp:
outOfObject := directChildren(node)
for _, f := range outOfObject {
// This actually is evaluated outside of object
addContext(f, context, anonymous)
}
objContext := objectContext(bind)
inObject := inObjectFieldsChildren(node.Fields)
for _, f := range inObject {
// This actually is evaluated outside of object
addContext(f, objContext, anonymous)
}
case *ast.Local:
for _, bind := range node.Binds {
namedThunkContext := "thunk <" + string(bind.Variable) + "> from <" + *context + ">"
if bind.Fun != nil {
addContext(bind.Fun, &namedThunkContext, string(bind.Variable))
} else {
addContext(bind.Body, &namedThunkContext, string(bind.Variable))
}
}
addContext(node.Body, context, bind)
default:
for _, child := range directChildren(node) {
addContext(child, context, anonymous)
}
// TODO(sbarzowski) avoid "thunk from <thunk from..."
thunkContext := "thunk from <" + *context + ">"
for _, child := range thunkChildren(node) {
addContext(child, &thunkContext, anonymous)
}
}
}

View File

@ -245,6 +245,7 @@ type position struct {
type lexer struct {
fileName string // The file name being lexed, only used for errors
input string // The input string
source *ast.Source
pos position // Current position in input
prev position // Previous position in input
@ -263,6 +264,7 @@ func makeLexer(fn string, input string) *lexer {
return &lexer{
fileName: fn,
input: input,
source: ast.BuildSource(input),
pos: position{byteNo: 0, lineNo: 1, lineStart: 0},
prev: position{byteNo: lexEOF, lineNo: 0, lineStart: 0},
tokenStartLoc: ast.Location{Line: 1, Column: 1},
@ -337,7 +339,7 @@ func (l *lexer) emitFullToken(kind tokenKind, data, stringBlockIndent, stringBlo
data: data,
stringBlockIndent: stringBlockIndent,
stringBlockTermIndent: stringBlockTermIndent,
loc: ast.MakeLocationRange(l.fileName, l.tokenStartLoc, l.location()),
loc: ast.MakeLocationRange(l.fileName, l.source, l.tokenStartLoc, l.location()),
})
l.fodder = fodder{}
}
@ -367,6 +369,10 @@ func (l *lexer) addFodder(kind fodderKind, data string) {
l.fodder = append(l.fodder, fodderElement{kind: kind, data: data})
}
func (l *lexer) makeStaticErrorPoint(msg string, loc ast.Location) StaticError {
return StaticError{Msg: msg, Loc: ast.MakeLocationRange(l.fileName, l.source, loc, loc)}
}
// lexNumber will consume a number and emit a token. It is assumed
// that the next rune to be served by the lexer will be a leading digit.
func (l *lexer) lexNumber() error {
@ -431,9 +437,9 @@ outerLoop:
case r >= '0' && r <= '9':
state = numAfterDigit
default:
return MakeStaticErrorPoint(
return l.makeStaticErrorPoint(
fmt.Sprintf("Couldn't lex number, junk after decimal point: %v", strconv.QuoteRuneToASCII(r)),
l.fileName, l.prevLocation())
l.prevLocation())
}
case numAfterDigit:
switch {
@ -451,17 +457,17 @@ outerLoop:
case r >= '0' && r <= '9':
state = numAfterExpDigit
default:
return MakeStaticErrorPoint(
return l.makeStaticErrorPoint(
fmt.Sprintf("Couldn't lex number, junk after 'E': %v", strconv.QuoteRuneToASCII(r)),
l.fileName, l.prevLocation())
l.prevLocation())
}
case numAfterExpSign:
if r >= '0' && r <= '9' {
state = numAfterExpDigit
} else {
return MakeStaticErrorPoint(
return l.makeStaticErrorPoint(
fmt.Sprintf("Couldn't lex number, junk after exponent sign: %v", strconv.QuoteRuneToASCII(r)),
l.fileName, l.prevLocation())
l.prevLocation())
}
case numAfterExpDigit:
@ -558,8 +564,8 @@ func (l *lexer) lexSymbol() error {
l.resetTokenStart() // Throw out the leading /*
for r = l.next(); ; r = l.next() {
if r == lexEOF {
return MakeStaticErrorPoint("Multi-line comment has no terminating */",
l.fileName, commentStartLoc)
return l.makeStaticErrorPoint("Multi-line comment has no terminating */",
commentStartLoc)
}
if r == '*' && l.peek() == '/' {
commentData := l.input[l.tokenStart : l.pos.byteNo-1] // Don't include trailing */
@ -584,8 +590,8 @@ func (l *lexer) lexSymbol() error {
numWhiteSpace := checkWhitespace(l.input[l.pos.byteNo:], l.input[l.pos.byteNo:])
stringBlockIndent := l.input[l.pos.byteNo : l.pos.byteNo+numWhiteSpace]
if numWhiteSpace == 0 {
return MakeStaticErrorPoint("Text block's first line must start with whitespace",
l.fileName, commentStartLoc)
return l.makeStaticErrorPoint("Text block's first line must start with whitespace",
commentStartLoc)
}
for {
@ -595,8 +601,7 @@ func (l *lexer) lexSymbol() error {
l.acceptN(numWhiteSpace)
for r = l.next(); r != '\n'; r = l.next() {
if r == lexEOF {
return MakeStaticErrorPoint("Unexpected EOF",
l.fileName, commentStartLoc)
return l.makeStaticErrorPoint("Unexpected EOF", commentStartLoc)
}
cb.WriteRune(r)
}
@ -618,8 +623,7 @@ func (l *lexer) lexSymbol() error {
}
l.backup()
if !strings.HasPrefix(l.input[l.pos.byteNo:], "|||") {
return MakeStaticErrorPoint("Text block not terminated with |||",
l.fileName, commentStartLoc)
return l.makeStaticErrorPoint("Text block not terminated with |||", commentStartLoc)
}
l.acceptN(3) // Skip '|||'
l.emitFullToken(tokenStringBlock, cb.String(),
@ -710,7 +714,7 @@ func Lex(fn string, input string) (tokens, error) {
l.resetTokenStart() // Don't include the quotes in the token data
for r = l.next(); ; r = l.next() {
if r == lexEOF {
return nil, MakeStaticErrorPoint("Unterminated String", l.fileName, stringStartLoc)
return nil, l.makeStaticErrorPoint("Unterminated String", stringStartLoc)
}
if r == '"' {
l.backup()
@ -728,7 +732,7 @@ func Lex(fn string, input string) (tokens, error) {
l.resetTokenStart() // Don't include the quotes in the token data
for r = l.next(); ; r = l.next() {
if r == lexEOF {
return nil, MakeStaticErrorPoint("Unterminated String", l.fileName, stringStartLoc)
return nil, l.makeStaticErrorPoint("Unterminated String", stringStartLoc)
}
if r == '\'' {
l.backup()
@ -757,15 +761,14 @@ func Lex(fn string, input string) (tokens, error) {
} else if quot == '\'' {
kind = tokenVerbatimStringSingle
} else {
return nil, MakeStaticErrorPoint(
return nil, l.makeStaticErrorPoint(
fmt.Sprintf("Couldn't lex verbatim string, junk after '@': %v", quot),
l.fileName,
stringStartLoc,
)
}
for r = l.next(); ; r = l.next() {
if r == lexEOF {
return nil, MakeStaticErrorPoint("Unterminated String", l.fileName, stringStartLoc)
return nil, l.makeStaticErrorPoint("Unterminated String", stringStartLoc)
} else if r == quot {
if l.peek() == quot {
l.next()
@ -799,9 +802,9 @@ func Lex(fn string, input string) (tokens, error) {
return nil, err
}
} else {
return nil, MakeStaticErrorPoint(
return nil, l.makeStaticErrorPoint(
fmt.Sprintf("Could not lex the character %s", strconv.QuoteRuneToASCII(r)),
l.fileName, l.prevLocation())
l.prevLocation())
}
}

View File

@ -61,11 +61,11 @@ func makeUnexpectedError(t *token, while string) error {
}
func locFromTokens(begin, end *token) ast.LocationRange {
return ast.MakeLocationRange(begin.loc.FileName, begin.loc.Begin, end.loc.End)
return ast.LocationRangeBetween(&begin.loc, &end.loc)
}
func locFromTokenAST(begin *token, end ast.Node) ast.LocationRange {
return ast.MakeLocationRange(begin.loc.FileName, begin.loc.Begin, end.Loc().End)
return ast.LocationRangeBetween(&begin.loc, end.Loc())
}
// ---------------------------------------------------------------------------
@ -219,36 +219,36 @@ func (p *parser) parseBind(binds *ast.LocalBinds) error {
}
}
var fun *ast.Function
if p.peek().kind == tokenParenL {
p.pop()
params, gotComma, err := p.parseParameters("function parameter")
if err != nil {
return err
}
_, err = p.popExpectOp("=")
if err != nil {
return err
}
body, err := p.parse(maxPrecedence)
if err != nil {
return err
}
*binds = append(*binds, ast.LocalBind{
Variable: ast.Identifier(varID.data),
Body: body,
FunctionSugar: true,
Params: params,
fun = &ast.Function{
Parameters: *params,
TrailingComma: gotComma,
}
}
_, err = p.popExpectOp("=")
if err != nil {
return err
}
body, err := p.parse(maxPrecedence)
if err != nil {
return err
}
if fun != nil {
fun.Body = body
*binds = append(*binds, ast.LocalBind{
Variable: ast.Identifier(varID.data),
Body: body,
Fun: fun,
})
} else {
_, err = p.popExpectOp("=")
if err != nil {
return err
}
body, err := p.parse(maxPrecedence)
if err != nil {
return err
}
*binds = append(*binds, ast.LocalBind{
Variable: ast.Identifier(varID.data),
Body: body,
@ -299,6 +299,7 @@ func (p *parser) parseObjectAssignmentOp() (plusSugar bool, hide ast.ObjectField
// +gen set
type LiteralField string
// Parse object or object comprehension without leading brace
func (p *parser) parseObjectRemainder(tok *token) (ast.Node, *token, error) {
var fields ast.ObjectFields
literalFields := make(literalFieldSet)
@ -451,11 +452,21 @@ func (p *parser) parseObjectRemainder(tok *token) (ast.Node, *token, error) {
return nil, nil, err
}
var method *ast.Function
if isMethod {
method = &ast.Function{
Parameters: *params,
TrailingComma: methComma,
Body: body,
}
}
fields = append(fields, ast.ObjectField{
Kind: kind,
Hide: hide,
SuperSugar: plusSugar,
MethodSugar: isMethod,
Method: method,
Expr1: expr1,
Id: id,
Params: params,
@ -475,6 +486,8 @@ func (p *parser) parseObjectRemainder(tok *token) (ast.Node, *token, error) {
return nil, nil, MakeStaticError(fmt.Sprintf("Duplicate local var: %v", id), varID.loc)
}
// TODO(sbarzowski) Can we reuse regular local bind parsing here?
isMethod := false
funcComma := false
var params *ast.Parameters
@ -496,6 +509,15 @@ func (p *parser) parseObjectRemainder(tok *token) (ast.Node, *token, error) {
return nil, nil, err
}
var method *ast.Function
if isMethod {
method = &ast.Function{
Parameters: *params,
TrailingComma: funcComma,
Body: body,
}
}
binds.Add(id)
fields = append(fields, ast.ObjectField{
@ -503,6 +525,7 @@ func (p *parser) parseObjectRemainder(tok *token) (ast.Node, *token, error) {
Hide: ast.ObjectFieldVisible,
SuperSugar: false,
MethodSugar: isMethod,
Method: method,
Id: &id,
Params: params,
TrailingComma: funcComma,
@ -1165,5 +1188,7 @@ func Parse(t tokens) (ast.Node, error) {
return nil, MakeStaticError(fmt.Sprintf("Did not expect: %v", p.peek()), p.peek().loc)
}
addContext(expr, &topLevelContext, anonymous)
return expr, nil
}

View File

@ -16,6 +16,7 @@ limitations under the License.
package parser
import (
"fmt"
"testing"
)
@ -124,15 +125,19 @@ var tests = []string{
func TestParser(t *testing.T) {
for _, s := range tests {
tokens, err := Lex("test", s)
if err != nil {
t.Errorf("Unexpected lex error\n input: %v\n error: %v", s, err)
continue
}
_, err = Parse(tokens)
if err != nil {
t.Errorf("Unexpected parse error\n input: %v\n error: %v", s, err)
}
t.Run(s, func(t *testing.T) {
fmt.Println(s)
tokens, err := Lex("test", s)
if err != nil {
t.Errorf("Unexpected lex error\n input: %v\n error: %v", s, err)
return
}
_, err = Parse(tokens)
if err != nil {
t.Errorf("Unexpected parse error\n input: %v\n error: %v", s, err)
}
})
}
}
@ -246,19 +251,21 @@ var errorTests = []testError{
func TestParserErrors(t *testing.T) {
for _, s := range errorTests {
tokens, err := Lex("test", s.input)
if err != nil {
t.Errorf("Unexpected lex error\n input: %v\n error: %v", s.input, err)
continue
}
_, err = Parse(tokens)
if err == nil {
t.Errorf("Expected parse error but got success\n input: %v", s.input)
continue
}
if err.Error() != s.err {
t.Errorf("Error string not as expected\n input: %v\n expected error: %v\n actual error: %v", s.input, s.err, err.Error())
}
t.Run(s.input, func(t *testing.T) {
tokens, err := Lex("test", s.input)
if err != nil {
t.Errorf("Unexpected lex error\n input: %v\n error: %v", s.input, err)
return
}
_, err = Parse(tokens)
if err == nil {
t.Errorf("Expected parse error but got success\n input: %v", s.input)
return
}
if err.Error() != s.err {
t.Errorf("Error string not as expected\n input: %v\n expected error: %v\n actual error: %v", s.input, s.err, err.Error())
}
})
}
}

View File

@ -36,10 +36,6 @@ func MakeStaticErrorMsg(msg string) StaticError {
return StaticError{Msg: msg}
}
func MakeStaticErrorPoint(msg string, fn string, l ast.Location) StaticError {
return StaticError{Msg: msg, Loc: ast.MakeLocationRange(fn, l, l)}
}
func MakeStaticError(msg string, lr ast.LocationRange) StaticError {
return StaticError{Msg: msg, Loc: lr}
}

View File

@ -48,20 +48,15 @@ func traceElementToTraceFrame(trace *TraceElement) TraceFrame {
tf := TraceFrame{Loc: *trace.loc}
if trace.context != nil {
// TODO(sbarzowski) maybe it should never be nil
tf.Name = trace.context.Name
tf.Name = *trace.context
} else {
tf.Name = ""
}
return tf
}
type TraceContext struct {
// Human readable name - e.g. function <foo>
Name string
}
// TODO(sbarzowski) better name
type TraceElement struct {
loc *ast.LocationRange
context *TraceContext
context ast.Context
}

4
testdata/".golden vendored
View File

@ -1 +1,5 @@
testdata/":2:1 Unexpected end of file.

4
testdata/'.golden vendored
View File

@ -1 +1,5 @@
testdata/':2:1 Unexpected end of file.

View File

@ -1 +1,5 @@
testdata/arrcomp5:1:14-15 Unknown variable: x
[y for y in [x + 1] for x in [1, 2, 3]]

View File

@ -1 +1,5 @@
testdata/arrcomp_if4:1:33-34 Unknown variable: y
[[x, y] for x in [1,2,3,4,5] if y == 3 for y in [1,2,3,4,5]]

View File

@ -1 +1,13 @@
RUNTIME ERROR: x
-------------------------------------------------
testdata/arrcomp_if6:1:20-28 $
[x for x in [1] if error "x"]
-------------------------------------------------
<builtin> builtin function <flatMap>
-------------------------------------------------
During evaluation

View File

@ -1 +1,11 @@
RUNTIME ERROR: Unexpected type number, expected boolean
-------------------------------------------------
-------------------------------------------------
<builtin> builtin function <flatMap>
-------------------------------------------------
During evaluation

View File

@ -1 +1,8 @@
RUNTIME ERROR: Assertion failed
-------------------------------------------------
-------------------------------------------------
During evaluation

View File

@ -1 +1,15 @@
RUNTIME ERROR: Assertion failed. {"x": 1} != {"x": 2}
-------------------------------------------------
<std>:649:13-56 function <anonymous>
error "Assertion failed. " + a + " != " + b,
-------------------------------------------------
testdata/assert_equal4:1:1-32 $
std.assertEqual({x: 1}, {x: 2})
-------------------------------------------------
During evaluation

17
testdata/assert_equal5.golden vendored Normal file
View File

@ -0,0 +1,17 @@
RUNTIME ERROR: Assertion failed.
!=
-------------------------------------------------
<std>:649:13-56 function <anonymous>
error "Assertion failed. " + a + " != " + b,
-------------------------------------------------
testdata/assert_equal5:1:1-29 $
std.assertEqual("\n ", "\n")
-------------------------------------------------
During evaluation

1
testdata/assert_equal5.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
std.assertEqual("\n ", "\n")

15
testdata/assert_equal6.golden vendored Normal file
View File

@ -0,0 +1,15 @@
RUNTIME ERROR: Assertion failed.  !=
-------------------------------------------------
<std>:649:13-56 function <anonymous>
error "Assertion failed. " + a + " != " + b,
-------------------------------------------------
testdata/assert_equal6:1:1-34 $
std.assertEqual("\u001b[31m", "")
-------------------------------------------------
During evaluation

1
testdata/assert_equal6.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
std.assertEqual("\u001b[31m", "")

View File

@ -1 +1,8 @@
RUNTIME ERROR: Assertion failed
-------------------------------------------------
-------------------------------------------------
During evaluation

View File

@ -1 +1,8 @@
RUNTIME ERROR: Custom Message
-------------------------------------------------
-------------------------------------------------
During evaluation

View File

@ -1 +1,10 @@
RUNTIME ERROR: function expected 1 argument(s), but got 0
-------------------------------------------------
testdata/bad_function_call:1:1-18 $
(function(x) x)()
-------------------------------------------------
During evaluation

View File

@ -1 +1,10 @@
RUNTIME ERROR: function expected 1 argument(s), but got 2
-------------------------------------------------
testdata/bad_function_call2:1:1-22 $
(function(x) x)(1, 2)
-------------------------------------------------
During evaluation

View File

@ -1 +1,10 @@
RUNTIME ERROR: function expected 1 argument(s), but got 2
-------------------------------------------------
testdata/bad_function_call_and_error:1:1-38 $
(function(x) x)(error "x", error "y")
-------------------------------------------------
During evaluation

View File

@ -1 +1,15 @@
RUNTIME ERROR: x
-------------------------------------------------
testdata/bitwise_and4:1:5-13 $
1 & error "x"
-------------------------------------------------
testdata/bitwise_and4:1:1-13 $
1 & error "x"
-------------------------------------------------
During evaluation

View File

@ -1 +1,15 @@
RUNTIME ERROR: x
-------------------------------------------------
testdata/bitwise_xor7:1:5-13 $
1 ^ error "x"
-------------------------------------------------
testdata/bitwise_xor7:1:1-13 $
1 ^ error "x"
-------------------------------------------------
During evaluation

1
testdata/block_string.golden vendored Normal file
View File

@ -0,0 +1 @@
"x\n\ty\nz\n"

5
testdata/block_string.jsonnet vendored Normal file
View File

@ -0,0 +1,5 @@
|||
x
y
z
|||

View File

@ -1 +1,8 @@
RUNTIME ERROR: Codepoints must be >= 0, got -1
-------------------------------------------------
<builtin> builtin function <char>
-------------------------------------------------
During evaluation

View File

@ -1 +1,8 @@
RUNTIME ERROR: Invalid unicode codepoint, got 1.114112e+06
-------------------------------------------------
<builtin> builtin function <char>
-------------------------------------------------
During evaluation

View File

@ -1 +1,8 @@
RUNTIME ERROR: Unexpected type number, expected string
-------------------------------------------------
<builtin> builtin function <objectHasEx>
-------------------------------------------------
During evaluation

View File

@ -1 +1,8 @@
RUNTIME ERROR: Unexpected type number, expected object
-------------------------------------------------
<builtin> builtin function <objectHasEx>
-------------------------------------------------
During evaluation

View File

@ -1 +1,8 @@
RUNTIME ERROR: Overflow
-------------------------------------------------
<builtin> builtin function <exp>
-------------------------------------------------
During evaluation

View File

@ -1 +1,8 @@
RUNTIME ERROR: Overflow
-------------------------------------------------
<builtin> builtin function <exp>
-------------------------------------------------
During evaluation

View File

@ -1 +1,8 @@
RUNTIME ERROR: Overflow
-------------------------------------------------
<builtin> builtin function <log>
-------------------------------------------------
During evaluation

View File

@ -1 +1,8 @@
RUNTIME ERROR: Not a number
-------------------------------------------------
<builtin> builtin function <log>
-------------------------------------------------
During evaluation

View File

@ -1 +1,8 @@
RUNTIME ERROR: Not a number
-------------------------------------------------
<builtin> builtin function <log>
-------------------------------------------------
During evaluation

View File

@ -1 +1,10 @@
RUNTIME ERROR: Unexpected type number, expected function
-------------------------------------------------
testdata/call_number:1:1-5 $
42()
-------------------------------------------------
During evaluation

View File

@ -1 +1,10 @@
RUNTIME ERROR: Overflow
-------------------------------------------------
testdata/div4:1:1-19 $
1/(1e-160)/(1e-160)
-------------------------------------------------
During evaluation

View File

@ -1 +1,10 @@
RUNTIME ERROR: Division by zero.
-------------------------------------------------
testdata/div_by_zero:1:1-4 $
1/0
-------------------------------------------------
During evaluation

20
testdata/double_thunk.golden vendored Normal file
View File

@ -0,0 +1,20 @@
RUNTIME ERROR: xxx
-------------------------------------------------
testdata/double_thunk:1:21-31 thunk <y> from <thunk <x> from <$>>
local x = local y = error "xxx"; y; x
-------------------------------------------------
testdata/double_thunk:1:34-35 thunk <x> from <$>
local x = local y = error "xxx"; y; x
-------------------------------------------------
testdata/double_thunk:1:37-38 $
local x = local y = error "xxx"; y; x
-------------------------------------------------
During evaluation

1
testdata/double_thunk.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
local x = local y = error "xxx"; y; x

View File

@ -1 +1,10 @@
RUNTIME ERROR: 42
-------------------------------------------------
testdata/error:1:1-10 $
error "42"
-------------------------------------------------
During evaluation

15
testdata/error_from_array.golden vendored Normal file
View File

@ -0,0 +1,15 @@
RUNTIME ERROR: xxx
-------------------------------------------------
testdata/error_from_array:1:2-12 thunk from <$>
[error "xxx"][0]
-------------------------------------------------
testdata/error_from_array:1:1-17 $
[error "xxx"][0]
-------------------------------------------------
During evaluation

1
testdata/error_from_array.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
[error "xxx"][0]

15
testdata/error_from_func.golden vendored Normal file
View File

@ -0,0 +1,15 @@
RUNTIME ERROR: xxx
-------------------------------------------------
testdata/error_from_func:1:25-32 function <foo>
local foo = function(x) error x; foo("xxx")
-------------------------------------------------
testdata/error_from_func:1:34-44 $
local foo = function(x) error x; foo("xxx")
-------------------------------------------------
During evaluation

1
testdata/error_from_func.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
local foo = function(x) error x; foo("xxx")

View File

@ -1 +1,5 @@
testdata/error_hexnumber:1:2-5 Did not expect: (IDENTIFIER, "x42")
0x42

15
testdata/error_in_method.golden vendored Normal file
View File

@ -0,0 +1,15 @@
RUNTIME ERROR: xxx
-------------------------------------------------
testdata/error_in_method:1:23-30 function <anonymous>
local obj = { foo(x): error x }; obj.foo("xxx")
-------------------------------------------------
testdata/error_in_method:1:34-48 $
local obj = { foo(x): error x }; obj.foo("xxx")
-------------------------------------------------
During evaluation

4
testdata/error_in_method.jsonnet vendored Normal file
View File

@ -0,0 +1,4 @@
local obj = { foo(x): error x }; obj.foo("xxx")

15
testdata/error_in_object_local.golden vendored Normal file
View File

@ -0,0 +1,15 @@
RUNTIME ERROR: xxx
-------------------------------------------------
testdata/error_in_object_local:1:20-29 function <anonymous>
{ local foo(bar) = error bar, baz: foo("xxx") }
-------------------------------------------------
testdata/error_in_object_local:1:36-46 object <anonymous>
{ local foo(bar) = error bar, baz: foo("xxx") }
-------------------------------------------------
During manifestation

View File

@ -0,0 +1 @@
{ local foo(bar) = error bar, baz: foo("xxx") }

View File

@ -1 +1,13 @@
RUNTIME ERROR: xxx
-------------------------------------------------
<extvar:errorVar>:1:1-11 $
error 'xxx'
-------------------------------------------------
<builtin> builtin function <extVar>
-------------------------------------------------
During evaluation

View File

@ -1 +1,5 @@
<extvar:UndeclaredX>:1:1-2 Unknown variable: x
x

View File

@ -1 +1,5 @@
<extvar:staticErrorVar>:1:1-2 Unexpected: (")", ")") while parsing terminal
)

View File

@ -1 +1,8 @@
RUNTIME ERROR: Undefined external variable: UNKNOWN
-------------------------------------------------
<builtin> builtin function <extVar>
-------------------------------------------------
During evaluation

View File

@ -1 +1,10 @@
RUNTIME ERROR: Field name must be string, got number
-------------------------------------------------
testdata/fieldname_not_string:1:1-15 $
{ [1234]: 42 }
-------------------------------------------------
During evaluation

View File

@ -1 +1,5 @@
RUNTIME ERROR: Couldn't manifest function in JSON output.
-------------------------------------------------
During manifestation

View File

@ -1 +1,7 @@
testdata/import_block_literal:(1:8)-(3:4) Block string literals not allowed in imports
import |||
block_literals_for_imports_are_not_allowed_and_make_exactly_zero_sense
|||

View File

@ -1 +1,5 @@
testdata/import_computed:1:9-16 Computed imports are not allowed
import "a" + "b"

View File

@ -1 +1,10 @@
RUNTIME ERROR: read testdata: is a directory
-------------------------------------------------
testdata/import_failure_directory:3:1-10 $
import '.'
-------------------------------------------------
During evaluation

View File

@ -1 +1,5 @@
testdata/".jsonnet:2:1 Unexpected end of file.

View File

@ -1 +1,7 @@
testdata/importstr_block_literal:(1:11)-(3:4) Block string literals not allowed in imports
importstr |||
block_literals_for_imports_are_not_allowed_and_make_exactly_zero_sense
|||

View File

@ -1 +1,5 @@
testdata/importstr_computed:1:12-19 Computed imports are not allowed
importstr "a" + "b"

View File

@ -1 +1,5 @@
testdata/insuper2:1:11-13 Expected token OPERATOR but got (in, "in")
{ } { "x" in super }

View File

@ -1 +1,5 @@
testdata/insuper4:1:2-13 Can't use super outside of an object.
"x" in super

View File

@ -1 +1,5 @@
testdata/insuper6:1:10-20 Unknown variable: undeclared
{ assert undeclared in super }

View File

@ -1 +1,15 @@
RUNTIME ERROR: should happen
-------------------------------------------------
testdata/lazy_operator2:1:9-29 $
true && error "should happen"
-------------------------------------------------
testdata/lazy_operator2:1:1-29 $
true && error "should happen"
-------------------------------------------------
During evaluation

View File

@ -1 +1,10 @@
RUNTIME ERROR: Couldn't open import "no chance a file with this name exists": No match locally or in the Jsonnet library paths.
-------------------------------------------------
testdata/nonexistent_import:1:1-50 $
importstr 'no chance a file with this name exists'
-------------------------------------------------
During evaluation

View File

@ -1 +1,10 @@
RUNTIME ERROR: Couldn't open import "ąęółńśćźż \" ' \n\n\t\t": No match locally or in the Jsonnet library paths.
-------------------------------------------------
testdata/nonexistent_import_crazy:1:1-45 $
importstr "ąęółńśćźż \" \' \n\n\t\t"
-------------------------------------------------
During evaluation

View File

@ -1 +1,5 @@
testdata/number_leading_zero:1:2-4 Did not expect: (NUMBER, "42")
042

View File

@ -1 +1,5 @@
testdata/object_comp_assert:1:32-35 Object comprehension cannot have asserts.
{ assert self.x == 5, ["x"]: 5 for i in [42] }

View File

@ -1 +1,5 @@
testdata/object_comp_bad_field:1:9-12 Object comprehensions can only have [e] fields.
{ x: 42 for _ in [1] }

View File

@ -1 +1,5 @@
testdata/object_comp_bad_field2:1:11-14 Object comprehensions can only have [e] fields.
{ "x": 42 for _ in [1] }

View File

@ -1 +1,8 @@
RUNTIME ERROR: Duplicate field name: "x"
-------------------------------------------------
<builtin> builtin function <$objectFlatMerge>
-------------------------------------------------
During evaluation

View File

@ -1 +1,10 @@
RUNTIME ERROR: xxx
-------------------------------------------------
testdata/object_comp_err_elem:1:11-21 object <anonymous>
{ ["x"]: error "xxx" for x in [1] }
-------------------------------------------------
During manifestation

View File

@ -1 +1,13 @@
RUNTIME ERROR: xxx
-------------------------------------------------
testdata/object_comp_err_index:1:4-14 $
{ [error "xxx"]: 42 for x in [1] }
-------------------------------------------------
<builtin> builtin function <$objectFlatMerge>
-------------------------------------------------
During evaluation

View File

@ -1 +1,5 @@
testdata/object_comp_illegal:1:15-18 Object comprehension can only have one field.
{ local x = 5 for y in [1, 2, 3] }

View File

@ -1 +1,13 @@
RUNTIME ERROR: Field name must be string, got number
-------------------------------------------------
testdata/object_comp_int_index:1:1-30 $
{ [x]: x for x in [1, 2, 3] }
-------------------------------------------------
<builtin> builtin function <$objectFlatMerge>
-------------------------------------------------
During evaluation

View File

@ -1 +1,8 @@
RUNTIME ERROR: Object assertion failed.
-------------------------------------------------
-------------------------------------------------
During manifestation

View File

@ -1 +1,13 @@
RUNTIME ERROR: Object assertion failed.
-------------------------------------------------
-------------------------------------------------
testdata/object_invariant11:1:1-19 $
{ assert false }.x
-------------------------------------------------
During evaluation

View File

@ -1 +1,10 @@
RUNTIME ERROR: x
-------------------------------------------------
testdata/object_invariant13:1:10-18 object <anonymous>
{ assert error "x" }
-------------------------------------------------
During manifestation

View File

@ -1 +1,8 @@
RUNTIME ERROR: xxx
-------------------------------------------------
-------------------------------------------------
During manifestation

View File

@ -1 +1,8 @@
RUNTIME ERROR: Object assertion failed.
-------------------------------------------------
-------------------------------------------------
During manifestation

View File

@ -1 +1,34 @@
RUNTIME ERROR: Field does not exist: x
-------------------------------------------------
testdata/object_invariant7:1:16-21 object <anonymous>
{ x: 5, assert super.x == 5 }
-------------------------------------------------
<std>:969:29-30 thunk from <thunk <ta> from <function <anonymous>>>
local ta = std.type(a);
-------------------------------------------------
<builtin> builtin function <type>
-------------------------------------------------
<std>:971:33-35 thunk from <function <anonymous>>
if !std.primitiveEquals(ta, tb) then
-------------------------------------------------
<builtin> builtin function <primitiveEquals>
-------------------------------------------------
<std>:971:12-40 function <anonymous>
if !std.primitiveEquals(ta, tb) then
-------------------------------------------------
-------------------------------------------------
During manifestation

View File

@ -1 +1,8 @@
RUNTIME ERROR: Object assertion failed.
-------------------------------------------------
-------------------------------------------------
During manifestation

View File

@ -1 +1,8 @@
RUNTIME ERROR: Object assertion failed.
-------------------------------------------------
-------------------------------------------------
During manifestation

View File

@ -1 +1,8 @@
RUNTIME ERROR: Object assertion failed.
-------------------------------------------------
-------------------------------------------------
During manifestation

View File

@ -1 +1,8 @@
RUNTIME ERROR: Object assertion failed.
-------------------------------------------------
-------------------------------------------------
During manifestation

View File

@ -1 +1,8 @@
RUNTIME ERROR: yyy
-------------------------------------------------
-------------------------------------------------
During manifestation

3
testdata/object_local.golden vendored Normal file
View File

@ -0,0 +1,3 @@
{
"baz": 42
}

2
testdata/object_local.jsonnet vendored Normal file
View File

@ -0,0 +1,2 @@
{ local foo(bar) = bar, baz: foo(42) }

Some files were not shown because too many files have changed in this diff Show More