chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:32:25 +08:00
commit e014feafe1
2285 changed files with 1131979 additions and 0 deletions
+299
View File
@@ -0,0 +1,299 @@
// Copyright 2023-2024 Dolthub, Inc.
//
// 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 utils
import (
"github.com/cockroachdb/errors"
"github.com/dolthub/doltgresql/utils"
)
// StatementGeneratorStackElement represents an element within the StatementGeneratorStack.
type StatementGeneratorStackElement struct {
depth int
generators []StatementGenerator
}
// StatementGeneratorStack handles the creation of a StatementGenerator by contextually applying operations based on the
// current state of the internal stack whenever calls are made.
type StatementGeneratorStack struct {
stack *utils.Stack[*StatementGeneratorStackElement]
depth int
}
// NewStatementGeneratorStack returns a new *StatementGeneratorStack.
func NewStatementGeneratorStack() *StatementGeneratorStack {
sgs := &StatementGeneratorStack{
stack: utils.NewStack[*StatementGeneratorStackElement](),
depth: 0,
}
sgs.stack.Push(NewStatementGeneratorStackElement(0))
return sgs
}
// NewStatementGeneratorStackElement returns a new *StatementGeneratorStackElement.
func NewStatementGeneratorStackElement(depth int, gens ...StatementGenerator) *StatementGeneratorStackElement {
return &StatementGeneratorStackElement{
depth: depth,
generators: gens,
}
}
// AddText creates a new TextGen at the current depth.
func (sgs *StatementGeneratorStack) AddText(text string) {
sgs.stack.Peek().Append(Text(text))
}
// AddVariable creates a new VariableGen at the current depth.
func (sgs *StatementGeneratorStack) AddVariable(name string) {
sgs.stack.Peek().Append(Variable(name, nil))
}
// Or will take all items from the current depth and add them to a parent OrGen. Either the previous depth is an OrGen,
// or the stack will increment the sub depth to insert an OrGen.
func (sgs *StatementGeneratorStack) Or() error {
if sgs.stack.Empty() {
return errors.Errorf("cannot apply Or to an empty stack")
}
parentElement := sgs.stack.PeekDepth(1)
current := sgs.aggregate(sgs.stack.Pop().generators)
if parentElement == nil {
// We're the root, so we put an OrGen as the root, and make the current a child of the new OrGen
sgs.stack.Push(NewStatementGeneratorStackElement(sgs.depth, Or(current)))
sgs.stack.Push(NewStatementGeneratorStackElement(sgs.depth))
} else if len(parentElement.generators) == 0 {
if parentElement.depth == sgs.depth {
// We're still at the same depth, so it's safe to append an OrGen
parentElement.Append(Or(current))
} else {
// We should retain the depth, so we need to create a new element since there's a depth boundary
sgs.stack.Push(NewStatementGeneratorStackElement(sgs.depth, Or(current)))
}
sgs.stack.Push(NewStatementGeneratorStackElement(sgs.depth))
} else {
if parentElement.depth == sgs.depth {
// We're still at the same depth, so we need to check if the parent is an OrGen or not
if orGen, ok := parentElement.LastGenerator().(*OrGen); ok {
// The parent is an OrGen, so we can simply add this as another option
if err := orGen.AddChildren(current); err != nil {
return err
}
} else {
// The parent is not an OrGen, so we need to add to the depth
sgs.stack.Push(NewStatementGeneratorStackElement(sgs.depth, Or(current)))
}
} else {
// We should retain the depth, so we need to create a new element since there's a depth boundary
sgs.stack.Push(NewStatementGeneratorStackElement(sgs.depth, Or(current)))
}
sgs.stack.Push(NewStatementGeneratorStackElement(sgs.depth))
}
return nil
}
// NewScope increases the depth.
func (sgs *StatementGeneratorStack) NewScope() {
sgs.depth++
sgs.stack.Push(NewStatementGeneratorStackElement(sgs.depth))
}
// NewOptionalScope increases the depth, creating an OptionalGen at the depth root, and adding elements to the sub depth.
func (sgs *StatementGeneratorStack) NewOptionalScope() {
sgs.depth++
sgs.stack.Push(NewStatementGeneratorStackElement(sgs.depth, Optional()))
sgs.stack.Push(NewStatementGeneratorStackElement(sgs.depth))
}
// NewParenScope increases the depth, creating a CollectionGen at the depth root, adding Text("(") to the CollectionGen,
// and adding any new elements to the sub depth.
func (sgs *StatementGeneratorStack) NewParenScope() {
sgs.depth++
sgs.stack.Push(NewStatementGeneratorStackElement(sgs.depth, Collection(Text("("))))
sgs.stack.Push(NewStatementGeneratorStackElement(sgs.depth))
}
// ExitScope decrements the depth, writing all elements from the current depth (and all sub depths) to the preceding
// depth.
func (sgs *StatementGeneratorStack) ExitScope() error {
defer func() {
sgs.depth--
}()
current := sgs.aggregate(sgs.stack.Pop().generators)
for parentElement := sgs.stack.Peek(); parentElement.depth == sgs.depth; parentElement = sgs.stack.Peek() {
if orGen, ok := parentElement.LastGenerator().(*OrGen); ok {
if err := orGen.AddChildren(current); err != nil {
return err
}
} else {
parentElement.Append(current)
}
current = sgs.aggregate(sgs.stack.Pop().generators)
}
sgs.stack.Peek().Append(current)
return nil
}
// ExitOptionalScope decrements the depth, writing all elements from the current depth (and all sub depths) to the
// preceding depth. This will fail if the root of the current depth is not an OptionalGen.
func (sgs *StatementGeneratorStack) ExitOptionalScope() error {
defer func() {
sgs.depth--
}()
current := sgs.aggregate(sgs.stack.Pop().generators)
for parentElement := sgs.stack.Peek(); sgs.stack.PeekDepth(1).depth == sgs.depth; parentElement = sgs.stack.Peek() {
if orGen, ok := parentElement.LastGenerator().(*OrGen); ok {
if err := orGen.AddChildren(current); err != nil {
return err
}
} else {
parentElement.Append(current)
}
current = sgs.aggregate(sgs.stack.Pop().generators)
}
optionalElement := sgs.stack.Pop()
if optionalElement.depth != sgs.depth {
return errors.Errorf("internal bookkeeping error, attempted to exit from optional scope but the depth is incorrect")
}
if optionalGen, ok := optionalElement.LastGenerator().(*OptionalGen); ok {
if err := optionalGen.AddChildren(current); err != nil {
return err
}
} else {
return errors.Errorf("internal bookkeeping error, attempted to exit from optional scope but missing OptionalGen")
}
sgs.stack.Peek().Append(sgs.aggregate(optionalElement.generators))
return nil
}
// ExitParenScope decrements the depth, writing all elements from the current depth (and all sub depths) to the
// preceding depth, while appending an ending Text(")").
func (sgs *StatementGeneratorStack) ExitParenScope() error {
defer func() {
sgs.depth--
}()
current := sgs.aggregate(sgs.stack.Pop().generators)
for parentElement := sgs.stack.Peek(); sgs.stack.PeekDepth(1).depth == sgs.depth; parentElement = sgs.stack.Peek() {
if orGen, ok := parentElement.LastGenerator().(*OrGen); ok {
if err := orGen.AddChildren(current); err != nil {
return err
}
} else {
parentElement.Append(current)
}
current = sgs.aggregate(sgs.stack.Pop().generators)
}
collectionElement := sgs.stack.Pop()
if collectionElement.depth != sgs.depth {
return errors.Errorf("internal bookkeeping error, attempted to exit from paren scope but the depth is incorrect")
}
if collectionGen, ok := collectionElement.LastGenerator().(*CollectionGen); ok {
if err := collectionGen.AddChildren(current, Text(")")); err != nil {
return err
}
} else {
return errors.Errorf("internal bookkeeping error, attempted to exit from paren scope but missing CollectionGen")
}
sgs.stack.Peek().Append(sgs.aggregate(collectionElement.generators))
return nil
}
// Repeat will add the last StatementGenerator at the current depth to an OptionalGen.
func (sgs *StatementGeneratorStack) Repeat() error {
current := sgs.stack.Peek()
lastGen := current.LastGenerator()
if lastGen == nil {
return errors.Errorf("unable to repeat as no generators exist at the current depth")
}
current.Append(Optional(lastGen.Copy()))
return nil
}
// OptionalRepeat will add the last StatementGenerator at the current depth to an OptionalGen. If a prefix is given,
// then it will be added as a TextGen before the repeated generator.
func (sgs *StatementGeneratorStack) OptionalRepeat(prefix string) error {
current := sgs.stack.Peek()
lastGen := current.LastGenerator()
if lastGen == nil {
return errors.Errorf("unable to optionally repeat as no generators exist at the current depth")
}
if len(prefix) > 0 {
current.Append(Optional(Collection(Text(prefix), lastGen.Copy())))
} else {
current.Append(Optional(lastGen.Copy()))
}
return nil
}
// Finish returns a StatementGenerator containing all of the generators that have been added. This returns an error if
// the stack is not at the root depth, as that indicates a missing scope exit, or too many scope entries.
func (sgs *StatementGeneratorStack) Finish() (StatementGenerator, error) {
if sgs.depth != 0 {
if sgs.depth < 0 {
return nil, errors.Errorf("depth is invalid, too many scope exits")
} else {
return nil, errors.Errorf("depth is invalid, too many scope entries")
}
} else if !sgs.stack.Empty() && sgs.stack.Peek().depth != 0 {
return nil, errors.Errorf("internal bookkeeping error, stack depth does not match handle depth")
}
if sgs.stack.Len() == 1 && len(sgs.stack.Peek().generators) == 0 {
return nil, nil
}
var lastDepth []StatementGenerator
for !sgs.stack.Empty() {
currentDepth := sgs.stack.Pop()
if len(currentDepth.generators) == 0 {
return nil, errors.Errorf("internal bookkeeping error, stack has a depth with no generators")
}
if lastGen := currentDepth.LastGenerator(); lastGen != nil {
if orGen, ok := lastGen.(*OrGen); ok {
if err := orGen.AddChildren(sgs.aggregate(lastDepth)); err != nil {
return nil, err
}
} else {
currentDepth.Append(sgs.aggregate(lastDepth))
}
} else {
currentDepth.Append(sgs.aggregate(lastDepth))
}
lastDepth = currentDepth.generators
}
return sgs.aggregate(lastDepth), nil
}
// aggregate returns an aggregate of the given generators. Returns nil if the slice is empty.
func (sgs *StatementGeneratorStack) aggregate(gens []StatementGenerator) StatementGenerator {
gens = removeNilGenerators(gens)
if len(gens) == 0 {
return nil
} else if len(gens) == 1 {
return gens[0]
} else {
return Collection(gens...)
}
}
// LastGenerator returns the last StatementGenerator contained within its slice.
func (sgse *StatementGeneratorStackElement) LastGenerator() StatementGenerator {
if sgse == nil || len(sgse.generators) == 0 {
return nil
}
return sgse.generators[len(sgse.generators)-1]
}
// Append adds the given child to this element's collection.
func (sgse *StatementGeneratorStackElement) Append(child StatementGenerator) {
sgse.generators = append(sgse.generators, child)
}
+82
View File
@@ -0,0 +1,82 @@
// Copyright 2023-2024 Dolthub, Inc.
//
// 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 utils
import (
"crypto/rand"
"math"
"math/big"
"sort"
)
var (
BigIntZero = big.NewInt(0)
BigIntOne = big.NewInt(1)
BigIntTwo = big.NewInt(2)
BigIntMaxInt64 = big.NewInt(math.MaxInt64)
BigIntMaxUint64 = new(big.Int).Add(new(big.Int).Mul(BigIntMaxInt64, BigIntTwo), BigIntOne)
)
// GenerateRandomInts generates a slice of random integers, with each integer ranging from [0, max). The returned slice
// will be sorted from smallest to largest. If count <= 0 or max <= 0, then they will be set to 1. If count >= max, then
// the returned slice will contain all incrementing integers [0, max).
func GenerateRandomInts(count int64, max *big.Int) (randInts []*big.Int, err error) {
if count <= 0 {
count = 1
}
if max.Cmp(BigIntZero) == -1 {
max = BigIntOne
}
// If count >= max, then we'll just shortcut and add incrementing integers up to the max (not including the max)
if big.NewInt(count).Cmp(max) >= 0 {
max64 := max.Int64()
randInts = make([]*big.Int, max64)
for i := int64(0); i < max64; i++ {
randInts[i] = big.NewInt(i)
}
return randInts, nil
}
randInts = make([]*big.Int, count)
randIntSet := make(map[string]struct{}, count*2)
for i := range randInts {
for {
randInts[i], err = rand.Int(rand.Reader, max)
if err != nil {
return nil, err
}
if _, ok := randIntSet[randInts[i].String()]; !ok {
randIntSet[randInts[i].String()] = struct{}{}
break
}
}
}
sort.Slice(randInts, func(i, j int) bool {
return randInts[i].Cmp(randInts[j]) == -1
})
return randInts, nil
}
// GetPercentages converts the slice of numbers to percentages. The max defines the number that would equal 100%. All
// floats will be between [0.0, 100.0], unless the number is not between [0, max].
func GetPercentages(numbers []*big.Int, max *big.Int) []float64 {
maxAsFloat, _ := max.Float64()
percentages := make([]float64, len(numbers))
for i, number := range numbers {
numberAsFloat, _ := number.Float64()
percentages[i] = (numberAsFloat / maxAsFloat) * 100.0
}
return percentages
}
+124
View File
@@ -0,0 +1,124 @@
// Copyright 2023-2024 Dolthub, Inc.
//
// 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 utils
import "github.com/cockroachdb/errors"
// ParseTokens parses the given tokens into a StatementGenerator.
func ParseTokens(tokens []Token, includeRepetition bool) (StatementGenerator, error) {
stack := NewStatementGeneratorStack()
var statements []StatementGenerator
variables := make(map[string]StatementGenerator)
currentVariable := ""
tokenReader := NewTokenReader(tokens)
ForLoop:
for {
token, ok := tokenReader.Next()
if !ok {
break ForLoop
}
switch token.Type {
case TokenType_Text:
stack.AddText(token.Literal)
case TokenType_Variable:
stack.AddVariable(token.Literal)
case TokenType_VariableDefinition:
currentVariable = token.Literal
if token, _ = tokenReader.Next(); token.Type != TokenType_LongSpace {
return nil, errors.Errorf("expected a long space after a variable definition declaration")
}
case TokenType_Or:
if err := stack.Or(); err != nil {
return nil, err
}
case TokenType_Repeat:
if includeRepetition {
if err := stack.Repeat(); err != nil {
return nil, err
}
}
case TokenType_OptionalRepeat:
if includeRepetition {
if err := stack.OptionalRepeat(token.Literal); err != nil {
return nil, err
}
}
case TokenType_ShortSpace, TokenType_MediumSpace:
return nil, errors.Errorf("token reader should have removed all short and medium spaces")
case TokenType_LongSpace, TokenType_EOF:
newStatement, err := stack.Finish()
if err != nil {
return nil, err
}
if newStatement == nil {
return nil, errors.Errorf("long space encountered before writing to the stack")
}
if len(currentVariable) > 0 {
if _, ok = variables[currentVariable]; ok {
return nil, errors.Errorf("multiple definitions for the same variable: %s", currentVariable)
}
variables[currentVariable] = newStatement
currentVariable = ""
} else {
statements = append(statements, newStatement)
}
if token.Type == TokenType_EOF {
break ForLoop
} else {
stack = NewStatementGeneratorStack()
}
case TokenType_ParenOpen:
stack.NewParenScope()
case TokenType_ParenClose:
if err := stack.ExitParenScope(); err != nil {
return nil, err
}
case TokenType_OptionalOpen:
stack.NewOptionalScope()
case TokenType_OptionalClose:
if err := stack.ExitOptionalScope(); err != nil {
return nil, err
}
case TokenType_OneOfOpen:
stack.NewScope()
case TokenType_OneOfClose:
if err := stack.ExitScope(); err != nil {
return nil, err
}
default:
panic("unhandled token type")
}
}
finalStackContents, err := stack.Finish()
if err != nil {
return nil, err
}
if finalStackContents != nil {
return nil, errors.Errorf("encountered an early EOF, as the stack was still processing")
}
if len(statements) == 0 {
return nil, errors.Errorf("no statements were generated from the token stream")
}
var finalStatementGenerator StatementGenerator
if len(statements) == 1 {
finalStatementGenerator = statements[0]
} else {
finalStatementGenerator = Or(statements...)
}
if err = ApplyVariableDefinition(finalStatementGenerator, variables); err != nil {
return nil, err
}
return finalStatementGenerator, nil
}
+80
View File
@@ -0,0 +1,80 @@
// Copyright 2024 Dolthub, Inc.
//
// 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 utils
import (
"os"
"path/filepath"
"runtime"
"github.com/cockroachdb/errors"
)
// RootFolderLocation is the location of this project's root folder.
type RootFolderLocation struct {
path string
}
// GetRootFolder returns the location of this project's root folder. This is useful to locate relative directories,
// which will often be read from and written to for generation. It is assumed that this will always be called from
// within an IDE.
func GetRootFolder() (RootFolderLocation, error) {
_, currentFileLocation, _, ok := runtime.Caller(0)
if !ok {
return RootFolderLocation{}, errors.Errorf("failed to fetch the location of the current file")
}
return RootFolderLocation{filepath.Clean(filepath.Join(filepath.Dir(currentFileLocation), "../../.."))}, nil
}
// MoveRoot returns a new RootFolderLocation that defines the root at the new path. The parameter should be relative to
// the current root.
func (root RootFolderLocation) MoveRoot(relativePath string) RootFolderLocation {
return RootFolderLocation{filepath.Clean(filepath.Join(root.path, relativePath))}
}
// GetAbsolutePath returns the absolute path of the given path, which should be relative to the project's root
// folder.
func (root RootFolderLocation) GetAbsolutePath(relativePath string) string {
return filepath.ToSlash(filepath.Join(root.path, relativePath))
}
// ReadDir is equivalent to os.ReadDir, except that it uses the root path and the given relative path.
func (root RootFolderLocation) ReadDir(relativePath string) ([]os.DirEntry, error) {
return os.ReadDir(filepath.ToSlash(filepath.Join(root.path, relativePath)))
}
// ReadFile is equivalent to os.ReadFile, except that it uses the root path and the given relative path.
func (root RootFolderLocation) ReadFile(relativePath string) ([]byte, error) {
return os.ReadFile(filepath.ToSlash(filepath.Join(root.path, relativePath)))
}
// ReadFileFromDirectory is similar to ReadFile, except that it takes in a relative path for the directory containing
// the file, along with the file name to be read. This is purely for convenience, as the same behavior can be
// accomplished using ReadFile.
func (root RootFolderLocation) ReadFileFromDirectory(relativeDirectoryPath string, fileName string) ([]byte, error) {
return os.ReadFile(filepath.ToSlash(filepath.Join(root.path, relativeDirectoryPath, fileName)))
}
// WriteFile is equivalent to os.WriteFile, except that it uses the root path and the given relative path.
func (root RootFolderLocation) WriteFile(relativePath string, data []byte, perm os.FileMode) error {
return os.WriteFile(filepath.ToSlash(filepath.Join(root.path, relativePath)), data, perm)
}
// WriteFileToDirectory is similar to WriteFile, except that it takes in a relative path for the directory containing
// the file, along with the file name to be written. This is purely for convenience, as the same behavior can be
// accomplished using WriteFile.
func (root RootFolderLocation) WriteFileToDirectory(relativeDirectoryPath string, fileName string, data []byte, perm os.FileMode) error {
return os.WriteFile(filepath.ToSlash(filepath.Join(root.path, relativeDirectoryPath, fileName)), data, perm)
}
@@ -0,0 +1,42 @@
// Copyright 2023-2024 Dolthub, Inc.
//
// 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 utils
import (
"fmt"
"strings"
)
// SectionMarker returns a marker that may be used to denote sections.
//
// For example, SectionMarker("abc", '-', 21) would return:
//
// -------- abc --------
func SectionMarker(centeredText string, fillerCharacter rune, totalLength int) string {
fillerStr := string(fillerCharacter)
remainingLength := totalLength - (len(centeredText) + 2)
if remainingLength <= 0 {
return fmt.Sprintf(" %s ", centeredText)
}
left := remainingLength / 2
right := remainingLength - left // Integer division doesn't do fractions, so this will handle odd counts
return fmt.Sprintf("%s %s %s",
strings.Repeat(fillerStr, left), centeredText, strings.Repeat(fillerStr, right))
}
// PrintSectionMarker is a convenience function that prints the result of SectionMarker with a newline appended.
func PrintSectionMarker(centeredText string, fillerCharacter rune, totalLength int) {
fmt.Println(SectionMarker(centeredText, fillerCharacter, totalLength))
}
@@ -0,0 +1,766 @@
// Copyright 2023-2024 Dolthub, Inc.
//
// 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 utils
import (
"fmt"
"math"
"math/big"
"sort"
"strings"
)
// StatementGenerator represents a statement, and is able to produce all valid variations of the statement.
type StatementGenerator interface {
// AddChildren adds the given children to the generator. Not all generators accept all children, so this may error.
AddChildren(child ...StatementGenerator) error
// Consume returns true when the generator is able to produce a unique mutation, and false if it is not. Only one
// generator should mutate per call, meaning a parent generator should only mutate when its children return false.
// If the top-level generator returns false, then all permutations have been created.
Consume() bool
// SetConsumeIterations is equivalent to calling Copy then Consume the given number of times, without allocating a
// new StatementGenerator. This allows you to generate a specific statement efficiently, rather than calling Consume
// the given number of times. If the count is <= 0, then the statement will be in its original state (the same state
// as a StatementGenerator copy).
SetConsumeIterations(count *big.Int)
// SetConsumeIterationsFast is the same as SetConsumeIterations, except far more efficient due to using uint64,
// however it only works for iteration counts <= MAX_SIZE(uint64).
SetConsumeIterationsFast(count uint64)
// String returns a string based on the current permutation.
String() string
// Copy returns a copy of the given generator (along with all of its children) in its original setting. This means
// that the copy is in the same state that the target would be in if it had never called Consume.
Copy() StatementGenerator
// Reset sets the StatementGenerator back to its original state, which would be as though Consume was never called.
// This is equivalent to calling SetConsumeIterations(0), albeit slightly more efficient.
Reset()
// SourceString returns a string that may be used to recreate the StatementGenerator in a Go source file.
SourceString() string
// Permutations returns the number of unique permutations that the generator can return.
Permutations() *big.Int
// PermutationsUint64 returns the number of unique permutations that the generator can return. Returns true if the
// number fits within an uint64, false if it's larger than an uint64.
PermutationsUint64() (uint64, bool)
}
// TextGen is a generator that returns a simple string.
type TextGen string
var _ StatementGenerator = (*TextGen)(nil)
// Text creates a new StatementGenerator representing a simple string.
func Text(str string) *TextGen {
gen := TextGen(str)
return &gen
}
// AddChildren implements the interface StatementGenerator.
func (t *TextGen) AddChildren(children ...StatementGenerator) error {
return fmt.Errorf("text cannot have children")
}
// Consume implements the interface StatementGenerator.
func (t *TextGen) Consume() bool {
return false
}
// SetConsumeIterations implements the interface StatementGenerator.
func (t *TextGen) SetConsumeIterations(count *big.Int) {}
// SetConsumeIterationsFast implements the interface StatementGenerator.
func (t *TextGen) SetConsumeIterationsFast(count uint64) {}
// Copy implements the interface StatementGenerator.
func (t *TextGen) Copy() StatementGenerator {
if t == nil {
return nil
}
return Text(string(*t))
}
// String implements the interface StatementGenerator.
func (t *TextGen) String() string {
return string(*t)
}
// Reset implements the interface StatementGenerator.
func (t *TextGen) Reset() {}
// SourceString implements the interface StatementGenerator.
func (t *TextGen) SourceString() string {
return fmt.Sprintf(`Text("%s")`, string(*t))
}
// Permutations implements the interface StatementGenerator.
func (t *TextGen) Permutations() *big.Int {
return BigIntOne
}
// PermutationsUint64 implements the interface StatementGenerator.
func (t *TextGen) PermutationsUint64() (uint64, bool) {
return 1, true
}
// OrGen is a generator that contains multiple child generators, and will print only one at a time. Consuming will
// cycle to the next child.
type OrGen struct {
children []StatementGenerator
index int
localInt *big.Int
}
var _ StatementGenerator = (*OrGen)(nil)
// Or creates a new StatementGenerator representing an OrGen.
func Or(children ...StatementGenerator) *OrGen {
return &OrGen{
children: copyGenerators(children),
index: 0,
localInt: new(big.Int),
}
}
// AddChildren implements the interface StatementGenerator.
func (o *OrGen) AddChildren(children ...StatementGenerator) error {
o.children = append(o.children, copyGenerators(children)...)
return nil
}
// Consume implements the interface StatementGenerator.
func (o *OrGen) Consume() bool {
if len(o.children) == 0 {
return false
}
if o.children[o.index].Consume() {
return true
}
o.index++
if o.index >= len(o.children) {
o.index = 0
return false
}
return true
}
// SetConsumeIterations implements the interface StatementGenerator.
func (o *OrGen) SetConsumeIterations(count *big.Int) {
// If we're given zero, then we'll just call Reset
if count.Cmp(BigIntZero) <= 0 {
o.Reset()
return
}
count = o.localInt.Mod(count, o.Permutations())
for i, child := range o.children {
// The index is equal to whichever child we stop on
o.index = i
childPermutations := child.Permutations()
if childPermutations.Cmp(count) > 0 {
// The child has more permutations than the count, so we'll stop here
if count.Cmp(BigIntMaxUint64) <= 0 {
child.SetConsumeIterationsFast(count.Uint64())
} else {
child.SetConsumeIterations(count)
}
break
} else {
// The child's permutations are <= the count, so we'll reset it and subtract it from the total.
// Subtraction here is the opposite of the addition we do to determine the permutation count.
// Important to note that the permutations equaling the count means that the index increments to the next
// item, but since the count will be zero, it matches the original state of that item.
child.Reset()
count.Sub(count, childPermutations)
}
}
// We still need to reset any children that we never looped over
for i := o.index + 1; i < len(o.children); i++ {
o.children[i].Reset()
}
}
// SetConsumeIterationsFast implements the interface StatementGenerator.
func (o *OrGen) SetConsumeIterationsFast(count uint64) {
// This is a copy of SetConsumeIterations, except rewritten to use uint64
if count <= 0 {
o.Reset()
return
}
permutations, _ := o.PermutationsUint64()
count = count % permutations
for i, child := range o.children {
o.index = i
childPermutations, _ := child.PermutationsUint64()
if childPermutations > count {
child.SetConsumeIterationsFast(count)
break
} else {
child.Reset()
count -= childPermutations
}
}
for i := o.index + 1; i < len(o.children); i++ {
o.children[i].Reset()
}
}
// Copy implements the interface StatementGenerator.
func (o *OrGen) Copy() StatementGenerator {
if o == nil {
return nil
}
return Or(o.children...)
}
// String implements the interface StatementGenerator.
func (o *OrGen) String() string {
return o.children[o.index].String()
}
// Reset implements the interface StatementGenerator.
func (o *OrGen) Reset() {
o.index = 0
for _, child := range o.children {
child.Reset()
}
}
// SourceString implements the interface StatementGenerator.
func (o *OrGen) SourceString() string {
return fmt.Sprintf(`Or(%s)`, sourceGenerators(o.children))
}
// Permutations implements the interface StatementGenerator.
func (o *OrGen) Permutations() *big.Int {
sum := big.NewInt(0)
for _, child := range o.children {
sum.Add(sum, child.Permutations())
}
return sum
}
// PermutationsUint64 implements the interface StatementGenerator.
func (o *OrGen) PermutationsUint64() (uint64, bool) {
sum := uint64(0)
for _, child := range o.children {
childCount, ok := child.PermutationsUint64()
if !ok || sum > (math.MaxUint64-childCount) {
return math.MaxUint64, false
}
sum += childCount
}
return sum, true
}
// VariableGen represents a variable in the synopsis. Its values are user-configurable if they cannot be deduced from
// the synopsis.
type VariableGen struct {
name string
options StatementGenerator
}
var _ StatementGenerator = (*VariableGen)(nil)
// Variable creates a new StatementGenerator representing a VariableGen.
func Variable(name string, child StatementGenerator) *VariableGen {
if child != nil {
return &VariableGen{
name: name,
options: child.Copy(),
}
} else {
return &VariableGen{
name: name,
options: nil,
}
}
}
// AddChildren implements the interface StatementGenerator.
func (v *VariableGen) AddChildren(children ...StatementGenerator) error {
children = removeNilGenerators(children)
if len(children) == 0 {
return nil
}
if len(children) > 1 {
return fmt.Errorf("attempting to give variable `%s` too many children", v.name)
}
if v.options != nil {
return fmt.Errorf("variable `%s` has already been assigned", v.name)
}
v.options = children[0].Copy()
return nil
}
// Consume implements the interface StatementGenerator.
func (v *VariableGen) Consume() bool {
if v.options != nil {
return v.options.Consume()
}
return false
}
// SetConsumeIterations implements the interface StatementGenerator.
func (v *VariableGen) SetConsumeIterations(count *big.Int) {
if v.options != nil {
v.options.SetConsumeIterations(count)
}
}
// SetConsumeIterationsFast implements the interface StatementGenerator.
func (v *VariableGen) SetConsumeIterationsFast(count uint64) {
if v.options != nil {
v.options.SetConsumeIterationsFast(count)
}
}
// Copy implements the interface StatementGenerator.
func (v *VariableGen) Copy() StatementGenerator {
if v == nil {
return nil
}
return Variable(v.name, v.options)
}
// String implements the interface StatementGenerator.
func (v *VariableGen) String() string {
if v.options != nil {
return v.options.String()
} else {
return v.name
}
}
// Reset implements the interface StatementGenerator.
func (v *VariableGen) Reset() {
if v.options != nil {
v.options.Reset()
}
}
// SourceString implements the interface StatementGenerator.
func (v *VariableGen) SourceString() string {
if v.options != nil {
return fmt.Sprintf(`Variable("%s", %s)`, v.name, v.options.SourceString())
} else {
return fmt.Sprintf(`Variable("%s", nil)`, v.name)
}
}
// Permutations implements the interface StatementGenerator.
func (v *VariableGen) Permutations() *big.Int {
if v.options != nil {
return v.options.Permutations()
} else {
return BigIntOne
}
}
// PermutationsUint64 implements the interface StatementGenerator.
func (v *VariableGen) PermutationsUint64() (uint64, bool) {
if v.options != nil {
return v.options.PermutationsUint64()
} else {
return 1, true
}
}
// CollectionGen is a generator that contains multiple child generators, and will print all of its children.
type CollectionGen struct {
children []StatementGenerator
localInt *big.Int
}
var _ StatementGenerator = (*CollectionGen)(nil)
// Collection creates a new StatementGenerator representing a CollectionGen.
func Collection(children ...StatementGenerator) *CollectionGen {
return &CollectionGen{
children: copyGenerators(children),
localInt: new(big.Int),
}
}
// AddChildren implements the interface StatementGenerator.
func (c *CollectionGen) AddChildren(children ...StatementGenerator) error {
c.children = append(c.children, copyGenerators(children)...)
return nil
}
// Consume implements the interface StatementGenerator.
func (c *CollectionGen) Consume() bool {
for i := range c.children {
if c.children[i].Consume() {
return true
}
}
return false
}
// SetConsumeIterations implements the interface StatementGenerator.
func (c *CollectionGen) SetConsumeIterations(count *big.Int) {
// We handle this one as though it's a non-uniform numbering system (binary and decimal are uniform systems).
// In a traditional number system like binary, you can find each bit's value using the following:
//
// bit = number % 2; number = number / 2;
//
// Collections behave similarly to that system, where we increment the second generator after fully incrementing the
// first generator. Then we have to iterate over the first generator again before we can increment the second
// generator again. Do this until the second generator has exhausted its permutations, and then the third generator
// can increment.
//
// Going back to our binary example, we can achieve that same counting effect by replacing 2 with the permutation
// count. This lets us have our non-uniform numbering system, and allows us to efficiently find the exact number for
// each generator.
count = c.localInt.Mod(count, c.Permutations())
index := 0
for i, child := range c.children {
// The index is equal to whichever child we stop on
index = i
childPermutations := child.Permutations()
// We give the child the modulo of the count versus its permutation count, which will determine how many
// iterations it's supposed to simulate from the total.
childIterations := new(big.Int).Mod(count, childPermutations)
if childIterations.Cmp(BigIntMaxUint64) <= 0 {
child.SetConsumeIterationsFast(childIterations.Uint64())
} else {
child.SetConsumeIterations(childIterations)
}
// We divide the count by this child's permutation count to move to the next "base".
count.Div(count, childPermutations)
// If we're at zero now, then this child used up the remaining count, so we'll stop here
if count.Cmp(BigIntZero) <= 0 {
break
}
}
// We still need to reset any children that we never looped over
for index += 1; index < len(c.children); index++ {
c.children[index].Reset()
}
}
// SetConsumeIterationsFast implements the interface StatementGenerator.
func (c *CollectionGen) SetConsumeIterationsFast(count uint64) {
// This is a copy of SetConsumeIterations, except rewritten to use uint64
permutations, _ := c.PermutationsUint64()
count = count % permutations
index := 0
for i, child := range c.children {
index = i
childPermutations, _ := child.PermutationsUint64()
child.SetConsumeIterationsFast(count % childPermutations)
count /= childPermutations
if count <= 0 {
break
}
}
for index += 1; index < len(c.children); index++ {
c.children[index].Reset()
}
}
// Copy implements the interface StatementGenerator.
func (c *CollectionGen) Copy() StatementGenerator {
if c == nil {
return nil
}
return Collection(c.children...)
}
// String implements the interface StatementGenerator.
func (c *CollectionGen) String() string {
var childrenStrings []string
for i := range c.children {
childString := c.children[i].String()
if len(childString) > 0 {
childrenStrings = append(childrenStrings, childString)
}
}
return strings.Join(childrenStrings, " ")
}
// Reset implements the interface StatementGenerator.
func (c *CollectionGen) Reset() {
for _, child := range c.children {
child.Reset()
}
}
// SourceString implements the interface StatementGenerator.
func (c *CollectionGen) SourceString() string {
return fmt.Sprintf(`Collection(%s)`, sourceGenerators(c.children))
}
// Permutations implements the interface StatementGenerator.
func (c *CollectionGen) Permutations() *big.Int {
total := big.NewInt(1)
for _, child := range c.children {
childPermutations := child.Permutations()
if childPermutations.Cmp(BigIntZero) != 0 {
total.Mul(total, childPermutations)
}
}
return total
}
// PermutationsUint64 implements the interface StatementGenerator.
func (c *CollectionGen) PermutationsUint64() (uint64, bool) {
total := uint64(1)
for _, child := range c.children {
childPermutations, ok := child.PermutationsUint64()
if !ok {
return math.MaxUint64, false
}
if childPermutations == 0 {
continue
}
if total > math.MaxUint64/childPermutations {
return math.MaxUint64, false
}
total *= childPermutations
}
return total, true
}
// OptionalGen is a generator that will toggle between displaying its children and not displaying its children.
type OptionalGen struct {
children *CollectionGen
display bool
localInt *big.Int
}
var _ StatementGenerator = (*OptionalGen)(nil)
// Optional creates a new StatementGenerator representing an OptionalGen.
func Optional(children ...StatementGenerator) *OptionalGen {
return &OptionalGen{
children: Collection(children...),
display: false,
localInt: new(big.Int),
}
}
// AddChildren implements the interface StatementGenerator.
func (o *OptionalGen) AddChildren(children ...StatementGenerator) error {
return o.children.AddChildren(children...)
}
// Consume implements the interface StatementGenerator.
func (o *OptionalGen) Consume() bool {
if !o.display {
o.display = true
return true
} else if o.children.Consume() {
return true
} else {
o.display = false
return false
}
}
// SetConsumeIterations implements the interface StatementGenerator.
func (o *OptionalGen) SetConsumeIterations(count *big.Int) {
// If we're given zero, then we'll just call Reset
if count.Cmp(BigIntZero) <= 0 {
o.Reset()
return
}
// The count is >= 1, so display will be true
o.display = true
count = o.localInt.Mod(count, o.Permutations())
// Setting display to true uses a single Consume, so we subtract it before passing the count to the child
count.Sub(count, BigIntOne)
// We'll pass the rest of the remaining count to the child, which will be >= 0
o.children.SetConsumeIterations(count)
}
// SetConsumeIterationsFast implements the interface StatementGenerator.
func (o *OptionalGen) SetConsumeIterationsFast(count uint64) {
// This is a copy of SetConsumeIterations, except rewritten to use uint64
if count <= 0 {
o.Reset()
return
}
o.display = true
permutations, _ := o.PermutationsUint64()
count = count % permutations
count -= 1
o.children.SetConsumeIterationsFast(count)
}
// Copy implements the interface StatementGenerator.
func (o *OptionalGen) Copy() StatementGenerator {
if o == nil {
return nil
}
return Optional(o.children.children...)
}
// String implements the interface StatementGenerator.
func (o *OptionalGen) String() string {
if o.display {
return o.children.String()
} else {
return ""
}
}
// Reset implements the interface StatementGenerator.
func (o *OptionalGen) Reset() {
o.display = false
o.children.Reset()
}
// SourceString implements the interface StatementGenerator.
func (o *OptionalGen) SourceString() string {
return fmt.Sprintf(`Optional(%s)`, sourceGenerators(o.children.children))
}
// Permutations implements the interface StatementGenerator.
func (o *OptionalGen) Permutations() *big.Int {
return new(big.Int).Add(BigIntOne, o.children.Permutations())
}
// PermutationsUint64 implements the interface StatementGenerator.
func (o *OptionalGen) PermutationsUint64() (uint64, bool) {
childCount, ok := o.children.PermutationsUint64()
if !ok || childCount == math.MaxUint64 {
return math.MaxUint64, false
}
return 1 + childCount, true
}
// ApplyVariableDefinition applies the given map of variable definitions to the statement generator. This modifies the
// statement generator, rather than returning a copy.
func ApplyVariableDefinition(gen StatementGenerator, definitions map[string]StatementGenerator) error {
if len(definitions) == 0 {
return nil
}
switch gen := gen.(type) {
case *CollectionGen:
for _, child := range gen.children {
if err := ApplyVariableDefinition(child, definitions); err != nil {
return err
}
}
case *OptionalGen:
if err := ApplyVariableDefinition(gen.children, definitions); err != nil {
return err
}
case *OrGen:
for _, child := range gen.children {
if err := ApplyVariableDefinition(child, definitions); err != nil {
return err
}
}
case *TextGen:
// Nothing to do here
case *VariableGen:
if gen.options == nil {
if definition, ok := definitions[gen.name]; ok {
if err := gen.AddChildren(definition); err != nil {
return err
}
if err := ApplyVariableDefinition(gen.options, definitions); err != nil {
return err
}
}
} else {
if err := ApplyVariableDefinition(gen.options, definitions); err != nil {
return err
}
}
case nil:
return nil
default:
return fmt.Errorf("unknown generator encountered: %T", gen)
}
return nil
}
// UnsetVariables returns the name of all variables that do not have a definition. Sorted in ascending order.
func UnsetVariables(gen StatementGenerator) ([]string, error) {
varNames := make(map[string]struct{})
switch gen := gen.(type) {
case *CollectionGen:
for _, child := range gen.children {
children, err := UnsetVariables(child)
if err != nil {
return nil, err
}
for _, childName := range children {
varNames[childName] = struct{}{}
}
}
case *OptionalGen:
return UnsetVariables(gen.children)
case *OrGen:
return UnsetVariables(Collection(gen.children...))
case *TextGen:
// Nothing to do here
case *VariableGen:
if gen.options == nil {
return []string{gen.name}, nil
} else {
return UnsetVariables(gen.options)
}
default:
return nil, fmt.Errorf("unknown generator encountered: %T", gen)
}
var varNamesSlice []string
for varName := range varNames {
varNamesSlice = append(varNamesSlice, varName)
}
sort.Strings(varNamesSlice)
return varNamesSlice, nil
}
// copyGenerators returns a full copy of the given slice of generators. Each generator will be in its original state.
func copyGenerators(gens []StatementGenerator) []StatementGenerator {
gens = removeNilGenerators(gens)
if len(gens) == 0 {
return nil
}
newGens := make([]StatementGenerator, len(gens))
for i, gen := range gens {
newGens[i] = gen.Copy()
}
return newGens
}
// sourceGenerators returns a comma-separated SourceString from the given generator slice.
func sourceGenerators(gens []StatementGenerator) string {
gens = removeNilGenerators(gens)
if len(gens) == 0 {
return ""
}
sourceStrs := make([]string, len(gens))
for i, gen := range gens {
sourceStrs[i] = gen.SourceString()
}
return strings.Join(sourceStrs, ", ")
}
// removeNilGenerators returns a new slice of generators with all nils removed.
func removeNilGenerators(gens []StatementGenerator) []StatementGenerator {
newGens := make([]StatementGenerator, 0, len(gens))
for i := range gens {
if gens[i] != nil {
newGens = append(newGens, gens[i])
}
}
if len(newGens) == 0 {
return nil
}
return newGens
}
+113
View File
@@ -0,0 +1,113 @@
// Copyright 2023-2024 Dolthub, Inc.
//
// 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 utils
// TokenType is the type of the token.
type TokenType uint8
const (
TokenType_Text TokenType = iota
TokenType_Variable
TokenType_VariableDefinition
TokenType_Or
TokenType_Repeat
TokenType_OptionalRepeat
TokenType_ShortSpace
TokenType_MediumSpace
TokenType_LongSpace
TokenType_ParenOpen
TokenType_ParenClose
TokenType_OptionalOpen
TokenType_OptionalClose
TokenType_OneOfOpen
TokenType_OneOfClose
TokenType_EOF
)
// Token represents a token in the synopsis. Unlike with traditional lexers, whitespace is tokenized, with multiple
// token types for differing lengths, as the whitespace has some significance to the definition.
type Token struct {
Type TokenType
Literal string
}
// IsSpace returns whether the token represents one of the space types.
func (t Token) IsSpace() bool {
return t.Type == TokenType_ShortSpace || t.Type == TokenType_MediumSpace || t.Type == TokenType_LongSpace
}
// IsStandardSpace returns whether the token represents a short or medium space, which will not end a statement.
func (t Token) IsStandardSpace() bool {
return t.Type == TokenType_ShortSpace || t.Type == TokenType_MediumSpace
}
// IsNewStatement returns whether the token represents one a long space, which will end a statement
func (t Token) IsNewStatement() bool {
return t.Type == TokenType_LongSpace
}
// CreatesNewScope returns whether the token creates a new scope.
func (t Token) CreatesNewScope() bool {
return t.Type == TokenType_ParenOpen || t.Type == TokenType_OptionalOpen || t.Type == TokenType_OneOfOpen
}
// ExitsScope returns whether the token exits the current scope.
func (t Token) ExitsScope() bool {
return t.Type == TokenType_ParenClose || t.Type == TokenType_OptionalClose || t.Type == TokenType_OneOfClose
}
// String returns a string representation of the token.
func (t Token) String() string {
switch t.Type {
case TokenType_Text:
return t.Literal
case TokenType_Variable:
return "$" + t.Literal + "$"
case TokenType_VariableDefinition:
return "where $" + t.Literal + "$ is:"
case TokenType_Or:
return "|"
case TokenType_Repeat:
return "..."
case TokenType_OptionalRepeat:
if len(t.Literal) > 0 {
return "[ " + t.Literal + " ... ]"
} else {
return "[ ... ]"
}
case TokenType_ShortSpace:
return " "
case TokenType_MediumSpace:
return "\n "
case TokenType_LongSpace:
return "\n\n"
case TokenType_ParenOpen:
return "("
case TokenType_ParenClose:
return ")"
case TokenType_OptionalOpen:
return "["
case TokenType_OptionalClose:
return "]"
case TokenType_OneOfOpen:
return "{"
case TokenType_OneOfClose:
return "}"
case TokenType_EOF:
return ""
default:
panic("unexpected token type")
}
}
+80
View File
@@ -0,0 +1,80 @@
// Copyright 2023-2024 Dolthub, Inc.
//
// 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 utils
// TokenReader may be used to easily read through a collection of tokens generated from a synopsis. Of note, a
// TokenReader will skip all short and medium spaces, as clients of a reader will only care about statement boundaries.
type TokenReader struct {
tokens []Token
index int
}
// NewTokenReader returns a new *TokenReader created from the given tokens.
func NewTokenReader(tokens []Token) *TokenReader {
sanitizedTokens := make([]Token, 0, len(tokens))
for _, token := range tokens {
if token.IsStandardSpace() {
continue
}
sanitizedTokens = append(sanitizedTokens, token)
}
return &TokenReader{
tokens: sanitizedTokens,
index: -1,
}
}
// Next returns the next token while advancing the reader. Returns false if there are no more tokens.
func (reader *TokenReader) Next() (Token, bool) {
if reader.index+1 >= len(reader.tokens) {
return Token{Type: TokenType_EOF}, false
}
reader.index++
return reader.tokens[reader.index], true
}
// Peek returns the next token. Does not advance the reader. Returns false if there are no more tokens.
func (reader *TokenReader) Peek() (Token, bool) {
if reader.index+1 >= len(reader.tokens) {
return Token{Type: TokenType_EOF}, false
}
return reader.tokens[reader.index+1], true
}
// PeekBy returns the next token that is n positions from the current token. Does not advance the reader. Returns false
// if we are peeking beyond the slice.
func (reader *TokenReader) PeekBy(n int) (Token, bool) {
if reader.index+n >= len(reader.tokens) || reader.index+n < 0 {
return Token{Type: TokenType_EOF}, false
}
return reader.tokens[reader.index+n], true
}
// Advance is equivalent to calling AdvanceBy(1).
func (reader *TokenReader) Advance() {
reader.AdvanceBy(1)
}
// AdvanceBy advances the reader by the given amount. If the amount is greater than the number of remaining tokens, then
// it advances to the end. Cannot advance backwards.
func (reader *TokenReader) AdvanceBy(n int) {
if n < 0 {
n = 0
}
if reader.index+n >= len(reader.tokens) {
n = len(reader.tokens) - reader.index - 1
}
reader.index += n
}