// /* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */

%name CSS_Parser
%include {
/*
require_once('CSS/DOM/CSSCharsetRule.php');
require_once('CSS/DOM/CSSImportRule.php');
require_once('CSS/DOM/CSSMediaRule.php');
*/
require_once('Structures/CSS.php');
require_once('Structures/CSS/Value/Generic.php');
require_once('Structures/CSS/Value/Numeric.php');
require_once('Structures/CSS/Value/Unit.php');
require_once('Structures/CSS/Value/Color.php');
require_once('Structures/CSS/Value/Function.php');
require_once('Structures/CSS/Value/Uri.php');
require_once('Structures/CSS/Declaration.php');
require_once('Structures/CSS/Rule/Set.php');
require_once('Structures/CSS/Rule/AtPage.php');
require_once('Structures/CSS/Rule/AtMedia.php');
require_once('Structures/CSS/Rule/AtImport.php');
require_once('Structures/CSS/Selector.php');
require_once('CSS/Parser/Exception/Syntax.php');
}
%declare_class {class CSS_Parser}
%syntax_error {
    throw new CSS_Parser_Exception_Syntax();
    var_dump($this);
    echo "Syntax error\n";
    exit;
}
%include_class {
    public $result;
    public function tokenizeAndParse($lexer, $input) 
    {
        $lexer->setInput($input);
        try {
            $lastTokenLength = 0;
            while ($token = $lexer->nextToken()) {
                $this->doParse($token['token'], $token['value']);
                $lastTokenLength = strlen($token['value']); // We may need this for error reporting
            }
            $unexpectedEOF = true;
            $this->doParse(0,0);
            unset($unexpectedEOF);
            return $this->result;
        } catch (Exception $e) {
            // We have a parsing error. Collect some usefull info
            // 1. Was this an EOF error?
            if (isset($unexpectedEOF) && $unexpectedEOF) {
                require_once('CSS/Parser/Exception/EOF.php');
                throw new CSS_Parser_Exception_EOF;
            } else {
                require_once('CSS/Parser/Exception/Syntax.php');
                // 2. No eof error, find out which line/column it ocurred in
                $rows = explode("\n", $input);
                $errorCharacter = $lexer->_cursor - $lastTokenLength;

                $line = 0;
                $marker = 0;
                while ($marker <= $errorCharacter) {
                    $marker += strlen($rows[$line]) + 1; // explode removed the \n so we add 1 phantom character
                    $line++;
                }
                $line--; // The cycle overshoots the line, so go back one line
                $marker -= strlen($rows[$line]) + 1; // and reset the marker
                $column = $errorCharacter - $marker; // What's leftover in the marker is the column
                $line++; $column++; // Humans tend to start counting by 1, not 0

                // 3. Let's grab contextual input for presentation purposes
                $contextual = array();
                for ($i = 0; $i < 3; $i++) if (array_key_exists($line - 2 + $i, $rows)) $contextual[$i] = $rows[$line - 2 + $i]; else $contextual[$i] = null;

                throw new CSS_Parser_Exception_Syntax($line, $column, $lastTokenLength, $contextual);
            }
        }
    }
}
start ::= stylesheet(B). { $this->result = B; }

