/*
 * Copyright 2000-2013 Vaadin Ltd.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
/* -*-java-extended-*-
 * Copyright (c) 1999 World Wide Web Consortium
 * (Massachusetts Institute of Technology, Institut National de Recherche
 *  en Informatique et en Automatique, Keio University).
 * All Rights Reserved. http://www.w3.org/Consortium/Legal/
 *
 * $Id: Parser.jj,v 1.15 2000/10/27 21:09:37 plehegar Exp $
 */

options {
    IGNORE_CASE  = true;
    STATIC = false;
    USER_CHAR_STREAM = true;
    /* DEBUG_TOKEN_MANAGER = true;
       DEBUG_PARSER = true; */
}

PARSER_BEGIN(Parser)

package com.vaadin.sass.internal.parser;

import java.io.*;
import java.net.*;
import java.util.ArrayList;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;

import org.w3c.css.sac.ConditionFactory;
import org.w3c.css.sac.Condition;
import org.w3c.css.sac.SelectorFactory;
import org.w3c.css.sac.SelectorList;
import org.w3c.css.sac.Selector;
import org.w3c.css.sac.SimpleSelector;
import org.w3c.css.sac.DocumentHandler;
import org.w3c.css.sac.InputSource;
import org.w3c.css.sac.ErrorHandler;
import org.w3c.css.sac.CSSException;
import org.w3c.css.sac.CSSParseException;
import org.w3c.css.sac.Locator;
import org.w3c.css.sac.LexicalUnit;

import org.w3c.flute.parser.selectors.SelectorFactoryImpl;
import org.w3c.flute.parser.selectors.ConditionFactoryImpl;

import org.w3c.flute.util.Encoding;

import com.vaadin.sass.internal.handler.*;

import com.vaadin.sass.internal.tree.*;

/**
 * A CSS2 parser
 *
 * @author Philippe Le H�garet
 * @version $Revision: 1.15 $
 */
public class Parser implements org.w3c.css.sac.Parser {

    // replaces all \t, \n, etc with this StringBuffer.
    static final StringBuilder SPACE = new StringBuilder(" ");

    // the document handler for the parser
    protected SCSSDocumentHandlerImpl documentHandler;
    // the error handler for the parser
    protected ErrorHandler errorHandler;
    // the input source for the parser
    protected InputSource source;

    protected ConditionFactory conditionFactory;
    protected SelectorFactory selectorFactory;

    // temporary place holder for pseudo-element ...
    private String pseudoElt;
    
    /**
     * Creates a new Parser
     */
    public Parser() {
	this((CharStream) null);
    }

    /**
     * @@TODO
     * @exception CSSException Not yet implemented
     */    
    public void setLocale(Locale locale) throws CSSException {
	throw new CSSException(CSSException.SAC_NOT_SUPPORTED_ERR);
    }
    
    public InputSource getInputSource(){
        return source;
    }

    /**
     * Set the document handler for this parser
     */    
    public void setDocumentHandler(DocumentHandler handler) {
	this.documentHandler = (SCSSDocumentHandlerImpl) handler;
    }

    public void setSelectorFactory(SelectorFactory selectorFactory) {
	this.selectorFactory = selectorFactory;
    }

    public void setConditionFactory(ConditionFactory conditionFactory) {
	this.conditionFactory = conditionFactory;
    }

    /**
     * Set the error handler for this parser
     */    
    public void setErrorHandler(ErrorHandler error) {
	this.errorHandler = error;
    }
    
    /**
     * Main parse methods
     *
     * @param source the source of the style sheet.
     * @exception IOException the source can't be parsed.
     * @exception CSSException the source is not CSS valid.
     */
    public void parseStyleSheet(InputSource source) 
	    throws CSSException, IOException {
	this.source = source;
	ReInit(getCharStreamWithLurk(source));
	if (selectorFactory == null) {
	    selectorFactory = new SelectorFactoryImpl();
	}
	if (conditionFactory == null) {
	    conditionFactory = new ConditionFactoryImpl();
	}

	parserUnit();
    }

    /**
     * Convenient method for URIs.
     *
     * @param systemId the fully resolved URI of the style sheet.
     * @exception IOException the source can't be parsed.
     * @exception CSSException the source is not CSS valid.
     */    
    public void parseStyleSheet(String systemId) 
	    throws CSSException, IOException {
	parseStyleSheet(new InputSource(systemId));
    }

    /**
     * This method parses only one rule (style rule or at-rule, except @charset).
     *
     * @param source the source of the rule.
     * @exception IOException the source can't be parsed.
     * @exception CSSException the source is not CSS valid.
     */    
    // TODO required by original parser but not used by Vaadin?
    public void parseRule(InputSource source) 
	    throws CSSException, IOException {
	this.source = source;
	ReInit(getCharStreamWithLurk(source));

	if (selectorFactory == null) {
	    selectorFactory = new SelectorFactoryImpl();
	}
	if (conditionFactory == null) {
	    conditionFactory = new ConditionFactoryImpl();
	}
	_parseRule();
    }

    /**
     * This method parses a style declaration (including the surrounding curly
     * braces).
     *
     * @param source the source of the style declaration.
     * @exception IOException the source can't be parsed.
     * @exception CSSException the source is not CSS valid.
     */
    public void parseStyleDeclaration(InputSource source) 
	    throws CSSException, IOException {
	this.source = source;
	ReInit(getCharStreamWithLurk(source));

	if (selectorFactory == null) {
	    selectorFactory = new SelectorFactoryImpl();
	}
	if (conditionFactory == null) {
	    conditionFactory = new ConditionFactoryImpl();
	}
	_parseDeclarationBlock();
    }

    /**
     * This methods returns "http://www.w3.org/TR/REC-CSS2".
     * @return the string "http://www.w3.org/TR/REC-CSS2".
     */    
    public String getParserVersion() {
	return "http://www.w3.org/TR/REC-CSS2";
    }

    /**
     * Parse methods used by DOM Level 2 implementation.
     */    
    public void parseImportRule(InputSource source) 
	    throws CSSException, IOException {
	this.source = source;
	ReInit(getCharStreamWithLurk(source));

	if (selectorFactory == null) {
	    selectorFactory = new SelectorFactoryImpl();
	}
	if (conditionFactory == null) {
	    conditionFactory = new ConditionFactoryImpl();
	}
	_parseImportRule();
    }

    public void parseMediaRule(InputSource source) 
	    throws CSSException, IOException {
	this.source = source;
	ReInit(getCharStreamWithLurk(source));

	if (selectorFactory == null) {
	    selectorFactory = new SelectorFactoryImpl();
	}
	if (conditionFactory == null) {
	    conditionFactory = new ConditionFactoryImpl();
	}
	_parseMediaRule();
    }

    public SelectorList parseSelectors(InputSource source) 
	    throws CSSException, IOException {
	this.source = source;
	ReInit(getCharStreamWithLurk(source));

	return null;
    }

    public LexicalUnit parsePropertyValue(InputSource source) 
	    throws CSSException, IOException {
	this.source = source;
	ReInit(getCharStreamWithLurk(source));

	return expr();
    }

    public boolean parsePriority(InputSource source) 
	    throws CSSException, IOException {
	this.source = source;
	ReInit(getCharStreamWithLurk(source));

	return prio();
    }

    /**
     * Convert the source into a Reader. Used only by DOM Level 2 parser methods.
     */    
    private Reader getReader(InputSource source) throws IOException {
	if (source.getCharacterStream() != null) {
	    return source.getCharacterStream();
	} else if (source.getByteStream() != null) {
	    // My DOM level 2 implementation doesn't use this case.
	    if (source.getEncoding() == null) {
		// unknown encoding, use ASCII as default.
		return new InputStreamReader(source.getByteStream(), "ASCII");
	    } else {
		return new InputStreamReader(source.getByteStream(), 
					     source.getEncoding());
	    }
	} else {
	    // systemId
	    // @@TODO
	    throw new CSSException("not yet implemented");
	}
    }

    /**
     * Convert the source into a CharStream with encoding informations.
     * The encoding can be found in the InputSource or in the CSS document.
     * Since this method marks the reader and make a reset after looking for
     * the charset declaration, you'll find the charset declaration into the
     * stream.  
     */
    private CharStream getCharStreamWithLurk(InputSource source)
	    throws CSSException, IOException {
	if (source.getCharacterStream() != null) {
	    // all encoding are supposed to be resolved by the user
	    // return the reader
	    return new Generic_CharStream(source.getCharacterStream(), 1, 1);
	} else if (source.getByteStream() == null) {
	    // @@CONTINUE ME. see also getReader() with systemId
	    try {
		source.setByteStream(new URL(source.getURI()).openStream());
	    } catch (Exception e) {
		try {
		    source.setByteStream(new FileInputStream(source.getURI()));
		} catch (IOException ex) {
		    throw new CSSException("invalid url ?");
		}
	    }
	}
	//use UTF-8 as the default encoding.
	String encoding = source.getEncoding();
	InputStream input = source.getByteStream();
	if (!input.markSupported()) {
            // If mark is not supported, wrap it in a stream which supports mark
            input = new BufferedInputStream(input);
            source.setByteStream(input);
        }
        // Mark either the original stream or the wrapped stream
        input.mark(100);
	if(encoding == null){
	    encoding = "ASCII";
	
	    char c = ' ';

	    c = (char) input.read();

	    if (c == '@') {
	        // hum, is it a charset ?
	        int size   = 100;
	        byte[] buf = new byte[size];
	        input.read(buf, 0, 7);
	        String keyword = new String(buf, 0, 7);
	        if (keyword.equals("charset")) {
	            // Yes, this is the charset declaration !

	            // here I don't use the right declaration : white space are ' '.
	            while ((c = (char) input.read()) == ' ') {
		        // find the first quote
		    }
		    char endChar = c;
		    int i = 0;

		    if ((endChar != '"') && (endChar != '\'')) {
		        // hum this is not a quote.
		        throw new CSSException("invalid charset declaration");
		    }

		    while ((c = (char) input.read()) != endChar) {
		        buf[i++] = (byte) c;
		        if (i == size) {
		            byte[] old = buf;
			    buf = new byte[size + 100];
			    System.arraycopy(old, 0, buf, 0, size);
			    size += 100;
		        }
		    }
    		    while ((c = (char) input.read()) == ' ') {
    		        // find the next relevant character
    		    }
		    if (c != ';') {
		        // no semi colon at the end ?
		        throw new CSSException("invalid charset declaration: "
					   + "missing semi colon");
		    }
		    encoding = new String(buf, 0, i);
		    if (source.getEncoding() != null) {
		        // compare the two encoding informations.
		        // For example, I don't accept to have ASCII and after UTF-8.
		        // Is it really good ? That is the question.
		        if (!encoding.equals(source.getEncoding())) {
		            throw new CSSException("invalid encoding information.");
		        }
		    }
	        } // else no charset declaration available
	    }
	}
	// ok set the real encoding of this source.
	source.setEncoding(encoding);
	// set the real reader of this source.
	source.setCharacterStream(new InputStreamReader(source.getByteStream(),
                                             Encoding.getJavaEncoding(encoding)));
	// reset the stream (leave the charset declaration in the stream).
	input.reset();

	return new Generic_CharStream(source.getCharacterStream(), 1, 1);
    }

