mirror of
https://github.com/google/go-jsonnet.git
synced 2025-09-29 01:11:02 +02:00
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:
parent
197b8c58d0
commit
82f949e7fe
@ -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"],
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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"}},
|
||||||
|
@ -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 = [
|
||||||
|
@ -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",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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"],
|
||||||
)
|
)
|
||||||
|
|
26
internal/transformations/BUILD.bazel
Normal file
26
internal/transformations/BUILD.bazel
Normal 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"],
|
||||||
|
)
|
@ -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
|
@ -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
|
@ -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"))
|
||||||
}
|
}
|
@ -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)
|
||||||
}
|
}
|
31
internal/transformations/transformations.go
Normal file
31
internal/transformations/transformations.go
Normal 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
|
||||||
|
}
|
@ -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",
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
4
value.go
4
value.go
@ -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
30
vm.go
@ -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.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user