//stylesheet  {{{ 
//  : [ CHARSET_SYM STRING ';' ]?
//    [S|CDO|CDC]* [ import [S|CDO|CDC]* ]*
//    [ [ ruleset | media | page ] [S|CDO|CDC]* ]*
//  ;
stylesheet(A) ::= stylesheet_1(B) stylesheet_2(C). { 
    A = new Structures_CSS(C, B);
}
stylesheet(A) ::= stylesheet_1(B). { 
    $a = array();
    A = new Structures_CSS($a, B);
}
stylesheet(A) ::= stylesheet_2(B). { 
    A = new Structures_CSS(B);
}
stylesheet(A) ::= . { 
    A = new Structures_CSS();
}
 stylesheet_1(A) ::= stylesheet_charset(B) s_cdo_cdc_plus. { A =& B; }
 stylesheet_1(A) ::= s_cdo_cdc_plus. { A = null; }
  stylesheet_charset(A) ::= charset_sym_sstar string_sstar(B) SEMICOLON. { A =& B; }
   charset_sym_sstar ::= CHARSET_SYM.
   charset_sym_sstar ::= CHARSET_SYM splus.
    splus ::= SS.
    splus ::= SS splus.
   string_sstar(A) ::= STRING(B). { A = substr(substr(B, 0, strlen(B)-1), 1); }
   string_sstar(A) ::= STRING(B) splus. { A = substr(substr(B, 0, strlen(B)-1), 1); }
  s_cdo_cdc_plus ::= s_cdo_cdc.
  s_cdo_cdc_plus ::= s_cdo_cdc_plus s_cdo_cdc.
   s_cdo_cdc ::= SS|CDO|CDC.
 stylesheet_2(A) ::= stylesheet_import(B) stylesheet_ruleset_media_page(C). { A = array_merge(B,C); }
 stylesheet_2(A) ::= stylesheet_ruleset_media_page(B). { A =& B; }
  stylesheet_import(A) ::= import(B) s_cdo_cdc_plus. { A =& B; }
  stylesheet_import(A) ::= import(B). { A =& B; }
  stylesheet_import(A) ::= stylesheet_import(B) import(C) s_cdo_cdc_plus. { A = array_merge(B, C); }
  stylesheet_import(A) ::= stylesheet_import(B) import(C). { A = array_merge(B, C); }
  stylesheet_ruleset_media_page(A) ::= ruleset(B). { A =& B; }
  stylesheet_ruleset_media_page(A) ::= media(B). { A =& B; }
  stylesheet_ruleset_media_page(A) ::= page(B). { A =& B; }
  stylesheet_ruleset_media_page(A) ::= stylesheet_ruleset_media_page(B) ruleset(C). { A = array_merge(B, C); }
  stylesheet_ruleset_media_page(A) ::= stylesheet_ruleset_media_page(B) media(C). { A = array_merge(B, C); }
  stylesheet_ruleset_media_page(A) ::= stylesheet_ruleset_media_page(B) page(C). { A = array_merge(B, C); }
// }}}

// import  {{{ 
//  : IMPORT_SYM S*
//    [STRING|URI] S* [ medium [ COMMA S* medium]* ]? ';' S*
//  ;
// NOTE: I removed the end S* from the rule, as it conflicts with "import s_cdo_cdc" on the stylesheet rule above
import(A) ::= import_sym_sstar string_or_uri_sstar(B) SEMICOLON. { A = array(new Structures_CSS_Rule_AtImport(B)); }
import(A) ::= import_sym_sstar string_or_uri_sstar(B) medialist(C) SEMICOLON. { A = array(new Structures_CSS_Rule_AtImport(B, C)); }
 import_sym_sstar ::= IMPORT_SYM.
 import_sym_sstar ::= IMPORT_SYM splus.
 string_or_uri_sstar(A) ::= string_sstar(B). { A = B; }
 string_or_uri_sstar(A) ::= URI(B) splus. { A = B; }
 string_or_uri_sstar(A) ::= URI(B). { A = B; }
 medialist(A) ::= medium(B). { A = array(B); }
 medialist(A) ::= medialist(B) COMMA medium(C). { A =& B; A[] =& C; }
 medialist(A) ::= medialist(B) COMMA splus medium(C). { A =& B; A[] =& C; }
// }}} 

