mirror of
https://github.com/google/go-jsonnet.git
synced 2025-09-29 17:31:02 +02:00
401 lines
8.6 KiB
Go
401 lines
8.6 KiB
Go
/*
|
|
Copyright 2016 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 jsonnet
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"reflect"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
func makeStr(s string) *astLiteralString {
|
|
return &astLiteralString{astNodeBase{loc: LocationRange{}}, s, astStringDouble, ""}
|
|
}
|
|
|
|
func stringUnescape(loc *LocationRange, s string) (string, error) {
|
|
var buf bytes.Buffer
|
|
// read one rune at a time
|
|
for i := 0; i < len(s); {
|
|
r, w := utf8.DecodeRuneInString(s[i:])
|
|
i += w
|
|
switch r {
|
|
case '\\':
|
|
if i >= len(s) {
|
|
return "", makeStaticError("Truncated escape sequence in string literal.", *loc)
|
|
}
|
|
r2, w := utf8.DecodeRuneInString(s[i:])
|
|
i += w
|
|
switch r2 {
|
|
case '"':
|
|
buf.WriteRune('"')
|
|
case '\\':
|
|
buf.WriteRune('\\')
|
|
case '/':
|
|
buf.WriteRune('/') // This one is odd, maybe a mistake.
|
|
case 'b':
|
|
buf.WriteRune('\b')
|
|
case 'f':
|
|
buf.WriteRune('\f')
|
|
case 'n':
|
|
buf.WriteRune('\n')
|
|
case 'r':
|
|
buf.WriteRune('\r')
|
|
case 't':
|
|
buf.WriteRune('\t')
|
|
case 'u':
|
|
if i+4 > len(s) {
|
|
return "", makeStaticError("Truncated unicode escape sequence in string literal.", *loc)
|
|
}
|
|
codeBytes, err := hex.DecodeString(s[0:4])
|
|
if err != nil {
|
|
return "", makeStaticError(fmt.Sprintf("Unicode escape sequence was malformed: %s", s[0:4]), *loc)
|
|
}
|
|
code := int(codeBytes[0])*256 + int(codeBytes[1])
|
|
buf.WriteRune(rune(code))
|
|
i += 4
|
|
default:
|
|
return "", makeStaticError(fmt.Sprintf("Unknown escape sequence in string literal: \\%c", r2), *loc)
|
|
}
|
|
|
|
default:
|
|
buf.WriteRune(r)
|
|
}
|
|
}
|
|
return buf.String(), nil
|
|
}
|
|
|
|
func desugarFields(location LocationRange, fields *astObjectFields, objLevel int) error {
|
|
|
|
// Desugar children
|
|
for _, field := range *fields {
|
|
if field.expr1 != nil {
|
|
err := desugar(&field.expr1, objLevel)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
err := desugar(&field.expr2, objLevel+1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if field.expr3 != nil {
|
|
err := desugar(&field.expr3, objLevel+1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
// Simplify asserts
|
|
// TODO(dcunnin): this
|
|
for _, field := range *fields {
|
|
if field.kind != astObjectAssert {
|
|
continue
|
|
}
|
|
/*
|
|
AST *msg = field.expr3
|
|
field.expr3 = nil
|
|
if (msg == nil) {
|
|
auto msg_str = U"Object assertion failed."
|
|
msg = alloc->make<LiteralString>(field.expr2->location, msg_str,
|
|
LiteralString::DOUBLE, "")
|
|
}
|
|
|
|
// if expr2 then true else error msg
|
|
field.expr2 = alloc->make<Conditional>(
|
|
ast->location,
|
|
field.expr2,
|
|
alloc->make<LiteralBoolean>(E, true),
|
|
alloc->make<Error>(msg->location, msg))
|
|
*/
|
|
}
|
|
|
|
// Remove methods
|
|
// TODO(dcunnin): this
|
|
for _, field := range *fields {
|
|
if !field.methodSugar {
|
|
continue
|
|
}
|
|
/*
|
|
field.expr2 = alloc->make<Function>(
|
|
field.expr2->location, field.ids, false, field.expr2)
|
|
field.methodSugar = false
|
|
field.ids.clear()
|
|
*/
|
|
}
|
|
|
|
// Remove object-level locals
|
|
newFields := []astObjectField{}
|
|
var binds astLocalBinds
|
|
for _, local := range *fields {
|
|
if local.kind != astObjectLocal {
|
|
continue
|
|
}
|
|
binds = append(binds, astLocalBind{variable: *local.id, body: local.expr2})
|
|
}
|
|
for _, field := range *fields {
|
|
if field.kind == astObjectLocal {
|
|
continue
|
|
}
|
|
if len(binds) > 0 {
|
|
field.expr2 = &astLocal{astNodeBase{loc: *field.expr2.Loc()}, binds, field.expr2}
|
|
}
|
|
newFields = append(newFields, field)
|
|
}
|
|
*fields = newFields
|
|
|
|
// Change all to FIELD_EXPR
|
|
for i := range *fields {
|
|
field := &(*fields)[i]
|
|
switch field.kind {
|
|
case astObjectAssert:
|
|
// Nothing to do.
|
|
|
|
case astObjectFieldID:
|
|
field.expr1 = makeStr(string(*field.id))
|
|
field.kind = astObjectFieldExpr
|
|
|
|
case astObjectFieldExpr:
|
|
// Nothing to do.
|
|
|
|
case astObjectFieldStr:
|
|
// Just set the flag.
|
|
field.kind = astObjectFieldExpr
|
|
|
|
case astObjectLocal:
|
|
return fmt.Errorf("INTERNAL ERROR: Locals should be removed by now")
|
|
}
|
|
}
|
|
|
|
// Remove +:
|
|
// TODO(dcunnin): this
|
|
for _, field := range *fields {
|
|
if !field.superSugar {
|
|
continue
|
|
}
|
|
/*
|
|
AST *super_f = alloc->make<SuperIndex>(field.expr1->location, field.expr1, nil)
|
|
field.expr2 = alloc->make<Binary>(ast->location, super_f, BOP_PLUS, field.expr2)
|
|
field.superSugar = false
|
|
*/
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func desugar(astPtr *astNode, objLevel int) (err error) {
|
|
ast := *astPtr
|
|
// TODO(dcunnin): Remove all uses of unimplErr.
|
|
unimplErr := makeStaticError(fmt.Sprintf("Desugarer does not yet implement ast: %s", reflect.TypeOf(ast)), *ast.Loc())
|
|
|
|
switch ast := ast.(type) {
|
|
case *astApply:
|
|
err = desugar(&ast.target, objLevel)
|
|
if err != nil {
|
|
return
|
|
}
|
|
for i := range ast.arguments {
|
|
err = desugar(&ast.arguments[i], objLevel)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
case *astApplyBrace:
|
|
err = desugar(&ast.left, objLevel)
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = desugar(&ast.right, objLevel)
|
|
if err != nil {
|
|
return
|
|
}
|
|
*astPtr = &astBinary{
|
|
astNodeBase: ast.astNodeBase,
|
|
left: ast.left,
|
|
op: bopPlus,
|
|
right: ast.right,
|
|
}
|
|
|
|
case *astArray:
|
|
for i := range ast.elements {
|
|
err = desugar(&ast.elements[i], objLevel)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
case *astArrayComp:
|
|
return unimplErr
|
|
|
|
case *astAssert:
|
|
return unimplErr
|
|
|
|
case *astBinary:
|
|
err = desugar(&ast.left, objLevel)
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = desugar(&ast.right, objLevel)
|
|
if err != nil {
|
|
return
|
|
}
|
|
// TODO(dcunnin): Need to handle bopPercent, bopManifestUnequal, bopManifestEqual
|
|
|
|
case *astBuiltin:
|
|
// Nothing to do.
|
|
|
|
case *astConditional:
|
|
err = desugar(&ast.cond, objLevel)
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = desugar(&ast.branchTrue, objLevel)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if ast.branchFalse != nil {
|
|
ast.branchFalse = &astLiteralNull{}
|
|
}
|
|
|
|
case *astDollar:
|
|
if objLevel == 0 {
|
|
return makeStaticError("No top-level object found.", *ast.Loc())
|
|
}
|
|
*astPtr = &astVar{astNodeBase: ast.astNodeBase, id: identifier("$")}
|
|
|
|
case *astError:
|
|
err = desugar(&ast.expr, objLevel)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
case *astFunction:
|
|
err = desugar(&ast.body, objLevel)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
case *astImport:
|
|
// Nothing to do.
|
|
|
|
case *astImportStr:
|
|
// Nothing to do.
|
|
|
|
case *astIndex:
|
|
return unimplErr
|
|
|
|
case *astLocal:
|
|
for _, bind := range ast.binds {
|
|
err = desugar(&bind.body, objLevel)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
err = desugar(&ast.body, objLevel)
|
|
if err != nil {
|
|
return
|
|
}
|
|
// TODO(dcunnin): Desugar local functions
|
|
|
|
case *astLiteralBoolean:
|
|
// Nothing to do.
|
|
|
|
case *astLiteralNull:
|
|
// Nothing to do.
|
|
|
|
case *astLiteralNumber:
|
|
// Nothing to do.
|
|
|
|
case *astLiteralString:
|
|
unescaped, err := stringUnescape(ast.Loc(), ast.value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ast.value = unescaped
|
|
ast.kind = astStringDouble
|
|
ast.blockIndent = ""
|
|
|
|
case *astObject:
|
|
// Hidden variable to allow $ binding.
|
|
if objLevel == 0 {
|
|
dollar := identifier("$")
|
|
ast.fields = append(ast.fields, astObjectFieldLocalNoMethod(&dollar, &astSelf{}))
|
|
}
|
|
|
|
err = desugarFields(*ast.Loc(), &ast.fields, objLevel)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
var newFields astDesugaredObjectFields
|
|
var newAsserts astNodes
|
|
|
|
for _, field := range ast.fields {
|
|
if field.kind == astObjectAssert {
|
|
newAsserts = append(newAsserts, field.expr2)
|
|
} else if field.kind == astObjectFieldExpr {
|
|
newFields = append(newFields, astDesugaredObjectField{field.hide, field.expr1, field.expr2})
|
|
} else {
|
|
return fmt.Errorf("INTERNAL ERROR: field should have been desugared: %s", field.kind)
|
|
}
|
|
}
|
|
|
|
*astPtr = &astDesugaredObject{ast.astNodeBase, newAsserts, newFields}
|
|
|
|
case *astDesugaredObject:
|
|
return unimplErr
|
|
|
|
case *astObjectComp:
|
|
return unimplErr
|
|
|
|
case *astObjectComprehensionSimple:
|
|
return unimplErr
|
|
|
|
case *astSelf:
|
|
// Nothing to do.
|
|
|
|
case *astSuperIndex:
|
|
return unimplErr
|
|
|
|
case *astUnary:
|
|
err = desugar(&ast.expr, objLevel)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
case *astVar:
|
|
// Nothing to do.
|
|
|
|
default:
|
|
return makeStaticError(fmt.Sprintf("Desugarer does not recognize ast: %s", reflect.TypeOf(ast)), *ast.Loc())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func desugarFile(ast *astNode) error {
|
|
err := desugar(ast, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// TODO(dcunnin): wrap in std local
|
|
return nil
|
|
}
|