using System;
using System.Text.RegularExpressions;
namespace GherkinSharp
{
public class Lexer
{
private readonly ITokenReceiver _tokenReceiver;
public interface ITokenReceiver
{
void EndOfFile();
void Given(string text);
void When(string text);
void Then(string text);
}
public Lexer(ITokenReceiver tokenReceiver)
{
_tokenReceiver = tokenReceiver;
}
public void Lex(string gherkin)
{
foreach (var gherkinLine in GetGherkinLines(gherkin))
{
MatchAndCallback("^Given (.*)", _tokenReceiver.Given, gherkinLine);
MatchAndCallback("^When (.*)", _tokenReceiver.When, gherkinLine);
MatchAndCallback("^Then (.*)", _tokenReceiver.Then, gherkinLine);
}
_tokenReceiver.EndOfFile();
}
private static void MatchAndCallback(string pattern, Action<string> callback, string gherkinLine)
{
var m = Regex.Match(gherkinLine, pattern);
while (m.Success)
{
var theText = m.Groups[1].Value;
callback(theText);
m = m.NextMatch();
}
}
private static string[] GetGherkinLines(string gherkin)
{
return gherkin.Split(new[] { "\n", "\r\n" }, StringSplitOptions.None);
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Gherkin;
using Gherkin.Ast;
namespace SevenFt10.SpecFlow.Gherkin.Ast
{
public class GherkinParser : Parser
{
public GherkinParser() : base() { }
public new GherkinDocument Parse(TextReader reader)
{
return ReparseDocument(base.Parse(reader));
}
public new GherkinDocument Parse(string sourceFile)
{
using (var fileStream = new FileStream(sourceFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (var textReader = new StreamReader(fileStream))
{
return ReparseDocument(base.Parse(textReader));
}
}
private GherkinDialect GetDialect(GherkinDocument doc)
{
var provider = new GherkinDialectProvider(doc.Feature.Language);
return provider.GetDialect(doc.Feature.Language, doc.Feature.Location);
}
private Step[] UpdateStepsX(IHasSteps parent, global::Gherkin.GherkinDialect dialect)
{
var steps = new List<Step>();
StepX previous = new StepX(dialect.GivenStepKeywords.Where(g => g.Trim() != "*").First());
foreach (var step in parent.Steps)
{
var name = previous.Name ?? dialect.GivenStepKeywords.Where(g => g.Trim() != "*").First();
if (dialect.WhenStepKeywords.Contains(step.Keyword) || dialect.ThenStepKeywords.Contains(step.Keyword))
{
name = step.Keyword;
}
steps.Add(previous = new StepX(step, name));
}
return steps.ToArray();
}
private IHasChildren UpdateSteps(IHasChildren parent, GherkinDialect dialect)
{
var newChilds = new List<IHasLocation>();
foreach (var top in parent.Children)
{
if (top is IHasChildren)
{
newChilds.Add(UpdateSteps((IHasChildren)top, dialect) as IHasLocation);
}
else if (top is IHasSteps)
{
var newSteps = UpdateStepsX((IHasSteps)top, dialect);
newChilds.Add(top.ReplaceSteps(newSteps));
}
}
if (parent is Rule)
{
newChilds = RuleElements(dialect, (Rule)parent).Union(newChilds).ToList();
}
return parent.ReplaceChildren(newChilds);
}
private GherkinDocument ReparseDocument(GherkinDocument doc)
{
return new GherkinDocument(UpdateSteps(doc.Feature, GetDialect(doc)) as Feature, doc.Comments.ToArray());
}
private static string CleanDescription(string desc)
{
while (desc.IndexOf(" ") > -1) desc = desc.Replace(" ", " ").Trim();
return desc.Replace("\r\n ", "\r\n").Trim();
}
private static IEnumerable<IHasLocation> RuleElements(GherkinDialect dialect, Rule rule)
{
var desc = CleanDescription(rule.Description);
var lineNumber = rule.Location.Line;
var hasOneRule = false;
foreach (var meta in dialect.MetaKeywords)
{
if (desc.StartsWith(meta.Trim()))
{
lineNumber++;
foreach (Match match in Regex.Matches(desc, @"\*{2}(.*?)\*{2}", RegexOptions.Singleline))
{
var comment = match.Groups[0].Value.Trim().TrimStart(new char[] { '*' }).TrimEnd(new char[] { '*' }).Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
foreach (var line in comment)
{
if (line.IndexOf(":") > -1)
{
yield return new RuleMeta(
new Location(++lineNumber, 0),
line.Substring(0, line.IndexOf(":")).Trim(),
line.Substring(line.IndexOf(":") + 1).Trim());
hasOneRule = true;
}
}
desc = CleanDescription(desc.Replace(match.Groups[0].Value, string.Empty));
}
}
}
if (hasOneRule) lineNumber = lineNumber + 2;
var lines = desc.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
var current = 0;
var hasTopLevel = false;
IEnumerable<NarrativeMap> mapping;
mapping = dialect.WhyKeywords.Select(w => new NarrativeMap { Key = w, Type = typeof(NarrativeWhy) });
foreach (var map in mapping)
{
if (current >= lines.Length) yield break;
var gherkinLine = new GherkinLine(lines[current], current);
if (gherkinLine.StartsWith(map.Key))
{
yield return CreateNarrative(map, gherkinLine.GetRestTrimmed(map.Key.Length).Trim(), lineNumber + current);
current++;
hasTopLevel = true;
break;
}
}
if (hasTopLevel)
{
hasTopLevel = false;
// process And , But
mapping = dialect.AndStepKeywords.Select(w => new NarrativeMap { Key = w, Type = typeof(NarrativeAndWhy) })
.Union(dialect.ButStepKeywords.Select(w => new NarrativeMap { Key = w, Type = typeof(NarrativeButWhy) }));
var gotOne = false;
while (true)
{
foreach (var map in mapping)
{
if (current >= lines.Length) yield break;
var gherkinLine = new GherkinLine(lines[current], current);
if (gherkinLine.StartsWith(map.Key))
{
yield return CreateNarrative(map, gherkinLine.GetRestTrimmed(map.Key.Length), lineNumber + current);
current++;
gotOne = true;
break;
}
}
if (!gotOne) break;
gotOne = false;
}
}
mapping = dialect.WhoKeywords.Select(w => new NarrativeMap { Key = w, Type = typeof(NarrativeWho) });
foreach (var map in mapping)
{
if (current >= lines.Length) yield break;
var gherkinLine = new GherkinLine(lines[current], current);
if (gherkinLine.StartsWith(map.Key))
{
yield return CreateNarrative(map, gherkinLine.GetRestTrimmed(map.Key.Length), lineNumber + current);
current++;
hasTopLevel = true;
break;
}
}
if (hasTopLevel)
{
hasTopLevel = false;
// process And , But
mapping = dialect.AndStepKeywords.Select(w => new NarrativeMap { Key = w, Type = typeof(NarrativeAndWho) })
.Union(dialect.ButStepKeywords.Select(w => new NarrativeMap { Key = w, Type = typeof(NarrativeButWho) }));
var gotOne = false;
while (true)
{
foreach (var map in mapping)
{
if (current >= lines.Length) yield break;
var gherkinLine = new GherkinLine(lines[current], current);
if (gherkinLine.StartsWith(map.Key))
{
yield return CreateNarrative(map, gherkinLine.GetRestTrimmed(map.Key.Length), lineNumber + current);
current++;
gotOne = true;
break;
}
}
if (!gotOne) break;
gotOne = false;
}
}
mapping = dialect.WhereKeywords.Select(w => new NarrativeMap { Key = w, Type = typeof(NarrativeWhere) });
foreach (var map in mapping)
{
if (current >= lines.Length) yield break;
var gherkinLine = new GherkinLine(lines[current], current);
if (gherkinLine.StartsWith(map.Key))
{
yield return CreateNarrative(map, gherkinLine.GetRestTrimmed(map.Key.Length), lineNumber + current);
current++;
hasTopLevel = true;
break;
}
}
if (hasTopLevel)
{
hasTopLevel = false;
// process And , But
mapping = dialect.AndStepKeywords.Select(w => new NarrativeMap { Key = w, Type = typeof(NarrativeAndWhere) })
.Union(dialect.ButStepKeywords.Select(w => new NarrativeMap { Key = w, Type = typeof(NarrativeButWhere) }));
var gotOne = false;
while (true)
{
foreach (var map in mapping)
{
if (current >= lines.Length) yield break;
var gherkinLine = new GherkinLine(lines[current], current);
if (gherkinLine.StartsWith(map.Key))
{
yield return CreateNarrative(map, gherkinLine.GetRestTrimmed(map.Key.Length), lineNumber + current);
current++;
gotOne = true;
break;
}
}
if (!gotOne) break;
gotOne = false;
}
}
mapping = dialect.WhatKeywords.Select(w => new NarrativeMap { Key = w, Type = typeof(NarrativeWhat) });
foreach (var map in mapping)
{
if (current >= lines.Length) yield break;
var gherkinLine = new GherkinLine(lines[current], current);
if (gherkinLine.StartsWith(map.Key))
{
yield return CreateNarrative(map, gherkinLine.GetRestTrimmed(map.Key.Length), lineNumber + current);
current++;
hasTopLevel = true;
break;
}
}
if (hasTopLevel)
{
hasTopLevel = false;
// process And , But
mapping = dialect.AndStepKeywords.Select(w => new NarrativeMap { Key = w, Type = typeof(NarrativeAndWhat) })
.Union(dialect.ButStepKeywords.Select(w => new NarrativeMap { Key = w, Type = typeof(NarrativeButWhat) }));
foreach (var map in mapping)
{
if (current >= lines.Length) yield break;
var gherkinLine = new GherkinLine(lines[current], current);
if (gherkinLine.StartsWith(map.Key))
{
yield return CreateNarrative(map, gherkinLine.GetRestTrimmed(map.Key.Length), lineNumber + current);
current++;
break;
}
}
}
}
private struct NarrativeMap
{
public string Key;
public Type Type;
}
private static INarrative CreateNarrative(NarrativeMap map, string description, int lineNumber)
{
var narrative = (INarrative)Activator.CreateInstance(map.Type, new object[] { });
narrative.Name = map.Key.TrimEnd();
narrative.Description = description;
narrative.Location = new Location(lineNumber, 0);
return narrative;
}
}
}
using System;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Linq;
using Gherkin.Ast;
namespace Gherkin
{
public class GherkinLine : IGherkinLine
{
private static char[] inlineWhitespaceChars = new char[] { ' ', '\t', '\u00A0'};
private readonly string lineText;
private readonly string trimmedLineText;
public int LineNumber { get; private set; }
public GherkinLine(string line, int lineNumber)
{
this.LineNumber = lineNumber;
this.lineText = line;
this.trimmedLineText = this.lineText.TrimStart();
}
public void Detach()
{
//nop
}
public int Indent
{
get { return lineText.Length - trimmedLineText.Length; }
}
public bool IsEmpty()
{
return trimmedLineText.Length == 0;
}
public bool StartsWith(string text)
{
return trimmedLineText.StartsWith(text);
}
public bool StartsWithTitleKeyword(string text)
{
return StringUtils.StartsWith(trimmedLineText, text) &&
StartsWithFrom(trimmedLineText, text.Length, GherkinLanguageConstants.TITLE_KEYWORD_SEPARATOR);
}
private static bool StartsWithFrom(string text, int textIndex, string value)
{
return string.CompareOrdinal(text, textIndex, value, 0, value.Length) == 0;
}
public string GetLineText(int indentToRemove)
{
if (indentToRemove < 0 || indentToRemove > Indent)
return trimmedLineText;
return lineText.Substring(indentToRemove);
}
public string GetRestTrimmed(int length)
{
return trimmedLineText.Substring(length).Trim();
}
public IEnumerable<GherkinLineSpan> GetTags()
{
var uncommentedLine = Regex.Split(trimmedLineText, @"\s" + GherkinLanguageConstants.COMMENT_PREFIX)[0];
int position = Indent;
foreach (string item in uncommentedLine.Split(GherkinLanguageConstants.TAG_PREFIX[0]))
{
if (item.Length > 0)
{
var tagName = GherkinLanguageConstants.TAG_PREFIX + item.TrimEnd(inlineWhitespaceChars);
if (tagName.Length == 1)
continue;
if (tagName.IndexOfAny(inlineWhitespaceChars) >= 0)
throw new InvalidTagException("A tag may not contain whitespace", new Location(LineNumber, position));
yield return new GherkinLineSpan(position, tagName);
position += item.Length;
}
position++; // separator
}
}
public IEnumerable<GherkinLineSpan> GetTableCells()
{
var items = SplitCells(trimmedLineText).ToList();
bool isBeforeFirst = true;
foreach (var item in items.Take(items.Count - 1)) // skipping the one after last
{
if (!isBeforeFirst)
{
int trimmedStart;
var cellText = Trim(item.Item1, out trimmedStart);
var cellPosition = item.Item2 + trimmedStart;
if (cellText.Length == 0)
cellPosition = item.Item2;
yield return new GherkinLineSpan(Indent + cellPosition + 1, cellText);
}
isBeforeFirst = false;
}
}
private IEnumerable<Tuple<string, int>> SplitCells(string row)
{
var rowEnum = row.GetEnumerator();
string cell = "";
int pos = 0;
int startPos = 0;
while (rowEnum.MoveNext()) {
pos++;
char c = rowEnum.Current;
if (c.ToString() == GherkinLanguageConstants.TABLE_CELL_SEPARATOR) {
yield return Tuple.Create(cell, startPos);
cell = "";
startPos = pos;
} else if (c == GherkinLanguageConstants.TABLE_CELL_ESCAPE_CHAR) {
rowEnum.MoveNext();
pos++;
c = rowEnum.Current;
if (c == GherkinLanguageConstants.TABLE_CELL_NEWLINE_ESCAPE) {
cell += "\n";
} else {
if (c.ToString() != GherkinLanguageConstants.TABLE_CELL_SEPARATOR && c != GherkinLanguageConstants.TABLE_CELL_ESCAPE_CHAR) {
cell += GherkinLanguageConstants.TABLE_CELL_ESCAPE_CHAR;
}
cell += c;
}
} else {
cell += c;
}
}
yield return Tuple.Create(cell, startPos);
}
private string Trim(string s, out int trimmedStart)
{
trimmedStart = 0;
while (trimmedStart < s.Length && inlineWhitespaceChars.Contains(s[trimmedStart]))
trimmedStart++;
return s.Trim(inlineWhitespaceChars);
}
}
}