godog/gherkin/astbuilder.go

378 строки
9,1 КиБ
Go

package gherkin
import (
"strings"
)
type AstBuilder interface {
Builder
GetFeature() *Feature
}
type astBuilder struct {
stack []*astNode
comments []*Comment
}
func (t *astBuilder) Reset() {
t.comments = []*Comment{}
t.stack = []*astNode{}
t.push(newAstNode(RuleType_None))
}
func (t *astBuilder) GetFeature() *Feature {
res := t.currentNode().getSingle(RuleType_Feature)
if val, ok := res.(*Feature); ok {
return val
}
return nil
}
type astNode struct {
ruleType RuleType
subNodes map[RuleType][]interface{}
}
func (a *astNode) add(rt RuleType, obj interface{}) {
a.subNodes[rt] = append(a.subNodes[rt], obj)
}
func (a *astNode) getSingle(rt RuleType) interface{} {
if val, ok := a.subNodes[rt]; ok {
for i := range val {
return val[i]
}
}
return nil
}
func (a *astNode) getItems(rt RuleType) []interface{} {
var res []interface{}
if val, ok := a.subNodes[rt]; ok {
for i := range val {
res = append(res, val[i])
}
}
return res
}
func (a *astNode) getToken(tt TokenType) *Token {
if val, ok := a.getSingle(tt.RuleType()).(*Token); ok {
return val
}
return nil
}
func (a *astNode) getTokens(tt TokenType) []*Token {
var items = a.getItems(tt.RuleType())
var tokens []*Token
for i := range items {
if val, ok := items[i].(*Token); ok {
tokens = append(tokens, val)
}
}
return tokens
}
func (t *astBuilder) currentNode() *astNode {
if len(t.stack) > 0 {
return t.stack[len(t.stack)-1]
}
return nil
}
func newAstNode(rt RuleType) *astNode {
return &astNode{
ruleType: rt,
subNodes: make(map[RuleType][]interface{}),
}
}
func NewAstBuilder() AstBuilder {
builder := new(astBuilder)
builder.comments = []*Comment{}
builder.push(newAstNode(RuleType_None))
return builder
}
func (t *astBuilder) push(n *astNode) {
t.stack = append(t.stack, n)
}
func (t *astBuilder) pop() *astNode {
x := t.stack[len(t.stack)-1]
t.stack = t.stack[:len(t.stack)-1]
return x
}
func (t *astBuilder) Build(tok *Token) (bool, error) {
if tok.Type == TokenType_Comment {
comment := new(Comment)
comment.Type = "Comment"
comment.Location = astLocation(tok)
comment.Text = tok.Text
t.comments = append(t.comments, comment)
} else {
t.currentNode().add(tok.Type.RuleType(), tok)
}
return true, nil
}
func (t *astBuilder) StartRule(r RuleType) (bool, error) {
t.push(newAstNode(r))
return true, nil
}
func (t *astBuilder) EndRule(r RuleType) (bool, error) {
node := t.pop()
transformedNode, err := t.transformNode(node)
t.currentNode().add(node.ruleType, transformedNode)
return true, err
}
func (t *astBuilder) transformNode(node *astNode) (interface{}, error) {
switch node.ruleType {
case RuleType_Step:
stepLine := node.getToken(TokenType_StepLine)
step := new(Step)
step.Type = "Step"
step.Location = astLocation(stepLine)
step.Keyword = stepLine.Keyword
step.Text = stepLine.Text
step.Argument = node.getSingle(RuleType_DataTable)
if step.Argument == nil {
step.Argument = node.getSingle(RuleType_DocString)
}
return step, nil
case RuleType_DocString:
separatorToken := node.getToken(TokenType_DocStringSeparator)
contentType := separatorToken.Text
lineTokens := node.getTokens(TokenType_Other)
var text string
for i := range lineTokens {
if i > 0 {
text += "\n"
}
text += lineTokens[i].Text
}
ds := new(DocString)
ds.Type = "DocString"
ds.Location = astLocation(separatorToken)
ds.ContentType = contentType
ds.Content = text
ds.Delimitter = DOCSTRING_SEPARATOR // TODO: remember separator
return ds, nil
case RuleType_DataTable:
rows, err := astTableRows(node)
dt := new(DataTable)
dt.Type = "DataTable"
dt.Location = rows[0].Location
dt.Rows = rows
return dt, err
case RuleType_Background:
backgroundLine := node.getToken(TokenType_BackgroundLine)
description, _ := node.getSingle(RuleType_Description).(string)
bg := new(Background)
bg.Type = "Background"
bg.Location = astLocation(backgroundLine)
bg.Keyword = backgroundLine.Keyword
bg.Name = backgroundLine.Text
bg.Description = description
bg.Steps = astSteps(node)
return bg, nil
case RuleType_Scenario_Definition:
tags := astTags(node)
scenarioNode, _ := node.getSingle(RuleType_Scenario).(*astNode)
if scenarioNode != nil {
scenarioLine := scenarioNode.getToken(TokenType_ScenarioLine)
description, _ := scenarioNode.getSingle(RuleType_Description).(string)
sc := new(Scenario)
sc.Type = "Scenario"
sc.Tags = tags
sc.Location = astLocation(scenarioLine)
sc.Keyword = scenarioLine.Keyword
sc.Name = scenarioLine.Text
sc.Description = description
sc.Steps = astSteps(scenarioNode)
return sc, nil
} else {
scenarioOutlineNode, ok := node.getSingle(RuleType_ScenarioOutline).(*astNode)
if !ok {
panic("Internal grammar error")
}
scenarioOutlineLine := scenarioOutlineNode.getToken(TokenType_ScenarioOutlineLine)
description, _ := scenarioOutlineNode.getSingle(RuleType_Description).(string)
sc := new(ScenarioOutline)
sc.Type = "ScenarioOutline"
sc.Tags = tags
sc.Location = astLocation(scenarioOutlineLine)
sc.Keyword = scenarioOutlineLine.Keyword
sc.Name = scenarioOutlineLine.Text
sc.Description = description
sc.Steps = astSteps(scenarioOutlineNode)
sc.Examples = astExamples(scenarioOutlineNode)
return sc, nil
}
case RuleType_Examples_Definition:
tags := astTags(node)
examplesNode, _ := node.getSingle(RuleType_Examples).(*astNode)
examplesLine := examplesNode.getToken(TokenType_ExamplesLine)
description, _ := examplesNode.getSingle(RuleType_Description).(string)
allRows, err := astTableRows(examplesNode)
ex := new(Examples)
ex.Type = "Examples"
ex.Tags = tags
ex.Location = astLocation(examplesLine)
ex.Keyword = examplesLine.Keyword
ex.Name = examplesLine.Text
ex.Description = description
ex.TableHeader = allRows[0]
ex.TableBody = allRows[1:]
return ex, err
case RuleType_Description:
lineTokens := node.getTokens(TokenType_Other)
// Trim trailing empty lines
end := len(lineTokens)
for end > 0 && strings.TrimSpace(lineTokens[end-1].Text) == "" {
end--
}
var desc []string
for i := range lineTokens[0:end] {
desc = append(desc, lineTokens[i].Text)
}
return strings.Join(desc, "\n"), nil
case RuleType_Feature:
header, ok := node.getSingle(RuleType_Feature_Header).(*astNode)
if !ok {
return nil, nil
}
tags := astTags(header)
featureLine := header.getToken(TokenType_FeatureLine)
if featureLine == nil {
return nil, nil
}
background, _ := node.getSingle(RuleType_Background).(*Background)
scenarioDefinitions := node.getItems(RuleType_Scenario_Definition)
if scenarioDefinitions == nil {
scenarioDefinitions = []interface{}{}
}
description, _ := header.getSingle(RuleType_Description).(string)
feat := new(Feature)
feat.Type = "Feature"
feat.Tags = tags
feat.Location = astLocation(featureLine)
feat.Language = featureLine.GherkinDialect
feat.Keyword = featureLine.Keyword
feat.Name = featureLine.Text
feat.Description = description
feat.Background = background
feat.ScenarioDefinitions = scenarioDefinitions
feat.Comments = t.comments
return feat, nil
}
return node, nil
}
func astLocation(t *Token) *Location {
return &Location{
Line: t.Location.Line,
Column: t.Location.Column,
}
}
func astTableRows(t *astNode) (rows []*TableRow, err error) {
rows = []*TableRow{}
tokens := t.getTokens(TokenType_TableRow)
for i := range tokens {
row := new(TableRow)
row.Type = "TableRow"
row.Location = astLocation(tokens[i])
row.Cells = astTableCells(tokens[i])
rows = append(rows, row)
}
err = ensureCellCount(rows)
return
}
func ensureCellCount(rows []*TableRow) error {
if len(rows) <= 1 {
return nil
}
cellCount := len(rows[0].Cells)
for i := range rows {
if cellCount != len(rows[i].Cells) {
return &parseError{"inconsistent cell count within the table", &Location{
Line: rows[i].Location.Line,
Column: rows[i].Location.Column,
}}
}
}
return nil
}
func astTableCells(t *Token) (cells []*TableCell) {
cells = []*TableCell{}
for i := range t.Items {
item := t.Items[i]
cell := new(TableCell)
cell.Type = "TableCell"
cell.Location = &Location{
Line: t.Location.Line,
Column: item.Column,
}
cell.Value = item.Text
cells = append(cells, cell)
}
return
}
func astSteps(t *astNode) (steps []*Step) {
steps = []*Step{}
tokens := t.getItems(RuleType_Step)
for i := range tokens {
step, _ := tokens[i].(*Step)
steps = append(steps, step)
}
return
}
func astExamples(t *astNode) (examples []*Examples) {
examples = []*Examples{}
tokens := t.getItems(RuleType_Examples_Definition)
for i := range tokens {
example, _ := tokens[i].(*Examples)
examples = append(examples, example)
}
return
}
func astTags(node *astNode) (tags []*Tag) {
tags = []*Tag{}
tagsNode, ok := node.getSingle(RuleType_Tags).(*astNode)
if !ok {
return
}
tokens := tagsNode.getTokens(TokenType_TagLine)
for i := range tokens {
token := tokens[i]
for k := range token.Items {
item := token.Items[k]
tag := new(Tag)
tag.Type = "Tag"
tag.Location = &Location{
Line: token.Location.Line,
Column: item.Column,
}
tag.Name = item.Text
tags = append(tags, tag)
}
}
return
}