mirror of
https://github.com/google/go-jsonnet.git
synced 2025-08-07 23:07:14 +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
24
ast/ast.go
24
ast/ast.go
@ -30,10 +30,14 @@ type Identifiers []Identifier
|
|||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type Context *string
|
||||||
|
|
||||||
type Node interface {
|
type Node interface {
|
||||||
|
Context() Context
|
||||||
Loc() *LocationRange
|
Loc() *LocationRange
|
||||||
FreeVariables() Identifiers
|
FreeVariables() Identifiers
|
||||||
SetFreeVariables(Identifiers)
|
SetFreeVariables(Identifiers)
|
||||||
|
SetContext(Context)
|
||||||
}
|
}
|
||||||
type Nodes []Node
|
type Nodes []Node
|
||||||
|
|
||||||
@ -41,6 +45,7 @@ type Nodes []Node
|
|||||||
|
|
||||||
type NodeBase struct {
|
type NodeBase struct {
|
||||||
loc LocationRange
|
loc LocationRange
|
||||||
|
context Context
|
||||||
freeVariables Identifiers
|
freeVariables Identifiers
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,6 +75,14 @@ func (n *NodeBase) SetFreeVariables(idents Identifiers) {
|
|||||||
n.freeVariables = idents
|
n.freeVariables = idents
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *NodeBase) Context() Context {
|
||||||
|
return n.context
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NodeBase) SetContext(context Context) {
|
||||||
|
n.context = context
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
type IfSpec struct {
|
type IfSpec struct {
|
||||||
@ -358,9 +371,7 @@ type Slice struct {
|
|||||||
type LocalBind struct {
|
type LocalBind struct {
|
||||||
Variable Identifier
|
Variable Identifier
|
||||||
Body Node
|
Body Node
|
||||||
FunctionSugar bool
|
Fun *Function
|
||||||
Params *Parameters // if functionSugar is true
|
|
||||||
TrailingComma bool
|
|
||||||
}
|
}
|
||||||
type LocalBinds []LocalBind
|
type LocalBinds []LocalBind
|
||||||
|
|
||||||
@ -439,6 +450,7 @@ type ObjectField struct {
|
|||||||
Hide ObjectFieldHide // (ignore if kind != astObjectField*)
|
Hide ObjectFieldHide // (ignore if kind != astObjectField*)
|
||||||
SuperSugar bool // +: (ignore if kind != astObjectField*)
|
SuperSugar bool // +: (ignore if kind != astObjectField*)
|
||||||
MethodSugar bool // f(x, y, z): ... (ignore if kind == astObjectAssert)
|
MethodSugar bool // f(x, y, z): ... (ignore if kind == astObjectAssert)
|
||||||
|
Method *Function
|
||||||
Expr1 Node // Not in scope of the object
|
Expr1 Node // Not in scope of the object
|
||||||
Id *Identifier
|
Id *Identifier
|
||||||
Params *Parameters // If methodSugar == true then holds the params.
|
Params *Parameters // If methodSugar == true then holds the params.
|
||||||
@ -448,12 +460,8 @@ type ObjectField struct {
|
|||||||
|
|
||||||
// TODO(jbeda): Add the remaining constructor helpers here
|
// 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 {
|
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
|
type ObjectFields []ObjectField
|
||||||
|
121
ast/location.go
121
ast/location.go
@ -16,7 +16,14 @@ limitations under the License.
|
|||||||
|
|
||||||
package ast
|
package ast
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Source struct {
|
||||||
|
lines []string
|
||||||
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
// Location
|
// Location
|
||||||
@ -24,6 +31,7 @@ import "fmt"
|
|||||||
// Location represents a single location in an (unspecified) file.
|
// Location represents a single location in an (unspecified) file.
|
||||||
type Location struct {
|
type Location struct {
|
||||||
Line int
|
Line int
|
||||||
|
// Column is a byte offset from the beginning of the line
|
||||||
Column int
|
Column int
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,6 +44,13 @@ func (l *Location) String() string {
|
|||||||
return fmt.Sprintf("%v:%v", l.Line, l.Column)
|
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
|
// LocationRange
|
||||||
|
|
||||||
@ -43,7 +58,15 @@ func (l *Location) String() string {
|
|||||||
type LocationRange struct {
|
type LocationRange struct {
|
||||||
FileName string
|
FileName string
|
||||||
Begin Location
|
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.
|
// 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())
|
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.
|
// This is useful for special locations, e.g. manifestation entry point.
|
||||||
func MakeLocationRangeMessage(msg string) LocationRange {
|
func MakeLocationRangeMessage(msg string) LocationRange {
|
||||||
return LocationRange{FileName: msg}
|
return LocationRange{FileName: msg}
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakeLocationRange(fn string, begin Location, end Location) LocationRange {
|
func MakeLocationRange(fn string, fc *Source, begin Location, end Location) LocationRange {
|
||||||
return LocationRange{FileName: fn, Begin: begin, End: end}
|
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 {
|
func getBuiltinEvaluator(e *evaluator, name ast.Identifier) *evaluator {
|
||||||
loc := ast.MakeLocationRangeMessage("<builtin>")
|
loc := ast.MakeLocationRangeMessage("<builtin>")
|
||||||
context := TraceContext{Name: "builtin function <" + string(name) + ">"}
|
context := "builtin function <" + string(name) + ">"
|
||||||
trace := TraceElement{loc: &loc, context: &context}
|
trace := TraceElement{loc: &loc, context: &context}
|
||||||
return &evaluator{i: e.i, trace: &trace}
|
return &evaluator{i: e.i, trace: &trace}
|
||||||
}
|
}
|
||||||
|
30
desugarer.go
30
desugarer.go
@ -128,22 +128,14 @@ func desugarFields(location ast.LocationRange, fields *ast.ObjectFields, objLeve
|
|||||||
field.Expr2 = assertion
|
field.Expr2 = assertion
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove methods
|
|
||||||
for i := range *fields {
|
for i := range *fields {
|
||||||
field := &((*fields)[i])
|
field := &((*fields)[i])
|
||||||
if !field.MethodSugar {
|
if field.Method == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
origBody := field.Expr2
|
field.Expr2 = field.Method
|
||||||
function := &ast.Function{
|
field.Method = nil
|
||||||
// TODO(sbarzowski) better location
|
// Body of the function already desugared through expr2
|
||||||
NodeBase: ast.NewNodeBaseLoc(*origBody.Loc()),
|
|
||||||
Parameters: *field.Params,
|
|
||||||
Body: origBody,
|
|
||||||
}
|
|
||||||
field.MethodSugar = false
|
|
||||||
field.Params = nil
|
|
||||||
field.Expr2 = function
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove object-level locals
|
// Remove object-level locals
|
||||||
@ -483,19 +475,11 @@ func desugar(astPtr *ast.Node, objLevel int) (err error) {
|
|||||||
|
|
||||||
case *ast.Local:
|
case *ast.Local:
|
||||||
for i := range node.Binds {
|
for i := range node.Binds {
|
||||||
if node.Binds[i].FunctionSugar {
|
if node.Binds[i].Fun != nil {
|
||||||
origBody := node.Binds[i].Body
|
|
||||||
function := &ast.Function{
|
|
||||||
// TODO(sbarzowski) better location
|
|
||||||
NodeBase: ast.NewNodeBaseLoc(*origBody.Loc()),
|
|
||||||
Parameters: *node.Binds[i].Params,
|
|
||||||
Body: origBody,
|
|
||||||
}
|
|
||||||
node.Binds[i] = ast.LocalBind{
|
node.Binds[i] = ast.LocalBind{
|
||||||
Variable: node.Binds[i].Variable,
|
Variable: node.Binds[i].Variable,
|
||||||
Body: function,
|
Body: node.Binds[i].Fun,
|
||||||
FunctionSugar: false,
|
Fun: nil,
|
||||||
Params: nil,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err = desugar(&node.Binds[i].Body, objLevel)
|
err = desugar(&node.Binds[i].Body, objLevel)
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/fatih/color"
|
||||||
"github.com/google/go-jsonnet/ast"
|
"github.com/google/go-jsonnet/ast"
|
||||||
"github.com/google/go-jsonnet/parser"
|
"github.com/google/go-jsonnet/parser"
|
||||||
)
|
)
|
||||||
@ -27,10 +28,13 @@ type ErrorFormatter struct {
|
|||||||
// TODO(sbarzowski) use this
|
// TODO(sbarzowski) use this
|
||||||
// MaxStackTraceSize is the maximum length of stack trace before cropping
|
// MaxStackTraceSize is the maximum length of stack trace before cropping
|
||||||
MaxStackTraceSize int
|
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
|
pretty bool
|
||||||
colorful bool
|
colorful bool
|
||||||
SP SourceProvider
|
SP *ast.SourceProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ef *ErrorFormatter) format(err error) string {
|
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 {
|
func (ef *ErrorFormatter) formatRuntime(err *RuntimeError) string {
|
||||||
return err.Error() + "\n" + ef.buildStackTrace(err.StackTrace)
|
return err.Error() + "\n" + ef.buildStackTrace(err.StackTrace)
|
||||||
// TODO(sbarzowski) pretty stuff
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ef *ErrorFormatter) formatStatic(err *parser.StaticError) string {
|
func (ef *ErrorFormatter) formatStatic(err *parser.StaticError) string {
|
||||||
return err.Error() + "\n"
|
var buf bytes.Buffer
|
||||||
// TODO(sbarzowski) pretty stuff
|
buf.WriteString(err.Error() + "\n")
|
||||||
|
ef.showCode(&buf, err.Loc)
|
||||||
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
const bugURL = "https://github.com/google/go-jsonnet/issues"
|
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"
|
"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 {
|
func (ef *ErrorFormatter) buildStackTrace(frames []TraceFrame) string {
|
||||||
// https://github.com/google/jsonnet/blob/master/core/libjsonnet.cpp#L594
|
// https://github.com/google/jsonnet/blob/master/core/libjsonnet.cpp#L594
|
||||||
var buf bytes.Buffer
|
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)
|
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) handle max stack trace size
|
||||||
// TODO(sbarzowski) I think the order of frames is reversed
|
// TODO(sbarzowski) I think the order of frames is reversed
|
||||||
}
|
}
|
||||||
return buf.String()
|
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) {
|
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) {
|
func (e *evaluator) evalInCleanEnv(env *environment, ast ast.Node) (value, error) {
|
||||||
return e.i.EvalInCleanEnv(e.trace, newContext, env, ast)
|
return e.i.EvalInCleanEnv(e.trace, env, ast)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *evaluator) lookUpVar(ident ast.Identifier) potentialValue {
|
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.
|
// The same thinking applies to external variables.
|
||||||
return makeErrorThunk(err)
|
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) {
|
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...
|
// This makes callFrame a misnomer as it is technically not always a call...
|
||||||
isCall bool
|
isCall bool
|
||||||
|
|
||||||
// Tracing information about the place where (TODO)
|
// Tracing information about the place where it was called from.
|
||||||
trace *TraceElement
|
trace *TraceElement
|
||||||
|
|
||||||
/** Reuse this stack frame for the purpose of tail call optimization. */
|
/** 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{
|
e := &evaluator{
|
||||||
trace: &TraceElement{
|
trace: &TraceElement{
|
||||||
loc: a.Loc(),
|
loc: a.Loc(),
|
||||||
context: context,
|
context: a.Context(),
|
||||||
},
|
},
|
||||||
i: i,
|
i: i,
|
||||||
}
|
}
|
||||||
@ -257,7 +257,7 @@ func (i *interpreter) evaluate(a ast.Node, context *TraceContext) (value, error)
|
|||||||
var elements []potentialValue
|
var elements []potentialValue
|
||||||
for _, el := range ast.Elements {
|
for _, el := range ast.Elements {
|
||||||
env := makeEnvironment(i.capture(el.FreeVariables()), sb)
|
env := makeEnvironment(i.capture(el.FreeVariables()), sb)
|
||||||
elThunk := makeThunk("array_element", env, el)
|
elThunk := makeThunk(env, el)
|
||||||
elements = append(elements, elThunk)
|
elements = append(elements, elThunk)
|
||||||
}
|
}
|
||||||
return makeValueArray(elements), nil
|
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
|
// 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
|
// 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.
|
// of stack trace now, and it's not that nice.
|
||||||
left := makeThunk("x", env, ast.Left)
|
left := makeThunk(env, ast.Left)
|
||||||
right := makeThunk("y", env, ast.Right)
|
right := makeThunk(env, ast.Right)
|
||||||
|
|
||||||
builtin := bopBuiltins[ast.Op]
|
builtin := bopBuiltins[ast.Op]
|
||||||
|
|
||||||
@ -282,7 +282,7 @@ func (i *interpreter) evaluate(a ast.Node, context *TraceContext) (value, error)
|
|||||||
|
|
||||||
case *ast.Unary:
|
case *ast.Unary:
|
||||||
env := i.getCurrentEnv(ast)
|
env := i.getCurrentEnv(ast)
|
||||||
arg := makeThunk("x", env, ast.Expr)
|
arg := makeThunk(env, ast.Expr)
|
||||||
|
|
||||||
builtin := uopBuiltins[ast.Op]
|
builtin := uopBuiltins[ast.Op]
|
||||||
|
|
||||||
@ -401,7 +401,7 @@ func (i *interpreter) evaluate(a ast.Node, context *TraceContext) (value, error)
|
|||||||
vars := make(bindingFrame)
|
vars := make(bindingFrame)
|
||||||
bindEnv := i.getCurrentEnv(a)
|
bindEnv := i.getCurrentEnv(a)
|
||||||
for _, bind := range ast.Binds {
|
for _, bind := range ast.Binds {
|
||||||
th := makeThunk(bind.Variable, bindEnv, bind.Body)
|
th := makeThunk(bindEnv, bind.Body)
|
||||||
|
|
||||||
// recursive locals
|
// recursive locals
|
||||||
vars[bind.Variable] = th
|
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)),
|
positional: make([]potentialValue, len(ast.Arguments.Positional)),
|
||||||
}
|
}
|
||||||
for i, arg := range ast.Arguments.Positional {
|
for i, arg := range ast.Arguments.Positional {
|
||||||
// TODO(sbarzowski) better thunk name
|
arguments.positional[i] = makeThunk(argEnv, arg)
|
||||||
arguments.positional[i] = makeThunk("arg", argEnv, arg)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return e.evaluate(function.call(arguments))
|
return e.evaluate(function.call(arguments))
|
||||||
@ -700,13 +699,12 @@ func (i *interpreter) manifestAndSerializeJSON(trace *TraceElement, v value, mul
|
|||||||
return buf.String(), nil
|
return buf.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *interpreter) EvalInCleanEnv(fromWhere *TraceElement, newContext *TraceContext,
|
func (i *interpreter) EvalInCleanEnv(fromWhere *TraceElement, env *environment, ast ast.Node) (value, error) {
|
||||||
env *environment, ast ast.Node) (value, error) {
|
|
||||||
err := i.newCall(fromWhere, *env)
|
err := i.newCall(fromWhere, *env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
val, err := i.evaluate(ast, newContext)
|
val, err := i.evaluate(ast)
|
||||||
i.stack.pop()
|
i.stack.pop()
|
||||||
return val, err
|
return val, err
|
||||||
}
|
}
|
||||||
@ -736,12 +734,11 @@ func evaluateStd(i *interpreter) (value, error) {
|
|||||||
)
|
)
|
||||||
evalLoc := ast.MakeLocationRangeMessage("During evaluation of std")
|
evalLoc := ast.MakeLocationRangeMessage("During evaluation of std")
|
||||||
evalTrace := &TraceElement{loc: &evalLoc}
|
evalTrace := &TraceElement{loc: &evalLoc}
|
||||||
node, err := snippetToAST("std.jsonnet", getStdCode())
|
node, err := snippetToAST("<std>", getStdCode())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
context := TraceContext{Name: "<stdlib>"}
|
return i.EvalInCleanEnv(evalTrace, &beforeStdEnv, node)
|
||||||
return i.EvalInCleanEnv(evalTrace, &context, &beforeStdEnv, node)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareExtVars(i *interpreter, ext vmExtMap) map[ast.Identifier]potentialValue {
|
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{
|
evalTrace := &TraceElement{
|
||||||
loc: &evalLoc,
|
loc: &evalLoc,
|
||||||
}
|
}
|
||||||
context := TraceContext{Name: "<main>"}
|
result, err := i.EvalInCleanEnv(evalTrace, &i.initialEnv, node)
|
||||||
result, err := i.EvalInCleanEnv(evalTrace, &context, &i.initialEnv, node)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
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})
|
mainTests = append(mainTests, mainTest{name: name, input: input, golden: golden})
|
||||||
}
|
}
|
||||||
|
errFormatter := ErrorFormatter{pretty: true}
|
||||||
for _, test := range mainTests {
|
for _, test := range mainTests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
vm := MakeVM()
|
vm := MakeVM()
|
||||||
@ -109,7 +110,7 @@ func TestMain(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO(sbarzowski) perhaps somehow mark that we are processing
|
// TODO(sbarzowski) perhaps somehow mark that we are processing
|
||||||
// an error. But for now we can treat them the same.
|
// an error. But for now we can treat them the same.
|
||||||
output = err.Error()
|
output = errFormatter.format(err)
|
||||||
}
|
}
|
||||||
output += "\n"
|
output += "\n"
|
||||||
if *update {
|
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?
|
// TODO(sbarzowski) checking if the whitespace is right is quite unpleasant, what can we do about it?
|
||||||
var minimalErrorTests = []errorFormattingTest{
|
var minimalErrorTests = []errorFormattingTest{
|
||||||
{"error", `error "x"`, "RUNTIME ERROR: x\n" +
|
{"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" +
|
" 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", `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" +
|
" 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", `error (error "x")`, "RUNTIME ERROR: x\n" +
|
||||||
|
" error_in_error:1:8-16 $\n" +
|
||||||
" During evaluation \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)
|
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 {
|
type lexer struct {
|
||||||
fileName string // The file name being lexed, only used for errors
|
fileName string // The file name being lexed, only used for errors
|
||||||
input string // The input string
|
input string // The input string
|
||||||
|
source *ast.Source
|
||||||
|
|
||||||
pos position // Current position in input
|
pos position // Current position in input
|
||||||
prev position // Previous position in input
|
prev position // Previous position in input
|
||||||
@ -263,6 +264,7 @@ func makeLexer(fn string, input string) *lexer {
|
|||||||
return &lexer{
|
return &lexer{
|
||||||
fileName: fn,
|
fileName: fn,
|
||||||
input: input,
|
input: input,
|
||||||
|
source: ast.BuildSource(input),
|
||||||
pos: position{byteNo: 0, lineNo: 1, lineStart: 0},
|
pos: position{byteNo: 0, lineNo: 1, lineStart: 0},
|
||||||
prev: position{byteNo: lexEOF, lineNo: 0, lineStart: 0},
|
prev: position{byteNo: lexEOF, lineNo: 0, lineStart: 0},
|
||||||
tokenStartLoc: ast.Location{Line: 1, Column: 1},
|
tokenStartLoc: ast.Location{Line: 1, Column: 1},
|
||||||
@ -337,7 +339,7 @@ func (l *lexer) emitFullToken(kind tokenKind, data, stringBlockIndent, stringBlo
|
|||||||
data: data,
|
data: data,
|
||||||
stringBlockIndent: stringBlockIndent,
|
stringBlockIndent: stringBlockIndent,
|
||||||
stringBlockTermIndent: stringBlockTermIndent,
|
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{}
|
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})
|
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
|
// 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.
|
// that the next rune to be served by the lexer will be a leading digit.
|
||||||
func (l *lexer) lexNumber() error {
|
func (l *lexer) lexNumber() error {
|
||||||
@ -431,9 +437,9 @@ outerLoop:
|
|||||||
case r >= '0' && r <= '9':
|
case r >= '0' && r <= '9':
|
||||||
state = numAfterDigit
|
state = numAfterDigit
|
||||||
default:
|
default:
|
||||||
return MakeStaticErrorPoint(
|
return l.makeStaticErrorPoint(
|
||||||
fmt.Sprintf("Couldn't lex number, junk after decimal point: %v", strconv.QuoteRuneToASCII(r)),
|
fmt.Sprintf("Couldn't lex number, junk after decimal point: %v", strconv.QuoteRuneToASCII(r)),
|
||||||
l.fileName, l.prevLocation())
|
l.prevLocation())
|
||||||
}
|
}
|
||||||
case numAfterDigit:
|
case numAfterDigit:
|
||||||
switch {
|
switch {
|
||||||
@ -451,17 +457,17 @@ outerLoop:
|
|||||||
case r >= '0' && r <= '9':
|
case r >= '0' && r <= '9':
|
||||||
state = numAfterExpDigit
|
state = numAfterExpDigit
|
||||||
default:
|
default:
|
||||||
return MakeStaticErrorPoint(
|
return l.makeStaticErrorPoint(
|
||||||
fmt.Sprintf("Couldn't lex number, junk after 'E': %v", strconv.QuoteRuneToASCII(r)),
|
fmt.Sprintf("Couldn't lex number, junk after 'E': %v", strconv.QuoteRuneToASCII(r)),
|
||||||
l.fileName, l.prevLocation())
|
l.prevLocation())
|
||||||
}
|
}
|
||||||
case numAfterExpSign:
|
case numAfterExpSign:
|
||||||
if r >= '0' && r <= '9' {
|
if r >= '0' && r <= '9' {
|
||||||
state = numAfterExpDigit
|
state = numAfterExpDigit
|
||||||
} else {
|
} else {
|
||||||
return MakeStaticErrorPoint(
|
return l.makeStaticErrorPoint(
|
||||||
fmt.Sprintf("Couldn't lex number, junk after exponent sign: %v", strconv.QuoteRuneToASCII(r)),
|
fmt.Sprintf("Couldn't lex number, junk after exponent sign: %v", strconv.QuoteRuneToASCII(r)),
|
||||||
l.fileName, l.prevLocation())
|
l.prevLocation())
|
||||||
}
|
}
|
||||||
|
|
||||||
case numAfterExpDigit:
|
case numAfterExpDigit:
|
||||||
@ -558,8 +564,8 @@ func (l *lexer) lexSymbol() error {
|
|||||||
l.resetTokenStart() // Throw out the leading /*
|
l.resetTokenStart() // Throw out the leading /*
|
||||||
for r = l.next(); ; r = l.next() {
|
for r = l.next(); ; r = l.next() {
|
||||||
if r == lexEOF {
|
if r == lexEOF {
|
||||||
return MakeStaticErrorPoint("Multi-line comment has no terminating */",
|
return l.makeStaticErrorPoint("Multi-line comment has no terminating */",
|
||||||
l.fileName, commentStartLoc)
|
commentStartLoc)
|
||||||
}
|
}
|
||||||
if r == '*' && l.peek() == '/' {
|
if r == '*' && l.peek() == '/' {
|
||||||
commentData := l.input[l.tokenStart : l.pos.byteNo-1] // Don't include trailing */
|
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:])
|
numWhiteSpace := checkWhitespace(l.input[l.pos.byteNo:], l.input[l.pos.byteNo:])
|
||||||
stringBlockIndent := l.input[l.pos.byteNo : l.pos.byteNo+numWhiteSpace]
|
stringBlockIndent := l.input[l.pos.byteNo : l.pos.byteNo+numWhiteSpace]
|
||||||
if numWhiteSpace == 0 {
|
if numWhiteSpace == 0 {
|
||||||
return MakeStaticErrorPoint("Text block's first line must start with whitespace",
|
return l.makeStaticErrorPoint("Text block's first line must start with whitespace",
|
||||||
l.fileName, commentStartLoc)
|
commentStartLoc)
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
@ -595,8 +601,7 @@ func (l *lexer) lexSymbol() error {
|
|||||||
l.acceptN(numWhiteSpace)
|
l.acceptN(numWhiteSpace)
|
||||||
for r = l.next(); r != '\n'; r = l.next() {
|
for r = l.next(); r != '\n'; r = l.next() {
|
||||||
if r == lexEOF {
|
if r == lexEOF {
|
||||||
return MakeStaticErrorPoint("Unexpected EOF",
|
return l.makeStaticErrorPoint("Unexpected EOF", commentStartLoc)
|
||||||
l.fileName, commentStartLoc)
|
|
||||||
}
|
}
|
||||||
cb.WriteRune(r)
|
cb.WriteRune(r)
|
||||||
}
|
}
|
||||||
@ -618,8 +623,7 @@ func (l *lexer) lexSymbol() error {
|
|||||||
}
|
}
|
||||||
l.backup()
|
l.backup()
|
||||||
if !strings.HasPrefix(l.input[l.pos.byteNo:], "|||") {
|
if !strings.HasPrefix(l.input[l.pos.byteNo:], "|||") {
|
||||||
return MakeStaticErrorPoint("Text block not terminated with |||",
|
return l.makeStaticErrorPoint("Text block not terminated with |||", commentStartLoc)
|
||||||
l.fileName, commentStartLoc)
|
|
||||||
}
|
}
|
||||||
l.acceptN(3) // Skip '|||'
|
l.acceptN(3) // Skip '|||'
|
||||||
l.emitFullToken(tokenStringBlock, cb.String(),
|
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
|
l.resetTokenStart() // Don't include the quotes in the token data
|
||||||
for r = l.next(); ; r = l.next() {
|
for r = l.next(); ; r = l.next() {
|
||||||
if r == lexEOF {
|
if r == lexEOF {
|
||||||
return nil, MakeStaticErrorPoint("Unterminated String", l.fileName, stringStartLoc)
|
return nil, l.makeStaticErrorPoint("Unterminated String", stringStartLoc)
|
||||||
}
|
}
|
||||||
if r == '"' {
|
if r == '"' {
|
||||||
l.backup()
|
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
|
l.resetTokenStart() // Don't include the quotes in the token data
|
||||||
for r = l.next(); ; r = l.next() {
|
for r = l.next(); ; r = l.next() {
|
||||||
if r == lexEOF {
|
if r == lexEOF {
|
||||||
return nil, MakeStaticErrorPoint("Unterminated String", l.fileName, stringStartLoc)
|
return nil, l.makeStaticErrorPoint("Unterminated String", stringStartLoc)
|
||||||
}
|
}
|
||||||
if r == '\'' {
|
if r == '\'' {
|
||||||
l.backup()
|
l.backup()
|
||||||
@ -757,15 +761,14 @@ func Lex(fn string, input string) (tokens, error) {
|
|||||||
} else if quot == '\'' {
|
} else if quot == '\'' {
|
||||||
kind = tokenVerbatimStringSingle
|
kind = tokenVerbatimStringSingle
|
||||||
} else {
|
} else {
|
||||||
return nil, MakeStaticErrorPoint(
|
return nil, l.makeStaticErrorPoint(
|
||||||
fmt.Sprintf("Couldn't lex verbatim string, junk after '@': %v", quot),
|
fmt.Sprintf("Couldn't lex verbatim string, junk after '@': %v", quot),
|
||||||
l.fileName,
|
|
||||||
stringStartLoc,
|
stringStartLoc,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
for r = l.next(); ; r = l.next() {
|
for r = l.next(); ; r = l.next() {
|
||||||
if r == lexEOF {
|
if r == lexEOF {
|
||||||
return nil, MakeStaticErrorPoint("Unterminated String", l.fileName, stringStartLoc)
|
return nil, l.makeStaticErrorPoint("Unterminated String", stringStartLoc)
|
||||||
} else if r == quot {
|
} else if r == quot {
|
||||||
if l.peek() == quot {
|
if l.peek() == quot {
|
||||||
l.next()
|
l.next()
|
||||||
@ -799,9 +802,9 @@ func Lex(fn string, input string) (tokens, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return nil, MakeStaticErrorPoint(
|
return nil, l.makeStaticErrorPoint(
|
||||||
fmt.Sprintf("Could not lex the character %s", strconv.QuoteRuneToASCII(r)),
|
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 {
|
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 {
|
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,12 +219,19 @@ func (p *parser) parseBind(binds *ast.LocalBinds) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var fun *ast.Function
|
||||||
if p.peek().kind == tokenParenL {
|
if p.peek().kind == tokenParenL {
|
||||||
p.pop()
|
p.pop()
|
||||||
params, gotComma, err := p.parseParameters("function parameter")
|
params, gotComma, err := p.parseParameters("function parameter")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
fun = &ast.Function{
|
||||||
|
Parameters: *params,
|
||||||
|
TrailingComma: gotComma,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_, err = p.popExpectOp("=")
|
_, err = p.popExpectOp("=")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -233,22 +240,15 @@ func (p *parser) parseBind(binds *ast.LocalBinds) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if fun != nil {
|
||||||
|
fun.Body = body
|
||||||
*binds = append(*binds, ast.LocalBind{
|
*binds = append(*binds, ast.LocalBind{
|
||||||
Variable: ast.Identifier(varID.data),
|
Variable: ast.Identifier(varID.data),
|
||||||
Body: body,
|
Body: body,
|
||||||
FunctionSugar: true,
|
Fun: fun,
|
||||||
Params: params,
|
|
||||||
TrailingComma: gotComma,
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
_, err = p.popExpectOp("=")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
body, err := p.parse(maxPrecedence)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*binds = append(*binds, ast.LocalBind{
|
*binds = append(*binds, ast.LocalBind{
|
||||||
Variable: ast.Identifier(varID.data),
|
Variable: ast.Identifier(varID.data),
|
||||||
Body: body,
|
Body: body,
|
||||||
@ -299,6 +299,7 @@ func (p *parser) parseObjectAssignmentOp() (plusSugar bool, hide ast.ObjectField
|
|||||||
// +gen set
|
// +gen set
|
||||||
type LiteralField string
|
type LiteralField string
|
||||||
|
|
||||||
|
// Parse object or object comprehension without leading brace
|
||||||
func (p *parser) parseObjectRemainder(tok *token) (ast.Node, *token, error) {
|
func (p *parser) parseObjectRemainder(tok *token) (ast.Node, *token, error) {
|
||||||
var fields ast.ObjectFields
|
var fields ast.ObjectFields
|
||||||
literalFields := make(literalFieldSet)
|
literalFields := make(literalFieldSet)
|
||||||
@ -451,11 +452,21 @@ func (p *parser) parseObjectRemainder(tok *token) (ast.Node, *token, error) {
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var method *ast.Function
|
||||||
|
if isMethod {
|
||||||
|
method = &ast.Function{
|
||||||
|
Parameters: *params,
|
||||||
|
TrailingComma: methComma,
|
||||||
|
Body: body,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fields = append(fields, ast.ObjectField{
|
fields = append(fields, ast.ObjectField{
|
||||||
Kind: kind,
|
Kind: kind,
|
||||||
Hide: hide,
|
Hide: hide,
|
||||||
SuperSugar: plusSugar,
|
SuperSugar: plusSugar,
|
||||||
MethodSugar: isMethod,
|
MethodSugar: isMethod,
|
||||||
|
Method: method,
|
||||||
Expr1: expr1,
|
Expr1: expr1,
|
||||||
Id: id,
|
Id: id,
|
||||||
Params: params,
|
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)
|
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
|
isMethod := false
|
||||||
funcComma := false
|
funcComma := false
|
||||||
var params *ast.Parameters
|
var params *ast.Parameters
|
||||||
@ -496,6 +509,15 @@ func (p *parser) parseObjectRemainder(tok *token) (ast.Node, *token, error) {
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var method *ast.Function
|
||||||
|
if isMethod {
|
||||||
|
method = &ast.Function{
|
||||||
|
Parameters: *params,
|
||||||
|
TrailingComma: funcComma,
|
||||||
|
Body: body,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
binds.Add(id)
|
binds.Add(id)
|
||||||
|
|
||||||
fields = append(fields, ast.ObjectField{
|
fields = append(fields, ast.ObjectField{
|
||||||
@ -503,6 +525,7 @@ func (p *parser) parseObjectRemainder(tok *token) (ast.Node, *token, error) {
|
|||||||
Hide: ast.ObjectFieldVisible,
|
Hide: ast.ObjectFieldVisible,
|
||||||
SuperSugar: false,
|
SuperSugar: false,
|
||||||
MethodSugar: isMethod,
|
MethodSugar: isMethod,
|
||||||
|
Method: method,
|
||||||
Id: &id,
|
Id: &id,
|
||||||
Params: params,
|
Params: params,
|
||||||
TrailingComma: funcComma,
|
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)
|
return nil, MakeStaticError(fmt.Sprintf("Did not expect: %v", p.peek()), p.peek().loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addContext(expr, &topLevelContext, anonymous)
|
||||||
|
|
||||||
return expr, nil
|
return expr, nil
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ limitations under the License.
|
|||||||
package parser
|
package parser
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -124,15 +125,19 @@ var tests = []string{
|
|||||||
|
|
||||||
func TestParser(t *testing.T) {
|
func TestParser(t *testing.T) {
|
||||||
for _, s := range tests {
|
for _, s := range tests {
|
||||||
|
t.Run(s, func(t *testing.T) {
|
||||||
|
fmt.Println(s)
|
||||||
tokens, err := Lex("test", s)
|
tokens, err := Lex("test", s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Unexpected lex error\n input: %v\n error: %v", s, err)
|
t.Errorf("Unexpected lex error\n input: %v\n error: %v", s, err)
|
||||||
continue
|
return
|
||||||
}
|
}
|
||||||
_, err = Parse(tokens)
|
_, err = Parse(tokens)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Unexpected parse error\n input: %v\n error: %v", s, err)
|
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) {
|
func TestParserErrors(t *testing.T) {
|
||||||
for _, s := range errorTests {
|
for _, s := range errorTests {
|
||||||
|
t.Run(s.input, func(t *testing.T) {
|
||||||
tokens, err := Lex("test", s.input)
|
tokens, err := Lex("test", s.input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Unexpected lex error\n input: %v\n error: %v", s.input, err)
|
t.Errorf("Unexpected lex error\n input: %v\n error: %v", s.input, err)
|
||||||
continue
|
return
|
||||||
}
|
}
|
||||||
_, err = Parse(tokens)
|
_, err = Parse(tokens)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("Expected parse error but got success\n input: %v", s.input)
|
t.Errorf("Expected parse error but got success\n input: %v", s.input)
|
||||||
continue
|
return
|
||||||
}
|
}
|
||||||
if err.Error() != s.err {
|
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.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}
|
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 {
|
func MakeStaticError(msg string, lr ast.LocationRange) StaticError {
|
||||||
return StaticError{Msg: msg, Loc: lr}
|
return StaticError{Msg: msg, Loc: lr}
|
||||||
}
|
}
|
||||||
|
@ -48,20 +48,15 @@ func traceElementToTraceFrame(trace *TraceElement) TraceFrame {
|
|||||||
tf := TraceFrame{Loc: *trace.loc}
|
tf := TraceFrame{Loc: *trace.loc}
|
||||||
if trace.context != nil {
|
if trace.context != nil {
|
||||||
// TODO(sbarzowski) maybe it should never be nil
|
// TODO(sbarzowski) maybe it should never be nil
|
||||||
tf.Name = trace.context.Name
|
tf.Name = *trace.context
|
||||||
} else {
|
} else {
|
||||||
tf.Name = ""
|
tf.Name = ""
|
||||||
}
|
}
|
||||||
return tf
|
return tf
|
||||||
}
|
}
|
||||||
|
|
||||||
type TraceContext struct {
|
|
||||||
// Human readable name - e.g. function <foo>
|
|
||||||
Name string
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(sbarzowski) better name
|
// TODO(sbarzowski) better name
|
||||||
type TraceElement struct {
|
type TraceElement struct {
|
||||||
loc *ast.LocationRange
|
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.
|
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.
|
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
|
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
|
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
|
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
|
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
|
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}
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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.
|
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
|
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")
|
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
|
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
|
<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
|
<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
|
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
|
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.
|
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
|
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
|
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
|
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.
|
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
|
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
|
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")
|
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.
|
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
|
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
|
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.
|
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.
|
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")
|
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.
|
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.
|
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.
|
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"
|
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
|
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
|
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.
|
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
|
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.
|
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.
|
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
|
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
|
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.
|
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
|
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.
|
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.
|
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.
|
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.
|
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
|
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