    private LocatorImpl currentLocator;
    private Locator getLocator() {
	if (currentLocator == null) {
	    currentLocator = new LocatorImpl(this);
	    return currentLocator;
	}
	return currentLocator.reInit(this);
    }
    private LocatorImpl getLocator(Token save) {
	if (currentLocator == null) {
	    currentLocator = new LocatorImpl(this, save);
	    return currentLocator;
	}
	return currentLocator.reInit(this, save);
    }

    private void reportError(Locator l, Exception e) {
	if (errorHandler != null) {
	    if (e instanceof ParseException) {
		// construct a clean error message.
		ParseException pe = (ParseException) e;
		if (pe.specialConstructor) {
		    StringBuffer errorM = new StringBuffer();
		    if (pe.currentToken != null) {
			errorM.append("encountered \"")
			    .append(pe.currentToken.next);
		    }
		    errorM.append('"');
		    if (pe.expectedTokenSequences.length != 0) {
			errorM.append(". Was expecting one of: ");
			for (int i = 0; i < pe.expectedTokenSequences.length; i++) {
			    for (int j = 0; j < pe.expectedTokenSequences[i].length; j++) {
				int kind = pe.expectedTokenSequences[i][j];
				if (kind != S) {
				    errorM.append(pe.tokenImage[kind]);
				    errorM.append(' ');
				}
			    }
			}
		    }
		    errorHandler.error(new CSSParseException(errorM.toString(), 
							     l, e));
		} else {
		    errorHandler.error(new CSSParseException(e.getMessage(), 
							     l, e));
		}
	    } else if (e == null) {
		errorHandler.error(new CSSParseException("error", l, null));
	    } else {
		errorHandler.error(new CSSParseException(e.getMessage(), l, e));
	    }
	}
    }

    private void reportWarningSkipText(Locator l, String text) {
	if (errorHandler != null && text  != null) {
	    errorHandler.warning(new CSSParseException("Skipping: " + text, l));
	}
    }
}

PARSER_END(Parser)

/*
 * The tokenizer 
 */

<DEFAULT>
TOKEN :
{
    < S : ( [ " ", "\t" , "\n" , "\r", "\f" ] )+ > 
	{ image = Parser.SPACE; }
}

/*
 * for fixing #11638: Ending an imported SCSS file with a comment causes an error in the Sass.
 * now the single line comment is parsed as special token, before, they were simply skipped.
 * solution got from http://www.engr.mun.ca/~theo/JavaCC-FAQ/javacc-faq-moz.htm#tth_sEc3.15
 */
<DEFAULT>
SPECIAL_TOKEN : {
< SINGLE_LINE_COMMENT: "//"(~["\n","\r"])* ("\n"|"\r"|"\r\n")? > }

<DEFAULT>
MORE :
{
  <"/**" ~["/"]> { input_stream.backup(1); } : IN_FORMAL_COMMENT
|
  "/*" : IN_MULTI_LINE_COMMENT
}
<IN_FORMAL_COMMENT>
SPECIAL_TOKEN :
{
  <FORMAL_COMMENT: "*/" > : DEFAULT
}

<IN_MULTI_LINE_COMMENT>
SKIP :
{
  <MULTI_LINE_COMMENT: "*/" > : DEFAULT
}

<IN_FORMAL_COMMENT,IN_MULTI_LINE_COMMENT>
MORE :
{
  < ~[] >
}

<DEFAULT>
TOKEN :
{
  < CDO : "<!--" >
  | < CDC : "-->" >
  | < LBRACE : "{" >
  | < RBRACE : "}">
  | < DASHMATCH : "|=" >
  | < CARETMATCH : "^=" >
  | < DOLLARMATCH : "$=" >
  | < STARMATCH : "*=" >
  | < INCLUDES  : "~=" >
  | < EQ        : "=" >
  | < PLUS      : "+" >
  | < MINUS     : "-" >
  | < COMMA     : "," >
  | < SEMICOLON : ";" >
  | < PRECEDES  : ">" >
  | < SIBLING   : "~" >
  | < SUCCEEDS : "<" >
  | < DIV       : "/" >
  | < LBRACKET  : "[" >
  | < RBRACKET  : "]" >
  | < ANY       : "*" >
  | < MOD       : "%" >
  | < PARENT    : "&" >
  | < DOT       : "." >
  | < LPARAN    : "(" >
  | < RPARAN    : ")">
  | < COMPARE   : "==" >
  | < OR		: "||" >
  | < AND		: "&&" >
  | < NOT_EQ : "!=" >
}

<DEFAULT>
TOKEN :
{
  < COLON     : ":" >
}

< DEFAULT >
TOKEN :
{
	< INTERPOLATION : "#{"< VARIABLE > "}">
}

<DEFAULT>
TOKEN : /* basic tokens */
{ 
    < NONASCII    : ["\200"-"\377"] >
  | < #H          : ["0"-"9", "a"-"f"] >
  | < #UNICODE    : "\\" <H> ( <H> )? /* I can't say {1,6} */
                         ( <H> )? ( <H> )?
                         ( <H> )? ( <H> )? 
                    ( [ " ", "\t" , "\n" , "\r", "\f" ] )? >
  | < #ESCAPE     : <UNICODE> | ( "\\" [ " "-"~","\200"-"\377" ] ) >
  | < #NMSTART    : ("-")?[ "a"-"z","_"] | <NONASCII> | <ESCAPE> >
  | < #NMCHAR	  : ["a"-"z", "0"-"9", "-", "_"] | <NONASCII> | <ESCAPE> >
  | < #STRINGCHAR : [ "\t"," ","!","#","$","%","&","("-"~" ] 
                    | "\\\n" | "\\\r\n" | "\\\r" | "\\\f" 
                    | <NONASCII> | <ESCAPE> >
  | < #D          : ["0"-"9"] >
  | < #NAME       : ( <NMCHAR> )+ >
 
}

<DEFAULT>
TOKEN :
{
    <TO         : "to">
    |<THROUGH   : "through">
    |<EACH_IN        : "in">
    |<FROM      : "from">
}

/* DERECTIVES */
<DEFAULT>
TOKEN :
{
    <MIXIN_SYM          : "@mixin">
  | <INCLUDE_SYM        : "@include">
  | <FUNCTION_SYM       : "@function">
  | <RETURN_SYM         : "@return">
  | <DEBUG_SYM          : "@debug">
  | <WARN_SYM           : "@warn">
  | <FOR_SYM            : "@for">
  | <EACH_SYM           : "@each">
  | <WHILE_SYM          : "@while">
  | <IF_SYM             : "@if">
  | <ELSE_SYM           : "@else">
  | <EXTEND_SYM         : "@extend">
  | <MOZ_DOCUMENT_SYM   : "@-moz-document">
  | <SUPPORTS_SYM       : "@supports">
  | <CONTENT_SYM        : "@content">
}

< DEFAULT >
TOKEN:
{
  < MICROSOFT_RULE : "filter"|"-ms-filter" > 
}

< DEFAULT >
TOKEN:
{
  < IF : "if" > 
}

<DEFAULT>
TOKEN:
{
        < GUARDED_SYM : "!" ( <S> )? "default">
}

<DEFAULT>
TOKEN :
{
  < STRING      : ( "\"" ( <STRINGCHAR> | "'" )* "\"" ) | 
                  ( "'" ( <STRINGCHAR> | "\"" )* "'" ) >
  | < IDENT         : <NMSTART> ( <NMCHAR> )* >
  | < NUMBER	  : ( <D> )+ | ( <D> )* "." ( <D> )+ > 
  | < #_URL       : [ "!","#","$","%","&","*"-"~" ] | <NONASCII> | <ESCAPE> >
  | < URL         : "url(" ( <S> )*
                    ( <STRING> | ( <_URL> )* ) ( <S> )* ")" >            
}

<DEFAULT>
TOKEN:
{
	< VARIABLE : "$" <IDENT>>
}

<DEFAULT>
TOKEN :
{
   < PERCENTAGE : <NUMBER> "%" >
 | < PT : <NUMBER> "pt" >
 | < MM : <NUMBER> "mm" >
 | < CM : <NUMBER> "cm" >
 | < PC : <NUMBER> "pc" >
 | < IN : <NUMBER> "in" >
 | < PX : <NUMBER> "px" >
 | < EMS : <NUMBER> "em" >
 | < LEM : <NUMBER> "lem" >
 | < REM : <NUMBER> "rem" >
 | < EXS : <NUMBER> "ex" >
 | < DEG : <NUMBER> "deg" >
 | < RAD : <NUMBER> "rad" >
 | < GRAD : <NUMBER> "grad" >
 | < MS : <NUMBER> "ms" >
 | < SECOND  : <NUMBER> "s" >
 | < HZ : <NUMBER> "Hz" >
 | < KHZ : <NUMBER> "kHz" >
 | < DIMEN  : <NUMBER> <IDENT> >
}

<DEFAULT>
TOKEN :
{
  < HASH : "#" <NAME> >
}

/* RESERVED ATRULE WORDS */
<DEFAULT>
TOKEN : 
{
    < IMPORT_SYM   : "@import">
  | < MEDIA_SYM    : "@media" >
  | < CHARSET_SYM  : "@charset" >
  | < PAGE_SYM     : "@page"  >
  | < FONT_FACE_SYM: "@font-face" >
  | < KEY_FRAME_SYM: "@keyframes" | "@-moz-keyframes" | "@-o-keyframes" | "@-webkit-keyframes" | "@-ms-keyframes">
  | < ATKEYWORD    : "@" <IDENT> >
}

<DEFAULT>
TOKEN :
{
 < IMPORTANT_SYM : "!" ( <S> )? "important" >
}

<DEFAULT>
TOKEN :
{
    < #RANGE0 : <H> <H> <H> <H>  <H> >
    | < #RANGE1 : <H> <H> <H> <H> <H> ( "?" )? >
    | < #RANGE2 : <H> <H> <H> <H> ( "?" )? ( "?" )? >
    | < #RANGE3 : <H> <H> <H> ( "?" )? ( "?" )? ( "?" )? >
    | < #RANGE4 : <H> <H> ( "?" )? ( "?" )? ( "?" )? ( "?" )? >
    | < #RANGE5 : <H> ( "?" )? ( "?" )? ( "?" )? ( "?" )? ( "?" )? >
    | < #RANGE6 : "?" ( "?" )? ( "?" )? ( "?" )? ( "?" )? ( "?" )? >
    | < #RANGE  : <RANGE0> | <RANGE1> | <RANGE2> 
                  | <RANGE3> | <RANGE4> | <RANGE5> | <RANGE6> >
    | < #UNI    : <H> ( <H> )? ( <H> )? ( <H> )? ( <H> )? ( <H> )? >
    | < UNICODERANGE : "U+" <RANGE>
                        | "U+" <UNI> "-" <UNI> >
}

