/* 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(field.expr2->location, msg_str, LiteralString::DOUBLE, "") } // if expr2 then true else error msg field.expr2 = alloc->make( ast->location, field.expr2, alloc->make(E, true), alloc->make(msg->location, msg)) */ } // Remove methods // TODO(dcunnin): this for _, field := range *fields { if !field.methodSugar { continue } /* field.expr2 = alloc->make( 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(field.expr1->location, field.expr1, nil) field.expr2 = alloc->make(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 }