package gherkin.lexer; import java.io.UnsupportedEncodingException; import java.util.List; import java.util.ArrayList; import java.util.regex.Pattern; import java.util.regex.Matcher; import gherkin.Lexer; import gherkin.Listener; import gherkin.LexingError; public class <%= @i18n.underscored_iso_code.upcase %> implements Lexer { %%{ machine lexer; alphtype byte; action begin_content { contentStart = p; currentLine = lineNumber; } action start_pystring { currentLine = lineNumber; startCol = p - lastNewline; } action begin_pystring_content { contentStart = p; } action store_pystring_content { String con = unindent(startCol, substring(data, contentStart, nextKeywordStart-1).replaceFirst("(\\r?\\n)?([\\t ])*\\Z", "").replaceAll("\\\\\"\\\\\"\\\\\"", "\"\"\"")); listener.pyString(con, currentLine); } action store_feature_content { String[] nameDescription = nameAndDescriptionWithPlatformNewlinesIntact(keywordContent(data, p, eof, nextKeywordStart, contentStart)); listener.feature(keyword, nameDescription[0], nameDescription[1], currentLine); if(nextKeywordStart != -1) p = nextKeywordStart - 1; nextKeywordStart = -1; } action store_background_content { String[] nameDescription = nameAndDescriptionWithPlatformNewlinesIntact(keywordContent(data, p, eof, nextKeywordStart, contentStart)); listener.background(keyword, nameDescription[0], nameDescription[1], currentLine); if(nextKeywordStart != -1) p = nextKeywordStart - 1; nextKeywordStart = -1; } action store_scenario_content { String[] nameDescription = nameAndDescriptionWithPlatformNewlinesIntact(keywordContent(data, p, eof, nextKeywordStart, contentStart)); listener.scenario(keyword, nameDescription[0], nameDescription[1], currentLine); if(nextKeywordStart != -1) p = nextKeywordStart - 1; nextKeywordStart = -1; } action store_scenario_outline_content { String[] nameDescription = nameAndDescriptionWithPlatformNewlinesIntact(keywordContent(data, p, eof, nextKeywordStart, contentStart)); listener.scenarioOutline(keyword, nameDescription[0], nameDescription[1], currentLine); if(nextKeywordStart != -1) p = nextKeywordStart - 1; nextKeywordStart = -1; } action store_examples_content { String[] nameDescription = nameAndDescriptionWithPlatformNewlinesIntact(keywordContent(data, p, eof, nextKeywordStart, contentStart)); listener.examples(keyword, nameDescription[0], nameDescription[1], currentLine); if(nextKeywordStart != -1) p = nextKeywordStart - 1; nextKeywordStart = -1; } action store_step_content { listener.step(keyword, substring(data, contentStart, p).trim(), currentLine); } action store_comment_content { listener.comment(substring(data, contentStart, p).trim(), lineNumber); keywordStart = -1; } action store_tag_content { listener.tag(substring(data, contentStart, p).trim(), currentLine); keywordStart = -1; } action inc_line_number { lineNumber++; } action last_newline { lastNewline = p + 1; } action start_keyword { if(keywordStart == -1) keywordStart = p; } action end_keyword { keyword = substring(data, keywordStart, p).replaceFirst(":$",""); keywordStart = -1; } action next_keyword_start { nextKeywordStart = p; } action start_row { p = p - 1; currentRow = new ArrayList(); currentLine = lineNumber; } action begin_cell_content { contentStart = p; } action store_cell_content { String con = substring(data, contentStart, p).trim(); currentRow.add(con.replaceAll("\\\\\\|", "|").replaceAll("\\\\\\\\", "\\\\")); } action store_row { listener.row(currentRow, currentLine); } action end_feature { if(cs < lexer_first_final) { String content = currentLineContent(data, lastNewline); throw new LexingError("Lexing error on line " + lineNumber + ": '" + content + "'. See http://wiki.github.com/aslakhellesoy/gherkin/lexingerror for more information."); } else { listener.eof(); } } include lexer_common "lexer_common.<%= @i18n.underscored_iso_code %>.rl"; }%% private final Listener listener; public <%= @i18n.underscored_iso_code.upcase %>(Listener listener) { this.listener = listener; } %% write data noerror; public void scan(String inputSequence, String uri, int offset) { listener.location(uri, offset); String input = inputSequence + "\n%_FEATURE_END_%"; byte[] data = null; try { data = input.getBytes("UTF-8"); } catch(UnsupportedEncodingException e) { throw new RuntimeException(e); } int cs, p = 0, pe = data.length; int eof = pe; int lineNumber = 1; int lastNewline = 0; int contentStart = -1; int currentLine = -1; int startCol = -1; int nextKeywordStart = -1; int keywordStart = -1; String keyword = null; List currentRow = null; %% write init; %% write exec; } private String keywordContent(byte[] data, int p, int eof, int nextKeywordStart, int contentStart) { int endPoint = (nextKeywordStart == -1 || (p == eof)) ? p : nextKeywordStart; return substring(data, contentStart, endPoint); } private static final Pattern CRLF_RE = Pattern.compile("\r\n"); private static final Pattern LF_RE = Pattern.compile("[^\r]\n"); private static final String CRLF = "\r\n"; private static final String LF = "\n"; private String[] nameAndDescriptionWithPlatformNewlinesIntact(String text) { int crlfCount = matchCount(CRLF_RE.matcher(text)); int lfCount = matchCount(LF_RE.matcher(text)); String eol = crlfCount > lfCount ? CRLF : LF; String name = null; StringBuffer description = new StringBuffer(); for(String s : text.split("\r?\n")) { if(name == null) { name = s.trim(); } else { description.append(s.trim()).append(eol); } } if(name == null) { name = ""; } return new String[]{name, description.toString().trim()}; } private int matchCount(Matcher m) { int count = 0; while(m.find()) { count++; } return count; } private String unindent(int startCol, String text) { return Pattern.compile("^[\t ]{0," + startCol + "}", Pattern.MULTILINE).matcher(text).replaceAll(""); } private String currentLineContent(byte[] data, int lastNewline) { return substring(data, lastNewline, data.length).trim(); } private String substring(byte[] data, int start, int end) { try { return new String(data, start, end-start, "utf-8"); } catch(java.io.UnsupportedEncodingException e) { throw new RuntimeException("Internal error", e); } } }