blob: 1765ee8335aba55ab51bd066eb17463375d1bf7e [file] [log] [blame]
// Copyright 2018 The CUE Authors
//
// 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 parser
import (
"fmt"
"cuelang.org/go/cue/ast"
"cuelang.org/go/cue/token"
)
// TODO: use ast.Walk or adopt that version to allow visitors.
// A visitor's before method is invoked for each node encountered by Walk.
// If the result visitor w is not nil, Walk visits each of the children
// of node with the visitor w, followed by a call of w.After.
type visitor interface {
Before(node ast.Node) (w visitor)
After(node ast.Node)
}
// Helper functions for common node lists. They may be empty.
func walkIdentList(v visitor, list []*ast.Ident) {
for _, x := range list {
walk(v, x)
}
}
func walkExprList(v visitor, list []ast.Expr) {
for _, x := range list {
walk(v, x)
}
}
func walkDeclList(v visitor, list []ast.Decl) {
for _, x := range list {
walk(v, x)
}
}
// walk traverses an AST in depth-first order: It starts by calling
// v.Visit(node); node must not be nil. If the visitor w returned by
// v.Visit(node) is not nil, walk is invoked recursively with visitor
// w for each of the non-nil children of node, followed by a call of
// w.Visit(nil).
//
func walk(v visitor, node ast.Node) {
if v = v.Before(node); v == nil {
return
}
// TODO: record the comment groups and interleave with the values like for
// parsing and printing?
for _, c := range node.Comments() {
walk(v, c)
}
// walk children
// (the order of the cases matches the order
// of the corresponding node types in go)
switch n := node.(type) {
// Comments and fields
case *ast.Comment:
// nothing to do
case *ast.CommentGroup:
for _, c := range n.List {
walk(v, c)
}
case *ast.Field:
walk(v, n.Label)
if n.Value != nil {
walk(v, n.Value)
}
case *ast.LambdaExpr:
for _, p := range n.Params {
walk(v, p)
}
walk(v, n.Expr)
case *ast.StructLit:
for _, f := range n.Elts {
walk(v, f)
}
// Expressions
case *ast.BottomLit, *ast.BadExpr, *ast.Ident, *ast.BasicLit:
// nothing to do
case *ast.TemplateLabel:
walk(v, n.Ident)
case *ast.Interpolation:
for _, e := range n.Elts {
walk(v, e)
}
case *ast.Ellipsis:
if n.Elt != nil {
walk(v, n.Elt)
}
case *ast.ListLit:
walkExprList(v, n.Elts)
if n.Type != nil {
walk(v, n.Type)
}
case *ast.ParenExpr:
walk(v, n.X)
case *ast.SelectorExpr:
walk(v, n.X)
walk(v, n.Sel)
case *ast.IndexExpr:
walk(v, n.X)
walk(v, n.Index)
case *ast.SliceExpr:
walk(v, n.X)
if n.Low != nil {
walk(v, n.Low)
}
if n.High != nil {
walk(v, n.High)
}
case *ast.CallExpr:
walk(v, n.Fun)
walkExprList(v, n.Args)
case *ast.UnaryExpr:
walk(v, n.X)
case *ast.BinaryExpr:
walk(v, n.X)
walk(v, n.Y)
// Declarations
case *ast.ImportSpec:
if n.Name != nil {
walk(v, n.Name)
}
walk(v, n.Path)
case *ast.BadDecl:
// nothing to do
case *ast.ImportDecl:
for _, s := range n.Specs {
walk(v, s)
}
case *ast.EmitDecl:
walk(v, n.Expr)
case *ast.Alias:
walk(v, n.Ident)
walk(v, n.Expr)
case *ast.ComprehensionDecl:
walk(v, n.Field)
for _, c := range n.Clauses {
walk(v, c)
}
// Files and packages
case *ast.File:
if n.Name != nil {
walk(v, n.Name)
}
walkDeclList(v, n.Decls)
// don't walk n.Comments - they have been
// visited already through the individual
// nodes
case *ast.ListComprehension:
walk(v, n.Expr)
for _, c := range n.Clauses {
walk(v, c)
}
case *ast.ForClause:
if n.Key != nil {
walk(v, n.Key)
}
walk(v, n.Value)
walk(v, n.Source)
case *ast.IfClause:
walk(v, n.Condition)
default:
panic(fmt.Sprintf("Walk: unexpected node type %T", n))
}
v.After(node)
}
type inspector struct {
before func(ast.Node) bool
after func(ast.Node)
commentStack []commentFrame
current commentFrame
}
type commentFrame struct {
cg []*ast.CommentGroup
pos int8
}
func (f *inspector) Before(node ast.Node) visitor {
if f.before == nil || f.before(node) {
f.commentStack = append(f.commentStack, f.current)
f.current = commentFrame{cg: node.Comments()}
f.visitComments(f.current.pos)
return f
}
return nil
}
func (f *inspector) After(node ast.Node) {
f.visitComments(127)
p := len(f.commentStack) - 1
f.current = f.commentStack[p]
f.commentStack = f.commentStack[:p]
f.current.pos++
if f.after != nil {
f.after(node)
}
}
func (f *inspector) Token(t token.Token) {
f.current.pos++
}
func (f *inspector) setPos(i int8) {
f.current.pos = i
}
func (f *inspector) visitComments(pos int8) {
c := &f.current
for ; len(c.cg) > 0; c.cg = c.cg[1:] {
cg := c.cg[0]
if cg.Position == pos {
continue
}
if f.before == nil || f.before(cg) {
for _, c := range cg.List {
if f.before == nil || f.before(c) {
if f.after != nil {
f.after(c)
}
}
}
if f.after != nil {
f.after(cg)
}
}
}
}