< DEFAULT >
TOKEN :
{
  	< REMOVE : "remove" (< S >)? "(" >
    | < APPEND : "append" (< S >)? "(" >
  	| < CONTAINS : "contains" (< S >)? "(" >
}

<DEFAULT>
TOKEN :
{
 < FUNCTION : <IDENT>(< S >)* "(" >
}

<DEFAULT, IN_MULTI_LINE_COMMENT>
TOKEN :
{ /* avoid token manager error */
    < UNKNOWN : ~[] >
}

/*
 * The grammar of CSS2
 */

/**
 * The main entry for the parser.
 *
 * @exception ParseException exception during the parse
 */
void parserUnit() :
{}
{
    try {
	{ documentHandler.startDocument(source); }
	( charset() )?
	( <S> comments()
	 | ignoreStatement() )*
	( importDeclaration() ( ignoreStatement() ( <S> )* )* )*
	afterImportDeclaration()
	<EOF>
    } finally {
	documentHandler.endDocument(source);
    }
}

void charset() :
{ Token n; }
{  
    try {
	<CHARSET_SYM> ( <S> )* n=<STRING> ( <S> )* ";"
    } catch (ParseException e) {
        reportError(getLocator(e.currentToken.next), e);
	skipStatement();
	// reportWarningSkipText(getLocator(), skipStatement());
    } catch (Exception e) {
        reportError(getLocator(), e);
	skipStatement();
	// reportWarningSkipText(getLocator(), skipStatement());
    }
}

void afterImportDeclaration() :
{String ret; 
 Locator l;
}
{
  ( 
     ( debuggingDirective() | mixinDirective() | controlDirective() | includeDirective() | styleRule() | media() 
             | page() | fontFace() | keyframes() | LOOKAHEAD(variable()) variable() | listModifyDirective()
      | { l = getLocator(); } ret=skipStatement() 
      {
	  if ((ret == null) || (ret.length() == 0)) {
	      return; 
	  }
	  if (ret.charAt(0) == '@') {
	      documentHandler.unrecognizedRule(ret);
	  } else {
	      reportWarningSkipText(l, ret);
	  }
      }
      )
    ( ignoreStatement() ( <S> )* )* )*
}

void ignoreStatement() :
{}
{
   <CDO> | <CDC> | atRuleDeclaration()
}

/**
 * The import statement
 *
 * @exception ParseException exception during the parse
 */
void importDeclaration() :
{Token n;
 String uri;
 MediaListImpl ml = new MediaListImpl();
 boolean isURL = false;
}
{
 try {
  <IMPORT_SYM> 
       ( <S> )* ( n=<STRING> { uri = convertStringIndex(n.image, 1, 
							n.image.length() -1); }
		  | n=<URL> 
	   {
	       isURL=true;	      
	       uri = n.image.substring(4, n.image.length()-1).trim();
	       if ((uri.charAt(0) == '"')
		   || (uri.charAt(0) == '\'')) {
		   uri = uri.substring(1, uri.length()-1);
	       }
	   }
		  )
       ( <S> )* mediaStatement(ml) ";"
       ( <S> )*
	   {
	       if (ml.getLength() == 0) {
		   // see section 6.3 of the CSS2 recommandation.
		   ml.addItem("all");
	       }
	       documentHandler.importStyle(uri, ml, isURL);
	   }
 } catch (ParseException e) {
     reportError(getLocator(), e);
     skipStatement();
     // reportWarningSkipText(getLocator(), skipStatement());
 }
}

/**
 * @exception ParseException exception during the parse
 */
void keyframes() :
{
    Token n;
    boolean start = false;
    String keyframeName = null;
    String animationname = "";
}
{
 try {
  n=<KEY_FRAME_SYM> ( <S> )* {keyframeName = n.image;}
  (n = <IDENT>{animationname += n.image; }|n = < INTERPOLATION >{ animationname += n.image; })+(<S>)*
   {start = true; documentHandler.startKeyFrames(keyframeName, animationname); }
   <LBRACE> ( <S> )* ( keyframeSelector() | contentDirective() )* <RBRACE> ( <S> )*
 } catch (ParseException e) {
     reportError(getLocator(), e);
     skipStatement();
 } finally {
     if (start) {
         documentHandler.endKeyFrames();
     }
 }
}

void keyframeSelector():
{
    Token n;
    String selector = "";
    boolean start = false;
}
{
    try{
        (n = <FROM> | n = <TO> | n = <PERCENTAGE>){selector += n.image;} (<S>)*
        (<COMMA> (<S>)* (n = <FROM> | n = <TO> | n = <PERCENTAGE>) {selector += (", " + n.image);} (<S>)* )*
          <LBRACE> (<S>)*
        {
          start = true;
          documentHandler.startKeyframeSelector(selector);
         }
        (ifContentStatement() | microsoftExtension() )*
        <RBRACE> (<S>)*
    }
    catch (ThrowedParseException e) {
        if (errorHandler != null) {
            LocatorImpl li = new LocatorImpl(this,
                                             e.e.currentToken.next.beginLine,
                                             e.e.currentToken.next.beginColumn-1);
            reportError(li, e.e);
        }
    } catch (ParseException e) {
        reportError(getLocator(), e);
        skipStatement();
        // reportWarningSkipText(getLocator(), skipStatement());
    } catch (TokenMgrError e) {
        reportWarningSkipText(getLocator(), skipStatement());
    } finally {
        if (start) {
            documentHandler.endKeyframeSelector();
        }
    }
}

/**
 * @exception ParseException exception during the parse
 */
/* see http://www.w3.org/TR/css3-mediaqueries/ */
void media() :
{ 
    boolean start = false;
    String ret;
    MediaListImpl ml = new MediaListImpl(); 
}
{
 try {
  <MEDIA_SYM> ( <S> )*
   mediaStatement(ml)
   { start = true; documentHandler.startMedia(ml); }
   <LBRACE> ( <S> )* ( debuggingDirective() | styleRule() | skipUnknownRule() )* <RBRACE> ( <S> )*
 } catch (ParseException e) {
     reportError(getLocator(), e);
     skipStatement();
     // reportWarningSkipText(getLocator(), skipStatement());
 } finally {
     if (start) {
	 documentHandler.endMedia(ml);
     }
 }
}

void mediaStatement(MediaListImpl ml) :
{
  Token t;
}
{
    {
      t = getToken(1);
      // loop over comma separated parts, add each to ml
      while ((t.kind != LBRACE) && (t.kind != EOF) && (t.kind != SEMICOLON)) {
        StringBuffer s = new StringBuffer();
        s.append(getToken(0).image);
        while ((t.kind != COMMA) && (t.kind != LBRACE) && (t.kind != EOF) && (t.kind != SEMICOLON)) {
	      s.append(t.image);
	      getNextToken();
          t = getToken(1);
        }
        if (t.kind == COMMA) {
          // skip the comma and the token before it that is still the active token
          getNextToken();
          getNextToken();
          t = getToken(1);
        }
        String str = s.toString().trim();
        if (str.length() > 0) {
          ml.addItem(str);
        }
      }
    }
}

/**
 * @exception ParseException exception during the parse
 */
String medium() : /* tv, projection, screen, ... */
{Token n;}
{
    n=<IDENT> { return convertIdent(n.image); }
}

/**
 * @exception ParseException exception during the parse
 */
void page() :
{
 boolean start = false;
 Token n = null;
 String page = null;
 String pseudo = null;
}
{
 try {
  <PAGE_SYM> ( <S> )* ( n=<IDENT> ( <S> )* )?
  ( pseudo=pseudo_page() )? 
      {
	  if (n != null) {
	      page = convertIdent(n.image);
	  }
      }
  <LBRACE> (<S>)* 
      {
	  start = true;
	  documentHandler.startPage(page, pseudo);
      }
     ( declaration() )? ( ";" ( <S> )* ( declaration() )? )* 
     <RBRACE> (<S>)*
 } catch (ParseException e) {
     if (errorHandler != null) {
	 LocatorImpl li = new LocatorImpl(this,
					  e.currentToken.next.beginLine,
					  e.currentToken.next.beginColumn-1);
	 reportError(li, e);
	 skipStatement();
	 // reportWarningSkipText(li, skipStatement());
     } else {
	 skipStatement();
     }
 } finally {
     if (start) {
	 documentHandler.endPage(page, pseudo);
     }
 }
}

String pseudo_page() :
{ Token n; }
{
    ":" n=<IDENT> ( <S> )* { return convertIdent(n.image); }
}

void fontFace() :
{
    boolean start = false;
}
{
 try {
  <FONT_FACE_SYM> ( <S> )*
     <LBRACE> (<S>)*
      { start = true; documentHandler.startFontFace(); }
     ( declaration() )? ( ";" ( <S> )* ( declaration() )? )*
     <RBRACE> (<S>)*
 } catch (ParseException e) {
     reportError(getLocator(), e);
     skipStatement();
     // reportWarningSkipText(getLocator(), skipStatement());
 } finally {
     if (start) {
	 documentHandler.endFontFace();
     }
 }
}

/**
 * @exception ParseException exception during the parse
 */
void atRuleDeclaration() :
{Token n;
 String ret;
}
{
 n=<ATKEYWORD>
    {
	ret=skipStatement();
	if ((ret != null) && (ret.charAt(0) == '@')) {
	    documentHandler.unrecognizedRule(ret);
	} else {
	    reportWarningSkipText(getLocator(), ret);
	}
    }
}

void skipUnknownRule() :
{ Token n;}
{
 ( n=<ATKEYWORD>
| n=<CDO>
| n=<CHARSET_SYM>
| n=<COMMA>
| n=<DASHMATCH>
| n=<FONT_FACE_SYM>
| n=<FUNCTION>
| n=<IMPORTANT_SYM>
| n=<IMPORT_SYM>
| n=<INCLUDES>
| n=<LBRACE>
| n=<MEDIA_SYM>
| n=<NONASCII>
| n=<NUMBER>
| n=<PAGE_SYM>
| n=<PERCENTAGE>
| n=<STRING>
| n=<UNICODERANGE>
| n=<URL>
| n=";"
| n="-"
| n=<UNKNOWN>
   ) {
     String ret;
     Locator loc = getLocator();
     ret=skipStatement();
     if ((ret != null) && (n.image.charAt(0) == '@')) {
	 documentHandler.unrecognizedRule(ret);
     } else {
     reportWarningSkipText(loc, ret);
     }
 }
}

/**
 * @exception ParseException exception during the parse
 */
char combinator() :
{
char connector = ' ';
}
{
    (connector = combinatorChar()
     | <S>  (connector = combinatorChar())?) { return connector; }
}

/**to refactor combinator and reuse in selector().*/
char combinatorChar() :
{Token t;}
{
    (t = <PLUS> | t = <PRECEDES> | t = <SIBLING>) (<S>)*
    {
        return t.image.charAt(0);
    }
}

void microsoftExtension() :
{
  Token n;
  String name = "";
  String value = "";
}

