github.com/data-DOG/godog@v0.7.9/gherkin/astbuilder.go (about)

     1  package gherkin
     2  
     3  import (
     4  	"strings"
     5  )
     6  
     7  type AstBuilder interface {
     8  	Builder
     9  	GetFeature() *Feature
    10  }
    11  
    12  type astBuilder struct {
    13  	stack    []*astNode
    14  	comments []*Comment
    15  }
    16  
    17  func (t *astBuilder) Reset() {
    18  	t.comments = []*Comment{}
    19  	t.stack = []*astNode{}
    20  	t.push(newAstNode(RuleType_None))
    21  }
    22  
    23  func (t *astBuilder) GetFeature() *Feature {
    24  	res := t.currentNode().getSingle(RuleType_Feature)
    25  	if val, ok := res.(*Feature); ok {
    26  		return val
    27  	}
    28  	return nil
    29  }
    30  
    31  type astNode struct {
    32  	ruleType RuleType
    33  	subNodes map[RuleType][]interface{}
    34  }
    35  
    36  func (a *astNode) add(rt RuleType, obj interface{}) {
    37  	a.subNodes[rt] = append(a.subNodes[rt], obj)
    38  }
    39  
    40  func (a *astNode) getSingle(rt RuleType) interface{} {
    41  	if val, ok := a.subNodes[rt]; ok {
    42  		for i := range val {
    43  			return val[i]
    44  		}
    45  	}
    46  	return nil
    47  }
    48  
    49  func (a *astNode) getItems(rt RuleType) []interface{} {
    50  	var res []interface{}
    51  	if val, ok := a.subNodes[rt]; ok {
    52  		for i := range val {
    53  			res = append(res, val[i])
    54  		}
    55  	}
    56  	return res
    57  }
    58  
    59  func (a *astNode) getToken(tt TokenType) *Token {
    60  	if val, ok := a.getSingle(tt.RuleType()).(*Token); ok {
    61  		return val
    62  	}
    63  	return nil
    64  }
    65  
    66  func (a *astNode) getTokens(tt TokenType) []*Token {
    67  	var items = a.getItems(tt.RuleType())
    68  	var tokens []*Token
    69  	for i := range items {
    70  		if val, ok := items[i].(*Token); ok {
    71  			tokens = append(tokens, val)
    72  		}
    73  	}
    74  	return tokens
    75  }
    76  
    77  func (t *astBuilder) currentNode() *astNode {
    78  	if len(t.stack) > 0 {
    79  		return t.stack[len(t.stack)-1]
    80  	}
    81  	return nil
    82  }
    83  
    84  func newAstNode(rt RuleType) *astNode {
    85  	return &astNode{
    86  		ruleType: rt,
    87  		subNodes: make(map[RuleType][]interface{}),
    88  	}
    89  }
    90  
    91  func NewAstBuilder() AstBuilder {
    92  	builder := new(astBuilder)
    93  	builder.comments = []*Comment{}
    94  	builder.push(newAstNode(RuleType_None))
    95  	return builder
    96  }
    97  
    98  func (t *astBuilder) push(n *astNode) {
    99  	t.stack = append(t.stack, n)
   100  }
   101  
   102  func (t *astBuilder) pop() *astNode {
   103  	x := t.stack[len(t.stack)-1]
   104  	t.stack = t.stack[:len(t.stack)-1]
   105  	return x
   106  }
   107  
   108  func (t *astBuilder) Build(tok *Token) (bool, error) {
   109  	if tok.Type == TokenType_Comment {
   110  		comment := new(Comment)
   111  		comment.Type = "Comment"
   112  		comment.Location = astLocation(tok)
   113  		comment.Text = tok.Text
   114  		t.comments = append(t.comments, comment)
   115  	} else {
   116  		t.currentNode().add(tok.Type.RuleType(), tok)
   117  	}
   118  	return true, nil
   119  }
   120  func (t *astBuilder) StartRule(r RuleType) (bool, error) {
   121  	t.push(newAstNode(r))
   122  	return true, nil
   123  }
   124  func (t *astBuilder) EndRule(r RuleType) (bool, error) {
   125  	node := t.pop()
   126  	transformedNode, err := t.transformNode(node)
   127  	t.currentNode().add(node.ruleType, transformedNode)
   128  	return true, err
   129  }
   130  
   131  func (t *astBuilder) transformNode(node *astNode) (interface{}, error) {
   132  	switch node.ruleType {
   133  
   134  	case RuleType_Step:
   135  		stepLine := node.getToken(TokenType_StepLine)
   136  		step := new(Step)
   137  		step.Type = "Step"
   138  		step.Location = astLocation(stepLine)
   139  		step.Keyword = stepLine.Keyword
   140  		step.Text = stepLine.Text
   141  		step.Argument = node.getSingle(RuleType_DataTable)
   142  		if step.Argument == nil {
   143  			step.Argument = node.getSingle(RuleType_DocString)
   144  		}
   145  		return step, nil
   146  
   147  	case RuleType_DocString:
   148  		separatorToken := node.getToken(TokenType_DocStringSeparator)
   149  		contentType := separatorToken.Text
   150  		lineTokens := node.getTokens(TokenType_Other)
   151  		var text string
   152  		for i := range lineTokens {
   153  			if i > 0 {
   154  				text += "\n"
   155  			}
   156  			text += lineTokens[i].Text
   157  		}
   158  		ds := new(DocString)
   159  		ds.Type = "DocString"
   160  		ds.Location = astLocation(separatorToken)
   161  		ds.ContentType = contentType
   162  		ds.Content = text
   163  		ds.Delimitter = DOCSTRING_SEPARATOR // TODO: remember separator
   164  		return ds, nil
   165  
   166  	case RuleType_DataTable:
   167  		rows, err := astTableRows(node)
   168  		dt := new(DataTable)
   169  		dt.Type = "DataTable"
   170  		dt.Location = rows[0].Location
   171  		dt.Rows = rows
   172  		return dt, err
   173  
   174  	case RuleType_Background:
   175  		backgroundLine := node.getToken(TokenType_BackgroundLine)
   176  		description, _ := node.getSingle(RuleType_Description).(string)
   177  		bg := new(Background)
   178  		bg.Type = "Background"
   179  		bg.Location = astLocation(backgroundLine)
   180  		bg.Keyword = backgroundLine.Keyword
   181  		bg.Name = backgroundLine.Text
   182  		bg.Description = description
   183  		bg.Steps = astSteps(node)
   184  		return bg, nil
   185  
   186  	case RuleType_Scenario_Definition:
   187  		tags := astTags(node)
   188  		scenarioNode, _ := node.getSingle(RuleType_Scenario).(*astNode)
   189  		if scenarioNode != nil {
   190  			scenarioLine := scenarioNode.getToken(TokenType_ScenarioLine)
   191  			description, _ := scenarioNode.getSingle(RuleType_Description).(string)
   192  			sc := new(Scenario)
   193  			sc.Type = "Scenario"
   194  			sc.Tags = tags
   195  			sc.Location = astLocation(scenarioLine)
   196  			sc.Keyword = scenarioLine.Keyword
   197  			sc.Name = scenarioLine.Text
   198  			sc.Description = description
   199  			sc.Steps = astSteps(scenarioNode)
   200  			return sc, nil
   201  		} else {
   202  			scenarioOutlineNode, ok := node.getSingle(RuleType_ScenarioOutline).(*astNode)
   203  			if !ok {
   204  				panic("Internal grammar error")
   205  			}
   206  			scenarioOutlineLine := scenarioOutlineNode.getToken(TokenType_ScenarioOutlineLine)
   207  			description, _ := scenarioOutlineNode.getSingle(RuleType_Description).(string)
   208  			sc := new(ScenarioOutline)
   209  			sc.Type = "ScenarioOutline"
   210  			sc.Tags = tags
   211  			sc.Location = astLocation(scenarioOutlineLine)
   212  			sc.Keyword = scenarioOutlineLine.Keyword
   213  			sc.Name = scenarioOutlineLine.Text
   214  			sc.Description = description
   215  			sc.Steps = astSteps(scenarioOutlineNode)
   216  			sc.Examples = astExamples(scenarioOutlineNode)
   217  			return sc, nil
   218  		}
   219  
   220  	case RuleType_Examples_Definition:
   221  		tags := astTags(node)
   222  		examplesNode, _ := node.getSingle(RuleType_Examples).(*astNode)
   223  		examplesLine := examplesNode.getToken(TokenType_ExamplesLine)
   224  		description, _ := examplesNode.getSingle(RuleType_Description).(string)
   225  		allRows, err := astTableRows(examplesNode)
   226  		ex := new(Examples)
   227  		ex.Type = "Examples"
   228  		ex.Tags = tags
   229  		ex.Location = astLocation(examplesLine)
   230  		ex.Keyword = examplesLine.Keyword
   231  		ex.Name = examplesLine.Text
   232  		ex.Description = description
   233  		ex.TableHeader = allRows[0]
   234  		ex.TableBody = allRows[1:]
   235  		return ex, err
   236  
   237  	case RuleType_Description:
   238  		lineTokens := node.getTokens(TokenType_Other)
   239  		// Trim trailing empty lines
   240  		end := len(lineTokens)
   241  		for end > 0 && strings.TrimSpace(lineTokens[end-1].Text) == "" {
   242  			end--
   243  		}
   244  		var desc []string
   245  		for i := range lineTokens[0:end] {
   246  			desc = append(desc, lineTokens[i].Text)
   247  		}
   248  		return strings.Join(desc, "\n"), nil
   249  
   250  	case RuleType_Feature:
   251  		header, ok := node.getSingle(RuleType_Feature_Header).(*astNode)
   252  		if !ok {
   253  			return nil, nil
   254  		}
   255  		tags := astTags(header)
   256  		featureLine := header.getToken(TokenType_FeatureLine)
   257  		if featureLine == nil {
   258  			return nil, nil
   259  		}
   260  		background, _ := node.getSingle(RuleType_Background).(*Background)
   261  		scenarioDefinitions := node.getItems(RuleType_Scenario_Definition)
   262  		if scenarioDefinitions == nil {
   263  			scenarioDefinitions = []interface{}{}
   264  		}
   265  		description, _ := header.getSingle(RuleType_Description).(string)
   266  
   267  		feat := new(Feature)
   268  		feat.Type = "Feature"
   269  		feat.Tags = tags
   270  		feat.Location = astLocation(featureLine)
   271  		feat.Language = featureLine.GherkinDialect
   272  		feat.Keyword = featureLine.Keyword
   273  		feat.Name = featureLine.Text
   274  		feat.Description = description
   275  		feat.Background = background
   276  		feat.ScenarioDefinitions = scenarioDefinitions
   277  		feat.Comments = t.comments
   278  		return feat, nil
   279  	}
   280  	return node, nil
   281  }
   282  
   283  func astLocation(t *Token) *Location {
   284  	return &Location{
   285  		Line:   t.Location.Line,
   286  		Column: t.Location.Column,
   287  	}
   288  }
   289  
   290  func astTableRows(t *astNode) (rows []*TableRow, err error) {
   291  	rows = []*TableRow{}
   292  	tokens := t.getTokens(TokenType_TableRow)
   293  	for i := range tokens {
   294  		row := new(TableRow)
   295  		row.Type = "TableRow"
   296  		row.Location = astLocation(tokens[i])
   297  		row.Cells = astTableCells(tokens[i])
   298  		rows = append(rows, row)
   299  	}
   300  	err = ensureCellCount(rows)
   301  	return
   302  }
   303  
   304  func ensureCellCount(rows []*TableRow) error {
   305  	if len(rows) <= 1 {
   306  		return nil
   307  	}
   308  	cellCount := len(rows[0].Cells)
   309  	for i := range rows {
   310  		if cellCount != len(rows[i].Cells) {
   311  			return &parseError{"inconsistent cell count within the table", &Location{
   312  				Line:   rows[i].Location.Line,
   313  				Column: rows[i].Location.Column,
   314  			}}
   315  		}
   316  	}
   317  	return nil
   318  }
   319  
   320  func astTableCells(t *Token) (cells []*TableCell) {
   321  	cells = []*TableCell{}
   322  	for i := range t.Items {
   323  		item := t.Items[i]
   324  		cell := new(TableCell)
   325  		cell.Type = "TableCell"
   326  		cell.Location = &Location{
   327  			Line:   t.Location.Line,
   328  			Column: item.Column,
   329  		}
   330  		cell.Value = item.Text
   331  		cells = append(cells, cell)
   332  	}
   333  	return
   334  }
   335  
   336  func astSteps(t *astNode) (steps []*Step) {
   337  	steps = []*Step{}
   338  	tokens := t.getItems(RuleType_Step)
   339  	for i := range tokens {
   340  		step, _ := tokens[i].(*Step)
   341  		steps = append(steps, step)
   342  	}
   343  	return
   344  }
   345  
   346  func astExamples(t *astNode) (examples []*Examples) {
   347  	examples = []*Examples{}
   348  	tokens := t.getItems(RuleType_Examples_Definition)
   349  	for i := range tokens {
   350  		example, _ := tokens[i].(*Examples)
   351  		examples = append(examples, example)
   352  	}
   353  	return
   354  }
   355  
   356  func astTags(node *astNode) (tags []*Tag) {
   357  	tags = []*Tag{}
   358  	tagsNode, ok := node.getSingle(RuleType_Tags).(*astNode)
   359  	if !ok {
   360  		return
   361  	}
   362  	tokens := tagsNode.getTokens(TokenType_TagLine)
   363  	for i := range tokens {
   364  		token := tokens[i]
   365  		for k := range token.Items {
   366  			item := token.Items[k]
   367  			tag := new(Tag)
   368  			tag.Type = "Tag"
   369  			tag.Location = &Location{
   370  				Line:   token.Location.Line,
   371  				Column: item.Column,
   372  			}
   373  			tag.Name = item.Text
   374  			tags = append(tags, tag)
   375  		}
   376  	}
   377  	return
   378  }