parser/ast.go
// Copyright (c) 2020-2023 Ozan Hacıbekiroğlu.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
// Copyright (c) 2019 Daniel Kang.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE.tengo file.
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.golang file.
package parser
import (
"strings"
)
const (
nullRep = "<null>"
)
// Node represents a node in the AST.
type Node interface {
// Pos returns the position of first character belonging to the node.
Pos() Pos
// End returns the position of first character immediately after the node.
End() Pos
// String returns a string representation of the node.
String() string
}
// IdentList represents a list of identifiers.
type IdentList struct {
LParen Pos
VarArgs bool
List []*Ident
RParen Pos
}
// Pos returns the position of first character belonging to the node.
func (n *IdentList) Pos() Pos {
if n.LParen.IsValid() {
return n.LParen
}
if len(n.List) > 0 {
return n.List[0].Pos()
}
return NoPos
}
// End returns the position of first character immediately after the node.
func (n *IdentList) End() Pos {
if n.RParen.IsValid() {
return n.RParen + 1
}
if l := len(n.List); l > 0 {
return n.List[l-1].End()
}
return NoPos
}
// NumFields returns the number of fields.
func (n *IdentList) NumFields() int {
if n == nil {
return 0
}
return len(n.List)
}
func (n *IdentList) String() string {
var list []string
for i, e := range n.List {
if n.VarArgs && i == len(n.List)-1 {
list = append(list, "..."+e.String())
} else {
list = append(list, e.String())
}
}
return "(" + strings.Join(list, ", ") + ")"
}
// ----------------------------------------------------------------------------
// Comments
// A Comment node represents a single //-style or /*-style comment.
type Comment struct {
Slash Pos // position of "/" starting the comment
Text string // comment text (excluding '\n' for //-style comments)
}
// Pos returns the position of the comment's slash.
func (c *Comment) Pos() Pos { return c.Slash }
// End returns the position of first character immediately after the comment.
func (c *Comment) End() Pos {
return Pos(int(c.Slash) + len(c.Text))
}
// A CommentGroup represents a sequence of comments
// with no other tokens and no empty lines between.
type CommentGroup struct {
List []*Comment // len(List) > 0
}
// Pos returns the position of the first comment.
func (g *CommentGroup) Pos() Pos {
return g.List[0].Pos()
}
// End returns the position of last comment's end position.
func (g *CommentGroup) End() Pos {
return g.List[len(g.List)-1].End()
}
// Text returns the text of the comment.
// Comment markers (//, /*, and */), the first space of a line comment, and
// leading and trailing empty lines are removed.
// Multiple empty lines are reduced to one, and trailing space on lines is trimmed.
// Unless the result is empty, it is newline-terminated.
func (g *CommentGroup) Text() string {
if g == nil {
return ""
}
comments := make([]string, len(g.List))
for i, c := range g.List {
comments[i] = c.Text
}
lines := make([]string, 0, 10) // most comments are less than 10 lines
for _, c := range comments {
// Remove comment markers.
// The parser has given us exactly the comment text.
switch c[1] {
case '/':
//-style comment (no newline at the end)
c = c[2:]
if len(c) == 0 {
// empty line
break
}
if c[0] == ' ' {
// strip first space - required for Example tests
c = c[1:]
}
case '*':
/*-style comment */
c = c[2 : len(c)-2]
}
// Split on newlines.
cl := strings.Split(c, "\n")
// Walk lines, stripping trailing white space and adding to list.
for _, l := range cl {
lines = append(lines, stripTrailingWhitespace(l))
}
}
// Remove leading blank lines; convert runs of
// interior blank lines to a single blank line.
n := 0
for _, line := range lines {
if line != "" || n > 0 && lines[n-1] != "" {
lines[n] = line
n++
}
}
lines = lines[0:n]
// Add final "" entry to get trailing newline from Join.
if n > 0 && lines[n-1] != "" {
lines = append(lines, "")
}
return strings.Join(lines, "\n")
}
func isWhitespace(ch byte) bool {
return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'
}
func stripTrailingWhitespace(s string) string {
i := len(s)
for i > 0 && isWhitespace(s[i-1]) {
i--
}
return s[0:i]
}