{
  // This is not really taking the syntax of filter rules into account
  n = < MICROSOFT_RULE > (< S >)* { name = n.image; }
	< COLON >
	((n = < IDENT > { value += n.image; })
	| (n = < NUMBER > { value += n.image; }) 
	| (n = < STRING > { value += n.image; })
	| (n = < COMMA > { value += n.image; })
	| (n = < INTERPOLATION > { value += n.image; })
	| (n = < COLON > { value += n.image; })
	| (n = < FUNCTION > { value += n.image; })
	| (n = < RPARAN > { value += n.image; })
	| (n = < EQ > { value += n.image; })
	| (n = < DOT > { value += n.image; })
	| (n = < S > { if(value.lastIndexOf(' ') != value.length()-1)
	{ value += n.image; } }
	) )+
	< SEMICOLON >
	(< S >)*
  	{ documentHandler.microsoftDirective(name, value); 	}
}

/**
 * @exception ParseException exception during the parse
 */
String property() :
{Token t;String s = "";
}
{
    (t = <IDENT>{s += t.image; }|t = < INTERPOLATION >{ s += t.image; })+(< S >)*
    {
        return s;
    }
}

String variableName() :
{Token n;}
{
    n=<VARIABLE> (<S>)* {return convertIdent(n.image.substring(1));}
}

String functionName() :
{Token n;}
{
    n=<FUNCTION> ( <S> )* {return convertIdent(n.image.substring(0, n.image.length()-1));}
}
/**
 * @exception ParseException exception during the parse
 */
void styleRule() :
{
    boolean start = false;
    ArrayList<String> l = null;
    Token save;
    Locator loc;
}
{
 try {
     l=selectorList() { save = token; } <LBRACE> (<S>)*
     {
	 start = true;
	 documentHandler.startSelector(l);
     }
     // a CSS import here will not work
     ( ifContentStatement() | microsoftExtension() | importDeclaration() )*
     <RBRACE> (<S>)*
 } catch (ThrowedParseException e) {
     if (errorHandler != null) {
	 LocatorImpl li = new LocatorImpl(this,
					  e.e.currentToken.next.beginLine,
					  e.e.currentToken.next.beginColumn-1);
	 reportError(li, e.e);
     }
 } catch (ParseException e) {
     reportError(getLocator(), e);
     skipStatement();
     // reportWarningSkipText(getLocator(), skipStatement());
 } catch (TokenMgrError e) {
     reportWarningSkipText(getLocator(), skipStatement());
 } finally {
     if (start) {
	 documentHandler.endSelector();
     }
 }
}

 ArrayList<String> selectorList() :
{
     ArrayList<String> selectors = new  ArrayList<String>();
     String selector;
}
{
 selector=selector() ( <COMMA> (<S>)* { selectors.add(selector); } 
                       selector=selector() )* 
  { selectors.add(selector); 
    return selectors;
  }
}

/**
 * @exception ParseException exception during the parse
 */
String selector() :
{   
    String selector = null;
    char    comb;
}
{
 try {
     // the selector can begin either a simple_selector, or a combinatorChar(+, >, ~).
     // when beginning with combinatorChar, the next one should be a simple_selector().
   (selector=simple_selector(null, ' ') | (comb=combinatorChar() selector=simple_selector(selector, comb)))
       ( LOOKAHEAD(2) comb=combinator() 
	 selector=simple_selector(selector, comb) )* (<S>)*
   {
       return selector; 
   }
 } catch (ParseException e) {
     /*
     Token t = getToken(1);
     StringBuffer s = new StringBuffer();
     s.append(getToken(0).image);
     while ((t.kind != COMMA) && (t.kind != SEMICOLON) 
	    && (t.kind != LBRACE) && (t.kind != EOF)) {
	 s.append(t.image);
	 getNextToken();
	 t = getToken(1);
     }
     reportWarningSkipText(getLocator(), s.toString());
     */
     Token t = getToken(1);
     while ((t.kind != COMMA) && (t.kind != SEMICOLON) 
	    && (t.kind != LBRACE) && (t.kind != EOF)) {
	 getNextToken();
	 t = getToken(1);
     }

     throw new ThrowedParseException(e);
  }
}

/**
 * @exception ParseException exception during the parse
 */
String simple_selector(String selector, char comb) :
{
    String simple_current = null;
    String cond = null;

    pseudoElt = null;
}
{
    ( simple_current=element_name() 
	( cond=hash(cond) | cond=_class(cond) 
	  | cond=attrib(cond) | cond=pseudo(cond) )*
      | cond=hash(cond) ( cond=_class(cond) 
			  | cond=attrib(cond) | cond=pseudo(cond) )*
      | cond=_class(cond) ( cond=hash(cond) | cond=_class(cond) 
		       | cond=attrib(cond) | cond=pseudo(cond) )*
      | cond=pseudo(cond) ( cond=hash(cond) | cond=_class(cond) 
		       | cond=attrib(cond) | cond=pseudo(cond) )*
      | cond=attrib(cond) ( cond=hash(cond) | cond=_class(cond) 
			    | cond=attrib(cond) | cond=pseudo(cond) )*
      )
	{
	    if (simple_current == null) {
		simple_current = "";
	    }
	    if (cond != null) {
		simple_current = simple_current + cond;
	    }
	    StringBuilder builder = new StringBuilder();
	    switch (comb) {
		case ' ':
		    if(selector!=null){
		        builder.append(selector).append(" ");
		    }
		    break;
		case '+':
		case '>':
		case '~':
		    if(selector!=null){
		        builder.append(selector).append(" ");
		    }
		    builder.append(comb).append(" ");
		    break;
		default:
		    throw new ParseException("invalid state. send a bug report");
	    }
	    builder.append(simple_current);
	    selector = builder.toString();
	    
	    if (pseudoElt != null) {
		selector = selector + pseudoElt;
	    }
	    return selector;
	}
}

/**
 * @exception ParseException exception during the parse
 */
String _class(String pred) :
{Token t;
String s = ".";
}
{
  "." (t = <IDENT>{s += t.image; }|t =  < INTERPOLATION >{ s += t.image; })+
      { 
	  
	  if (pred == null) {
	      return s;
	  } else {
	      return pred + s;
	  }
      }
}

/**
 * @exception ParseException exception during the parse
 */
String element_name() :
{Token t; String s = "";}
{
    (t = <IDENT>{s += t.image; }|t = < INTERPOLATION >{ s += t.image; })+
    { 
      return s; 
    }
      | "*"
    { return "*"; }
      | "&"
    { return "&"; }
}

/**
 * @exception ParseException exception during the parse
 */
String attrib(String pred) :
{
    int cases = 0;
    Token att = null;
    Token val = null;
    String attValue = null;
}
{
  "[" ( <S> )* att=<IDENT> ( <S> )*
      ( ( "="           { cases = 1; }
	  | <INCLUDES>  { cases = 2; }	  
	  | <DASHMATCH> { cases = 3; }
	  | <CARETMATCH> { cases = 4; }
	  | <DOLLARMATCH> { cases = 5; }
	  | <STARMATCH> { cases = 6; } ) ( <S> )* 
	( val=<IDENT> { attValue = val.image; }
	  | val=<STRING> { attValue = val.image; }
	)
	( <S> )* )?
  "]"
  {
      String name = convertIdent(att.image);
      String c;
      switch (cases) {
	  case 0:
	  c = name;
	  break;
	  case 1:
	  c = name + "=" + attValue;
	  break;
	  case 2:
	  c = name + "~=" + attValue;
	  break;
	  case 3:
	  c = name + "|=" +attValue;
	  break;
	  case 4:
	  c = name + "^=" +attValue;
	  break;
	  case 5:
	  c = name + "$=" +attValue;
	  break;
	  case 6:
	  c = name + "*=" +attValue;
	  break;
	  default:
	  // never reached.
	  c = null;
      }
      c = "[" + c + "]";
      if (pred == null) {
	  return c;
      } else {
	  return pred + c;
      }
  }
}

/**
 * @exception ParseException exception during the parse
 */
String pseudo(String pred) :
{Token n;
Token param;
String d;
boolean isPseudoElement = false;
}
{
    ":" (":"{isPseudoElement=true;})?( n=<IDENT>
	{
	    String s = ":" + convertIdent(n.image);
	    if (isPseudoElement) {
		if (pseudoElt != null) {
		    throw new CSSParseException("duplicate pseudo element definition " 
						+ s, getLocator());
		} else {
		    pseudoElt = ":"+s;
		    return pred;
		}
	    } else {
		String c = s;
		if (pred == null) {
		    return c;
		} else {
		    return pred + c;
		}
	    }
	}
	  | ( n=<FUNCTION> ( <S> )* d=skipStatementUntilMatchingRightParan() <RPARAN>
	          {
	          // accept anything between function and a right parenthesis
	          String f = convertIdent(n.image);
	          String colons = isPseudoElement ? "::" : ":";
	          String pseudofn = colons + f + d + ")";
		      if (pred == null) {
			  return pseudofn;
		      } else {
			  return pred + pseudofn;
	          }
	          }
	      )
	  )
}

/**
 * @exception ParseException exception during the parse
 */
String hash(String pred) :
{Token n; }
{
  n=<HASH>
  {
      String d = n.image;
      if (pred == null) {
	  return d;
      } else {
	  return pred + d;
      }
  }
}

void variable() :
{
	String name;
	LexicalUnitImpl exp = null;
	boolean guarded = false;
	String raw;
}
{
	try{
		name = variableName()
		":" ( <S> )* exp=expr() ( guarded=guarded() )?(";"(<S>)*)+
		//raw=skipStatementUntilSemiColon()
		{
		    exp = replaceNullValues(exp);
		    documentHandler.variable(name, exp, guarded);
		}
	}catch (JumpException e) {
	    skipAfterExpression();
	} catch (NumberFormatException e) {
	    if (errorHandler != null) {
		errorHandler.error(new CSSParseException("Invalid number " 
						  + e.getMessage(),
						  getLocator(), 
						  e));
	    }
	    reportWarningSkipText(getLocator(), skipAfterExpression());
	 } catch (ParseException e) {
	    if (errorHandler != null) {
	    	if (e.currentToken != null) {
	    		LocatorImpl li = new LocatorImpl(this,
						      e.currentToken.next.beginLine,
						      e.currentToken.next.beginColumn-1);
	    		reportError(li, e);
	    	} else {
	    		reportError(getLocator(), e);
	    }
		skipAfterExpression();
	    } else {
		skipAfterExpression();
	    }
	}
}

JAVACODE
LexicalUnitImpl replaceNullValues(LexicalUnitImpl unit) {
    if(unit == null){
        return null;
    }
    if (unit.getNextLexicalUnit() != null) {
        unit.setNextLexicalUnit(replaceNullValues(unit.getNextLexicalUnit()));
    }
    if (unit.getLexicalUnitType() == SCSSLexicalUnit.SAC_IDENT
            && "null".equals(unit.getStringValue())) {
        LexicalUnitImpl next = unit.getNextLexicalUnit();
        unit = LexicalUnitImpl.createNull(unit.getLineNumber(), unit.getColumnNumber(), 
                                          unit.getPreviousLexicalUnit());
        unit.setNextLexicalUnit(next);
    }
    return unit;
}

