github.com/maps90/godog@v0.7.5-0.20170923143419-0093943021d4/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 }