123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366 |
- /*
- * EliasDB
- *
- * Copyright 2016 Matthias Ladkau. All rights reserved.
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
- */
- package parser
- import (
- "bytes"
- "fmt"
- "regexp"
- "strings"
- "text/template"
- "devt.de/krotik/common/errorutil"
- "devt.de/krotik/common/stringutil"
- )
- /*
- Map of pretty printer templates for AST nodes
- There is special treatment for NodeVALUE, NodeGET, NodeLOOKUP, NodeTRAVERSE,
- NodeFUNC, NodeSHOW, NodeSHOWTERM, NodeORDERING, NodeFILTERING, NodeWITH,
- NodeLPAREN, NodeRPAREN, NodeLBRACK and NodeRBRACK.
- */
- var prettyPrinterMap = map[string]*template.Template{
- NodeTRUE: template.Must(template.New(NodeTRUE).Parse("true")),
- NodeFALSE: template.Must(template.New(NodeFALSE).Parse("false")),
- NodeNULL: template.Must(template.New(NodeNULL).Parse("null")),
- NodeNULLTRAVERSAL + "_1": template.Must(template.New(NodeNULLTRAVERSAL).Parse("nulltraversal({{.c1}})")),
- // Special tokens - always handled in a denotation function
- NodeGROUP + "_1": template.Must(template.New(NodeGROUP).Parse("group {{.c1}}")),
- NodeEND: template.Must(template.New(NodeEND).Parse("end")),
- NodeAS + "_1": template.Must(template.New(NodeAS).Parse("as {{.c1}}")),
- NodeFORMAT + "_1": template.Must(template.New(NodeFORMAT).Parse("format {{.c1}}")),
- // Keywords
- NodeFROM + "_1": template.Must(template.New(NodeFROM).Parse("from {{.c1}}")),
- NodeWHERE + "_1": template.Must(template.New(NodeWHERE).Parse("where {{.c1}}")),
- NodeUNIQUE + "_1": template.Must(template.New(NodeUNIQUE).Parse("unique {{.c1}}")),
- NodeUNIQUECOUNT + "_1": template.Must(template.New(NodeUNIQUECOUNT).Parse("uniquecount {{.c1}}")),
- NodeISNOTNULL + "_1": template.Must(template.New(NodeISNOTNULL).Parse("isnotnull {{.c1}}")),
- NodeASCENDING + "_1": template.Must(template.New(NodeASCENDING).Parse("ascending {{.c1}}")),
- NodeDESCENDING + "_1": template.Must(template.New(NodeDESCENDING).Parse("descending {{.c1}}")),
- NodePRIMARY + "_1": template.Must(template.New(NodePRIMARY).Parse("primary {{.c1}}")),
- NodeLIST: template.Must(template.New(NodeLIST).Parse("list")),
- // Boolean operations
- NodeNOT + "_1": template.Must(template.New(NodeNOT).Parse("not {{.c1}}")),
- NodeGEQ + "_2": template.Must(template.New(NodeGEQ).Parse("{{.c1}} >= {{.c2}}")),
- NodeLEQ + "_2": template.Must(template.New(NodeLEQ).Parse("{{.c1}} <= {{.c2}}")),
- NodeNEQ + "_2": template.Must(template.New(NodeNEQ).Parse("{{.c1}} != {{.c2}}")),
- NodeEQ + "_2": template.Must(template.New(NodeEQ).Parse("{{.c1}} = {{.c2}}")),
- NodeGT + "_2": template.Must(template.New(NodeGT).Parse("{{.c1}} > {{.c2}}")),
- NodeLT + "_2": template.Must(template.New(NodeLT).Parse("{{.c1}} < {{.c2}}")),
- // List operations
- NodeIN + "_2": template.Must(template.New(NodeIN).Parse("{{.c1}} in {{.c2}}")),
- NodeNOTIN + "_2": template.Must(template.New(NodeNOTIN).Parse("{{.c1}} notin {{.c2}}")),
- // String operations
- NodeLIKE + "_2": template.Must(template.New(NodeLIKE).Parse("{{.c1}} like {{.c2}}")),
- NodeCONTAINS + "_2": template.Must(template.New(NodeCONTAINS).Parse("{{.c1}} contains {{.c2}}")),
- NodeBEGINSWITH + "_2": template.Must(template.New(NodeBEGINSWITH).Parse("{{.c1}} beginswith {{.c2}}")),
- NodeENDSWITH + "_2": template.Must(template.New(NodeENDSWITH).Parse("{{.c1}} endswith {{.c2}}")),
- NodeCONTAINSNOT + "_2": template.Must(template.New(NodeCONTAINSNOT).Parse("{{.c1}} containsnot {{.c2}}")),
- // Simple arithmetic expressions
- NodePLUS + "_2": template.Must(template.New(NodePLUS).Parse("{{.c1}} + {{.c2}}")),
- NodeMINUS + "_1": template.Must(template.New(NodeMINUS).Parse("-{{.c1}}")),
- NodeMINUS + "_2": template.Must(template.New(NodeMINUS).Parse("{{.c1}} - {{.c2}}")),
- NodeTIMES + "_2": template.Must(template.New(NodeTIMES).Parse("{{.c1}} * {{.c2}}")),
- NodeDIV + "_2": template.Must(template.New(NodeDIV).Parse("{{.c1}} / {{.c2}}")),
- NodeMODINT + "_2": template.Must(template.New(NodeMODINT).Parse("{{.c1}} % {{.c2}}")),
- NodeDIVINT + "_2": template.Must(template.New(NodeDIVINT).Parse("{{.c1}} // {{.c2}}")),
- }
- /*
- Map of nodes where the precedence might have changed because of parentheses
- */
- var bracketPrecedenceMap = map[string]bool{
- NodePLUS: true,
- NodeMINUS: true,
- NodeAND: true,
- NodeOR: true,
- }
- /*
- PrettyPrint produces a pretty printed EQL query from a given AST.
- */
- func PrettyPrint(ast *ASTNode) (string, error) {
- var visit func(ast *ASTNode, level int) (string, error)
- quoteValue := func(val string, allowNonQuotation bool) string {
- if val == "" {
- return `""`
- }
- isNumber, _ := regexp.MatchString("^[0-9][0-9\\.e-+]*$", val)
- isInlineString, _ := regexp.MatchString("^[a-zA-Z0-9_:.]*$", val)
- if allowNonQuotation && (isNumber || isInlineString) {
- return val
- } else if strings.ContainsRune(val, '"') {
- if strings.ContainsRune(val, '\'') {
- val = strings.Replace(val, "\"", "\\\"", -1)
- } else {
- return fmt.Sprintf("'%v'", val)
- }
- }
- return fmt.Sprintf("\"%v\"", val)
- }
- visit = func(ast *ASTNode, level int) (string, error) {
- // Handle special cases which don't have children
- if ast.Name == NodeVALUE || (ast.Name == NodeSHOWTERM && len(ast.Children) == 0) {
- return quoteValue(ast.Token.Val, true), nil
- }
- var children map[string]string
- var tempKey = ast.Name
- var buf bytes.Buffer
- // First pretty print children
- if len(ast.Children) > 0 {
- children = make(map[string]string)
- for i, child := range ast.Children {
- res, err := visit(child, level+1)
- if err != nil {
- return "", err
- }
- if _, ok := bracketPrecedenceMap[child.Name]; ok && ast.binding > child.binding {
- res = fmt.Sprintf("(%v)", res)
- }
- children[fmt.Sprint("c", i+1)] = res
- }
- tempKey += fmt.Sprint("_", len(children))
- }
- // Handle special cases requiring children
- if ast.Name == NodeLIST {
- buf.WriteString("[")
- if children != nil {
- i := 1
- for ; i < len(children); i++ {
- buf.WriteString(children[fmt.Sprint("c", i)])
- buf.WriteString(", ")
- }
- buf.WriteString(children[fmt.Sprint("c", i)])
- }
- buf.WriteString("]")
- return buf.String(), nil
- } else if ast.Name == NodeLOOKUP {
- buf.WriteString("lookup ")
- buf.WriteString(children["c1"])
- if 1 < len(children) {
- buf.WriteString(" ")
- }
- i := 1
- for ; i < len(children) && ast.Children[i].Name == NodeVALUE; i++ {
- buf.WriteString(quoteValue(ast.Children[i].Token.Val, false))
- if i < len(children)-1 && ast.Children[i+1].Name == NodeVALUE {
- buf.WriteString(", ")
- }
- }
- if i < len(children) {
- buf.WriteString(" ")
- }
- for ; i < len(children); i++ {
- buf.WriteString(children[fmt.Sprint("c", i+1)])
- if i < len(children)-1 && ast.Children[i+1].Name != NodeSHOW {
- buf.WriteString(" ")
- }
- }
- return buf.String(), nil
- } else if ast.Name == NodeGET {
- buf.WriteString("get ")
- buf.WriteString(children["c1"])
- if 1 < len(children) {
- buf.WriteString(" ")
- }
- for i := 1; i < len(children); i++ {
- buf.WriteString(children[fmt.Sprint("c", i+1)])
- if i < len(children)-1 && ast.Children[i+1].Name != NodeSHOW {
- buf.WriteString(" ")
- }
- }
- return buf.String(), nil
- } else if ast.Name == NodeTRAVERSE {
- buf.WriteString("\n")
- buf.WriteString(stringutil.GenerateRollingString(" ", level*2))
- buf.WriteString("traverse ")
- for i := 0; i < len(children); i++ {
- buf.WriteString(children[fmt.Sprint("c", i+1)])
- if i < len(children)-1 {
- buf.WriteString(" ")
- }
- }
- buf.WriteString("\n")
- buf.WriteString(stringutil.GenerateRollingString(" ", level*2))
- buf.WriteString("end")
- return buf.String(), nil
- } else if ast.Name == NodeFUNC {
- buf.WriteString("@")
- buf.WriteString(children["c1"])
- buf.WriteString("(")
- for i := 1; i < len(children); i++ {
- buf.WriteString(children[fmt.Sprint("c", i+1)])
- if i < len(children)-1 {
- buf.WriteString(", ")
- }
- }
- buf.WriteString(")")
- return buf.String(), nil
- } else if ast.Name == NodeSHOW {
- buf.WriteString("\nshow\n ")
- for i := 0; i < len(children); i++ {
- buf.WriteString(children[fmt.Sprint("c", i+1)])
- if i < len(children)-1 {
- buf.WriteString(",\n ")
- }
- }
- return buf.String(), nil
- } else if ast.Name == NodeSHOWTERM {
- if ast.Token.Val != "" && ast.Token.Val != "@" {
- buf.WriteString(quoteValue(ast.Token.Val, true))
- buf.WriteString(" ")
- }
- for i := 0; i < len(children); i++ {
- buf.WriteString(children[fmt.Sprint("c", i+1)])
- if i < len(children)-1 {
- buf.WriteString(" ")
- }
- }
- return buf.String(), nil
- } else if ast.Name == NodeORDERING {
- buf.WriteString("ordering")
- buf.WriteString("(")
- for i := 0; i < len(children); i++ {
- buf.WriteString(children[fmt.Sprint("c", i+1)])
- if i < len(children)-1 {
- buf.WriteString(", ")
- }
- }
- buf.WriteString(")")
- return buf.String(), nil
- } else if ast.Name == NodeFILTERING {
- buf.WriteString("filtering")
- buf.WriteString("(")
- for i := 0; i < len(children); i++ {
- buf.WriteString(children[fmt.Sprint("c", i+1)])
- if i < len(children)-1 {
- buf.WriteString(", ")
- }
- }
- buf.WriteString(")")
- return buf.String(), nil
- } else if ast.Name == NodeWITH {
- buf.WriteString("\nwith\n")
- for i := 0; i < len(children); i++ {
- buf.WriteString(" ")
- buf.WriteString(children[fmt.Sprint("c", i+1)])
- if i < len(children)-1 {
- buf.WriteString(",\n")
- }
- }
- return buf.String(), nil
- } else if ast.Name == NodeAND || ast.Name == NodeOR {
- for i := 0; i < len(children); i++ {
- buf.WriteString(children[fmt.Sprint("c", i+1)])
- if i < len(children)-1 {
- buf.WriteString(" ")
- buf.WriteString(strings.ToLower(ast.Token.Val))
- buf.WriteString(" ")
- }
- }
- return buf.String(), nil
- }
- // Retrieve the template
- temp, ok := prettyPrinterMap[tempKey]
- if !ok {
- return "", fmt.Errorf("Could not find template for %v (tempkey: %v)",
- ast.Name, tempKey)
- }
- // Use the children as parameters for template
- errorutil.AssertOk(temp.Execute(&buf, children))
- return buf.String(), nil
- }
- return visit(ast, 0)
- }
|