package com.github.sommeri.less4j.core.parser;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import org.antlr.runtime.Parser;
import org.antlr.runtime.ParserRuleReturnScope;
import org.antlr.runtime.RecognitionException;
import org.antlr.runtime.RecognizerSharedState;
import org.antlr.runtime.Token;
import org.antlr.runtime.TokenStream;
import org.antlr.runtime.tree.TreeAdaptor;
import com.github.sommeri.less4j.LessCompiler.Problem;
import com.github.sommeri.less4j.core.parser.LexerLogic;
import com.github.sommeri.less4j.core.parser.AntlrException;
public abstract class SuperLessParser extends Parser {
public String[] tokenErrorNames;
//TODO: write about this to error handling post, it would be cool if I could do this declaratively in grammar
public static final Map<String, String> ALTERNATIVE_NAMES_FOR_ERROR_REPORTING = new HashMap<String, String>();
static {
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("STRING", "string");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("SEMI", ";");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("DOLLAR", "$");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("IMPORT_SYM", "@import");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("URI", "url(...)");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("COMMA", ",");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("MEDIA_SYM", "@media");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("LBRACE", "{");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("RBRACE", "}");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("IDENT", "identifier");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("IDENT_WHEN", "identifier");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("IDENT_NTH_CHILD", "identifier");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("NTH_LAST_CHILD", "identifier");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("NTH_OF_TYPE", "identifier");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("NTH_LAST_OF_TYPE", "identifier");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("IDENT_EXTEND", "identifier");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("IDENT_NOT", "identifier");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("IDENT_AND", "identifier");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("IDENT_OR", "identifier");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("LPAREN", "(");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("COLON", ":");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("RPAREN", ")");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("AT_NAME", "@<name>");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("AT_CHARSET", "@<name>");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("AT_KEYFRAMES", "@<name>");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("AT_DOCUMENT", "@<name>");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("AT_VIEWPORT", "@<name>");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("AT_SUPPORTS", "@<name>");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("DOT3", "...");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("INDIRECT_VARIABLE", "@@<variable name>");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("FONT_FACE_SYM", "@font-face");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("PAGE_SYM", "@page");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("GREATER", ">");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("PLUS", "+");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("UNDERSCORE", "_");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("TILDE", "~");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("HAT", "^");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("CAT", "^^");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("MINUS", "-");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("APPENDER", "&");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("HASH", "#name");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("DOT", ".");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("LBRACKET", "[");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("NUMBER", "number");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("STAR", "repeater");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("OPEQ", "*");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("INCLUDES", "=");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("DASHMATCH", "~=");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("PREFIXMATCH", "|=");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("SUFFIXMATCH", "^=");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("SUBSTRINGMATCH", "$=");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("RBRACKET", "*=");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("IMPORTANT_SYM", "]");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("GREATER_OR_EQUAL", "!important");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("LOWER_OR_EQUAL", ">=");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("LOWER", "<=");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("SOLIDUS", "<");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("EMPTY_COMBINATOR", "/");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("URL", "empty combinator");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("NL", "url");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("NEW_LINE", "new line");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("CDO", "<!--");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("CDC", "-->");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("APPENDER_FRAGMENT", "&");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("WS_FRAGMENT", " ");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("ESCAPED_SYMBOL", "\\<escaped symbol>");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("MEANINGFULL_WHITESPACE", "whitespace");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("WS", "whitespace");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("HASH_FRAGMENT", "#<name>");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("VALUE_ESCAPE", "~'<escaped value>'");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("HASH_SYMBOL", "#");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("INTERPOLATED_VARIABLE", "@{<variable name>}");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("EMS", "em");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("EXS", "ex");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("LENGTH", "px, cm, mm, in, ...");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("ANGLE", "deg, rad, grad");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("TIME", "ms, s");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("FREQ", "khz, hz");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("REPEATER", "n");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("PERCENTAGE", "%");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("PERCENT", "%");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("UNKNOWN_DIMENSION", "%");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("HEXCHAR", "hex character");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("NONASCII", "non-ascii character");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("UNICODE", "unicode character");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("ESCAPE", "escaped character");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("COMMENT", "/* comment */");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("COMMENT_LITTLE", "// comment");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("INVALID", "invalid string like value");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("PURE_NUMBER", "number");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("PSEUDO_PAGE", "pseudo page");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("PAGE_MARGIN_BOX", "page margin box");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("IMPORT_ONCE_SYM", "@import-once");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("IMPORT_MULTIPLE_SYM", "@import-multiple");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("NAMED_EXPRESSION", "named expression");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("UNICODE_RANGE", "unicode range");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("MEDIA_EXPRESSION", "media expression");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("INTERPOLATED_MEDIA_EXPRESSION", "variable as media query");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("DOCUMENT", "@document");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("KEYFRAMES_DECLARATION", "@keyframes");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("URL_PREFIX", "url-prefix(...)");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("DOMAIN", "domain(...)");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("SUPPORTS", "@supports");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("SUPPORTS_CONDITION", "@supports condition");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("SUPPORTS_SIMPLE_CONDITION", "@supports condition");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("SUPPORTS_QUERY", "@supports condition");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("EXTEND_TARGET_SELECTOR", "target selector of extend");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("EXTEND_IN_DECLARATION", "extend declaration");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("INTERPOLABLE_NAME", "interpolable name");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("EMBEDDED_SCRIPT", "embedded script `...`");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("ESCAPED_SCRIPT", "escaped script ~`...`");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("DETACHED_RULESET", "detached ruleset");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("DETACHED_RULESET_REFERENCE", "detached ruleset call");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("UNKNOWN_AT_RULE", "unknown at-rule");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("UNKNOWN_AT_RULE_NAMES_SET", "unknown at-rule");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("IDENT_TERM", "identifier");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("NAMED_COMBINATOR", "named selector combinator");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("EXCLAMATION_MARK", "!");
ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.put("UNICODE_NON_BREAKING_WS", "nbsp U+00A0"); //this will never appear in output - it is fragment
}
protected static final String RULE_STYLESHEET = "stylesheet";
protected static final String RULE_FONT_FACE = "font-face";
protected static final String RULE_MEDIA = "media";
protected static final String RULE_NAMESPACE_REFERENCE = "namespace reference";
protected static final String RULE_VARIABLE_REFERENCE = "variable reference";
protected static final String RULE_VARIABLE_DECLARATION = "variable declaration";
protected static final String RULE_MIXIN_REFERENCE = "mixin reference";
protected static final String RULE_ABSTRACT_MIXIN_OR_NAMESPACE = "abstract mixin or namespace";
protected static final String RULE_PSEUDO_PAGE = "pseudo page";
protected static final String RULE_DECLARATION = "declaration";
protected static final String RULE_EXPRESSION = "expression";
protected static final String RULE_IMPORTS = "imports";
protected static final String RULE_CHARSET = "charset";
protected static final String RULE_PAGE = "page";
protected static final String RULE_RULESET = "ruleset";
protected static final String RULE_SELECTOR = "selectors";
protected static final String RULE_MEDIUM = "medium";
protected static final String RULE_KEYFRAME = "keyframe";
protected static final String RULE_VIEWPORT = "viewport";
protected static final String RULE_SUPPORTS = "supports";
protected static final String RULE_DOCUMENT = "document";
protected static final String RULE_PAGE_MARGIN_BOX = "page margin box";
protected static final String RULE_DETACHED_RULESET_REFERENCE = "detached ruleset reference";
protected static final String RULE_DETACHED_RULESET = "detached ruleset";
protected static final String RULE_UNKNOWN_AT_RULE = "unknown ruleset";
protected List<Problem> errors = new ArrayList<Problem>();
protected LexerLogic lexerLogic = new LexerLogic();
protected Stack<EnterRuleInfo> paraphrases = new Stack<EnterRuleInfo>();
public SuperLessParser(TokenStream input, List<Problem> errors) {
this(input, new RecognizerSharedState(), errors);
}
public SuperLessParser(TokenStream input, RecognizerSharedState state, List<Problem> errors) {
super(input, state);
this.errors = errors;
generateTokenErrorNames();
}
public abstract TreeAdaptor getTreeAdaptor();
private void generateTokenErrorNames() {
tokenErrorNames = getTokenNames().clone();
for (int i = 0; i < tokenErrorNames.length; i++) {
if (ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.containsKey(tokenErrorNames[i])) {
tokenErrorNames[i] = "'" + ALTERNATIVE_NAMES_FOR_ERROR_REPORTING.get(tokenErrorNames[i]) + "'";
}
}
}
public SuperLessParser(TokenStream input, RecognizerSharedState state) {
this(input, state, new ArrayList<Problem>());
}
public List<Problem> getAllErrors() {
return new ArrayList<Problem>(errors);
}
public boolean hasErrors() {
return !errors.isEmpty();
}
public void reportError(RecognitionException e) {
HiddenTokenAwareTreeAdaptor treeAdaptor = (HiddenTokenAwareTreeAdaptor)getTreeAdaptor();
errors.add(new AntlrException(treeAdaptor.getSource(), e, getErrorMessage(e, tokenErrorNames)));
}
public String getErrorHeader(RecognitionException e) {
if (getSourceName() != null)
return getSourceName() + " line " + e.line + ":" + (e.charPositionInLine + 1);
return "line " + e.line + ":" + (e.charPositionInLine + 1);
}
@Override
public String getErrorMessage(RecognitionException e, String[] tokenNames) {
String result = "" + super.getErrorMessage(e, tokenNames);
if (!paraphrases.isEmpty()) {
EnterRuleInfo info = paraphrases.peek();
String position = info.getStart().getLine() + ":" + (info.getStart().getCharPositionInLine() + 1);
result = result + " in " + info.getRulename() + " (which started at " + position + ")";
}
return result;
}
protected void enterRule(ParserRuleReturnScope retval, String rulename) {
//the init block is done always but the after block is done only if the thing is not backtracking
paraphrases.add(new EnterRuleInfo(retval.start, rulename));
}
protected void leaveRule() {
paraphrases.pop();
}
}
class EnterRuleInfo {
private final Token start;
private final String rulename;
public EnterRuleInfo(Token start, String rulename) {
this.start = start;
this.rulename = rulename;
}
public Token getStart() {
return start;
}
public String getRulename() {
return rulename;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("[");
builder.append(rulename);
builder.append("]");
return builder.toString();
}
}