void controlDirective() :
{}
{
  ifDirective() | eachDirective()
}

void ifContentStatement() :
{}
{
    contentDirective() | includeDirective() | media() | extendDirective() | styleRuleOrDeclarationOrNestedProperties()
    | keyframes() | LOOKAHEAD(variable()) variable() | listModifyDirective() | controlDirective() | atRuleDeclaration()
}

void ifDirective() :
{
  Token n = null;
  String s = null;
  String evaluator = "";
}
{
  < IF_SYM >
  ( s = booleanExpressionToken() { evaluator += s;} )+
  < LBRACE >(< S >)*    
  { documentHandler.startIfElseDirective();
    documentHandler.ifDirective(evaluator);
   }
  ( ifContentStatement() | fontFace() )*
  < RBRACE >(< S >)*
  (elseDirective())*
  { documentHandler.endIfElseDirective(); }
}

void elseDirective() :
{
  String evaluator = "";
  Token n = null;
  String s = null;
}
{
  < ELSE_SYM >(< S >)* 
  ( < IF > ( s = booleanExpressionToken() { evaluator += s; } )+ )?
  < LBRACE >(< S >)*
  { if(!evaluator.trim().equals("")){ documentHandler.ifDirective(evaluator); }
    else{ documentHandler.elseDirective(); }  
  }
  ( ifContentStatement() | fontFace() )*
  < RBRACE >(< S >)*
}

String booleanExpressionToken() :
{
  Token n = null;
  String s = null;
}
{
 (
   LOOKAHEAD(containsDirective())
   s = containsDirective()
   |n = < VARIABLE >
   |n = < IDENT >
   |n = < NUMBER >
   |n = < LPARAN >
   |n = < RPARAN >
   |n = < PLUS >
   |n = < MINUS >
   |n = < DIV >
   |n = < ANY >
   |n = < COMPARE >
   |n = < EQ >
   |n = < PRECEDES >
   |n = < SUCCEEDS >
   |n = < OR >
   |n = < AND >
   |n = < S >
   |n = < NOT_EQ > 
){
   if(n!=null){return n.image;}  
   else{return s;}
 }
}
  
void eachDirective() :
{
  Token var;
  ArrayList<String> list = null;
  String listVariable = null;
}
{
  < EACH_SYM >
  (< S >)*
  var = < VARIABLE > (< S >)* < EACH_IN > (< S >)*
  (list = stringList()
  {documentHandler.startEachDirective(var.image, list);}
  |listVariable = variableName()
  {documentHandler.startEachDirective(var.image, listVariable);}
  )
  < LBRACE >(< S >)*
  ( ifContentStatement() )*	  
  < RBRACE >(< S >)*
  { documentHandler.endEachDirective();}
}

ArrayList<String > stringList():
{
	ArrayList<String > strings = new ArrayList<String >();
	Token input;
}
{
	(input = < IDENT > (< S >)*)
	{ strings.add(input.image); }

	(< COMMA >(< S >)* input = < IDENT > { strings.add(input.image); } (< S >)*)*
	{ return strings; }

}

void mixinDirective() :
{
    String name;
    ArrayList<VariableNode> args = null;
    String body;
}
{
    <MIXIN_SYM>
    (<S>)*
    (name = property()
    |(name = functionName() 
      args = arglist()) <RPARAN> (<S>)*) <LBRACE> (<S>)*
    {documentHandler.startMixinDirective(name, args);}
    ( ifContentStatement() | fontFace() | page())*
    <RBRACE>(<S>)*
    {documentHandler.endMixinDirective(name, args);}
}

ArrayList<VariableNode> arglist() :
{
    ArrayList<VariableNode> args = new ArrayList<VariableNode>();
    VariableNode arg;
    boolean hasNonOptionalArgument = false;
}
{
    (arg=mixinArg() ( <COMMA> (<S>)* { hasNonOptionalArgument = checkMixinForNonOptionalArguments(arg, hasNonOptionalArgument); args.add(arg); }
        arg=mixinArg() )*
    { hasNonOptionalArgument = checkMixinForNonOptionalArguments(arg, hasNonOptionalArgument); args.add(arg); }
    )?
    {
        return args;
    }
}

JAVACODE
boolean checkMixinForNonOptionalArguments(VariableNode arg, boolean hasNonOptionalArguments)
{
	boolean currentArgHasArguments = arg.getExpr() != null && arg.getExpr().getLexicalUnitType() == LexicalUnitImpl.SCSS_VARIABLE && arg.getExpr().getNextLexicalUnit() != null;
  
	if(currentArgHasArguments)
	{
		if(hasNonOptionalArguments)
		{
			throw new ParseException("Sass Error: Required argument $"+ arg.getName() +" must come before any optional arguments.");
		}
		return hasNonOptionalArguments;
	}else
	{
	return true;
	}
}

VariableNode mixinArg() :
{
    String name;
    Token variable = null;
    LexicalUnitImpl first = null;
    LexicalUnitImpl prev = null;
    LexicalUnitImpl next = null;
}
{
    name=variableName() (< COLON > (< S >)*

    (
      first = nonVariableTerm(null)
      {
        prev = first;
      }
	  (LOOKAHEAD(3)(< COMMA >(< S >)*)? prev = nonVariableTerm(prev))*
	)
	| (variable = < VARIABLE >{ first = LexicalUnitImpl.createVariable(token.beginLine, token.beginColumn,
                 prev, variable.image);}
		
       )
    )?
    {
        VariableNode arg = new VariableNode(name, first, false);
        return arg;
    }
}

ArrayList<LexicalUnitImpl> argValuelist() :
{
    ArrayList<LexicalUnitImpl> args = new ArrayList<LexicalUnitImpl>();
    LexicalUnitImpl first = null;
    LexicalUnitImpl next = null;
    LexicalUnitImpl prev = null;
}
{
    (first = term(null) { args.add(first); prev = first;}((< COLON > (< S >)*)?next=term(prev){prev.setNextLexicalUnit(next); prev = next;})*
      ( <COMMA> (<S>)*
      first = term(null) { args.add(first); prev = first;}((< COLON > (< S >)*)?next=term(prev){prev.setNextLexicalUnit(next); prev = next;})*
      )*
    )?
    {return args;}
}

void includeDirective() :
{
    String name;
    ArrayList<LexicalUnitImpl> args=null;
}
{
    <INCLUDE_SYM>
    (<S>)*
    (name = property() | name = variableName(){ name = "$"+name;} 
     | (name = functionName() args = argValuelist()) <RPARAN>(<S>)*)
    {documentHandler.startInclude(name, args);}
    (includeDirectiveBlockContents() | includeDirectiveTerminator())
    {documentHandler.endInclude();}
}

void includeDirectiveTerminator():
{}
{
    try {
      (";"(<S>)*)+
    }
    catch (ParseException e) {
      acceptMissingSemicolonBeforeRbrace(e);
    }
}

void includeDirectiveBlockContents():
{}
{
 <LBRACE> (<S>)*
 (styleRuleOrDeclarationOrNestedProperties() | keyframeSelector())*
 <RBRACE> (<S>)* 
}


String interpolation() :
{
  Token n;
}
{
  n = < INTERPOLATION >
  {
    return n.image;
  }
}

void listModifyDirective() :
{
    String list = null;
    String remove = null;
    String separator = null;
    String variable = null;
    Token n = null;
    Token type = null;
}
{
  //refactor, remove those 3 LOOKAHEAD(5).
  n = < VARIABLE >{ variable = n.image; }(< S >)* ":" (< S >)*
  (type = < APPEND> | type = <REMOVE> | type=<CONTAINS> )(< S >)*
  (list = listModifyDirectiveArgs(0))
  (< RPARAN >)? < COMMA >(< S >)*
  (remove = listModifyDirectiveArgs(1))
  ( < COMMA >(< S >)* n = < IDENT >{ separator = n.image; } (< S >)*)?
  < RPARAN >
  {
      switch (type.kind) {
      case APPEND:
          documentHandler.appendDirective(variable,list,remove,separator);
          break;
      case REMOVE:
          documentHandler.removeDirective(variable,list,remove,separator);
          break;
      case CONTAINS:
          if(variable == null){
              variable = "$var_"+UUID.randomUUID();
          }
          documentHandler.containsDirective(variable,list,remove,separator);
          break;
      default:
          break;
      }
      }
      (< S >)*< SEMICOLON >(<S>)*
}


/**
 * @exception ParseException exception during the parse
 */
void appendDirective() :
{
	String list = null;
	String remove = null;
	String separator = null;
	String variable = null;
	Token n = null;
}
{
  n = < VARIABLE >{ variable = n.image; }(< S >)* ":" (< S >)*
  < APPEND >(< S >)*
  (list = listModifyDirectiveArgs(0))
  (< RPARAN >)? < COMMA >(< S >)*
  (remove = listModifyDirectiveArgs(1))
  ( < COMMA >(< S >)* n = < IDENT >{ separator = n.image; } (< S >)*)?
  < RPARAN >

  { documentHandler.appendDirective(variable,list,remove,separator); }
}

/**
 * @exception ParseException exception during the parse
 */
void removeDirective() :
{
    String list = null;
    String remove = null;
    String separator = null;
    String variable = null;
    Token n = null;
}
{
  n = < VARIABLE >{ variable = n.image; }(< S >)* ":" (< S >)*
  < REMOVE >(< S >)*
  (list = listModifyDirectiveArgs(0))
  (< RPARAN >)? < COMMA >(< S >)*
  (remove = listModifyDirectiveArgs(1))
  ( < COMMA >(< S >)* n = < IDENT >{ separator = n.image; } (< S >)*)?
  < RPARAN >

  { documentHandler.removeDirective(variable,list,remove,separator); }
}

/**
 * @exception ParseException exception during the parse
 */
String containsDirective() :
{
	String list = null;
	String remove = null;
	String separator = null;
	String variable = null;
	Token n = null;
}
{
  (n = < VARIABLE >{ variable = n.image; }(< S >)* ":" (< S >)*)?
  < CONTAINS >(< S >)*
  (list = listModifyDirectiveArgs(0))
  (< RPARAN >)? < COMMA >(< S >)*
  (remove = listModifyDirectiveArgs(1))
  ( < COMMA >(< S >)* n = < IDENT >{ separator = n.image; } (< S >)*)?
  < RPARAN >

  {   /*
       *if it is not in the form like "$contains : contains($items, .v-button);"
       *for example in @if, like "@if (contains(a b c, b))", then create a temp
       *variable for contains(a b c, b);
       */
      if(variable == null){
          variable = "$var_"+UUID.randomUUID();
      }
      documentHandler.containsDirective(variable,list,remove,separator); 
      return variable;
  }
}