//media  {{{
//  : MEDIA_SYM S* medium [ COMMA S* medium ]* LBRACE S* ruleset* '}' S*
//  ;
media(A) ::= media_sym_sstar medialist(B) LBRACE splus rulesetplus(C) RBRACE. {
    A = array(new Structures_CSS_Rule_AtMedia(B, C));
}
media(A) ::= media_sym_sstar medialist(B) LBRACE splus rulesetplus(C) RBRACE splus. {
    A = array(new Structures_CSS_Rule_AtMedia(B, C));
}
media(A) ::= media_sym_sstar medialist(B) LBRACE rulesetplus(C) RBRACE. {
    A = array(new Structures_CSS_Rule_AtMedia(B, C));
}
media(A) ::= media_sym_sstar medialist(B) LBRACE rulesetplus(C) RBRACE splus. {
    A = array(new Structures_CSS_Rule_AtMedia(B, C));
}
media(A) ::= media_sym_sstar medialist(B) LBRACE splus RBRACE. {
    A = array(new Structures_CSS_Rule_AtMedia(B, array()));
}
media(A) ::= media_sym_sstar medialist(B) LBRACE splus RBRACE splus. {
    A = array(new Structures_CSS_Rule_AtMedia(B, array()));
}
media(A) ::= media_sym_sstar medialist(B) LBRACE RBRACE. {
    A = array(new Structures_CSS_Rule_AtMedia(B, array()));
}
media(A) ::= media_sym_sstar medialist(B) LBRACE RBRACE splus. {
    A = array(new Structures_CSS_Rule_AtMedia(B, array()));
}
 media_sym_sstar ::= MEDIA_SYM.
 media_sym_sstar ::= MEDIA_SYM splus.
 rulesetplus(A) ::= ruleset(B). { A =& B; }
 rulesetplus(A) ::= rulesetplus(B) ruleset(C). { A =& B; A[] =& C; }
// }}}

//medium  {{{
//  : IDENT S*
//  ;
medium(A) ::= IDENT(B). { A = B; }
medium(A) ::= IDENT(B) splus. { A = B; }
// }}}

//page  {{{
//  : PAGE_SYM S* pseudo_page? S*
//    LBRACE S* declaration [ ';' S* declaration ]* '}' S*
//  ;
// NOTE: I removed the end S* from the rule, as it conflicts with "ruleset_media_page s_cdo_cdc" 
//       on the stylesheet rule above
page(A) ::= ppage(B). {
    A = array(new Structures_CSS_Rule_AtPage(B['declarations'], B['pseudo']));
}
ppage(A) ::= pagesym_sstar pseudo_page(B) splus LBRACE declarations(C) RBRACE. { A = array(); A['pseudo'] =& B; A['declarations'] =& C; }
ppage(A) ::= pagesym_sstar pseudo_page(B) splus LBRACE declarations(C) RBRACE splus. { A = array(); A['pseudo'] =& B; A['declarations'] =& C; }
ppage(A) ::= pagesym_sstar pseudo_page(B) LBRACE declarations(C) RBRACE. { A = array(); A['pseudo'] =& B; A['declarations'] =& C; }
ppage(A) ::= pagesym_sstar pseudo_page(B) LBRACE declarations(C) RBRACE splus. { A = array(); A['pseudo'] =& B; A['declarations'] =& C; }
ppage(A) ::= pagesym_sstar LBRACE declarations(C) RBRACE. { A = array(); A['pseudo'] = null; A['declarations'] =& C; }
ppage(A) ::= pagesym_sstar LBRACE declarations(C) RBRACE splus. { A = array(); A['pseudo'] = null; A['declarations'] =& C; }
 pagesym_sstar ::= PAGE_SYM.
 pagesym_sstar ::= PAGE_SYM splus.
 declarations(A) ::= sstar_declaration(B). { A = array(B); }
 declarations(A) ::= declarations(B) SEMICOLON sstar_declaration(C). { A =& B; A[] =& C; }
  sstar_declaration(A) ::= declaration(B). { A =& B; }
  sstar_declaration(A) ::= splus declaration(B). { A =& B; }
// }}}

//pseudo_page  {{{
//  : ':' IDENT
//  ;
pseudo_page(A) ::= COLON(B) IDENT(C). { A = B . C; }
// }}}

