package gherkin.lexer; 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.sanitized_key.capitalize %> 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)?( )*\\Z", "")); listener.py_string(con, currentLine); } action store_feature_content { String con = multilineStrip(keywordContent(data, p, eof, nextKeywordStart, contentStart).trim()); listener.feature(keyword, con, currentLine); if(nextKeywordStart != -1) p = nextKeywordStart - 1; nextKeywordStart = -1; } action store_background_content { String con = multilineStrip(keywordContent(data, p, eof, nextKeywordStart, contentStart)); listener.background(keyword, con, currentLine); if(nextKeywordStart != -1) p = nextKeywordStart - 1; nextKeywordStart = -1; } action store_scenario_content { String con = multilineStrip(keywordContent(data, p, eof, nextKeywordStart, contentStart)); listener.scenario(keyword, con, currentLine); if(nextKeywordStart != -1) p = nextKeywordStart - 1; nextKeywordStart = -1; } action store_scenario_outline_content { String con = multilineStrip(keywordContent(data, p, eof, nextKeywordStart, contentStart)); listener.scenario_outline(keyword, con, currentLine); if(nextKeywordStart != -1) p = nextKeywordStart - 1; nextKeywordStart = -1; } action store_examples_content { String con = multilineStrip(keywordContent(data, p, eof, nextKeywordStart, contentStart)); listener.examples(keyword, con, 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 { currentRow.add(substring(data, contentStart, p).trim()); } 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 + "'"); } else { listener.eof(); } } include lexer_common "lexer_common.<%= @i18n.sanitized_key %>.rl"; }%% private final Listener listener; public <%= @i18n.sanitized_key.capitalize %>(Listener listener) { this.listener = listener; } %% write data noerror; public void scan(CharSequence inputSequence) { String input = inputSequence.toString() + "\n%_FEATURE_END_%"; byte[] data = input.getBytes(); 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 multilineStrip(String text) { int crlfCount = matchCount(CRLF_RE.matcher(text)); int lfCount = matchCount(LF_RE.matcher(text)); String eol = crlfCount > lfCount ? CRLF : LF; StringBuffer result = new StringBuffer(); for(String s : text.split("\r?\n")) { result.append(s.trim()).append(eol); } return result.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("^ {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); } } }