JAVACODE
String listModifyDirectiveArgs(int nest)
{
  	String list = "";
	int nesting = nest;
	Token t = null;
	
	while(true)
	{
	        t = getToken(1);
		String s = t.image;
		if(t.kind == VARIABLE||t.kind == IDENT)
		{
		  list += s;
		}else if(s.toLowerCase().equals("auto")||s.toLowerCase().equals("space")||s.toLowerCase().equals("comma"))
		{
			int i = 2;
			Token temp = getToken(i);
			boolean isLast = true;
			while(temp.kind != SEMICOLON)
			{
				if(temp.kind != RPARAN || temp.kind != S)
				{
				  isLast = false;
				}
				i++;
				temp = getToken(i);
			}

		  	if(isLast)
		  	{
			return list;
			}
		}
		else if(t.kind == STRING)
		{
			list += s.substring(1,s.length()).substring(0,s.length()-2);
			
		}else if(t.kind == LPARAN)
		{
			nesting++;
			if(nesting > nest+1)
			{
				throw new CSSParseException("Only one ( ) pair per parameter allowed", getLocator());
			}
		}else if(t.kind == RPARAN)
		{
			nesting--;
			if(nesting == 0)
			{
				return list;
			}
		} else if(t.kind == COMMA)
		{
			if(nesting == nest)
			{
			  return list;
			}else
			{
			  list += ",";
			}
			  
		}else if(t.kind == S)
		{
		  list += " ";
		} else if(t.kind == LBRACE)
		{
		  throw new CSSParseException("Invalid token,'{' found", getLocator());
		}
		
		getNextToken();
	}
}

Node returnDirective() :
{
    String raw;
}
{
	raw = skipStatement()
	{return null;}
}

void debuggingDirective() :
{}
{
  debugDirective() | warnDirective()
}

void debugDirective() :
{}
{
    <DEBUG_SYM>
    { 
    String content = skipStatementUntil(new int[] {SEMICOLON,RBRACE,EOF});
    // TODO should evaluate the content expression, call documentHandler.debugDirective() etc.
    System.out.println(content);
    }
    try {
      ";" (<S>)*
    }
    catch (ParseException e) {
      acceptMissingSemicolonBeforeRbrace(e);
    }
}

void warnDirective() :
{}
{
    <WARN_SYM>
    { 
    String content = skipStatementUntil(new int[] {SEMICOLON,RBRACE,EOF});
    // TODO should evaluate the content expression, call documentHandler.warnDirective() etc.
    System.err.println(content);
    }
    try {
      ";" (<S>)*
    }
    catch (ParseException e) {
      acceptMissingSemicolonBeforeRbrace(e);
    }
}

Node forDirective() :
{
    String var;
    String from;
    String to;
    boolean exclusive;
    String body;
    Token tok;
}
{
    var = variableName()
    {
        int[] toThrough = {TO, THROUGH};
        from = skipStatementUntil(toThrough);
    }
    (tok = <TO> {exclusive = true;}
    | tok = <THROUGH> {exclusive = false;})
    to = skipStatementUntilLeftBrace()
    (<S>)*
    body = skipStatement()
    {return documentHandler.forDirective(var, from, to, exclusive, body);}
}

Node whileDirective() :
{
    String condition;
    String body;
}
{
    condition = skipStatementUntilLeftBrace()
    body = skipStatement()
    {   return documentHandler.whileDirective(condition, body);}
}

void extendDirective() : 
{ArrayList<String> list;}
{
    <EXTEND_SYM>
    (<S>)*
    list = selectorList()
    {documentHandler.extendDirective(list);}
    try {
      (";"(<S>)*)+
    }
    catch (ParseException e) {
      acceptMissingSemicolonBeforeRbrace(e);
    }
}

void contentDirective() : 
{}
{
    <CONTENT_SYM>
    (<S>)*
    try {
      (";"(<S>)*)+
    }
    catch (ParseException e) {
      acceptMissingSemicolonBeforeRbrace(e);
    }
    {documentHandler.contentDirective();}
}

JAVACODE
Node importDirective(){
    return null;
}

JAVACODE
Node charsetDirective(){
    return null;
}

JAVACODE
Node mozDocumentDirective(){
    return null;
}

JAVACODE
Node supportsDirective(){
    return null;
}


void nestedProperties():
{String name;
LexicalUnit exp;}
{
    name=property()
    ":" ( <S> )* 
    <LBRACE> (<S>)*
    {
      documentHandler.startNestedProperties(name);
    }
    ( declaration() )? ( ";" ( <S> )* ( declaration() )? )*
    <RBRACE>
    {
      documentHandler.endNestedProperties(name);
    }
    (<S>)*
}
/**
 * @exception ParseException exception during the parse
 */
void styleRuleOrDeclarationOrNestedProperties() :
{
}
{
  try {
    // differentiate between the colon of a pseudo and the colon of nested properties and the colon of a simple property
    // first check if it is a normal styleRule, if not check if it is declarationOrNestedProperties(), if still fails, most likely, it is
    // is styleRule with pseudo selector with contains functions. have to do it in this way, because both the styleRule and declarationOrNestedProperties()
    // have 'skipStatementUntilXXX', which cannot be LOOKAHEAD properly.
      ( debuggingDirective() | LOOKAHEAD(selectorList()<LBRACE>) styleRule() | LOOKAHEAD(3)declarationOrNestedProperties() | styleRule())
  } catch (JumpException e) {
     skipAfterExpression();
     // reportWarningSkipText(getLocator(), skipAfterExpression());
  } catch (ParseException e) {
     if (errorHandler != null) {
         if (e.currentToken != null) {
             LocatorImpl li = new LocatorImpl(this,
                                              e.currentToken.next.beginLine,
                                              e.currentToken.next.beginColumn-1);
             reportError(li, e);
         } else {
             reportError(getLocator(), e);
         }
         skipAfterExpression();
         /*
         LocatorImpl loc = (LocatorImpl) getLocator();
         loc.column--;
         reportWarningSkipText(loc, skipAfterExpression());
         */
     } else {
         skipAfterExpression();
     }
  }
}
/**
 * @exception ParseException exception during the parse
 */
void declarationOrNestedProperties() :
{ boolean important = false;
  String name;
  LexicalUnitImpl exp;
  Token save;
  String comment = null;
}
{
 try {
     name=property()
     { save = token; }
     ":" ( <S> )* 
     (exp=expr() ( important=prio() )?
     {
         Token next = getToken(1);
         if(next.kind == SEMICOLON || next.kind == RBRACE){
             while(next.kind == SEMICOLON){
                 skipStatement();
                 next = getToken(1);
             }
             //only add special token kept for sprites '/**'
             if(token.specialToken!=null && token.specialToken!=null && token.specialToken.image.startsWith("/**")){
                 documentHandler.property(name, exp, important, token.specialToken.image);
             }else{
                 documentHandler.property(name, exp, important, null);
             }
         }
     }
     |<LBRACE> (<S>)*
     {
         documentHandler.startNestedProperties(name);
     }
     ( declaration() )? ( ";" ( <S> )* ( declaration() )? )*
     <RBRACE>(<S>)*
     {
         documentHandler.endNestedProperties(name);
     }
     )
     
 } catch (JumpException e) {
     skipAfterExpression();
     // reportWarningSkipText(getLocator(), skipAfterExpression());
 } catch (NumberFormatException e) {
     if (errorHandler != null) {
         errorHandler.error(new CSSParseException("Invalid number " 
                                                  + e.getMessage(),
                                                  getLocator(), 
                                                  e));
     }
     reportWarningSkipText(getLocator(), skipAfterExpression());
 } catch (ParseException e) {
     if (errorHandler != null) {
         if (e.currentToken != null) {
             LocatorImpl li = new LocatorImpl(this,
                                              e.currentToken.next.beginLine,
                                              e.currentToken.next.beginColumn-1);
             reportError(li, e);
         } else {
             reportError(getLocator(), e);
         }
         skipAfterExpression();
         /*
         LocatorImpl loc = (LocatorImpl) getLocator();
         loc.column--;
         reportWarningSkipText(loc, skipAfterExpression());
         */
     } else {
         skipAfterExpression();
     }
 }
}

/**
 * @exception ParseException exception during the parse
 */
void declaration() :
{ boolean important = false;
  String name;
  LexicalUnit exp;
  Token save;
}
{
 try {
     name=property()
     { save = token; }
     ":" ( <S> )* exp=expr() ( important=prio() )?
     {
	 documentHandler.property(name, exp, important);
     }
 } catch (JumpException e) {
     skipAfterExpression();
     // reportWarningSkipText(getLocator(), skipAfterExpression());
 } catch (NumberFormatException e) {
     if (errorHandler != null) {
	 errorHandler.error(new CSSParseException("Invalid number " 
						  + e.getMessage(),
						  getLocator(), 
						  e));
     }
     reportWarningSkipText(getLocator(), skipAfterExpression());
 } catch (ParseException e) {
     if (errorHandler != null) {
	 if (e.currentToken != null) {
	     LocatorImpl li = new LocatorImpl(this,
					      e.currentToken.next.beginLine,
					      e.currentToken.next.beginColumn-1);
	     reportError(li, e);
	 } else {
	     reportError(getLocator(), e);
	 }
	 skipAfterExpression();
	 /*
	 LocatorImpl loc = (LocatorImpl) getLocator();
	 loc.column--;
	 reportWarningSkipText(loc, skipAfterExpression());
	 */
     } else {
	 skipAfterExpression();
     }
 }
}

/**
 * @exception ParseException exception during the parse
 */
boolean prio() :
{}
{
  <IMPORTANT_SYM> ( <S> )* { return true; }
}

boolean guarded() :
{}
{
    <GUARDED_SYM> (<S>)* {return true;}
}

/**
 * @exception ParseException exception during the parse
 */
LexicalUnitImpl operator(LexicalUnitImpl prev) :
{Token n;}
{
/* (comments copied from basic_arithmetics.scss)
*supports:
* 1. standard arithmetic operations (+, -, *, /, %)
* 2. / is treated as css operator, unless one of its operands is variable or there is another binary arithmetic operator
*limits:
* 1. cannot mix arithmetic and css operations, e.g. "margin: 1px + 3px 2px" will fail
* 2. space between add and minus operator and their following operand is mandatory. e.g. "1 + 2" is valid, "1+2" is not
* 3. parenthesis is not supported now.  
*/
n="," ( <S> )* { return LexicalUnitImpl.createComma(n.beginLine, 
            n.beginColumn,
            prev); }
|n="/" ( <S> )* { return LexicalUnitImpl.createSlash(n.beginLine, 
            n.beginColumn,
            prev); }
| n="*" ( <S> )* { return LexicalUnitImpl.createMultiply(n.beginLine,
            n.beginColumn,
            prev); }
| n="%" ( <S> )* { return LexicalUnitImpl.createModulo(n.beginLine,
            n.beginColumn,
            prev); }
/*
* for '+', since it can be either a binary operator or an unary operator,
* which is ambiguous. To avoid this, the binary operator '+' always has
* a space before the following term. so '2+3' is not a valid binary expression,
* but '2 + 3' is.  The same for '-' operator.
*/

| n="+" ( <S> )+{ return LexicalUnitImpl.createAdd(n.beginLine,
        n.beginColumn,
        prev); }
| n="-" ( <S> )+{ return LexicalUnitImpl.createMinus(n.beginLine,
        n.beginColumn,
        prev); }
}

/**
 * @exception ParseException exception during the parse
 */