//operator  {{{
//  : '/' S* | COMMA S* | /* empty */
//  ;
operator(A) ::= SLASH. { A = '/'; }
operator(A) ::= SLASH splus. { A = '/'; }
operator(A) ::= COMMA. { A = ','; }
operator(A) ::= COMMA splus. { A = ','; }
operator(A) ::= . { A = ''; }
// }}}

//combinator {{{ 
//  : PLUS S*
//  | GREATER S*
//  | S
//  ;
combinator(A) ::= GREATER splus. { A = '>'; }
combinator(A) ::= PLUS splus. { A = '+'; }
combinator(A) ::= SS. { A = ' '; }
// }}} 

//unary_operator  {{{ 
//  : '-' | PLUS
//  ;
unary_operator(A) ::= MINUS. { A = '-'; }
unary_operator(A) ::= PLUS. { A = '+'; }
// }}}

//property  {{{
//  : IDENT S*
//  ;
property(A) ::= IDENT(B). { A =& B; }
property(A) ::= IDENT(B) splus. { A =& B; }
// }}}

//ruleset  {{{
//  : selector [ COMMA S* selector ]*
//    LBRACE S* declaration [ ';' S* declaration ]* '}' S*
//  ;
ruleset(A) ::= rruleset(B). {
    A = array();
    $clone = null;
    foreach (B['selectors'] as $selector) {
        if (!is_null($clone)) B['declarations'] = unserialize($clone);
        A[] = new Structures_CSS_Rule_Set($selector, B['declarations']);
        if (is_null($clone)) $clone = serialize(B['declarations']);
    }
    unset($clone);
}
rruleset(A) ::= selectors(B) LBRACE declarations(C) RBRACE splus. { A = array('selectors' => B, 'declarations' => C);  }
rruleset(A) ::= selectors(B) LBRACE declarations(C) RBRACE. { A = array('selectors' => B, 'declarations' => C); }
 selectors(A) ::= selector(B). { A = array(B); }
 selectors(A) ::= selectors(B) comma_sstar selector(C). { A =& B; A[] =& C; }
  comma_sstar ::= COMMA.
  comma_sstar ::= COMMA splus.
// Extra spec addition!
// ruleset : selector COMMA S* selector LBRACE S*
rruleset(A) ::= selectors(B) LBRACE RBRACE. { A= array('selectors' => B, 'declarations' => array()); }
rruleset(A) ::= selectors(B) LBRACE splus RBRACE. { A= array('selectors' => B, 'declarations' => array()); }
rruleset(A) ::= selectors(B) LBRACE RBRACE splus. { A= array('selectors' => B, 'declarations' => array()); }
rruleset(A) ::= selectors(B) LBRACE splus RBRACE splus. { A= array('selectors' => B, 'declarations' => array()); }
// /Extra spec addition
// }}}

//selector  {{{
//  : simple_selector [ combinator simple_selector ]*
//  ;
selector(A) ::= simple_selector(B). { A =& B; }
selector(A) ::= simple_selector(B) combinator(C) selector(D). { A =& B; A->appendSelector(D, C); }
// }}}

//simple_selector  {{{
//  : element_name [ HASH | class | attrib | pseudo ]*
//  | [ HASH | class | attrib | pseudo ]+
//  ;
simple_selector(A) ::= element_name(B). { A= new Structures_CSS_Selector(strtolower(B), Structures_CSS_Selector::C); }
simple_selector(A) ::= element_name(B) hash_class_attrib_pseudo_plus(D). { 
    A = new Structures_CSS_Selector(strtolower(B), Structures_CSS_Selector::C); 
    A->appendSelector(D, '');
}
simple_selector(A) ::= hash_class_attrib_pseudo_plus(B). { A =& B; }
 hash_class_attrib_pseudo_plus(A) ::= hash_class_attrib_pseudo(B). { A =& B; }
 hash_class_attrib_pseudo_plus(A) ::= hash_class_attrib_pseudo_plus(B) hash_class_attrib_pseudo(C). { 
    A =& B;
    A->appendSelector(C, ''); 
 }
  hash_class_attrib_pseudo(A) ::= HASH(B). { A = new Structures_CSS_Selector(B, 1000000); }
  hash_class_attrib_pseudo(A) ::= class(B). { A = new Structures_CSS_Selector(B, 1000); }
  hash_class_attrib_pseudo(A) ::= attrib(B). { A = new Structures_CSS_Selector(B, 1000); }
  hash_class_attrib_pseudo(A) ::= pseudo(B). { A = new Structures_CSS_Selector(B, 1000); }
