context.go raw

   1  package parser
   2  
   3  import (
   4  	"fmt"
   5  	"strings"
   6  
   7  	"github.com/goccy/go-yaml/token"
   8  )
   9  
  10  // context context at parsing
  11  type context struct {
  12  	parent *context
  13  	idx    int
  14  	size   int
  15  	tokens token.Tokens
  16  	mode   Mode
  17  	path   string
  18  }
  19  
  20  var pathSpecialChars = []string{
  21  	"$", "*", ".", "[", "]",
  22  }
  23  
  24  func containsPathSpecialChar(path string) bool {
  25  	for _, char := range pathSpecialChars {
  26  		if strings.Contains(path, char) {
  27  			return true
  28  		}
  29  	}
  30  	return false
  31  }
  32  
  33  func normalizePath(path string) string {
  34  	if containsPathSpecialChar(path) {
  35  		return fmt.Sprintf("'%s'", path)
  36  	}
  37  	return path
  38  }
  39  
  40  func (c *context) withChild(path string) *context {
  41  	ctx := c.copy()
  42  	path = normalizePath(path)
  43  	ctx.path += fmt.Sprintf(".%s", path)
  44  	return ctx
  45  }
  46  
  47  func (c *context) withIndex(idx uint) *context {
  48  	ctx := c.copy()
  49  	ctx.path += fmt.Sprintf("[%d]", idx)
  50  	return ctx
  51  }
  52  
  53  func (c *context) copy() *context {
  54  	return &context{
  55  		parent: c,
  56  		idx:    c.idx,
  57  		size:   c.size,
  58  		tokens: append(token.Tokens{}, c.tokens...),
  59  		mode:   c.mode,
  60  		path:   c.path,
  61  	}
  62  }
  63  
  64  func (c *context) next() bool {
  65  	return c.idx < c.size
  66  }
  67  
  68  func (c *context) previousToken() *token.Token {
  69  	if c.idx > 0 {
  70  		return c.tokens[c.idx-1]
  71  	}
  72  	return nil
  73  }
  74  
  75  func (c *context) insertToken(idx int, tk *token.Token) {
  76  	if c.parent != nil {
  77  		c.parent.insertToken(idx, tk)
  78  	}
  79  	if c.size < idx {
  80  		return
  81  	}
  82  	if c.size == idx {
  83  		curToken := c.tokens[c.size-1]
  84  		tk.Next = curToken
  85  		curToken.Prev = tk
  86  
  87  		c.tokens = append(c.tokens, tk)
  88  		c.size = len(c.tokens)
  89  		return
  90  	}
  91  
  92  	curToken := c.tokens[idx]
  93  	tk.Next = curToken
  94  	curToken.Prev = tk
  95  
  96  	c.tokens = append(c.tokens[:idx+1], c.tokens[idx:]...)
  97  	c.tokens[idx] = tk
  98  	c.size = len(c.tokens)
  99  }
 100  
 101  func (c *context) currentToken() *token.Token {
 102  	if c.idx >= c.size {
 103  		return nil
 104  	}
 105  	return c.tokens[c.idx]
 106  }
 107  
 108  func (c *context) nextToken() *token.Token {
 109  	if c.idx+1 >= c.size {
 110  		return nil
 111  	}
 112  	return c.tokens[c.idx+1]
 113  }
 114  
 115  func (c *context) afterNextToken() *token.Token {
 116  	if c.idx+2 >= c.size {
 117  		return nil
 118  	}
 119  	return c.tokens[c.idx+2]
 120  }
 121  
 122  func (c *context) nextNotCommentToken() *token.Token {
 123  	for i := c.idx + 1; i < c.size; i++ {
 124  		tk := c.tokens[i]
 125  		if tk.Type == token.CommentType {
 126  			continue
 127  		}
 128  		return tk
 129  	}
 130  	return nil
 131  }
 132  
 133  func (c *context) afterNextNotCommentToken() *token.Token {
 134  	notCommentTokenCount := 0
 135  	for i := c.idx + 1; i < c.size; i++ {
 136  		tk := c.tokens[i]
 137  		if tk.Type == token.CommentType {
 138  			continue
 139  		}
 140  		notCommentTokenCount++
 141  		if notCommentTokenCount == 2 {
 142  			return tk
 143  		}
 144  	}
 145  	return nil
 146  }
 147  
 148  func (c *context) enabledComment() bool {
 149  	return c.mode&ParseComments != 0
 150  }
 151  
 152  func (c *context) isCurrentCommentToken() bool {
 153  	tk := c.currentToken()
 154  	if tk == nil {
 155  		return false
 156  	}
 157  	return tk.Type == token.CommentType
 158  }
 159  
 160  func (c *context) progressIgnoreComment(num int) {
 161  	if c.parent != nil {
 162  		c.parent.progressIgnoreComment(num)
 163  	}
 164  	if c.size <= c.idx+num {
 165  		c.idx = c.size
 166  	} else {
 167  		c.idx += num
 168  	}
 169  }
 170  
 171  func (c *context) progress(num int) {
 172  	if c.isCurrentCommentToken() {
 173  		return
 174  	}
 175  	c.progressIgnoreComment(num)
 176  }
 177  
 178  func newContext(tokens token.Tokens, mode Mode) *context {
 179  	filteredTokens := []*token.Token{}
 180  	if mode&ParseComments != 0 {
 181  		filteredTokens = tokens
 182  	} else {
 183  		for _, tk := range tokens {
 184  			if tk.Type == token.CommentType {
 185  				continue
 186  			}
 187  			// keep prev/next reference between tokens containing comments
 188  			// https://github.com/goccy/go-yaml/issues/254
 189  			filteredTokens = append(filteredTokens, tk)
 190  		}
 191  	}
 192  	return &context{
 193  		idx:    0,
 194  		size:   len(filteredTokens),
 195  		tokens: token.Tokens(filteredTokens),
 196  		mode:   mode,
 197  		path:   "$",
 198  	}
 199  }
 200