LexicalUnitImpl expr() :
{
    LexicalUnitImpl first, res;
    char op;
}
{
    first=term(null){ res = first; }
    ( LOOKAHEAD(2) ( LOOKAHEAD(2) res=operator(res) )? res=term(res))*
  { return first; }
}

/**
 * @exception ParseException exception during the parse
 */
char unaryOperator() :
{}
{
  "-" { return '-'; }
| "+" { return '+'; }
}

/**
 * @exception ParseException exception during the parse
 */
LexicalUnitImpl term(LexicalUnitImpl prev) :
{ LexicalUnitImpl result = null;
  Token n = null;
  char op = ' ';
}
{
  (result = nonVariableTerm(prev)| result = variableTerm(prev))
      {
	  return result;
      }
}

LexicalUnitImpl variableTerm(LexicalUnitImpl prev) :
{
  LexicalUnitImpl result = null;
  String varName = "";
}
{
  varName = variableName()
         {result = LexicalUnitImpl.createVariable(token.beginLine, token.beginColumn,
                 prev, varName); return result;}
}

LexicalUnitImpl nonVariableTerm(LexicalUnitImpl prev) :
{
LexicalUnitImpl result = null;
  Token n = null;
  char op = ' ';
  String varName;
  String s = "";
}
{
( ( ( 
    op=unaryOperator() )?
    (n=<NUMBER>
	{ result = LexicalUnitImpl.createNumber(n.beginLine, n.beginColumn,
						prev, number(op, n, 0)); }
    | n=<PERCENTAGE>
	{ result = LexicalUnitImpl.createPercentage(n.beginLine, n.beginColumn,
						    prev, number(op, n, 1)); }
    | n=<PT>
	{ result = LexicalUnitImpl.createPT(n.beginLine, n.beginColumn,
					    prev, number(op, n, 2)); }
    | n=<CM>
	{ result = LexicalUnitImpl.createCM(n.beginLine, n.beginColumn,
					    prev, number(op, n, 2)); }
    | n=<MM>
	{ result = LexicalUnitImpl.createMM(n.beginLine, n.beginColumn,
					    prev, number(op, n, 2)); }
    | n=<PC>
	{ result = LexicalUnitImpl.createPC(n.beginLine, n.beginColumn,
					    prev, number(op, n, 2)); }
    | n=<IN>
	{ result = LexicalUnitImpl.createIN(n.beginLine, n.beginColumn,
					    prev, number(op, n, 2)); }
    | n=<PX>
	{ result = LexicalUnitImpl.createPX(n.beginLine, n.beginColumn,
					    prev, number(op, n, 2)); }
    | n=<EMS>
	{ result = LexicalUnitImpl.createEMS(n.beginLine, n.beginColumn,
					     prev, number(op, n, 2)); }
    | n=<LEM>
        { result = LexicalUnitImpl.createLEM(n.beginLine, n.beginColumn,
                                             prev, number(op, n, 3)); }
    | n=<REM>       
        { result = LexicalUnitImpl.createREM(n.beginLine, n.beginColumn,
                                             prev, number(op, n, 3)); }
    | n=<EXS>
	{ result = LexicalUnitImpl.createEXS(n.beginLine, n.beginColumn,
					     prev, number(op, n, 2)); }
    | n=<DEG>
	{ result = LexicalUnitImpl.createDEG(n.beginLine, n.beginColumn,
					     prev, number(op, n, 3)); }
    | n=<RAD>
	{ result = LexicalUnitImpl.createRAD(n.beginLine, n.beginColumn,
					     prev, number(op, n, 3)); }
    | n=<GRAD>
	{ result = LexicalUnitImpl.createGRAD(n.beginLine, n.beginColumn,
					      prev, number(op, n, 3)); }
    | n=<SECOND>
	{ result = LexicalUnitImpl.createS(n.beginLine, n.beginColumn,
					   prev, number(op, n, 1)); }
    | n=<MS>
	{ result = LexicalUnitImpl.createMS(n.beginLine, n.beginColumn,
					    prev, number(op, n, 2)); }
    | n=<HZ>
	{ result = LexicalUnitImpl.createHZ(n.beginLine, n.beginColumn,
					    prev, number(op, n, 2)); }
    | n=<KHZ>
	{ result = LexicalUnitImpl.createKHZ(n.beginLine, n.beginColumn,
					     prev, number(op, n, 3)); }
    | n=<DIMEN>
	{
	    s = n.image;
	    int i = 0;
	    while (i < s.length() 
		   && (Character.isDigit(s.charAt(i)) || (s.charAt(i) == '.'))) {
		i++;
	    }

	    result = LexicalUnitImpl.createDimen(n.beginLine, n.beginColumn, prev, 
						 number(op,n,s.length()-i),
						 s.substring(i));
	}
    | result=function(op, prev) ) )
  | ( n=<STRING>
	{ result =
	  LexicalUnitImpl.createString(n.beginLine, n.beginColumn, prev,
				       convertStringIndex(n.image, 1, 
							  n.image.length() -1));}
  | (< DOT >{ s+="."; })?(n=<IDENT> | n=<TO> | n=<THROUGH> | n=<FROM>) 
      { s += convertIdent(n.image);
	if ("inherit".equals(s)) {
	    result = LexicalUnitImpl.createInherit(n.beginLine, n.beginColumn,
					       prev);
	} else {
	    result = LexicalUnitImpl.createIdent(n.beginLine, n.beginColumn,
					       prev, convertIdent(n.image));
	}

	  /* /
         Auto correction code used in the CSS Validator but must not
          be used by a conformant CSS2 parser.
	 * Common error :
	 * H1 {
	 *   color : black
	 *   background : white
	 * }
	 *
	Token t = getToken(1);
	Token semicolon = new Token();
	semicolon.kind = SEMICOLON;
	semicolon.image = ";";
	if (t.kind == COLON) {
	    // @@SEEME. (generate a warning?)
	    // @@SEEME if expression is a single ident, 
	       generate an error ?
	    rejectToken(semicolon);
	    
	    result = prev;
	}
	/ */
    }
	| result=hexcolor(prev)	
        | result=url(prev)
	| result=unicode(prev)
         ) ) ( <S> )*
  {
    return result;
  }
}

/**
 * Handle all CSS2 functions.
 * @exception ParseException exception during the parse
 */
LexicalUnitImpl function(char operator, LexicalUnitImpl prev) :
{Token n;
 LexicalUnit params = null;
}
{
    n=<FUNCTION> ( <S> )*  
    {
        String fname = convertIdent(n.image);
        if("alpha(".equals(fname)){
            String body = skipStatementUntilSemiColon();
            return LexicalUnitImpl.createIdent(n.beginLine, n.beginColumn,
                 null, "alpha("+body);
        }else if("expression(".equals(fname)){
            String body = skipStatementUntilSemiColon();
            return LexicalUnitImpl.createIdent(n.beginLine, n.beginColumn,
                 null, "expression("+body);
        }
    }
    ( params=expr() )? ")"
    {
        if (operator != ' ') {
	    throw new CSSParseException("invalid operator before a function.", 
					getLocator());
	}
	String f = convertIdent(n.image);
        LexicalUnitImpl l = (LexicalUnitImpl) params;
	boolean loop = true;
	if ("rgb(".equals(f)) {
	    // this is a RGB declaration (e.g. rgb(255, 50%, 0) )
	    int i = 0;
	    while (loop && l != null && i < 5) {
		switch (i) {
		    case 0:
		    case 2:
		    case 4:
			if ((l.getLexicalUnitType() != LexicalUnit.SAC_INTEGER)
			    && (l.getLexicalUnitType() != LexicalUnit.SAC_PERCENTAGE)) {
			    loop = false;
			}
			break;
		    case 1:
		    case 3:
			if (l.getLexicalUnitType() != LexicalUnit.SAC_OPERATOR_COMMA) {
			    loop = false;
			}
			break;
		    default:
                        throw new ParseException("implementation error");
		}
		if (loop) {
		    l = (LexicalUnitImpl) l.getNextLexicalUnit();
		    i ++;
		}
	    }
	    if ((i == 5) && loop && (l == null)) {
		return LexicalUnitImpl.createRGBColor(n.beginLine,
						      n.beginColumn,
						      prev, params);
	    } else {
		if (errorHandler != null) {
		    String errorText;
		    Locator loc;
		    if (i < 5) {
			if (params == null) {
			    loc = new LocatorImpl(this, n.beginLine,
						  n.beginColumn-1);
			    errorText = "not enough parameters.";
			} else if (l == null) {
			    loc = new LocatorImpl(this, n.beginLine,
						  n.beginColumn-1);
			    errorText = "not enough parameters: " 
				+ params.toString();
			} else {
			    loc = new LocatorImpl(this, l.getLineNumber(),
						  l.getColumnNumber());
			    errorText = "invalid parameter: "
				+ l.toString();
			}
		    } else {
			loc = new LocatorImpl(this, l.getLineNumber(),
					      l.getColumnNumber());
			errorText = "too many parameters: "
			    + l.toString();
		    }
		    errorHandler.error(new CSSParseException(errorText, loc));
		}

		throw new JumpException();
	    }
	} else if ("counter".equals(f)) {
	    int i = 0;
	    while (loop && l != null && i < 3) {
		switch (i) {
		    case 0:
		    case 2:
			if (l.getLexicalUnitType() != LexicalUnit.SAC_IDENT) {
			    loop = false;
			}
			break;
		    case 1:
			if (l.getLexicalUnitType() != LexicalUnit.SAC_OPERATOR_COMMA) {
			    loop = false;
			}
			break;
		    default:
                        throw new ParseException("implementation error");
		}
		l = (LexicalUnitImpl) l.getNextLexicalUnit();
		i ++;
	    }
	    if (((i == 1) || (i == 3)) && loop && (l == null)) {
		return LexicalUnitImpl.createCounter(n.beginLine, n.beginColumn,
						     prev, params);
	    }

	} else if ("counters(".equals(f)) {
	    
	    int i = 0;
	    while (loop && l != null && i < 5) {
		switch (i) {
		    case 0:
		    case 4:
			if (l.getLexicalUnitType() != LexicalUnit.SAC_IDENT) {
			    loop = false;
			}
			break;
		    case 2:
			if (l.getLexicalUnitType() != LexicalUnit.SAC_STRING_VALUE) {
			    loop = false;
			}
			break;
		    case 1:
		    case 3:
			if (l.getLexicalUnitType() != LexicalUnit.SAC_OPERATOR_COMMA) {
			    loop = false;
			}
			break;
		    default:
                        throw new ParseException("implementation error");
		}
		l = (LexicalUnitImpl) l.getNextLexicalUnit();
		i ++;
	    }
	    if (((i == 3) || (i == 5)) && loop && (l == null)) {
		return LexicalUnitImpl.createCounters(n.beginLine, n.beginColumn,
						      prev, params);
	    }
	} else if ("attr(".equals(f)) {
	    if ((l != null)
		&& (l.getNextLexicalUnit() == null)
		&& (l.getLexicalUnitType() == LexicalUnit.SAC_IDENT)) {
                return LexicalUnitImpl.createAttr(l.getLineNumber(), 
						  l.getColumnNumber(),
						  prev, l.getStringValue());
	    }
	} else if ("rect(".equals(f)) {
	    int i = 0;
	    while (loop && l != null && i < 7) {
		switch (i) {
		    case 0:
		    case 2:
		    case 4:
		    case 6:
			switch (l.getLexicalUnitType()) {
			case LexicalUnit.SAC_INTEGER:
			    if (l.getIntegerValue() != 0) {
				loop = false;
			    }
			    break;
			case LexicalUnit.SAC_IDENT:
			    if (!l.getStringValue().equals("auto")) {
				loop = false;
			    }
			    break;
			case LexicalUnit.SAC_EM:
			case LexicalUnit.SAC_EX:
			case LexicalUnit.SAC_PIXEL:
			case LexicalUnit.SAC_CENTIMETER:
			case LexicalUnit.SAC_MILLIMETER:
			case LexicalUnit.SAC_INCH:
			case LexicalUnit.SAC_POINT:
			case LexicalUnit.SAC_PICA:
			    // nothing
			    break;
			default:
			    loop = false;
			}
			break;
		    case 1:
		    case 3:
		    case 5:
			if (l.getLexicalUnitType() != LexicalUnit.SAC_OPERATOR_COMMA) {
			    loop = false;
			}
			break;
		    default:
                        throw new ParseException("implementation error");
		}
		l = (LexicalUnitImpl) l.getNextLexicalUnit();
		i ++;
	    }
	    if ((i == 7) && loop && (l == null)) {
		return LexicalUnitImpl.createRect(n.beginLine, n.beginColumn,
						  prev, params);
	    }
	} 
	return LexicalUnitImpl.createFunction(n.beginLine, n.beginColumn, prev, 
					      f.substring(0, 
							f.length() -1),
					      params);
    }
}