// }}}

//class  {{{
//  : '.' IDENT
//  ;
class(A) ::= DOT IDENT(B). { A = '.' . B; }
// }}}

//element_name  {{{
//  : IDENT | '*'
//  ;
element_name(A) ::= IDENT(B). { A =& B; }
element_name(A) ::= STAR(B). { A =& B; }
//}}}

//attrib  {{{
//  : '[' S* IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S*
//    [ IDENT | STRING ] S* ]? ']'
//  ;
attrib(A) ::= LBRACKET trimmed_ident(B) RBRACKET. { A = '[' . B . ']'; }
attrib(A) ::= LBRACKET trimmed_ident(B) attrib_operator_sstar(C) attrib_operator_rhand_sstar(D) RBRACKET.   { A = '[' . B . C . D . ']'; }
attrib(A) ::= LBRACKET trimmed_ident(B) attrib_operator_sstar(C) RBRACKET. { A = '[' . B . C . ']'; }
 trimmed_ident(A) ::= IDENT(B). { A =& B; }
 trimmed_ident(A) ::= splus IDENT(B). { A =& B; }
 trimmed_ident(A) ::= IDENT(B) splus. { A =& B; }
 trimmed_ident(A) ::= splus IDENT(B) splus. { A =& B; }
 attrib_operator_sstar(A) ::= attrib_operator(B). { A =& B; }
 attrib_operator_sstar(A) ::= attrib_operator(B) splus. { A =& B; }
  attrib_operator(A) ::= INCLUDES(B). { A =& B; }
  attrib_operator(A) ::= DASHMATCH(B). { A =& B; }
  attrib_operator(A) ::= EQUAL(B). { A =& B; }
 attrib_operator_rhand_sstar(A) ::= attrib_operator_rhand(B). { A =& B; }
 attrib_operator_rhand_sstar(A) ::= attrib_operator_rhand(B) splus. { A =& B; }
  attrib_operator_rhand(A) ::= STRING(B). { A =& B; }
  attrib_operator_rhand(A) ::= IDENT(B). { A =& B; }
// }}}

//pseudo   {{{
//  : ':' [ IDENT | FUNCTION S* IDENT? S* ')' ]
//  ;
pseudo(A) ::= COLON IDENT(B). { A = ':' . B; }
pseudo(A) ::= COLON CSSFUNCTION(B) trimmed_ident(C) RPAREN. { A = ':' . B . C; }
pseudo(A) ::= COLON CSSFUNCTION(B) splus RPAREN. { A =& B; }
pseudo(A) ::= COLON CSSFUNCTION(B) RPAREN. { A =& B; }
// }}}

//declaration  {{{
//  : property ':' S* expr prio?
//  | /* empty */
//  ;
declaration(A) ::= ddeclaration(B). { 
    A = Structures_CSS_Declaration::create(B['property'], B['expressions'], B['priority']);
}
ddeclaration(A) ::= property(B) COLON splus expr(C) prio. { A = array( 'property' => B, 'expressions' => C, 'priority' => true ); }
ddeclaration(A) ::= property(B) COLON splus expr(C). { A = array( 'property' => B, 'expressions' => C, 'priority' => false ); }
ddeclaration(A) ::= property(B) COLON expr(C) prio. { A = array( 'property' => B, 'expressions' => C, 'priority' => true ); }
ddeclaration(A) ::= property(B) COLON expr(C). { A = array( 'property' => B, 'expressions' => C, 'priority' => false ); }
// }}}

