Extract AST processing to separate packages

Making it independent from the jsonnet package breaks the circular
dependency during stdast generation.
This commit is contained in:
Stanisław Barzowski 2019-07-26 16:28:34 +02:00 committed by Dave Cunningham
parent 197b8c58d0
commit 82f949e7fe
24 changed files with 116 additions and 69 deletions

View File

@ -13,13 +13,11 @@ go_library(
name = "go_default_library", name = "go_default_library",
srcs = [ srcs = [
"builtins.go", "builtins.go",
"desugarer.go",
"doc.go", "doc.go",
"error_formatter.go", "error_formatter.go",
"imports.go", "imports.go",
"interpreter.go", "interpreter.go",
"runtime_error.go", "runtime_error.go",
"static_analyzer.go",
"thunks.go", "thunks.go",
"value.go", "value.go",
"vm.go", "vm.go",
@ -28,6 +26,7 @@ go_library(
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
"//ast:go_default_library", "//ast:go_default_library",
"//internal/transformations:go_default_library",
"//parser:go_default_library", "//parser:go_default_library",
], ],
) )
@ -35,11 +34,9 @@ go_library(
go_test( go_test(
name = "go_default_test", name = "go_default_test",
srcs = [ srcs = [
"desugarer_test.go",
"interpreter_test.go", "interpreter_test.go",
"jsonnet_test.go", "jsonnet_test.go",
"main_test.go", "main_test.go",
"static_analyzer_test.go",
], ],
data = glob(["testdata/**"]), data = glob(["testdata/**"]),
embed = [":go_default_library"], embed = [":go_default_library"],

View File

@ -47,6 +47,11 @@ Windows | _bazel build --platforms=@io_bazel_rules_go//go/toolchain:wind
For additional target platform names, see the per-Go release definitions [here](https://github.com/bazelbuild/rules_go/blob/master/go/private/sdk_list.bzl#L21-L31) in the _rules_go_ Bazel package. For additional target platform names, see the per-Go release definitions [here](https://github.com/bazelbuild/rules_go/blob/master/go/private/sdk_list.bzl#L21-L31) in the _rules_go_ Bazel package.
Additionally if files were moved around, you may need to run the following command to update Bazel files:
```
bazel run //:gazelle
```
## Build instructions (go 1.8 - 1.10) ## Build instructions (go 1.8 - 1.10)
```bash ```bash

View File

@ -298,6 +298,7 @@ func clone(astPtr *Node) {
cloneNodeBase(*astPtr) cloneNodeBase(*astPtr)
} }
// Clone creates an independent copy of an AST
func Clone(astPtr Node) Node { func Clone(astPtr Node) Node {
clone(&astPtr) clone(&astPtr)
return astPtr return astPtr

View File

@ -1027,15 +1027,10 @@ func (b *ternaryBuiltin) Name() ast.Identifier {
return b.name return b.name
} }
var desugaredBop = map[ast.BinaryOp]ast.Identifier{
ast.BopPercent: "mod",
ast.BopIn: "objectHasAll",
}
var bopBuiltins = []*binaryBuiltin{ var bopBuiltins = []*binaryBuiltin{
// Note that % and `in` are desugared instead of being handled here
ast.BopMult: &binaryBuiltin{name: "operator*", function: builtinMult, parameters: ast.Identifiers{"x", "y"}}, ast.BopMult: &binaryBuiltin{name: "operator*", function: builtinMult, parameters: ast.Identifiers{"x", "y"}},
ast.BopDiv: &binaryBuiltin{name: "operator/", function: builtinDiv, parameters: ast.Identifiers{"x", "y"}}, ast.BopDiv: &binaryBuiltin{name: "operator/", function: builtinDiv, parameters: ast.Identifiers{"x", "y"}},
// ast.BopPercent: <desugared>,
ast.BopPlus: &binaryBuiltin{name: "operator+", function: builtinPlus, parameters: ast.Identifiers{"x", "y"}}, ast.BopPlus: &binaryBuiltin{name: "operator+", function: builtinPlus, parameters: ast.Identifiers{"x", "y"}},
ast.BopMinus: &binaryBuiltin{name: "operator-", function: builtinMinus, parameters: ast.Identifiers{"x", "y"}}, ast.BopMinus: &binaryBuiltin{name: "operator-", function: builtinMinus, parameters: ast.Identifiers{"x", "y"}},

View File

@ -16,6 +16,7 @@ go_library(
"internal.h", "internal.h",
"json.cpp", "json.cpp",
"json.h", "json.h",
"libgojsonnet.h",
"libjsonnet.cpp", "libjsonnet.cpp",
], ],
cdeps = [ cdeps = [

View File

@ -6,8 +6,8 @@ go_library(
importpath = "github.com/google/go-jsonnet/cmd/dumpstdlibast", importpath = "github.com/google/go-jsonnet/cmd/dumpstdlibast",
visibility = ["//visibility:private"], visibility = ["//visibility:private"],
deps = [ deps = [
"//:go_default_library", "//internal/dump:go_default_library",
"//dump:go_default_library", "//internal/transformations:go_default_library",
], ],
) )

View File

@ -19,8 +19,8 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/google/go-jsonnet" "github.com/google/go-jsonnet/internal/dump"
"github.com/google/go-jsonnet/dump" "github.com/google/go-jsonnet/internal/transformations"
) )
func main() { func main() {
@ -33,7 +33,7 @@ func main() {
panic(err) panic(err)
} }
node, err := jsonnet.SnippetToAST("<std>", string(buf)) node, err := transformations.SnippetToAST("<std>", string(buf))
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@ -107,7 +107,7 @@ func (cache *importCache) importString(importedFrom, importedPath string, i *int
} }
func codeToPV(i *interpreter, filename string, code string) *cachedThunk { func codeToPV(i *interpreter, filename string, code string) *cachedThunk {
node, err := snippetToAST(filename, code) node, err := SnippetToAST(filename, code)
if err != nil { if err != nil {
// TODO(sbarzowski) we should wrap (static) error here // TODO(sbarzowski) we should wrap (static) error here
// within a RuntimeError. Because whether we get this error or not // within a RuntimeError. Because whether we get this error or not

View File

@ -7,7 +7,7 @@ go_library(
"pointermap.go", "pointermap.go",
"utils.go", "utils.go",
], ],
importpath = "github.com/google/go-jsonnet/dump", importpath = "github.com/google/go-jsonnet/internal/dump",
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
) )

View File

@ -0,0 +1,26 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"desugarer.go",
"static_analyzer.go",
"transformations.go",
],
importpath = "github.com/google/go-jsonnet/internal/transformations",
visibility = ["//:__subpackages__"],
deps = [
"//ast:go_default_library",
"//parser:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"desugarer_test.go",
"static_analyzer_test.go",
],
embed = [":go_default_library"],
deps = ["//ast:go_default_library"],
)

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package jsonnet package transformations
import ( import (
"bytes" "bytes"
@ -27,6 +27,11 @@ import (
"github.com/google/go-jsonnet/parser" "github.com/google/go-jsonnet/parser"
) )
var desugaredBop = map[ast.BinaryOp]ast.Identifier{
ast.BopPercent: "mod",
ast.BopIn: "objectHasAll",
}
func makeStr(s string) *ast.LiteralString { func makeStr(s string) *ast.LiteralString {
return &ast.LiteralString{ return &ast.LiteralString{
NodeBase: ast.NodeBase{}, NodeBase: ast.NodeBase{},
@ -232,8 +237,6 @@ func wrapInArray(inside ast.Node) ast.Node {
return &ast.Array{Elements: ast.Nodes{inside}} return &ast.Array{Elements: ast.Nodes{inside}}
} }
func desugarArrayComp(comp *ast.ArrayComp, objLevel int) (ast.Node, error) { func desugarArrayComp(comp *ast.ArrayComp, objLevel int) (ast.Node, error) {
err := desugar(&comp.Body, objLevel) err := desugar(&comp.Body, objLevel)
if err != nil { if err != nil {
@ -301,17 +304,6 @@ func desugarLocalBinds(binds ast.LocalBinds, objLevel int) (err error) {
return nil return nil
} }
// Desugar Jsonnet expressions to reduce the number of constructs the rest of the implementation
// needs to understand.
//
// Note that despite the name, desugar() is not idempotent. String literals have their escape
// codes translated to low-level characters during desugaring.
//
// Desugaring should happen immediately after parsing, i.e. before static analysis and execution.
// Temporary variables introduced here should be prefixed with $ to ensure they do not clash with
// variables used in user code.
// TODO(sbarzowski) Actually we may want to do some static analysis before desugaring, e.g.
// warning user about dangerous use of constructs that we desugar.
func desugar(astPtr *ast.Node, objLevel int) (err error) { func desugar(astPtr *ast.Node, objLevel int) (err error) {
node := *astPtr node := *astPtr
@ -579,7 +571,18 @@ func desugar(astPtr *ast.Node, objLevel int) (err error) {
return nil return nil
} }
func desugarFile(ast *ast.Node) error { // Desugar Jsonnet expressions to reduce the number of constructs the rest of the implementation
// needs to understand.
//
// Note that despite the name, desugar() is not idempotent. String literals have their escape
// codes translated to low-level characters during desugaring.
//
// Desugaring should happen immediately after parsing, i.e. before static analysis and execution.
// Temporary variables introduced here should be prefixed with $ to ensure they do not clash with
// variables used in user code.
// TODO(sbarzowski) Actually we may want to do some static analysis before desugaring, e.g.
// warning user about dangerous use of constructs that we desugar.
func Desugar(ast *ast.Node) error {
err := desugar(ast, 0) err := desugar(ast, 0)
if err != nil { if err != nil {
return err return err

View File

@ -14,4 +14,4 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package jsonnet package transformations

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package jsonnet package transformations
import ( import (
"fmt" "fmt"
@ -164,6 +164,9 @@ func analyzeVisit(a ast.Node, inObject bool, vars ast.IdentifierSet) error {
return s.err return s.err
} }
func analyze(node ast.Node) error { // Analyze checks variable references (these could be checked statically in Jsonnet).
// It enriches ast with additional information about free variables in every node,
// so it is necessary to always run it before executing AST.
func Analyze(node ast.Node) error {
return analyzeVisit(node, false, ast.NewIdentifierSet("std")) return analyzeVisit(node, false, ast.NewIdentifierSet("std"))
} }

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package jsonnet package transformations
import ( import (
"testing" "testing"
@ -28,7 +28,7 @@ import (
func TestSimpleNull(t *testing.T) { func TestSimpleNull(t *testing.T) {
ast := &ast.LiteralNull{} ast := &ast.LiteralNull{}
err := analyze(ast) err := Analyze(ast)
if err != nil { if err != nil {
t.Errorf("Unexpected error: %+v", err) t.Errorf("Unexpected error: %+v", err)
} }
@ -60,7 +60,7 @@ func TestSimpleLocal(t *testing.T) {
Body: &ast.Var{Id: "x"}, Body: &ast.Var{Id: "x"},
} }
err := analyze(node) err := Analyze(node)
if err != nil { if err != nil {
t.Errorf("Unexpected error: %+v", err) t.Errorf("Unexpected error: %+v", err)
} }

View File

@ -0,0 +1,31 @@
package transformations
import (
"github.com/google/go-jsonnet/ast"
"github.com/google/go-jsonnet/parser"
)
func snippetToRawAST(filename string, snippet string) (ast.Node, error) {
tokens, err := parser.Lex(filename, snippet)
if err != nil {
return nil, err
}
return parser.Parse(tokens)
}
// SnippetToAST converts Jsonnet code snippet to desugared and analyzed AST
func SnippetToAST(filename string, snippet string) (ast.Node, error) {
node, err := snippetToRawAST(filename, snippet)
if err != nil {
return nil, err
}
err = Desugar(&node)
if err != nil {
return nil, err
}
err = Analyze(node)
if err != nil {
return nil, err
}
return node, nil
}

View File

@ -137,7 +137,7 @@ func runInternalJsonnet(i jsonnetInput) jsonnetResult {
vm.NativeFunction(jsonToString) vm.NativeFunction(jsonToString)
vm.NativeFunction(nativeError) vm.NativeFunction(nativeError)
rawAST, err := snippetToRawAST(i.name, string(i.input)) rawAST, err := parser.SnippetToRawAST(i.name, string(i.input))
if err != nil { if err != nil {
return jsonnetResult{ return jsonnetResult{
output: errFormatter.Format(err) + "\n", output: errFormatter.Format(err) + "\n",
@ -146,7 +146,7 @@ func runInternalJsonnet(i jsonnetInput) jsonnetResult {
} }
testChildren(rawAST) testChildren(rawAST)
desugaredAST, err := snippetToAST(i.name, string(i.input)) desugaredAST, err := SnippetToAST(i.name, string(i.input))
if err != nil { if err != nil {
return jsonnetResult{ return jsonnetResult{
output: errFormatter.Format(err) + "\n", output: errFormatter.Format(err) + "\n",

View File

@ -1191,3 +1191,12 @@ func Parse(t Tokens) (ast.Node, error) {
return expr, nil return expr, nil
} }
// SnippetToRawAST converts Jsonnet code snippet to AST (without any transformations)
func SnippetToRawAST(filename string, snippet string) (ast.Node, error) {
tokens, err := Lex(filename, snippet)
if err != nil {
return nil, err
}
return Parse(tokens)
}

View File

@ -428,8 +428,8 @@ func (*valueObject) getType() *valueType {
return objectType return objectType
} }
func (o *valueObject) index(i *interpreter, trace TraceElement, field string) (value, error) { func (obj *valueObject) index(i *interpreter, trace TraceElement, field string) (value, error) {
return objectIndex(i, trace, objectBinding(o), field) return objectIndex(i, trace, objectBinding(obj), field)
} }
func (obj *valueObject) assertionsChecked() bool { func (obj *valueObject) assertionsChecked() bool {

30
vm.go
View File

@ -22,7 +22,7 @@ import (
"runtime/debug" "runtime/debug"
"github.com/google/go-jsonnet/ast" "github.com/google/go-jsonnet/ast"
"github.com/google/go-jsonnet/parser" "github.com/google/go-jsonnet/internal/transformations"
) )
// Note: There are no garbage collection params because we're using the native // Note: There are no garbage collection params because we're using the native
@ -137,7 +137,7 @@ func (vm *VM) evaluateSnippet(filename string, snippet string, kind evalKind) (o
err = fmt.Errorf("(CRASH) %v\n%s", r, debug.Stack()) err = fmt.Errorf("(CRASH) %v\n%s", r, debug.Stack())
} }
}() }()
node, err := snippetToAST(filename, snippet) node, err := SnippetToAST(filename, snippet)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -199,33 +199,9 @@ func (vm *VM) EvaluateSnippetMulti(filename string, snippet string) (files map[s
return return
} }
func snippetToRawAST(filename string, snippet string) (ast.Node, error) {
tokens, err := parser.Lex(filename, snippet)
if err != nil {
return nil, err
}
return parser.Parse(tokens)
}
func snippetToAST(filename string, snippet string) (ast.Node, error) {
node, err := snippetToRawAST(filename, snippet)
if err != nil {
return nil, err
}
err = desugarFile(&node)
if err != nil {
return nil, err
}
err = analyze(node)
if err != nil {
return nil, err
}
return node, nil
}
// SnippetToAST parses a snippet and returns the resulting AST. // SnippetToAST parses a snippet and returns the resulting AST.
func SnippetToAST(filename string, snippet string) (ast.Node, error) { func SnippetToAST(filename string, snippet string) (ast.Node, error) {
return snippetToAST(filename, snippet) return transformations.SnippetToAST(filename, snippet)
} }
// Version returns the Jsonnet version number. // Version returns the Jsonnet version number.