137 строки
2,6 КиБ
Go
137 строки
2,6 КиБ
Go
package gherkin
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
)
|
|
|
|
type Parser interface {
|
|
StopAtFirstError(b bool)
|
|
Parse(s Scanner, m Matcher) (err error)
|
|
}
|
|
|
|
/*
|
|
The scanner reads a gherkin doc (typically read from a .feature file) and creates a token for
|
|
each line. The tokens are passed to the parser, which outputs an AST (Abstract Syntax Tree).
|
|
|
|
If the scanner sees a # language header, it will reconfigure itself dynamically to look for
|
|
Gherkin keywords for the associated language. The keywords are defined in gherkin-languages.json.
|
|
*/
|
|
type Scanner interface {
|
|
Scan() (line *Line, atEof bool, err error)
|
|
}
|
|
|
|
type Builder interface {
|
|
Build(*Token) (bool, error)
|
|
StartRule(RuleType) (bool, error)
|
|
EndRule(RuleType) (bool, error)
|
|
Reset()
|
|
}
|
|
|
|
type Token struct {
|
|
Type TokenType
|
|
Keyword string
|
|
Text string
|
|
Items []*LineSpan
|
|
GherkinDialect string
|
|
Indent string
|
|
Location *Location
|
|
}
|
|
|
|
func (t *Token) IsEOF() bool {
|
|
return t.Type == TokenType_EOF
|
|
}
|
|
func (t *Token) String() string {
|
|
return fmt.Sprintf("%s: %s/%s", t.Type.Name(), t.Keyword, t.Text)
|
|
}
|
|
|
|
type LineSpan struct {
|
|
Column int
|
|
Text string
|
|
}
|
|
|
|
func (l *LineSpan) String() string {
|
|
return fmt.Sprintf("%d:%s", l.Column, l.Text)
|
|
}
|
|
|
|
type parser struct {
|
|
builder Builder
|
|
stopAtFirstError bool
|
|
}
|
|
|
|
func NewParser(b Builder) Parser {
|
|
return &parser{
|
|
builder: b,
|
|
}
|
|
}
|
|
|
|
func (p *parser) StopAtFirstError(b bool) {
|
|
p.stopAtFirstError = b
|
|
}
|
|
|
|
func NewScanner(r io.Reader) Scanner {
|
|
return &scanner{
|
|
s: bufio.NewScanner(r),
|
|
line: 0,
|
|
}
|
|
}
|
|
|
|
type scanner struct {
|
|
s *bufio.Scanner
|
|
line int
|
|
}
|
|
|
|
func (t *scanner) Scan() (line *Line, atEof bool, err error) {
|
|
scanning := t.s.Scan()
|
|
if !scanning {
|
|
err = t.s.Err()
|
|
if err == nil {
|
|
atEof = true
|
|
}
|
|
}
|
|
if err == nil {
|
|
t.line += 1
|
|
str := t.s.Text()
|
|
line = &Line{str, t.line, strings.TrimLeft(str, " \t"), atEof}
|
|
}
|
|
return
|
|
}
|
|
|
|
type Line struct {
|
|
LineText string
|
|
LineNumber int
|
|
TrimmedLineText string
|
|
AtEof bool
|
|
}
|
|
|
|
func (g *Line) Indent() int {
|
|
return len(g.LineText) - len(g.TrimmedLineText)
|
|
}
|
|
|
|
func (g *Line) IsEmpty() bool {
|
|
return len(g.TrimmedLineText) == 0
|
|
}
|
|
|
|
func (g *Line) IsEof() bool {
|
|
return g.AtEof
|
|
}
|
|
|
|
func (g *Line) StartsWith(prefix string) bool {
|
|
return strings.HasPrefix(g.TrimmedLineText, prefix)
|
|
}
|
|
|
|
func ParseFeature(in io.Reader) (feature *Feature, err error) {
|
|
|
|
builder := NewAstBuilder()
|
|
parser := NewParser(builder)
|
|
parser.StopAtFirstError(false)
|
|
matcher := NewMatcher(GherkinDialectsBuildin())
|
|
|
|
scanner := NewScanner(in)
|
|
|
|
err = parser.Parse(scanner, matcher)
|
|
|
|
return builder.GetFeature(), err
|
|
}
|