//prio    {{{
//  : IMPORTANT_SYM S*
//  ;
prio ::= important_sym_sstar.
 important_sym_sstar ::= IMPORTANT_SYM.
 important_sym_sstar ::= IMPORTANT_SYM splus.
// }}}

//expr    {{{
//  : term [ operator term ]*
//  ;
expr(A) ::= term(B). { A = array(B); }
expr(A) ::= expr(B) operator(C) term(D). { A =& B; A[] =& D; A[] =& C; }
// }}}

//term   {{{
//  : unary_operator?
//    [ NUMBER S* | PERCENTAGE S* | LENGTH S* | EMS S* | EXS S* | ANGLE S* |
//      TIME S* | FREQ S* ]
//  | STRING S* | IDENT S* | URI S* | hexcolor | function
//  ;
// NOTE: The reference to DIMENSION in term_nonnumeric is a deviation from standard
term(A) ::= term_numeric(B). { A =& B; }
term(A) ::= term_nonnumeric(B). { A =& B; }
 term_numeric(A)::= unary_operator(B) term_numeric_number(C). { 
  A =& C; 
  if (B == '-') A->setNumericValue(-1 * A->getNumericValue());
 }
 term_numeric(A) ::= term_numeric_number(B). { A =& B; }
  term_numeric_number(A) ::= NUMBER(B). {
   A = new Structures_CSS_Value_Numeric(B);
  }
  term_numeric_number(A) ::= NUMBER(B) splus. {
   A = new Structures_CSS_Value_Numeric(B);
  }
  term_numeric_number(A) ::= PERCENTAGE(B). {
   A = new Structures_CSS_Value_Unit(B);
  }
  term_numeric_number(A) ::= PERCENTAGE(B) splus. {
   A = new Structures_CSS_Value_Unit(B);
  }
  term_numeric_number(A) ::= LENGTH(B). {
   A = new Structures_CSS_Value_Unit(B);
  }
  term_numeric_number(A) ::= LENGTH(B) splus. {
   A = new Structures_CSS_Value_Unit(B);
  }
  term_numeric_number(A) ::= EMS(B). {
   A = new Structures_CSS_Value_Unit(B);
  }
  term_numeric_number(A) ::= EMS(B) splus. {
   A = new Structures_CSS_Value_Unit(B);
  }
  term_numeric_number(A) ::= EXS(B). {
   A = new Structures_CSS_Value_Unit(B);
  }
  term_numeric_number(A) ::= EXS(B) splus. {
   A = new Structures_CSS_Value_Unit(B);
  }
  term_numeric_number(A) ::= ANGLE(B). {
   A = new Structures_CSS_Value_Unit(B);
  }
  term_numeric_number(A) ::= ANGLE(B) splus. {
   A = new Structures_CSS_Value_Unit(B);
  }
  term_numeric_number(A) ::= TIME(B). {
   A = new Structures_CSS_Value_Unit(B);
  }
  term_numeric_number(A) ::= TIME(B) splus. {
   A = new Structures_CSS_Value_Unit(B);
  }
  term_numeric_number(A) ::= FREQ(B). {
   A = new Structures_CSS_Value_Unit(B);
  }
  term_numeric_number(A) ::= FREQ(B) splus. {
   A = new Structures_CSS_Value_Unit(B);
  }
  term_numeric_number(A) ::= DIMENSION(B). {
   A = Structures_CSS_Value_Generic::create(B);
  }
  term_numeric_number(A) ::= DIMENSION(B) splus. {
   A = Structures_CSS_Value_Generic::create(B);
  }
 term_nonnumeric(A) ::= STRING(B). {
  A = Structures_CSS_Value_Generic::create(B);
 }
 term_nonnumeric(A) ::= STRING(B) splus. {
  A = Structures_CSS_Value_Generic::create(B);
 }
 term_nonnumeric(A) ::= IDENT(B). {
  A = Structures_CSS_Value_Generic::create(B);
 }
 term_nonnumeric(A) ::= IDENT(B) splus. {
  A = Structures_CSS_Value_Generic::create(B);
 }
 term_nonnumeric(A) ::= URI(B). {
  B = preg_replace('/url\((.*)\)/', '\1', B);
  if (B[0] == "'" && B[strlen(B) - 1] == "'" || 
      B[0] == '"' && B[strlen(B) - 1] == '"') {
    B = substr(B, 1, strlen(B) - 2);
    B = strtr(B, array('\\"' => '"', "\\'" => "'", '\\\\' => '\\'));
  }
  A = new Structures_CSS_Value_URI(B);
 }
 term_nonnumeric(A) ::= URI(B) splus. {
  B = preg_replace('/url\((.*)\)/', '\1', B);
  if (B[0] == "'" && B[strlen(B) - 1] == "'" || 
      B[0] == '"' && B[strlen(B) - 1] == '"') {
    B = substr(B, 1, strlen(B) - 2);
    B = strtr(B, array('\\"' => '"', "\\'" => "'", '\\\\' => '\\'));
  }
  A = new Structures_CSS_Value_URI(B);
 }
 term_nonnumeric(A) ::= hexcolor(B). { A =& B; }
 term_nonnumeric(A) ::= function(B). { 
  if (strtolower(B['name']) == 'rgb' &&
      count(B['params']) == 5 &&
      B['params'][2] == ',' && 
      B['params'][4] == ',' &&
      B['params'][0] instanceof Structures_CSS_Value_Numeric &&
      B['params'][1] instanceof Structures_CSS_Value_Numeric &&
      B['params'][3] instanceof Structures_CSS_Value_Numeric
      ) {
      A = new Structures_CSS_Value_Color(B['params'][0]->getNumericValue(), B['params'][1]->getNumericValue(), B['params'][3]->getNumericValue());
  } else {
      A =& new Structures_CSS_Value_Function(B); 
  }
 }
