mirror of
https://github.com/google/go-jsonnet.git
synced 2025-08-07 14:57:24 +02:00
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:
parent
a1b8248e84
commit
c3459153df
30
ast/ast.go
30
ast/ast.go
@ -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
|
||||
|
123
ast/location.go
123
ast/location.go
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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}
|
||||
}
|
||||
|
32
desugarer.go
32
desugarer.go
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
|
21
main_test.go
21
main_test.go
@ -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
371
parser/context.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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())
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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}
|
||||
}
|
||||
|
@ -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
4
testdata/".golden
vendored
@ -1 +1,5 @@
|
||||
testdata/":2:1 Unexpected end of file.
|
||||
|
||||
|
||||
|
||||
|
||||
|
4
testdata/'.golden
vendored
4
testdata/'.golden
vendored
@ -1 +1,5 @@
|
||||
testdata/':2:1 Unexpected end of file.
|
||||
|
||||
|
||||
|
||||
|
||||
|
4
testdata/arrcomp5.golden
vendored
4
testdata/arrcomp5.golden
vendored
@ -1 +1,5 @@
|
||||
testdata/arrcomp5:1:14-15 Unknown variable: x
|
||||
|
||||
[y for y in [x + 1] for x in [1, 2, 3]]
|
||||
|
||||
|
||||
|
4
testdata/arrcomp_if4.golden
vendored
4
testdata/arrcomp_if4.golden
vendored
@ -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]]
|
||||
|
||||
|
||||
|
12
testdata/arrcomp_if6.golden
vendored
12
testdata/arrcomp_if6.golden
vendored
@ -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
|
||||
|
||||
|
||||
|
10
testdata/arrcomp_if7.golden
vendored
10
testdata/arrcomp_if7.golden
vendored
@ -1 +1,11 @@
|
||||
RUNTIME ERROR: Unexpected type number, expected boolean
|
||||
-------------------------------------------------
|
||||
|
||||
|
||||
-------------------------------------------------
|
||||
<builtin> builtin function <flatMap>
|
||||
|
||||
-------------------------------------------------
|
||||
During evaluation
|
||||
|
||||
|
||||
|
7
testdata/assert3.golden
vendored
7
testdata/assert3.golden
vendored
@ -1 +1,8 @@
|
||||
RUNTIME ERROR: Assertion failed
|
||||
-------------------------------------------------
|
||||
|
||||
|
||||
-------------------------------------------------
|
||||
During evaluation
|
||||
|
||||
|
||||
|
14
testdata/assert_equal4.golden
vendored
14
testdata/assert_equal4.golden
vendored
@ -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
17
testdata/assert_equal5.golden
vendored
Normal 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
1
testdata/assert_equal5.jsonnet
vendored
Normal file
@ -0,0 +1 @@
|
||||
std.assertEqual("\n ", "\n")
|
15
testdata/assert_equal6.golden
vendored
Normal file
15
testdata/assert_equal6.golden
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
RUNTIME ERROR: Assertion failed. [31m !=
|
||||
-------------------------------------------------
|
||||
<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
1
testdata/assert_equal6.jsonnet
vendored
Normal file
@ -0,0 +1 @@
|
||||
std.assertEqual("\u001b[31m", "")
|
7
testdata/assert_failed.golden
vendored
7
testdata/assert_failed.golden
vendored
@ -1 +1,8 @@
|
||||
RUNTIME ERROR: Assertion failed
|
||||
-------------------------------------------------
|
||||
|
||||
|
||||
-------------------------------------------------
|
||||
During evaluation
|
||||
|
||||
|
||||
|
7
testdata/assert_failed_custom.golden
vendored
7
testdata/assert_failed_custom.golden
vendored
@ -1 +1,8 @@
|
||||
RUNTIME ERROR: Custom Message
|
||||
-------------------------------------------------
|
||||
|
||||
|
||||
-------------------------------------------------
|
||||
During evaluation
|
||||
|
||||
|
||||
|
9
testdata/bad_function_call.golden
vendored
9
testdata/bad_function_call.golden
vendored
@ -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
|
||||
|
||||
|
||||
|
9
testdata/bad_function_call2.golden
vendored
9
testdata/bad_function_call2.golden
vendored
@ -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
|
||||
|
||||
|
||||
|
9
testdata/bad_function_call_and_error.golden
vendored
9
testdata/bad_function_call_and_error.golden
vendored
@ -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
|
||||
|
||||
|
||||
|
14
testdata/bitwise_and4.golden
vendored
14
testdata/bitwise_and4.golden
vendored
@ -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
|
||||
|
||||
|
||||
|
14
testdata/bitwise_xor7.golden
vendored
14
testdata/bitwise_xor7.golden
vendored
@ -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
1
testdata/block_string.golden
vendored
Normal file
@ -0,0 +1 @@
|
||||
"x\n\ty\nz\n"
|
5
testdata/block_string.jsonnet
vendored
Normal file
5
testdata/block_string.jsonnet
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
|||
|
||||
x
|
||||
y
|
||||
z
|
||||
|||
|
7
testdata/builtinChar3.golden
vendored
7
testdata/builtinChar3.golden
vendored
@ -1 +1,8 @@
|
||||
RUNTIME ERROR: Codepoints must be >= 0, got -1
|
||||
-------------------------------------------------
|
||||
<builtin> builtin function <char>
|
||||
|
||||
-------------------------------------------------
|
||||
During evaluation
|
||||
|
||||
|
||||
|
7
testdata/builtinChar5.golden
vendored
7
testdata/builtinChar5.golden
vendored
@ -1 +1,8 @@
|
||||
RUNTIME ERROR: Invalid unicode codepoint, got 1.114112e+06
|
||||
-------------------------------------------------
|
||||
<builtin> builtin function <char>
|
||||
|
||||
-------------------------------------------------
|
||||
During evaluation
|
||||
|
||||
|
||||
|
7
testdata/builtinObjectHasExBadField.golden
vendored
7
testdata/builtinObjectHasExBadField.golden
vendored
@ -1 +1,8 @@
|
||||
RUNTIME ERROR: Unexpected type number, expected string
|
||||
-------------------------------------------------
|
||||
<builtin> builtin function <objectHasEx>
|
||||
|
||||
-------------------------------------------------
|
||||
During evaluation
|
||||
|
||||
|
||||
|
7
testdata/builtinObjectHasExBadObject.golden
vendored
7
testdata/builtinObjectHasExBadObject.golden
vendored
@ -1 +1,8 @@
|
||||
RUNTIME ERROR: Unexpected type number, expected object
|
||||
-------------------------------------------------
|
||||
<builtin> builtin function <objectHasEx>
|
||||
|
||||
-------------------------------------------------
|
||||
During evaluation
|
||||
|
||||
|
||||
|
7
testdata/builtin_exp3.golden
vendored
7
testdata/builtin_exp3.golden
vendored
@ -1 +1,8 @@
|
||||
RUNTIME ERROR: Overflow
|
||||
-------------------------------------------------
|
||||
<builtin> builtin function <exp>
|
||||
|
||||
-------------------------------------------------
|
||||
During evaluation
|
||||
|
||||
|
||||
|
7
testdata/builtin_exp5.golden
vendored
7
testdata/builtin_exp5.golden
vendored
@ -1 +1,8 @@
|
||||
RUNTIME ERROR: Overflow
|
||||
-------------------------------------------------
|
||||
<builtin> builtin function <exp>
|
||||
|
||||
-------------------------------------------------
|
||||
During evaluation
|
||||
|
||||
|
||||
|
7
testdata/builtin_log5.golden
vendored
7
testdata/builtin_log5.golden
vendored
@ -1 +1,8 @@
|
||||
RUNTIME ERROR: Overflow
|
||||
-------------------------------------------------
|
||||
<builtin> builtin function <log>
|
||||
|
||||
-------------------------------------------------
|
||||
During evaluation
|
||||
|
||||
|
||||
|
7
testdata/builtin_log7.golden
vendored
7
testdata/builtin_log7.golden
vendored
@ -1 +1,8 @@
|
||||
RUNTIME ERROR: Not a number
|
||||
-------------------------------------------------
|
||||
<builtin> builtin function <log>
|
||||
|
||||
-------------------------------------------------
|
||||
During evaluation
|
||||
|
||||
|
||||
|
7
testdata/builtin_log8.golden
vendored
7
testdata/builtin_log8.golden
vendored
@ -1 +1,8 @@
|
||||
RUNTIME ERROR: Not a number
|
||||
-------------------------------------------------
|
||||
<builtin> builtin function <log>
|
||||
|
||||
-------------------------------------------------
|
||||
During evaluation
|
||||
|
||||
|
||||
|
9
testdata/call_number.golden
vendored
9
testdata/call_number.golden
vendored
@ -1 +1,10 @@
|
||||
RUNTIME ERROR: Unexpected type number, expected function
|
||||
-------------------------------------------------
|
||||
testdata/call_number:1:1-5 $
|
||||
|
||||
42()
|
||||
|
||||
-------------------------------------------------
|
||||
During evaluation
|
||||
|
||||
|
||||
|
9
testdata/div4.golden
vendored
9
testdata/div4.golden
vendored
@ -1 +1,10 @@
|
||||
RUNTIME ERROR: Overflow
|
||||
-------------------------------------------------
|
||||
testdata/div4:1:1-19 $
|
||||
|
||||
1/(1e-160)/(1e-160)
|
||||
|
||||
-------------------------------------------------
|
||||
During evaluation
|
||||
|
||||
|
||||
|
9
testdata/div_by_zero.golden
vendored
9
testdata/div_by_zero.golden
vendored
@ -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
20
testdata/double_thunk.golden
vendored
Normal 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
1
testdata/double_thunk.jsonnet
vendored
Normal file
@ -0,0 +1 @@
|
||||
local x = local y = error "xxx"; y; x
|
9
testdata/error.golden
vendored
9
testdata/error.golden
vendored
@ -1 +1,10 @@
|
||||
RUNTIME ERROR: 42
|
||||
-------------------------------------------------
|
||||
testdata/error:1:1-10 $
|
||||
|
||||
error "42"
|
||||
|
||||
-------------------------------------------------
|
||||
During evaluation
|
||||
|
||||
|
||||
|
15
testdata/error_from_array.golden
vendored
Normal file
15
testdata/error_from_array.golden
vendored
Normal 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
1
testdata/error_from_array.jsonnet
vendored
Normal file
@ -0,0 +1 @@
|
||||
[error "xxx"][0]
|
15
testdata/error_from_func.golden
vendored
Normal file
15
testdata/error_from_func.golden
vendored
Normal 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
1
testdata/error_from_func.jsonnet
vendored
Normal file
@ -0,0 +1 @@
|
||||
local foo = function(x) error x; foo("xxx")
|
4
testdata/error_hexnumber.golden
vendored
4
testdata/error_hexnumber.golden
vendored
@ -1 +1,5 @@
|
||||
testdata/error_hexnumber:1:2-5 Did not expect: (IDENTIFIER, "x42")
|
||||
|
||||
0x42
|
||||
|
||||
|
||||
|
15
testdata/error_in_method.golden
vendored
Normal file
15
testdata/error_in_method.golden
vendored
Normal 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
4
testdata/error_in_method.jsonnet
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
local obj = { foo(x): error x }; obj.foo("xxx")
|
||||
|
||||
|
||||
|
15
testdata/error_in_object_local.golden
vendored
Normal file
15
testdata/error_in_object_local.golden
vendored
Normal 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
|
||||
|
||||
|
1
testdata/error_in_object_local.jsonnet
vendored
Normal file
1
testdata/error_in_object_local.jsonnet
vendored
Normal file
@ -0,0 +1 @@
|
||||
{ local foo(bar) = error bar, baz: foo("xxx") }
|
12
testdata/extvar_error.golden
vendored
12
testdata/extvar_error.golden
vendored
@ -1 +1,13 @@
|
||||
RUNTIME ERROR: xxx
|
||||
-------------------------------------------------
|
||||
<extvar:errorVar>:1:1-11 $
|
||||
|
||||
error 'xxx'
|
||||
|
||||
-------------------------------------------------
|
||||
<builtin> builtin function <extVar>
|
||||
|
||||
-------------------------------------------------
|
||||
During evaluation
|
||||
|
||||
|
||||
|
4
testdata/extvar_hermetic.golden
vendored
4
testdata/extvar_hermetic.golden
vendored
@ -1 +1,5 @@
|
||||
<extvar:UndeclaredX>:1:1-2 Unknown variable: x
|
||||
|
||||
x
|
||||
|
||||
|
||||
|
4
testdata/extvar_static_error.golden
vendored
4
testdata/extvar_static_error.golden
vendored
@ -1 +1,5 @@
|
||||
<extvar:staticErrorVar>:1:1-2 Unexpected: (")", ")") while parsing terminal
|
||||
|
||||
)
|
||||
|
||||
|
||||
|
7
testdata/extvar_unknown.golden
vendored
7
testdata/extvar_unknown.golden
vendored
@ -1 +1,8 @@
|
||||
RUNTIME ERROR: Undefined external variable: UNKNOWN
|
||||
-------------------------------------------------
|
||||
<builtin> builtin function <extVar>
|
||||
|
||||
-------------------------------------------------
|
||||
During evaluation
|
||||
|
||||
|
||||
|
9
testdata/fieldname_not_string.golden
vendored
9
testdata/fieldname_not_string.golden
vendored
@ -1 +1,10 @@
|
||||
RUNTIME ERROR: Field name must be string, got number
|
||||
-------------------------------------------------
|
||||
testdata/fieldname_not_string:1:1-15 $
|
||||
|
||||
{ [1234]: 42 }
|
||||
|
||||
-------------------------------------------------
|
||||
During evaluation
|
||||
|
||||
|
||||
|
4
testdata/function.golden
vendored
4
testdata/function.golden
vendored
@ -1 +1,5 @@
|
||||
RUNTIME ERROR: Couldn't manifest function in JSON output.
|
||||
-------------------------------------------------
|
||||
During manifestation
|
||||
|
||||
|
||||
|
6
testdata/import_block_literal.golden
vendored
6
testdata/import_block_literal.golden
vendored
@ -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
|
||||
|||
|
||||
|
||||
|
||||
|
4
testdata/import_computed.golden
vendored
4
testdata/import_computed.golden
vendored
@ -1 +1,5 @@
|
||||
testdata/import_computed:1:9-16 Computed imports are not allowed
|
||||
|
||||
import "a" + "b"
|
||||
|
||||
|
||||
|
9
testdata/import_failure_directory.golden
vendored
9
testdata/import_failure_directory.golden
vendored
@ -1 +1,10 @@
|
||||
RUNTIME ERROR: read testdata: is a directory
|
||||
-------------------------------------------------
|
||||
testdata/import_failure_directory:3:1-10 $
|
||||
|
||||
import '.'
|
||||
|
||||
-------------------------------------------------
|
||||
During evaluation
|
||||
|
||||
|
||||
|
@ -1 +1,5 @@
|
||||
testdata/".jsonnet:2:1 Unexpected end of file.
|
||||
|
||||
|
||||
|
||||
|
||||
|
6
testdata/importstr_block_literal.golden
vendored
6
testdata/importstr_block_literal.golden
vendored
@ -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
|
||||
|||
|
||||
|
||||
|
||||
|
4
testdata/importstr_computed.golden
vendored
4
testdata/importstr_computed.golden
vendored
@ -1 +1,5 @@
|
||||
testdata/importstr_computed:1:12-19 Computed imports are not allowed
|
||||
|
||||
importstr "a" + "b"
|
||||
|
||||
|
||||
|
4
testdata/insuper2.golden
vendored
4
testdata/insuper2.golden
vendored
@ -1 +1,5 @@
|
||||
testdata/insuper2:1:11-13 Expected token OPERATOR but got (in, "in")
|
||||
|
||||
{ } { "x" in super }
|
||||
|
||||
|
||||
|
4
testdata/insuper4.golden
vendored
4
testdata/insuper4.golden
vendored
@ -1 +1,5 @@
|
||||
testdata/insuper4:1:2-13 Can't use super outside of an object.
|
||||
|
||||
"x" in super
|
||||
|
||||
|
||||
|
4
testdata/insuper6.golden
vendored
4
testdata/insuper6.golden
vendored
@ -1 +1,5 @@
|
||||
testdata/insuper6:1:10-20 Unknown variable: undeclared
|
||||
|
||||
{ assert undeclared in super }
|
||||
|
||||
|
||||
|
14
testdata/lazy_operator2.golden
vendored
14
testdata/lazy_operator2.golden
vendored
@ -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
|
||||
|
||||
|
||||
|
9
testdata/nonexistent_import.golden
vendored
9
testdata/nonexistent_import.golden
vendored
@ -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
|
||||
|
||||
|
||||
|
9
testdata/nonexistent_import_crazy.golden
vendored
9
testdata/nonexistent_import_crazy.golden
vendored
@ -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
|
||||
|
||||
|
||||
|
4
testdata/number_leading_zero.golden
vendored
4
testdata/number_leading_zero.golden
vendored
@ -1 +1,5 @@
|
||||
testdata/number_leading_zero:1:2-4 Did not expect: (NUMBER, "42")
|
||||
|
||||
042
|
||||
|
||||
|
||||
|
4
testdata/object_comp_assert.golden
vendored
4
testdata/object_comp_assert.golden
vendored
@ -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] }
|
||||
|
||||
|
||||
|
4
testdata/object_comp_bad_field.golden
vendored
4
testdata/object_comp_bad_field.golden
vendored
@ -1 +1,5 @@
|
||||
testdata/object_comp_bad_field:1:9-12 Object comprehensions can only have [e] fields.
|
||||
|
||||
{ x: 42 for _ in [1] }
|
||||
|
||||
|
||||
|
4
testdata/object_comp_bad_field2.golden
vendored
4
testdata/object_comp_bad_field2.golden
vendored
@ -1 +1,5 @@
|
||||
testdata/object_comp_bad_field2:1:11-14 Object comprehensions can only have [e] fields.
|
||||
|
||||
{ "x": 42 for _ in [1] }
|
||||
|
||||
|
||||
|
7
testdata/object_comp_duplicate.golden
vendored
7
testdata/object_comp_duplicate.golden
vendored
@ -1 +1,8 @@
|
||||
RUNTIME ERROR: Duplicate field name: "x"
|
||||
-------------------------------------------------
|
||||
<builtin> builtin function <$objectFlatMerge>
|
||||
|
||||
-------------------------------------------------
|
||||
During evaluation
|
||||
|
||||
|
||||
|
9
testdata/object_comp_err_elem.golden
vendored
9
testdata/object_comp_err_elem.golden
vendored
@ -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
|
||||
|
||||
|
||||
|
12
testdata/object_comp_err_index.golden
vendored
12
testdata/object_comp_err_index.golden
vendored
@ -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
|
||||
|
||||
|
||||
|
4
testdata/object_comp_illegal.golden
vendored
4
testdata/object_comp_illegal.golden
vendored
@ -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] }
|
||||
|
||||
|
||||
|
12
testdata/object_comp_int_index.golden
vendored
12
testdata/object_comp_int_index.golden
vendored
@ -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
|
||||
|
||||
|
||||
|
7
testdata/object_invariant10.golden
vendored
7
testdata/object_invariant10.golden
vendored
@ -1 +1,8 @@
|
||||
RUNTIME ERROR: Object assertion failed.
|
||||
-------------------------------------------------
|
||||
|
||||
|
||||
-------------------------------------------------
|
||||
During manifestation
|
||||
|
||||
|
||||
|
12
testdata/object_invariant11.golden
vendored
12
testdata/object_invariant11.golden
vendored
@ -1 +1,13 @@
|
||||
RUNTIME ERROR: Object assertion failed.
|
||||
-------------------------------------------------
|
||||
|
||||
|
||||
-------------------------------------------------
|
||||
testdata/object_invariant11:1:1-19 $
|
||||
|
||||
{ assert false }.x
|
||||
|
||||
-------------------------------------------------
|
||||
During evaluation
|
||||
|
||||
|
||||
|
9
testdata/object_invariant13.golden
vendored
9
testdata/object_invariant13.golden
vendored
@ -1 +1,10 @@
|
||||
RUNTIME ERROR: x
|
||||
-------------------------------------------------
|
||||
testdata/object_invariant13:1:10-18 object <anonymous>
|
||||
|
||||
{ assert error "x" }
|
||||
|
||||
-------------------------------------------------
|
||||
During manifestation
|
||||
|
||||
|
||||
|
7
testdata/object_invariant14.golden
vendored
7
testdata/object_invariant14.golden
vendored
@ -1 +1,8 @@
|
||||
RUNTIME ERROR: xxx
|
||||
-------------------------------------------------
|
||||
|
||||
|
||||
-------------------------------------------------
|
||||
During manifestation
|
||||
|
||||
|
||||
|
7
testdata/object_invariant2.golden
vendored
7
testdata/object_invariant2.golden
vendored
@ -1 +1,8 @@
|
||||
RUNTIME ERROR: Object assertion failed.
|
||||
-------------------------------------------------
|
||||
|
||||
|
||||
-------------------------------------------------
|
||||
During manifestation
|
||||
|
||||
|
||||
|
33
testdata/object_invariant7.golden
vendored
33
testdata/object_invariant7.golden
vendored
@ -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
|
||||
|
||||
|
||||
|
7
testdata/object_invariant8.golden
vendored
7
testdata/object_invariant8.golden
vendored
@ -1 +1,8 @@
|
||||
RUNTIME ERROR: Object assertion failed.
|
||||
-------------------------------------------------
|
||||
|
||||
|
||||
-------------------------------------------------
|
||||
During manifestation
|
||||
|
||||
|
||||
|
7
testdata/object_invariant9.golden
vendored
7
testdata/object_invariant9.golden
vendored
@ -1 +1,8 @@
|
||||
RUNTIME ERROR: Object assertion failed.
|
||||
-------------------------------------------------
|
||||
|
||||
|
||||
-------------------------------------------------
|
||||
During manifestation
|
||||
|
||||
|
||||
|
7
testdata/object_invariant_plus.golden
vendored
7
testdata/object_invariant_plus.golden
vendored
@ -1 +1,8 @@
|
||||
RUNTIME ERROR: Object assertion failed.
|
||||
-------------------------------------------------
|
||||
|
||||
|
||||
-------------------------------------------------
|
||||
During manifestation
|
||||
|
||||
|
||||
|
7
testdata/object_invariant_plus2.golden
vendored
7
testdata/object_invariant_plus2.golden
vendored
@ -1 +1,8 @@
|
||||
RUNTIME ERROR: Object assertion failed.
|
||||
-------------------------------------------------
|
||||
|
||||
|
||||
-------------------------------------------------
|
||||
During manifestation
|
||||
|
||||
|
||||
|
7
testdata/object_invariant_plus6.golden
vendored
7
testdata/object_invariant_plus6.golden
vendored
@ -1 +1,8 @@
|
||||
RUNTIME ERROR: yyy
|
||||
-------------------------------------------------
|
||||
|
||||
|
||||
-------------------------------------------------
|
||||
During manifestation
|
||||
|
||||
|
||||
|
3
testdata/object_local.golden
vendored
Normal file
3
testdata/object_local.golden
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"baz": 42
|
||||
}
|
2
testdata/object_local.jsonnet
vendored
Normal file
2
testdata/object_local.jsonnet
vendored
Normal 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
Loading…
Reference in New Issue
Block a user