LexicalUnitImpl unicode(LexicalUnitImpl prev) :
{ Token n;
}
{
  n=<UNICODERANGE>
 {
     LexicalUnitImpl params = null;
     String s = n.image.substring(2);
     int index = s.indexOf('-');
     if (index == -1) {
	 params = LexicalUnitImpl.createInteger(n.beginLine, n.beginColumn,
						params, Integer.parseInt(s, 16));
     } else {
	 String s1 = s.substring(0, index);
	 String s2 = s.substring(index);

	 params = LexicalUnitImpl.createInteger(n.beginLine, n.beginColumn,
						params, Integer.parseInt(s1, 16));
	 params = LexicalUnitImpl.createInteger(n.beginLine, n.beginColumn,
						params, Integer.parseInt(s2, 16));
     }

     return LexicalUnitImpl.createUnicodeRange(n.beginLine, n.beginColumn,
					       prev, params);
 }
}

LexicalUnitImpl url(LexicalUnitImpl prev) :
{ Token n;
}
{
  n=<URL>
 {
   String urlname = n.image.substring(4, n.image.length()-1).trim();
   return LexicalUnitImpl.createURL(n.beginLine, n.beginColumn, prev, urlname); 
 }
}

/**
 * @exception ParseException exception during the parse
 */
LexicalUnitImpl hexcolor(LexicalUnitImpl prev) :
{Token n; 
}
{
 n=<HASH>
 { 
     int r;
     LexicalUnitImpl first, params = null;
     String s = n.image.substring(1);
     
     if(s.length()!=3 && s.length()!=6) {
	 first = null;
	 throw new CSSParseException("invalid hexadecimal notation for RGB: " + s, 
				     getLocator());
     }
     return LexicalUnitImpl.createIdent(n.beginLine, n.beginColumn,
             prev, n.image);
 }
}

JAVACODE
float number(char operator, Token n, int lengthUnit) {
    String image  = n.image;
    float f = 0;

    if (lengthUnit != 0) {
	image = image.substring(0, image.length() - lengthUnit);
    }
    f = Float.valueOf(image).floatValue();
    return (operator == '-')? -f: f;
}

JAVACODE
String skipStatementUntilSemiColon(){
    int[] semicolon = {SEMICOLON};
    return skipStatementUntil(semicolon);
}

JAVACODE
String skipStatementUntilLeftBrace(){
    int[] lBrace = {LBRACE};
    return skipStatementUntil(lBrace);
}

JAVACODE
String skipStatementUntilMatchingRightParan(){
    int[] leftTokens = {LPARAN, FUNCTION}; // a FUNCTION also contains "("
    int[] rightTokens = {RPARAN};
    StringBuffer s = new StringBuffer();
    int difference = 1;
    Token tok;
    while(difference != 0){
        tok = getToken(1);
        if(tok.kind == EOF) {
            return null;
        }
        for(int sym : leftTokens){
            if(tok.kind == sym){
                difference++;
            }
        }
        for(int sym : rightTokens){
            if(tok.kind == sym){
                difference--;
            }
        }
        if(difference != 0){
            if (tok.image != null) {
                s.append(tok.image);
            }
            getNextToken();
        }
    }
    return s.toString().trim();
}

JAVACODE
String skipStatementUntil(int[] symbols){
    StringBuffer s = new StringBuffer();
    boolean found = false;
    Token tok;
    while(!found){
        tok = getToken(1);
        for(int sym : symbols){
            if(tok.kind == sym){
                found = true;
                break;
            }
        }
        if(tok.kind == EOF) {
            break;
        }
        if(!found){
            if (tok.image != null) {
                s.append(tok.image);
            }
            getNextToken();
        }
    }
    return found ? s.toString().trim() : null;
}


JAVACODE
String skipStatement() {
    StringBuffer s = new StringBuffer();
    Token tok = getToken(0);
    if (tok.image != null) {
        s.append(tok.image);    
    }
    while (true) {
        tok = getToken(1);
        if (tok.kind == EOF) {
            return null;
        } 
        s.append(tok.image);
        if (tok.kind == LBRACE) {
            getNextToken();
            s.append(skip_to_matching_brace());
            getNextToken();
            tok = getToken(1);
            break;
        } else if (tok.kind == RBRACE) {
            getNextToken();
            tok = getToken(1);
            break;
        } else if (tok.kind == SEMICOLON) {
            getNextToken();
            tok = getToken(1);
            break;
        }
        getNextToken();
    }
    
    // skip white space
    while (true) {
        if (tok.kind != S) {
            break;
        }
        tok = getNextToken();
        tok = getToken(1);
    }

    return s.toString().trim();
}

JAVACODE
String skip_to_matching_brace() {
    StringBuffer s = new StringBuffer();
    Token tok;
    int nesting = 1;
    while (true) {
        tok = getToken(1);
        if (tok.kind == EOF) {
            break;
        }
        s.append(tok.image);
        if (tok.kind == LBRACE) {
            nesting++;
        } else if (tok.kind == RBRACE) {
            nesting--;
            if (nesting == 0) {
                break;
            }
        }
        getNextToken();
    }
    return s.toString();
}

/*
 * Here I handle all CSS2 unicode character stuffs.
 * I convert all \XXXXXX character into a single character.
 * Don't forget that the parser has recognize the token before.
 * (So IDENT won't contain newline and stuffs like this).
 */
JAVACODE
String convertStringIndex(String s, int start, int len) {
    StringBuffer buf = new StringBuffer(len);
    int index = start;

    while (index < len) {
	char c = s.charAt(index);
	if (c == '\\') {
	    if (++index < len) {
		c = s.charAt(index);
		switch (c) {
		case '0': case '1': case '2': case '3': case '4':
		case '5': case '6': case '7': case '8': case '9':
		case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
		case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
		    buf.append('\\');
		    while (index < len) {
			buf.append(s.charAt(index++));
		    }
		    break;
		case '\n':
		case '\f':
		    break;
		case '\r':
		    if (index + 1 < len) {
			if (s.charAt(index + 1) == '\n') {
			    index ++;
			}
		    }
		    break;
		default:
		    buf.append(c);
		}
	    } else {
		throw new CSSParseException("invalid string " + s, getLocator());
	    }
	} else {
	    buf.append(c);
	}
	index++;
    }

    return buf.toString();
}

JAVACODE
String convertIdent(String s) {
    return convertStringIndex(s, 0, s.length());
}

JAVACODE
String convertString(String s) {
    return convertStringIndex(s, 0, s.length());
}

JAVACODE
void comments(){
	/*keeps only the multiple line comments, single line comments are skipped*/
    if (token.specialToken != null && token.specialToken.image!=null && token.specialToken.image.startsWith("/*")){
        Token tmp_t = token.specialToken;
        while (tmp_t.specialToken != null) tmp_t = tmp_t.specialToken;
        while (tmp_t != null) {
            documentHandler.comment(tmp_t.image);
            tmp_t = tmp_t.next;
        }
    }
} 

/*
 * @@HACK
 * I can't insert a token into the tokens flow.
 * It's jj_consume_token implementation dependant! :-(
 */
JAVACODE
void rejectToken(Token t) {
    Token fakeToken = new Token();
    t.next = token;
    fakeToken.next = t;
    token = fakeToken;
}

/**
 * skip after an expression
 */
JAVACODE
String skipAfterExpression() {
    Token t = getToken(1);
    StringBuffer s = new StringBuffer();
    s.append(getToken(0).image);
    
    while ((t.kind != RBRACE) && (t.kind != SEMICOLON) && (t.kind != EOF)) {
	s.append(t.image);
	getNextToken();
	t = getToken(1);
    }
    
    return s.toString();
}

/**
 * The following functions are useful for a DOM CSS implementation only and are
 * not part of the general CSS2 parser.
 */
// TODO required by original parser but not used by Vaadin?
void _parseRule() :
{String ret = null;
}
{
	( <S> )*
	   ( importDeclaration() | debuggingDirective() | styleRule() | media() | page() | fontFace() | ret=skipStatement() 
		{
		    if ((ret == null) || (ret.length() == 0)) {
			return; 
		    }
		    if (ret.charAt(0) == '@') {
			documentHandler.unrecognizedRule(ret);
		    } else {
			throw new CSSParseException("unrecognize rule: " + ret,
						    getLocator());
		    }
		}
	  )
}

void _parseImportRule() :
{
}
{
    ( <S> )* importDeclaration()
}

void _parseMediaRule() :
{
}
{
    ( <S> )* media()
}

void _parseDeclarationBlock() :
{
}
{
    ( <S> )*
	( declaration() )? ( ";" ( <S> )* ( declaration() )? )* 
 }

ArrayList<String> _parseSelectors() :
{ ArrayList<String> p = null;
}
{
    try {
	( <S> )* p = selectorList()
	{ return p; }
    } catch (ThrowedParseException e) {
	throw (ParseException) e.e.fillInStackTrace();
    }
}

JAVACODE
void acceptMissingSemicolonBeforeRbrace( ParseException parseException ) {
    Token next = getToken(1);
    if (next.kind != RBRACE && next.kind!=EOF) {
        throw parseException;
    }
}


/*
 * Local Variables:
 * compile-command: javacc Parser.jj & javac Parser.java
 * End:
 */