// }}}

//hexcolor   {{{
//  : HASH S*
//  ;
hexcolor(A) ::= HASH(B). { A = new Structures_CSS_Value_Color(B); }
hexcolor(A) ::= HASH(B) splus. { A = new Structures_CSS_Value_Color(B); }
// }}}

//function   {{{
//  : FUNCTION S* expr ')' S*
//  ;
function(A) ::= CSSFUNCTION(B) splus expr(C) RPAREN splus. { A = array( 'name' => strtr(B, array('(' => '')), 'params' => C ); }
function(A) ::= CSSFUNCTION(B) splus expr(C) RPAREN. { A = array( 'name' => strtr(B, array('(' => '')), 'params' => C ); }
function(A) ::= CSSFUNCTION(B) expr(C) RPAREN splus. { A = array( 'name' => strtr(B, array('(' => '')), 'params' => C ); }
function(A) ::= CSSFUNCTION(B) expr(C) RPAREN. { A = array( 'name' => strtr(B, array('(' => '')), 'params' => C ); }
// }}}

stylesheet ::= placeholder.
placeholder ::= COMMENT.
//placeholder ::= ANGLE|COMMENT|CSSFUNCTION|DIMENSION|EMS|EXS|FREQ|INCLUDES|INVALID|LBRACE|LENGTH|NUMBER|PERCENTAGE|RBRACE|TIME|URI|FONT_FACE_SYM|ATKEYWORD.


///*
// * There is a constraint on the color that it must
// * have either 3 or 6 hex-digits (i.e., [0-9a-fA-F])
// * after the "#"; e.g., "#000" is OK, but "#abcd" is not.
// */