package com.github.sommeri.less4j.core.parser; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import org.antlr.runtime.ANTLRStringStream; import org.antlr.runtime.CommonToken; import org.antlr.runtime.CommonTokenStream; import org.antlr.runtime.ParserRuleReturnScope; import org.antlr.runtime.RecognitionException; import org.antlr.runtime.Token; import org.antlr.runtime.TokenSource; import org.antlr.runtime.TokenStream; import org.antlr.runtime.tree.CommonTreeAdaptor; import org.antlr.runtime.tree.Tree; import com.github.sommeri.less4j.LessCompiler.Problem; import com.github.sommeri.less4j.LessSource; import com.github.sommeri.less4j.utils.debugonly.DebugAndTestPrint; /** * * */ public class ANTLRParser { private final boolean isDebug = false; private static final List<Integer> KEEP_HIDDEN_TOKENS = Arrays.asList(LessLexer.COMMENT, LessLexer.NEW_LINE); public ParseResult parseStyleSheet(String styleSheet, LessSource source) { return parse(styleSheet, source, InputType.STYLE_SHEET); } public ParseResult parseDeclaration(String declaration, LessSource source) { return parse(declaration, source, InputType.DECLARATION); } public ParseResult parseFullExpression(String expression, LessSource source) { return parse(expression, source, InputType.EXPRESSION_FULL); } public ParseResult parseVariableName(String expression, LessSource source) { return parse(expression, source, InputType.VARIABLE_NAME); } public ParseResult parseTerm(String term, LessSource source) { return parse(term, source, InputType.TERM); } public ParseResult parseSelector(String selector, LessSource source) { return parse(selector, source, InputType.SELECTOR); } public ParseResult parseRuleset(String ruleset, LessSource source) { return parse(ruleset, source, InputType.RULESET); } private ParseResult parse(String input, LessSource source, InputType inputType) { try { if (isDebug) DebugAndTestPrint.printTokenStream(input); List<Problem> errors = new ArrayList<Problem>(); LessLexer lexer = createLexer(input, source, errors); CollectorTokenSource tokenSource = new CollectorTokenSource(lexer, KEEP_HIDDEN_TOKENS); LessParser parser = createParser(tokenSource, source, errors); ParserRuleReturnScope returnScope = inputType.parseTree(parser); HiddenTokenAwareTree ast = (HiddenTokenAwareTree) returnScope.getTree(); merge(ast, tokenSource.getCollectedTokens()); if (isDebug) DebugAndTestPrint.print(ast); return new ParseResultImpl(ast, new ArrayList<Problem>(errors)); } catch (RecognitionException e) { throw new IllegalStateException("Recognition exception is never thrown, only declared."); } } private LessParser createParser(TokenSource tokenSource, LessSource source, List<Problem> errors) { CommonTokenStream tokens = new CommonTokenStream(tokenSource); LessParser parser = new LessParser(tokens, errors); parser.setTreeAdaptor(new HiddenTokenAwareTreeAdaptor(source)); return parser; } private LessLexer createLexer(String expression, LessSource source, List<Problem> errors) { ANTLRStringStream input = new ANTLRStringStream(expression); LessLexer lexer = new LessLexer(source, input, errors); return lexer; } private HiddenTokenAwareTree merge(HiddenTokenAwareTree ast, LinkedList<CommonToken> hiddenTokens) { ListToTreeCombiner combiner = new ListToTreeCombiner(); combiner.associate(ast, hiddenTokens); return ast; } public interface ParseResult { HiddenTokenAwareTree getTree(); List<Problem> getErrors(); boolean hasErrors(); } private class ParseResultImpl implements ParseResult { private final HiddenTokenAwareTree tree; private final List<Problem> errors; public ParseResultImpl(HiddenTokenAwareTree tree, List<Problem> errors) { super(); this.tree = tree; this.errors = errors; } public HiddenTokenAwareTree getTree() { return tree; } public List<Problem> getErrors() { return errors; } @Override public boolean hasErrors() { return !(getErrors()==null || getErrors().isEmpty()); } } private enum InputType { SELECTOR { @Override public ParserRuleReturnScope parseTree(LessParser parser) throws RecognitionException { return parser.selector(); } }, TERM { @Override public ParserRuleReturnScope parseTree(LessParser parser) throws RecognitionException { return parser.term(); } }, EXPRESSION_FULL { @Override public ParserRuleReturnScope parseTree(LessParser parser) throws RecognitionException { return parser.expression_full_done(); } }, VARIABLE_NAME { @Override public ParserRuleReturnScope parseTree(LessParser parser) throws RecognitionException { return parser.variablename(); } }, DECLARATION { @Override public ParserRuleReturnScope parseTree(LessParser parser) throws RecognitionException { return parser.declaration(); } }, STYLE_SHEET { @Override public ParserRuleReturnScope parseTree(LessParser parser) throws RecognitionException { return parser.styleSheet(); } }, RULESET { @Override public ParserRuleReturnScope parseTree(LessParser parser) throws RecognitionException { return parser.ruleSet(); } }; public abstract ParserRuleReturnScope parseTree(LessParser parser) throws RecognitionException; } } class CollectorTokenSource implements TokenSource { private final TokenSource source; private final Set<Integer> collectTokenTypes = new HashSet<Integer>(); private final LinkedList<CommonToken> collectedTokens = new LinkedList<CommonToken>(); public CollectorTokenSource(TokenSource source, Collection<Integer> collectTokenTypes) { super(); this.source = source; this.collectTokenTypes.addAll(collectTokenTypes); } /** * Returns next token from the wrapped token source. Stores it in a list if * necessary. * */ @Override public CommonToken nextToken() { CommonToken nextToken = (CommonToken)source.nextToken(); if (shouldCollect(nextToken)) { collectedTokens.add(nextToken); } return nextToken; } /** * Decide whether collect the token or not. */ protected boolean shouldCollect(Token nextToken) { // filter the token by its type return collectTokenTypes.contains(nextToken.getType()); } public LinkedList<CommonToken> getCollectedTokens() { return collectedTokens; } @Override public String getSourceName() { return source.getSourceName(); } } class HiddenTokenAwareTreeAdaptor extends CommonTreeAdaptor { private final LessSource source; public HiddenTokenAwareTreeAdaptor(LessSource source) { super(); this.source = source; } @Override public Object create(Token payload) { return new HiddenTokenAwareTree((CommonToken)payload, source); } @Override public Object errorNode(TokenStream input, Token start, Token stop, RecognitionException e) { return new HiddenTokenAwareErrorTree(input, start, stop, e, source); } @Override public void setTokenBoundaries(Object t, Token startToken, Token stopToken) { if ( t==null ) return; int start = 0; int stop = 0; if ( startToken!=null ) start = startToken.getTokenIndex(); if ( stopToken!=null ) stop = stopToken.getTokenIndex(); ((Tree)t).setTokenStartIndex(start); ((Tree)t).setTokenStopIndex(stop); if (t instanceof HiddenTokenAwareTree) { HiddenTokenAwareTree token = (HiddenTokenAwareTree) t; token.setStopToken(stopToken); } } public LessSource getSource() { return source; } }