CSSGrammar.y.in   [plain text]


/*
 *  Copyright (C) 2002-2003 Lars Knoll (knoll@kde.org)
 *  Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Apple Inc. All rights reserved.
 *  Copyright (C) 2006 Alexey Proskuryakov (ap@nypop.com)
 *  Copyright (C) 2008 Eric Seidel <eric@webkit.org>
 *  Copyright (C) 2012 Intel Corporation. All rights reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

%pure-parser

%parse-param { CSSParser* parser }
%lex-param { CSSParser* parser }

%union {
    double number;
    CSSParserString string;
    CSSSelector::MarginBoxType marginBox;
    CSSParserValue value;
}

%{

static inline int cssyyerror(void*, const char*)
{
    return 1;
}

#if YYDEBUG > 0

static inline bool isCSSTokenAString(int yytype)
{
    switch (yytype) {
    case IDENT:
    case STRING:
    case NTH:
    case HEX:
    case IDSEL:
    case DIMEN:
    case INVALIDDIMEN:
    case URI:
    case FUNCTION:
    case ANYFUNCTION:
    case NOTFUNCTION:
    case CALCFUNCTION:
    case MINFUNCTION:
    case MAXFUNCTION:
    case UNICODERANGE:
        return true;
    default:
        return false;
    }
}

#endif

static inline CSSParserValue makeIdentValue(CSSParserString string)
{
    CSSParserValue v;
    v.id = cssValueKeywordID(string);
    v.unit = CSSPrimitiveValue::CSS_IDENT;
    v.string = string;
    return v;
}

%}

#if ENABLE_CSS_GRID_LAYOUT
#if ENABLE_PICTURE_SIZES
%expect 34
#else
%expect 30
#endif
#else
#if ENABLE_PICTURE_SIZES
%expect 33
#else
%expect 29
#endif
#endif

%nonassoc LOWEST_PREC

%left UNIMPORTANT_TOK

%token WHITESPACE SGML_CD
%token TOKEN_EOF 0

%token INCLUDES
%token DASHMATCH
%token BEGINSWITH
%token ENDSWITH
%token CONTAINS

%token <string> STRING
%right <string> IDENT
%token <string> NTH

%nonassoc <string> HEX
%nonassoc <string> IDSEL
%nonassoc ':'
%nonassoc '.'
%nonassoc '['
%nonassoc <string> '*'
%nonassoc error
%left '|'

%token IMPORT_SYM
%token PAGE_SYM
%token MEDIA_SYM
%token FONT_FACE_SYM
%token CHARSET_SYM
%token NAMESPACE_SYM
%token WEBKIT_RULE_SYM
%token WEBKIT_DECLS_SYM
%token WEBKIT_KEYFRAME_RULE_SYM
%token WEBKIT_KEYFRAMES_SYM
%token WEBKIT_VALUE_SYM
%token WEBKIT_MEDIAQUERY_SYM
#if ENABLE_PICTURE_SIZES
%token WEBKIT_SIZESATTR_SYM
#endif
%token WEBKIT_SELECTOR_SYM
%token WEBKIT_REGION_RULE_SYM
%token WEBKIT_VIEWPORT_RULE_SYM
%token <marginBox> TOPLEFTCORNER_SYM
%token <marginBox> TOPLEFT_SYM
%token <marginBox> TOPCENTER_SYM
%token <marginBox> TOPRIGHT_SYM
%token <marginBox> TOPRIGHTCORNER_SYM
%token <marginBox> BOTTOMLEFTCORNER_SYM
%token <marginBox> BOTTOMLEFT_SYM
%token <marginBox> BOTTOMCENTER_SYM
%token <marginBox> BOTTOMRIGHT_SYM
%token <marginBox> BOTTOMRIGHTCORNER_SYM
%token <marginBox> LEFTTOP_SYM
%token <marginBox> LEFTMIDDLE_SYM
%token <marginBox> LEFTBOTTOM_SYM
%token <marginBox> RIGHTTOP_SYM
%token <marginBox> RIGHTMIDDLE_SYM
%token <marginBox> RIGHTBOTTOM_SYM

%token ATKEYWORD

%token IMPORTANT_SYM
%token MEDIA_ONLY
%token MEDIA_NOT
%token MEDIA_AND

%token <number> REMS
%token <number> CHS
%token <number> QEMS
%token <number> EMS
%token <number> EXS
%token <number> PXS
%token <number> CMS
%token <number> MMS
%token <number> INS
%token <number> PTS
%token <number> PCS
%token <number> DEGS
%token <number> RADS
%token <number> GRADS
%token <number> TURNS
%token <number> MSECS
%token <number> SECS
%token <number> HERTZ
%token <number> KHERTZ
%token <string> DIMEN
%token <string> INVALIDDIMEN
%token <number> PERCENTAGE
%token <number> FLOATTOKEN
%token <number> INTEGER
%token <number> VW
%token <number> VH
%token <number> VMIN
%token <number> VMAX
%token <number> DPPX
%token <number> DPI
%token <number> DPCM
%token <number> FR

%token <string> URI
%token <string> FUNCTION
%token <string> ANYFUNCTION
%token <string> NOTFUNCTION
%token <string> CALCFUNCTION
%token <string> MINFUNCTION
%token <string> MAXFUNCTION

%token <string> UNICODERANGE

%union { CSSSelector::Relation relation; }
%type <relation> combinator

%union { StyleRuleBase* rule; }
%type <rule> block_rule block_valid_rule font_face import keyframes media page region rule ruleset valid_rule
%destructor { if ($$) $$->deref(); } block_rule block_valid_rule font_face import keyframes media page region rule ruleset valid_rule

%union { Vector<RefPtr<StyleRuleBase>>* ruleList; }
%type <ruleList> block_rule_list block_valid_rule_list
%destructor { delete $$; } block_rule_list block_valid_rule_list

%type <string> ident_or_string maybe_ns_prefix namespace_selector string_or_uri

%type <marginBox> margin_sym

%union { MediaQuerySet* mediaList; }
%type <mediaList> media_list maybe_media_list
%destructor { if ($$) $$->deref(); } media_list maybe_media_list

%union { MediaQuery* mediaQuery; }
%type <mediaQuery> media_query
%destructor { delete $$; } media_query

%union { MediaQuery::Restrictor mediaQueryRestrictor; }
%type <mediaQueryRestrictor> maybe_media_restrictor

%union { MediaQueryExp* mediaQueryExp; }
%type <mediaQueryExp> media_query_exp
%destructor { delete $$; } media_query_exp

#if ENABLE_PICTURE_SIZES
%union { MediaQueryExp* mediaCondition; }
%type <mediaQueryExp> media_condition
%destructor { delete $$; } media_condition

%union { SourceSize* sourceSize; }
%type <sourceSize> source_size
%destructor { delete $$; } source_size

%union { SourceSizeList* sourceSizeList; }
%type <sourceSizeList> source_size_list
%destructor { delete $$; } source_size_list

%type <value> source_size_length
#endif

%union { Vector<std::unique_ptr<MediaQueryExp>>* mediaQueryExpList; }
%type <mediaQueryExpList> media_query_exp_list maybe_and_media_query_exp_list
%destructor { delete $$; } media_query_exp_list maybe_and_media_query_exp_list

%type <string> keyframe_name

%union { StyleKeyframe* keyframe; }
%type <keyframe> keyframe_rule
%destructor { if ($$) $$->deref(); } keyframe_rule

%union { Vector<RefPtr<StyleKeyframe>>* keyframeRuleList; }
%type <keyframeRuleList> keyframes_rule
%destructor { delete $$; } keyframes_rule

// These two parser values never need to be destroyed because they are never functions or value lists.
%type <value> key unary_term

// These parser values need to be destroyed because they might be functions.
%type <value> calc_func_term calc_function function min_or_max_function term
%destructor { destroy($$); } calc_func_term calc_function function min_or_max_function term

%union { CSSPropertyID id; }
%type <id> property

%union { CSSParserSelector* selector; }
%type <selector> attrib class page_selector pseudo pseudo_page selector selector_with_trailing_whitespace simple_selector specifier specifier_list
%destructor { delete $$; } attrib class page_selector pseudo pseudo_page selector selector_with_trailing_whitespace simple_selector specifier specifier_list

%union { Vector<std::unique_ptr<CSSParserSelector>>* selectorList; }
%type <selectorList> selector_list simple_selector_list
%destructor { delete $$; } selector_list simple_selector_list

%union { bool boolean; }
%type <boolean> declaration declaration_list decl_list priority

%union { CSSSelector::Match match; }
%type <match> match

%union { int integer; }
%type <integer> unary_operator maybe_unary_operator

%union { char character; }
%type <character> operator calc_func_operator

%union { CSSParserValueList* valueList; }
%type <valueList> calc_func_expr calc_func_expr_list calc_func_paren_expr expr key_list maybe_media_value valid_calc_func_expr valid_expr
%destructor { delete $$; } calc_func_expr calc_func_expr_list calc_func_paren_expr expr key_list maybe_media_value valid_calc_func_expr valid_expr

%type <string> min_or_max

%type <string> element_name

%union { CSSParser::Location location; }
%type <location> error_location

#if ENABLE_CSS_GRID_LAYOUT
%type <valueList> ident_list
%destructor { delete $$; } ident_list

%type <value> track_names_list
%destructor { destroy($$); } track_names_list
#endif

#if ENABLE_CSS3_CONDITIONAL_RULES

%token SUPPORTS_AND
%token SUPPORTS_NOT
%token SUPPORTS_OR
%token SUPPORTS_SYM
%token WEBKIT_SUPPORTS_CONDITION_SYM

%type <rule> supports
%destructor { if ($$) $$->deref(); } supports

%type <boolean> supports_condition supports_condition_in_parens supports_conjunction supports_declaration_condition supports_disjunction supports_error supports_negation

#endif

#if ENABLE_CSS_DEVICE_ADAPTATION

%type <rule> viewport
%destructor { if ($$) $$->deref(); } viewport

#endif

#if ENABLE_VIDEO_TRACK

%token <string> CUEFUNCTION

#endif

%%

stylesheet:
    maybe_space maybe_charset maybe_sgml rule_list
  | webkit_rule maybe_space
  | webkit_decls maybe_space
  | webkit_value maybe_space
  | webkit_mediaquery maybe_space
#if ENABLE_PICTURE_SIZES
  | webkit_source_size_list maybe_space
#endif
  | webkit_selector maybe_space
  | webkit_keyframe_rule maybe_space
#if ENABLE_CSS3_CONDITIONAL_RULES
  | webkit_supports_condition maybe_space
#endif
  ;

webkit_rule: WEBKIT_RULE_SYM '{' maybe_space valid_rule maybe_space '}' { parser->m_rule = adoptRef($4); } ;

webkit_keyframe_rule: WEBKIT_KEYFRAME_RULE_SYM '{' maybe_space keyframe_rule maybe_space '}' { parser->m_keyframe = adoptRef($4); } ;

webkit_decls: WEBKIT_DECLS_SYM '{' maybe_space_before_declaration declaration_list '}' ;

webkit_value:
    WEBKIT_VALUE_SYM '{' maybe_space expr '}' {
        if ($4) {
            parser->m_valueList = std::unique_ptr<CSSParserValueList>($4);
            int oldParsedProperties = parser->m_parsedProperties.size();
            if (!parser->parseValue(parser->m_id, parser->m_important))
                parser->rollbackLastProperties(parser->m_parsedProperties.size() - oldParsedProperties);
            parser->m_valueList = nullptr;
        }
    }
;

webkit_mediaquery: WEBKIT_MEDIAQUERY_SYM WHITESPACE maybe_space media_query '}' { parser->m_mediaQuery = std::unique_ptr<MediaQuery>($4); } ;

webkit_selector:
    WEBKIT_SELECTOR_SYM '{' maybe_space selector_list '}' {
        if ($4) {
            if (parser->m_selectorListForParseSelector)
                parser->m_selectorListForParseSelector->adoptSelectorVector(*$4);
            parser->recycleSelectorVector(std::unique_ptr<Vector<std::unique_ptr<CSSParserSelector>>>($4));
        }
    }
;

#if ENABLE_CSS3_CONDITIONAL_RULES

webkit_supports_condition: WEBKIT_SUPPORTS_CONDITION_SYM '{' maybe_space supports_condition '}' { parser->m_supportsCondition = $4; } ;

#endif

/* for expressions that require at least one whitespace to be present, like the + and - operators in calc expressions */
space: WHITESPACE | space WHITESPACE ;

maybe_space: /* empty */ %prec UNIMPORTANT_TOK | maybe_space WHITESPACE ;

maybe_sgml: /* empty */ | maybe_sgml SGML_CD | maybe_sgml WHITESPACE ;

maybe_charset: /* empty */ | charset ;

closing_brace: '}' | %prec LOWEST_PREC TOKEN_EOF ;

closing_parenthesis: ')' | %prec LOWEST_PREC TOKEN_EOF ;

charset:
  CHARSET_SYM maybe_space STRING maybe_space ';' {
     if (parser->m_styleSheet)
         parser->m_styleSheet->parserSetEncodingFromCharsetRule($3);
     if (parser->isExtractingSourceData() && parser->m_currentRuleDataStack->isEmpty() && parser->m_ruleSourceDataResult)
         parser->addNewRuleToSourceTree(CSSRuleSourceData::createUnknown());
  }
  | CHARSET_SYM error invalid_block
  | CHARSET_SYM error ';'
;

// Ignore any @charset rule not at the beginning of the style sheet.
ignored_charset: CHARSET_SYM maybe_space STRING maybe_space ';' | CHARSET_SYM maybe_space ';' ;

rule_list:
    /* empty */
    | rule_list rule maybe_sgml {
        if (RefPtr<StyleRuleBase> rule = adoptRef($2)) {
            if (parser->m_styleSheet)
                parser->m_styleSheet->parserAppendRule(rule.releaseNonNull());
        }
    }
    ;

valid_rule:
    ruleset
  | media
  | page
  | font_face
  | keyframes
  | namespace { $$ = nullptr; }
  | import
  | region
#if ENABLE_CSS3_CONDITIONAL_RULES
  | supports
#endif
#if ENABLE_CSS_DEVICE_ADAPTATION
  | viewport
#endif
  ;

rule:
    valid_rule {
        $$ = $1;
        parser->m_hadSyntacticallyValidCSSRule = true;
    }
    | ignored_charset { $$ = nullptr; }
    | invalid_rule { $$ = nullptr; }
    | invalid_at { $$ = nullptr; }
    ;

block_rule_list: 
    /* empty */ { $$ = nullptr; }
  | block_rule_list block_rule maybe_sgml {
      $$ = $1;
      if (RefPtr<StyleRuleBase> rule = adoptRef($2)) {
          if (!$$)
              $$ = new Vector<RefPtr<StyleRuleBase>>;
          $$->append(rule.release());
      }
  }
  ;

block_valid_rule_list:
    /* empty */ { $$ = nullptr; }
  | block_valid_rule_list block_valid_rule maybe_sgml {
      $$ = $1;
      if (RefPtr<StyleRuleBase> rule = adoptRef($2)) {
          if (!$$)
              $$ = new Vector<RefPtr<StyleRuleBase>>;
          $$->append(rule.release());
      }
  }
  ;

block_valid_rule:
    ruleset
  | page
  | font_face
  | media
  | keyframes
#if ENABLE_CSS3_CONDITIONAL_RULES
  | supports
#endif
#if ENABLE_CSS_DEVICE_ADAPTATION
  | viewport
#endif
  ;

block_rule: block_valid_rule | invalid_rule { $$ = nullptr; } | invalid_at { $$ = nullptr; } | namespace { $$ = nullptr; } | import | region ;

at_import_header_end_maybe_space:
    maybe_space {
        parser->markRuleHeaderEnd();
        parser->markRuleBodyStart();
    }
    ;

before_import_rule:
    /* empty */ {
        parser->markRuleHeaderStart(CSSRuleSourceData::IMPORT_RULE);
    }
    ;

import:
    before_import_rule IMPORT_SYM at_import_header_end_maybe_space string_or_uri maybe_space maybe_media_list ';' {
        $$ = parser->createImportRule($4, adoptRef($6)).leakRef();
    }
  | before_import_rule IMPORT_SYM at_import_header_end_maybe_space string_or_uri maybe_space maybe_media_list TOKEN_EOF {
        $$ = parser->createImportRule($4, adoptRef($6)).leakRef();
    }
  | before_import_rule IMPORT_SYM at_import_header_end_maybe_space string_or_uri maybe_space maybe_media_list invalid_block {
        $$ = nullptr;
        parser->popRuleData();
        if ($6)
            $6->deref();
    }
  | before_import_rule IMPORT_SYM error ';' {
        $$ = nullptr;
        parser->popRuleData();
    }
  | before_import_rule IMPORT_SYM error invalid_block {
        $$ = nullptr;
        parser->popRuleData();
    }
  ;

namespace:
    NAMESPACE_SYM maybe_space maybe_ns_prefix string_or_uri maybe_space ';' { parser->addNamespace($3, $4); }
    | NAMESPACE_SYM maybe_space maybe_ns_prefix string_or_uri maybe_space invalid_block
    | NAMESPACE_SYM error invalid_block
    | NAMESPACE_SYM error ';'
    ;

maybe_ns_prefix: /* empty */ { $$.clear(); } | IDENT maybe_space;

string_or_uri: STRING | URI ;

maybe_media_value: /*empty*/ { $$ = nullptr; } | ':' maybe_space expr maybe_space { $$ = $3; } ;

#if ENABLE_PICTURE_SIZES
media_condition:
    maybe_space '(' maybe_space IDENT maybe_space maybe_media_value ')' maybe_space {
        std::unique_ptr<CSSParserValueList> mediaValue($6);
        $4.lower();
        $$ = new MediaQueryExp($4, mediaValue.get());
    }
    ;

webkit_source_size_list:
    WEBKIT_SIZESATTR_SYM WHITESPACE source_size_list '}' { parser->m_sourceSizeList = std::unique_ptr<SourceSizeList>($3); };

source_size_list:
    maybe_space source_size maybe_space {
        $$ = new SourceSizeList();
        $$->append(std::unique_ptr<SourceSize>($2));
    }
    | maybe_space source_size maybe_space ',' maybe_space source_size_list maybe_space {
        $$ = $6;
        $$->append(std::unique_ptr<SourceSize>($2));
    };

source_size_length:
    unary_term {
        $$ = $1;
    }
    | calc_function {
        $$ = $1;
    };

source_size:
    media_condition source_size_length {
        $$ = new SourceSize(std::unique_ptr<MediaQueryExp>($1), $2);
    }
    | source_size_length {
        $$ = new SourceSize(std::make_unique<MediaQueryExp>(emptyString(), nullptr), $1);
    };
#endif

media_query_exp:
    maybe_media_restrictor maybe_space '(' maybe_space IDENT maybe_space maybe_media_value ')' maybe_space {
        // If restrictor is specified, media query expression is invalid.
        // Create empty media query expression and continue parsing media query.
        std::unique_ptr<CSSParserValueList> mediaValue($7);
        if ($1 != MediaQuery::None)
            $$ = new MediaQueryExp(emptyString(), nullptr);
        else {
            $5.lower();
            $$ = new MediaQueryExp($5, mediaValue.get());
        }
    }
    ;

media_query_exp_list:
    media_query_exp {
        $$ = new Vector<std::unique_ptr<MediaQueryExp>>;
        $$->append(std::unique_ptr<MediaQueryExp>($1));
    }
    | media_query_exp_list maybe_space MEDIA_AND maybe_space media_query_exp {
        $$ = $1;
        $$->append(std::unique_ptr<MediaQueryExp>($5));
    }
    ;

maybe_and_media_query_exp_list:
    /*empty*/ {
        $$ = new Vector<std::unique_ptr<MediaQueryExp>>;
    }
    | MEDIA_AND maybe_space media_query_exp_list {
        $$ = $3;
    }
    ;

maybe_media_restrictor:
    /*empty*/ {
        $$ = MediaQuery::None;
    }
    | MEDIA_ONLY {
        $$ = MediaQuery::Only;
    }
    | MEDIA_NOT {
        $$ = MediaQuery::Not;
    }
    ;

media_query:
    media_query_exp_list {
        $$ = new MediaQuery(MediaQuery::None, "all", std::unique_ptr<Vector<std::unique_ptr<MediaQueryExp>>>($1));
    }
    |
    maybe_media_restrictor maybe_space IDENT maybe_space maybe_and_media_query_exp_list {
        $3.lower();
        $$ = new MediaQuery($1, $3, std::unique_ptr<Vector<std::unique_ptr<MediaQueryExp>>>($5));
    }
    ;

maybe_media_list: /* empty */ { $$ = MediaQuerySet::create().leakRef(); } | media_list ;

media_list:
    media_query {
        $$ = MediaQuerySet::create().leakRef();
        $$->addMediaQuery(std::unique_ptr<MediaQuery>($1));
        parser->updateLastMediaLine($$);
    }
    | media_list ',' maybe_space media_query {
        $$ = $1;
        std::unique_ptr<MediaQuery> mediaQuery($4);
        if ($$) {
            $$->addMediaQuery(WTF::move(mediaQuery));
            parser->updateLastMediaLine($$);
        }
    }
    | media_list error {
        $$ = nullptr;
        if ($1)
            $1->deref();
    }
    ;

at_rule_body_start:
    /* empty */ {
        parser->markRuleBodyStart();
    }
    ;

before_media_rule:
    /* empty */ {
        parser->markRuleHeaderStart(CSSRuleSourceData::MEDIA_RULE);
    }
    ;

at_rule_header_end_maybe_space:
    maybe_space {
        parser->markRuleHeaderEnd();
    }
    ;

media:
    before_media_rule MEDIA_SYM maybe_space media_list at_rule_header_end '{' at_rule_body_start maybe_space block_rule_list save_block {
        $$ = parser->createMediaRule(adoptRef($4), std::unique_ptr<Vector<RefPtr<StyleRuleBase>>>($9).get()).leakRef();
    }
    | before_media_rule MEDIA_SYM at_rule_header_end_maybe_space '{' at_rule_body_start maybe_space block_rule_list save_block {
        $$ = parser->createEmptyMediaRule(std::unique_ptr<Vector<RefPtr<StyleRuleBase>>>($7).get()).leakRef();
    }
    | before_media_rule MEDIA_SYM at_rule_header_end_maybe_space ';' {
        $$ = nullptr;
        parser->popRuleData();
    }
    ;

#if ENABLE_CSS3_CONDITIONAL_RULES

supports:
    before_supports_rule SUPPORTS_SYM maybe_space supports_condition at_supports_rule_header_end '{' at_rule_body_start maybe_space block_rule_list save_block {
        $$ = parser->createSupportsRule($4, std::unique_ptr<Vector<RefPtr<StyleRuleBase>>>($9).get()).leakRef();
    }
    | before_supports_rule SUPPORTS_SYM supports_error {
        $$ = nullptr;
        parser->popRuleData();
        parser->popSupportsRuleData();
    }
    ;

supports_error: error ';' | error invalid_block ;

before_supports_rule:
    /* empty */ {
        parser->markRuleHeaderStart(CSSRuleSourceData::SUPPORTS_RULE);
        parser->markSupportsRuleHeaderStart();
    }
    ;

at_supports_rule_header_end:
    /* empty */ {
        parser->markRuleHeaderEnd();
        parser->markSupportsRuleHeaderEnd();
    }
    ;

supports_condition: supports_condition_in_parens | supports_negation | supports_conjunction | supports_disjunction ;

supports_negation: SUPPORTS_NOT maybe_space supports_condition_in_parens { $$ = !$3; } ;

supports_conjunction:
    supports_condition_in_parens SUPPORTS_AND maybe_space supports_condition_in_parens { $$ = $1 && $4; }
    | supports_conjunction SUPPORTS_AND maybe_space supports_condition_in_parens { $$ = $1 && $4; }
    ;

supports_disjunction:
    supports_condition_in_parens SUPPORTS_OR maybe_space supports_condition_in_parens { $$ = $1 || $4; }
    | supports_disjunction SUPPORTS_OR maybe_space supports_condition_in_parens { $$ = $1 || $4; }
    ;

supports_condition_in_parens:
    '(' maybe_space supports_condition ')' maybe_space { $$ = $3; }
    | supports_declaration_condition { $$ = $1; }
    | '(' error ')' { $$ = false; }
    ;

supports_declaration_condition:
    '(' maybe_space property ':' maybe_space expr priority ')' maybe_space {
        $$ = false;
        CSSParser* p = static_cast<CSSParser*>(parser);
        std::unique_ptr<CSSParserValueList> propertyValue($6);
        if ($3 && propertyValue) {
            p->m_valueList = WTF::move(propertyValue);
            int oldParsedProperties = p->m_parsedProperties.size();
            $$ = p->parseValue($3, $7);
            // We just need to know if the declaration is supported as it is written. Rollback any additions.
            if ($$)
                p->rollbackLastProperties(p->m_parsedProperties.size() - oldParsedProperties);
            p->m_valueList = nullptr;
        }
        p->markPropertyEnd($7, false);
    }
    ;

#endif

before_keyframes_rule:
    /* empty */ {
        parser->markRuleHeaderStart(CSSRuleSourceData::KEYFRAMES_RULE);
    }
    ;

keyframes:
    before_keyframes_rule WEBKIT_KEYFRAMES_SYM maybe_space keyframe_name at_rule_header_end_maybe_space '{' at_rule_body_start maybe_space keyframes_rule closing_brace {
        $$ = parser->createKeyframesRule($4, std::unique_ptr<Vector<RefPtr<StyleKeyframe>>>($9)).leakRef();
    }
    ;
  
keyframe_name: IDENT | STRING ;

keyframes_rule:
    /* empty */ { $$ = new Vector<RefPtr<StyleKeyframe>>; }
    | keyframes_rule keyframe_rule maybe_space {
        $$ = $1;
        if (RefPtr<StyleKeyframe> keyframe = adoptRef($2))
            $$->append(keyframe.release());
    }
    ;

keyframe_rule: key_list maybe_space '{' maybe_space declaration_list closing_brace { $$ = parser->createKeyframe(*std::unique_ptr<CSSParserValueList>($1)).leakRef(); } ;

key_list:
    key {
        $$ = new CSSParserValueList;
        $$->addValue($1);
    }
    | key_list maybe_space ',' maybe_space key {
        $$ = $1;
        ASSERT($5.unit != CSSParserValue::Function); // No need to call destroy.
        if ($$)
            $$->addValue($5);
    }
    ;

key:
    maybe_unary_operator PERCENTAGE { $$.id = CSSValueInvalid; $$.isInt = false; $$.fValue = $1 * $2; $$.unit = CSSPrimitiveValue::CSS_NUMBER; }
    | IDENT {
        $$.id = CSSValueInvalid; $$.isInt = false; $$.unit = CSSPrimitiveValue::CSS_NUMBER;
        CSSParserString& str = $1;
        if (str.equalIgnoringCase("from"))
            $$.fValue = 0;
        else if (str.equalIgnoringCase("to"))
            $$.fValue = 100;
        else {
            $$.unit = 0;
            YYERROR;
        }
    }
    | error {
        $$.unit = 0;
    }
    ;

before_page_rule:
    /* empty */ {
        parser->markRuleHeaderStart(CSSRuleSourceData::PAGE_RULE);
    }
    ;

page:
    before_page_rule PAGE_SYM maybe_space page_selector at_rule_header_end_maybe_space
    '{' at_rule_body_start maybe_space_before_declaration declarations_and_margins closing_brace {
        if ($4)
            $$ = parser->createPageRule(std::unique_ptr<CSSParserSelector>($4)).leakRef();
        else {
            // Clear properties in the invalid @page rule.
            parser->clearProperties();
            // Also clear margin at-rules here once we fully implement margin at-rules parsing.
            $$ = nullptr;
            parser->popRuleData();
        }
    }
    | before_page_rule PAGE_SYM error invalid_block {
        parser->popRuleData();
        $$ = nullptr;
    }
    | before_page_rule PAGE_SYM error ';' {
        parser->popRuleData();
        $$ = nullptr;
    }
    ;

page_selector:
    IDENT {
        $$ = new CSSParserSelector(QualifiedName(nullAtom, $1, parser->m_defaultNamespace));
        $$->setForPage();
    }
    | IDENT pseudo_page {
        $$ = $2;
        if ($$) {
            $$->prependTagSelector(QualifiedName(nullAtom, $1, parser->m_defaultNamespace));
            $$->setForPage();
        }
    }
    | pseudo_page {
        $$ = $1;
        if ($$)
            $$->setForPage();
    }
    | /* empty */ {
        $$ = new CSSParserSelector;
        $$->setForPage();
    }
    ;

declarations_and_margins: declaration_list | declarations_and_margins margin_box maybe_space declaration_list ;

margin_box:
    margin_sym {
        parser->startDeclarationsForMarginBox();
    } maybe_space '{' maybe_space declaration_list closing_brace {
        parser->createMarginAtRule($1);
    }
    ;

margin_sym:
    TOPLEFTCORNER_SYM {
        $$ = CSSSelector::TopLeftCornerMarginBox;
    }
    | TOPLEFT_SYM {
        $$ = CSSSelector::TopLeftMarginBox;
    }
    | TOPCENTER_SYM {
        $$ = CSSSelector::TopCenterMarginBox;
    }
    | TOPRIGHT_SYM {
        $$ = CSSSelector::TopRightMarginBox;
    }
    | TOPRIGHTCORNER_SYM {
        $$ = CSSSelector::TopRightCornerMarginBox;
    }
    | BOTTOMLEFTCORNER_SYM {
        $$ = CSSSelector::BottomLeftCornerMarginBox;
    }
    | BOTTOMLEFT_SYM {
        $$ = CSSSelector::BottomLeftMarginBox;
    }
    | BOTTOMCENTER_SYM {
        $$ = CSSSelector::BottomCenterMarginBox;
    }
    | BOTTOMRIGHT_SYM {
        $$ = CSSSelector::BottomRightMarginBox;
    }
    | BOTTOMRIGHTCORNER_SYM {
        $$ = CSSSelector::BottomRightCornerMarginBox;
    }
    | LEFTTOP_SYM {
        $$ = CSSSelector::LeftTopMarginBox;
    }
    | LEFTMIDDLE_SYM {
        $$ = CSSSelector::LeftMiddleMarginBox;
    }
    | LEFTBOTTOM_SYM {
        $$ = CSSSelector::LeftBottomMarginBox;
    }
    | RIGHTTOP_SYM {
        $$ = CSSSelector::RightTopMarginBox;
    }
    | RIGHTMIDDLE_SYM {
        $$ = CSSSelector::RightMiddleMarginBox;
    }
    | RIGHTBOTTOM_SYM {
        $$ = CSSSelector::RightBottomMarginBox;
    }
    ;

before_font_face_rule:
    /* empty */ {
        parser->markRuleHeaderStart(CSSRuleSourceData::FONT_FACE_RULE);
    }
    ;

font_face:
    before_font_face_rule FONT_FACE_SYM at_rule_header_end_maybe_space '{' at_rule_body_start maybe_space_before_declaration declaration_list closing_brace {
        $$ = parser->createFontFaceRule().leakRef();
    }
    | before_font_face_rule FONT_FACE_SYM error invalid_block {
        $$ = nullptr;
        parser->popRuleData();
    }
    | before_font_face_rule FONT_FACE_SYM error ';' {
        $$ = nullptr;
        parser->popRuleData();
    }
;

#if ENABLE_CSS_DEVICE_ADAPTATION

before_viewport_rule:
    /* empty */ {
        parser->markViewportRuleBodyStart();
        parser->markRuleHeaderStart(CSSRuleSourceData::VIEWPORT_RULE);
    }
    ;

viewport:
    before_viewport_rule WEBKIT_VIEWPORT_RULE_SYM at_rule_header_end_maybe_space
    '{' at_rule_body_start maybe_space_before_declaration declaration_list closing_brace {
        $$ = parser->createViewportRule().leakRef();
        parser->markViewportRuleBodyEnd();
    }
    | before_viewport_rule WEBKIT_VIEWPORT_RULE_SYM error invalid_block {
        $$ = nullptr;
        parser->popRuleData();
        parser->markViewportRuleBodyEnd();
    }
    | before_viewport_rule WEBKIT_VIEWPORT_RULE_SYM error ';' {
        $$ = nullptr;
        parser->popRuleData();
        parser->markViewportRuleBodyEnd();
    }
;

#endif

before_region_rule:
    /* empty */ {
        parser->markRuleHeaderStart(CSSRuleSourceData::REGION_RULE);
    }
    ;

region:
    before_region_rule WEBKIT_REGION_RULE_SYM maybe_space selector_list at_rule_header_end '{' at_rule_body_start maybe_space block_valid_rule_list save_block {
        std::unique_ptr<Vector<RefPtr<StyleRuleBase>>> ruleList($9);
        if ($4)
            $$ = parser->createRegionRule(std::unique_ptr<Vector<std::unique_ptr<CSSParserSelector>>>($4).get(), ruleList.get()).leakRef();
        else {
            $$ = nullptr;
            parser->popRuleData();
        }
    }
;

combinator:
    '+' maybe_space { $$ = CSSSelector::DirectAdjacent; }
  | '~' maybe_space { $$ = CSSSelector::IndirectAdjacent; }
  | '>' maybe_space { $$ = CSSSelector::Child; }
  ;

maybe_unary_operator: unary_operator | { $$ = 1; } ;

unary_operator: '-' { $$ = -1; } | '+' { $$ = 1; } ;

maybe_space_before_declaration: maybe_space { parser->markPropertyStart(); } ;

before_selector_list:
    {
        parser->markRuleHeaderStart(CSSRuleSourceData::STYLE_RULE);
        parser->markSelectorStart();
    }
  ;

at_rule_header_end: { parser->markRuleHeaderEnd(); } ;

at_selector_end: { parser->markSelectorEnd(); } ;

ruleset:
    before_selector_list selector_list at_selector_end at_rule_header_end '{' at_rule_body_start maybe_space_before_declaration declaration_list closing_brace {
        $$ = parser->createStyleRule($2).leakRef();
        parser->recycleSelectorVector(std::unique_ptr<Vector<std::unique_ptr<CSSParserSelector>>>($2));
    }
  ;

before_selector_group_item: { parser->markSelectorStart(); } ;

selector_list:
    selector %prec UNIMPORTANT_TOK {
        $$ = nullptr;
        if ($1) {
            $$ = parser->createSelectorVector().release();
            $$->append(std::unique_ptr<CSSParserSelector>($1));
            parser->updateLastSelectorLineAndPosition();
        }
    }
    | selector_list at_selector_end ',' maybe_space before_selector_group_item selector %prec UNIMPORTANT_TOK {
        std::unique_ptr<Vector<std::unique_ptr<CSSParserSelector>>> selectorList($1);
        std::unique_ptr<CSSParserSelector> selector($6);
        $$ = nullptr;
        if (selectorList && selector) {
            $$ = selectorList.release();
            $$->append(WTF::move(selector));
            parser->updateLastSelectorLineAndPosition();
        }
    }
    | selector_list error {
        $$ = nullptr;
        delete $1;
    }
   ;

selector_with_trailing_whitespace:
    selector WHITESPACE;

selector:
    simple_selector
    | selector_with_trailing_whitespace
    | selector_with_trailing_whitespace simple_selector {
        std::unique_ptr<CSSParserSelector> left($1);
        std::unique_ptr<CSSParserSelector> right($2);
        $$ = nullptr;
        if (left && right) {
            right->appendTagHistory(CSSSelector::Descendant, WTF::move(left));
            $$ = right.release();
        }
    }
    | selector combinator simple_selector {
        std::unique_ptr<CSSParserSelector> left($1);
        std::unique_ptr<CSSParserSelector> right($3);
        $$ = nullptr;
        if (left && right) {
            right->appendTagHistory($2, WTF::move(left));
            $$ = right.release();
        }
    }
    | selector error {
        $$ = nullptr;
        delete $1;
    }
    ;

namespace_selector:
    '|' { $$.clear(); }
    | '*' '|' { static LChar star = '*'; $$.init(&star, 1); }
    | IDENT '|'
;

simple_selector:
    element_name {
        $$ = new CSSParserSelector(QualifiedName(nullAtom, $1, parser->m_defaultNamespace));
    }
    | element_name specifier_list {
        $$ = $2;
        if ($$)
            parser->rewriteSpecifiersWithElementName(nullAtom, $1, *$$);
    }
    | specifier_list {
        $$ = $1;
        if ($$)
            parser->rewriteSpecifiersWithNamespaceIfNeeded(*$$);
    }
    | namespace_selector element_name {
        $$ = new CSSParserSelector(parser->determineNameInNamespace($1, $2));
    }
    | namespace_selector element_name specifier_list {
        $$ = $3;
        if ($$)
            parser->rewriteSpecifiersWithElementName($1, $2, *$$);
    }
    | namespace_selector specifier_list {
        $$ = $2;
        if ($$)
            parser->rewriteSpecifiersWithElementName($1, starAtom, *$$);
    }
  ;

simple_selector_list:
    simple_selector %prec UNIMPORTANT_TOK {
        $$ = nullptr;
        if ($1) {
            $$ = parser->createSelectorVector().release();
            $$->append(std::unique_ptr<CSSParserSelector>($1));
        }
    }
    | simple_selector_list maybe_space ',' maybe_space simple_selector %prec UNIMPORTANT_TOK {
        std::unique_ptr<Vector<std::unique_ptr<CSSParserSelector>>> list($1);
        std::unique_ptr<CSSParserSelector> selector($5);
        $$ = nullptr;
        if (list && selector) {
            $$ = list.release();
            $$->append(WTF::move(selector));
        }
    }
    | simple_selector_list error {
        $$ = nullptr;
        delete $1;
    }
    ;

element_name:
    IDENT {
        if (parser->m_context.isHTMLDocument)
            $1.lower();
        $$ = $1;
    }
    | '*' {
        static LChar star = '*';
        $$.init(&star, 1);
    }
    ;

specifier_list:
    specifier
    | specifier_list specifier {
        std::unique_ptr<CSSParserSelector> list($1);
        std::unique_ptr<CSSParserSelector> specifier($2);
        $$ = nullptr;
        if (list && specifier)
            $$ = parser->rewriteSpecifiers(WTF::move(list), WTF::move(specifier)).release();
    }
    | specifier_list error {
        $$ = nullptr;
        delete $1;
    }
;

specifier:
    IDSEL {
        $$ = new CSSParserSelector;
        $$->setMatch(CSSSelector::Id);
        if (parser->m_context.mode == CSSQuirksMode)
            $1.lower();
        $$->setValue($1);
    }
  | HEX {
        if ($1[0] >= '0' && $1[0] <= '9')
            $$ = nullptr;
        else {
            $$ = new CSSParserSelector;
            $$->setMatch(CSSSelector::Id);
            if (parser->m_context.mode == CSSQuirksMode)
                $1.lower();
            $$->setValue($1);
        }
    }
  | class
  | attrib
  | pseudo
    ;

class:
    '.' IDENT {
        $$ = new CSSParserSelector;
        $$->setMatch(CSSSelector::Class);
        if (parser->m_context.mode == CSSQuirksMode)
            $2.lower();
        $$->setValue($2);
    }
  ;

attrib:
    '[' maybe_space IDENT maybe_space ']' {
        $$ = new CSSParserSelector;
        $$->setAttribute(QualifiedName(nullAtom, $3, nullAtom), parser->m_context.isHTMLDocument);
        $$->setMatch(CSSSelector::Set);
    }
    | '[' maybe_space IDENT maybe_space match maybe_space ident_or_string maybe_space ']' {
        $$ = new CSSParserSelector;
        $$->setAttribute(QualifiedName(nullAtom, $3, nullAtom), parser->m_context.isHTMLDocument);
        $$->setMatch($5);
        $$->setValue($7);
    }
    | '[' maybe_space namespace_selector IDENT maybe_space ']' {
        $$ = new CSSParserSelector;
        $$->setAttribute(parser->determineNameInNamespace($3, $4), parser->m_context.isHTMLDocument);
        $$->setMatch(CSSSelector::Set);
    }
    | '[' maybe_space namespace_selector IDENT maybe_space match maybe_space ident_or_string maybe_space ']' {
        $$ = new CSSParserSelector;
        $$->setAttribute(parser->determineNameInNamespace($3, $4), parser->m_context.isHTMLDocument);
        $$->setMatch($6);
        $$->setValue($8);
    }
  ;

match:
    '=' {
        $$ = CSSSelector::Exact;
    }
    | INCLUDES {
        $$ = CSSSelector::List;
    }
    | DASHMATCH {
        $$ = CSSSelector::Hyphen;
    }
    | BEGINSWITH {
        $$ = CSSSelector::Begin;
    }
    | ENDSWITH {
        $$ = CSSSelector::End;
    }
    | CONTAINS {
        $$ = CSSSelector::Contain;
    }
    ;

ident_or_string: IDENT | STRING ;

pseudo_page:
    ':' IDENT {
        $$ = CSSParserSelector::parsePagePseudoSelector($2);
    }

pseudo:
    ':' IDENT {
        $$ = CSSParserSelector::parsePseudoClassAndCompatibilityElementSelector($2);
    }
    | ':' ':' IDENT {
        $$ = CSSParserSelector::parsePseudoElementSelector($3);
    }
#if ENABLE_VIDEO_TRACK
    // used by ::cue(:past/:future)
    | ':' ':' CUEFUNCTION maybe_space simple_selector_list maybe_space ')' {
        $$ = CSSParserSelector::parsePseudoElementCueFunctionSelector($3, $5);
    }
#endif
    // use by :-webkit-any.
    // FIXME: should we support generic selectors here or just simple_selectors?
    // Use simple_selector_list for now to match -moz-any.
    // See http://lists.w3.org/Archives/Public/www-style/2010Sep/0566.html for some
    // related discussion with respect to :not.
    | ':' ANYFUNCTION maybe_space simple_selector_list maybe_space ')' {
        $$ = nullptr;
        if ($4) {
            auto selector = std::make_unique<CSSParserSelector>();
            selector->setMatch(CSSSelector::PseudoClass);
            selector->adoptSelectorVector(*std::unique_ptr<Vector<std::unique_ptr<CSSParserSelector>>>($4));
            selector->setPseudoClassValue($2);
            if (selector->pseudoClassType() == CSSSelector::PseudoClassAny)
                $$ = selector.release();
        }
    }
    // used by :nth-*(ax+b)
    | ':' FUNCTION maybe_space NTH maybe_space ')' {
        $$ = nullptr;
        auto selector = std::make_unique<CSSParserSelector>();
        selector->setMatch(CSSSelector::PseudoClass);
        selector->setArgument($4);
        selector->setPseudoClassValue($2);
        if (selector->pseudoClassType() != CSSSelector::PseudoClassUnknown)
            $$ = selector.release();
    }
    // used by :nth-*
    | ':' FUNCTION maybe_space maybe_unary_operator INTEGER maybe_space ')' {
        $$ = nullptr;
        auto selector = std::make_unique<CSSParserSelector>();
        selector->setMatch(CSSSelector::PseudoClass);
        selector->setArgument(AtomicString::number($4 * $5));
        selector->setPseudoClassValue($2);
        if (selector->pseudoClassType() != CSSSelector::PseudoClassUnknown)
            $$ = selector.release();
    }
    // used by :nth-*(odd/even) and :lang
    | ':' FUNCTION maybe_space IDENT maybe_space ')' {
        auto selector = std::make_unique<CSSParserSelector>();
        selector->setMatch(CSSSelector::PseudoClass);
        selector->setArgument($4);
        selector->setPseudoClassValue($2);
        CSSSelector::PseudoClassType type = selector->pseudoClassType();
        if (type == CSSSelector::PseudoClassUnknown)
            selector = nullptr;
        else if (type == CSSSelector::PseudoClassNthChild ||
                 type == CSSSelector::PseudoClassNthOfType ||
                 type == CSSSelector::PseudoClassNthLastChild ||
                 type == CSSSelector::PseudoClassNthLastOfType) {
            if (!isValidNthToken($4))
                selector = nullptr;
        }
        $$ = selector.release();
    }
    // used by :not
    | ':' NOTFUNCTION maybe_space simple_selector maybe_space ')' {
        std::unique_ptr<CSSParserSelector> selector($4);
        $$ = nullptr;
        if (selector && selector->isSimple()) {
            $$ = new CSSParserSelector;
            $$->setMatch(CSSSelector::PseudoClass);

            Vector<std::unique_ptr<CSSParserSelector>> selectorVector;
            selectorVector.append(WTF::move(selector));
            $$->adoptSelectorVector(selectorVector);
            $$->setPseudoClassValue($2);
        }
    }
  ;

declaration_list:
    /* empty */ { $$ = false; }
    | declaration
    | decl_list declaration { $$ = $1 || $2; }
    | decl_list
    | decl_list_recovery { $$ = false; }
    | decl_list decl_list_recovery
    ;

decl_list:
    declaration ';' maybe_space {
        parser->markPropertyStart();
        $$ = $1;
    }
    | decl_list_recovery ';' maybe_space {
        parser->markPropertyStart();
        $$ = false;
    }
    | decl_list declaration ';' maybe_space {
        parser->markPropertyStart();
        $$ = $1;
        if ($2)
            $$ = $2;
    }
    | decl_list decl_list_recovery ';' maybe_space {
        parser->markPropertyStart();
        $$ = $1;
    }
    ;

decl_list_recovery:
    error error_location error_recovery {
        parser->syntaxError($2, CSSParser::PropertyDeclarationError);
    }
    ;

declaration:
    property ':' maybe_space expr priority {
        $$ = false;
        bool isPropertyParsed = false;
        std::unique_ptr<CSSParserValueList> propertyValue($4);
        if ($1 && propertyValue) {
            parser->m_valueList = WTF::move(propertyValue);
            int oldParsedProperties = parser->m_parsedProperties.size();
            $$ = parser->parseValue($1, $5);
            if (!$$)
                parser->rollbackLastProperties(parser->m_parsedProperties.size() - oldParsedProperties);
            else
                isPropertyParsed = true;
            parser->m_valueList = nullptr;
        }
        parser->markPropertyEnd($5, isPropertyParsed);
    }
    | property declaration_recovery { $$ = false; }
    | property ':' maybe_space expr priority declaration_recovery {
        // When we encounter something like p {color: red !important fail;} we should drop the declaration.
        parser->markPropertyEnd(false, false);
        delete $4;
        $$ = false;
    }
    | IMPORTANT_SYM maybe_space declaration_recovery {
        // Handle this case -- div { text-align: center; !important } -- by just reducing away the stray !important.
        $$ = false;
    }
    | property ':' maybe_space declaration_recovery {
        // If we come across rules with invalid values like this case: p { weight: *; }, just discard the rule.
        parser->markPropertyEnd(false, false);
        $$ = false;
    }
  ;

declaration_recovery: error error_location error_recovery { parser->syntaxError($2); } ;

property: IDENT maybe_space { $$ = cssPropertyID($1); } ;

priority: IMPORTANT_SYM maybe_space { $$ = true; } | /* empty */ { $$ = false; } ;

#if ENABLE_CSS_GRID_LAYOUT

ident_list:
    IDENT maybe_space {
        $$ = new CSSParserValueList;
        $$->addValue(makeIdentValue($1));
    }
    | ident_list IDENT maybe_space {
        $$ = $1;
        $$->addValue(makeIdentValue($2));
    }
    ;

track_names_list:
    '(' maybe_space closing_parenthesis {
        $$.setFromValueList(std::make_unique<CSSParserValueList>());
    }
    | '(' maybe_space ident_list closing_parenthesis {
        $$.setFromValueList(std::unique_ptr<CSSParserValueList>($3));
    }
    | '(' maybe_space expr_recovery closing_parenthesis {
        $$.id = CSSValueInvalid;
        $$.unit = 0;
        YYERROR;
    }
  ;

#endif

expr: valid_expr | valid_expr expr_recovery { $$ = nullptr; delete $1; } ;

valid_expr:
    term {
        $$ = new CSSParserValueList;
        $$->addValue($1);
    }
    | valid_expr operator term {
        $$ = $1;
        if (!$$)
            destroy($3);
        else {
            if ($2) {
                CSSParserValue v;
                v.id = CSSValueInvalid;
                v.unit = CSSParserValue::Operator;
                v.iValue = $2;
                $$->addValue(v);
            }
            $$->addValue($3);
        }
    }
  ;

expr_recovery: error error_location error_recovery ;

operator: '/' maybe_space { $$ = '/'; } | ',' maybe_space { $$ = ','; } | /* empty */ { $$ = 0; } ;

term:
  unary_term maybe_space
  | unary_operator unary_term maybe_space { $$ = $2; $$.fValue *= $1; }
  | STRING maybe_space { $$.id = CSSValueInvalid; $$.string = $1; $$.unit = CSSPrimitiveValue::CSS_STRING; }
  | IDENT maybe_space { $$ = makeIdentValue($1); }
  /* We might need to actually parse the number from a dimension, but we can't just put something that uses $$.string into unary_term. */
  | DIMEN maybe_space { $$.id = CSSValueInvalid; $$.string = $1; $$.unit = CSSPrimitiveValue::CSS_DIMENSION; }
  | unary_operator DIMEN maybe_space { $$.id = CSSValueInvalid; $$.string = $2; $$.unit = CSSPrimitiveValue::CSS_DIMENSION; }
  | URI maybe_space { $$.id = CSSValueInvalid; $$.string = $1; $$.unit = CSSPrimitiveValue::CSS_URI; }
  | UNICODERANGE maybe_space { $$.id = CSSValueInvalid; $$.string = $1; $$.unit = CSSPrimitiveValue::CSS_UNICODE_RANGE; }
  | HEX maybe_space { $$.id = CSSValueInvalid; $$.string = $1; $$.unit = CSSPrimitiveValue::CSS_PARSER_HEXCOLOR; }
  | '#' maybe_space { $$.id = CSSValueInvalid; $$.string = CSSParserString(); $$.unit = CSSPrimitiveValue::CSS_PARSER_HEXCOLOR; } /* Handle error case: "color: #;" */
  /* FIXME: according to the specs a function can have a unary_operator in front. I know no case where this makes sense */
  | function maybe_space
  | calc_function maybe_space
  | min_or_max_function maybe_space
  | '%' maybe_space { /* Handle width: %; */
      $$.id = CSSValueInvalid; $$.unit = 0;
  }
#if ENABLE_CSS_GRID_LAYOUT
  | track_names_list maybe_space
#endif
  ;

unary_term:
  INTEGER { $$.id = CSSValueInvalid; $$.isInt = true; $$.fValue = $1; $$.unit = CSSPrimitiveValue::CSS_NUMBER; }
  | FLOATTOKEN { $$.id = CSSValueInvalid; $$.isInt = false; $$.fValue = $1; $$.unit = CSSPrimitiveValue::CSS_NUMBER; }
  | PERCENTAGE { $$.id = CSSValueInvalid; $$.fValue = $1; $$.unit = CSSPrimitiveValue::CSS_PERCENTAGE; }
  | PXS { $$.id = CSSValueInvalid; $$.fValue = $1; $$.unit = CSSPrimitiveValue::CSS_PX; }
  | CMS { $$.id = CSSValueInvalid; $$.fValue = $1; $$.unit = CSSPrimitiveValue::CSS_CM; }
  | MMS { $$.id = CSSValueInvalid; $$.fValue = $1; $$.unit = CSSPrimitiveValue::CSS_MM; }
  | INS { $$.id = CSSValueInvalid; $$.fValue = $1; $$.unit = CSSPrimitiveValue::CSS_IN; }
  | PTS { $$.id = CSSValueInvalid; $$.fValue = $1; $$.unit = CSSPrimitiveValue::CSS_PT; }
  | PCS { $$.id = CSSValueInvalid; $$.fValue = $1; $$.unit = CSSPrimitiveValue::CSS_PC; }
  | DEGS { $$.id = CSSValueInvalid; $$.fValue = $1; $$.unit = CSSPrimitiveValue::CSS_DEG; }
  | RADS { $$.id = CSSValueInvalid; $$.fValue = $1; $$.unit = CSSPrimitiveValue::CSS_RAD; }
  | GRADS { $$.id = CSSValueInvalid; $$.fValue = $1; $$.unit = CSSPrimitiveValue::CSS_GRAD; }
  | TURNS { $$.id = CSSValueInvalid; $$.fValue = $1; $$.unit = CSSPrimitiveValue::CSS_TURN; }
  | MSECS { $$.id = CSSValueInvalid; $$.fValue = $1; $$.unit = CSSPrimitiveValue::CSS_MS; }
  | SECS { $$.id = CSSValueInvalid; $$.fValue = $1; $$.unit = CSSPrimitiveValue::CSS_S; }
  | HERTZ { $$.id = CSSValueInvalid; $$.fValue = $1; $$.unit = CSSPrimitiveValue::CSS_HZ; }
  | KHERTZ { $$.id = CSSValueInvalid; $$.fValue = $1; $$.unit = CSSPrimitiveValue::CSS_KHZ; }
  | EMS { $$.id = CSSValueInvalid; $$.fValue = $1; $$.unit = CSSPrimitiveValue::CSS_EMS; }
  | QEMS { $$.id = CSSValueInvalid; $$.fValue = $1; $$.unit = CSSParserValue::Q_EMS; }
  | EXS { $$.id = CSSValueInvalid; $$.fValue = $1; $$.unit = CSSPrimitiveValue::CSS_EXS; }
  | REMS {
      $$.id = CSSValueInvalid;
      $$.fValue = $1;
      $$.unit = CSSPrimitiveValue::CSS_REMS;
      if (parser->m_styleSheet)
          parser->m_styleSheet->parserSetUsesRemUnits(true);
  }
  | CHS { $$.id = CSSValueInvalid; $$.fValue = $1; $$.unit = CSSPrimitiveValue::CSS_CHS; }
  | VW { $$.id = CSSValueInvalid; $$.fValue = $1; $$.unit = CSSPrimitiveValue::CSS_VW; }
  | VH { $$.id = CSSValueInvalid; $$.fValue = $1; $$.unit = CSSPrimitiveValue::CSS_VH; }
  | VMIN { $$.id = CSSValueInvalid; $$.fValue = $1; $$.unit = CSSPrimitiveValue::CSS_VMIN; }
  | VMAX { $$.id = CSSValueInvalid; $$.fValue = $1; $$.unit = CSSPrimitiveValue::CSS_VMAX; }
  | DPPX { $$.id = CSSValueInvalid; $$.fValue = $1; $$.unit = CSSPrimitiveValue::CSS_DPPX; }
  | DPI { $$.id = CSSValueInvalid; $$.fValue = $1; $$.unit = CSSPrimitiveValue::CSS_DPI; }
  | DPCM { $$.id = CSSValueInvalid; $$.fValue = $1; $$.unit = CSSPrimitiveValue::CSS_DPCM; }
  | FR { $$.id = CSSValueInvalid; $$.fValue = $1; $$.unit = CSSPrimitiveValue::CSS_FR; }
  ;

function:
    FUNCTION maybe_space expr closing_parenthesis {
        CSSParserFunction* f = new CSSParserFunction;
        f->name = $1;
        f->args = std::unique_ptr<CSSParserValueList>($3);
        $$.id = CSSValueInvalid;
        $$.unit = CSSParserValue::Function;
        $$.function = f;
    } |
    FUNCTION maybe_space closing_parenthesis {
        CSSParserFunction* f = new CSSParserFunction;
        f->name = $1;
        f->args = std::unique_ptr<CSSParserValueList>(new CSSParserValueList);
        $$.id = CSSValueInvalid;
        $$.unit = CSSParserValue::Function;
        $$.function = f;
    } |
    FUNCTION maybe_space expr_recovery closing_parenthesis {
        CSSParserFunction* f = new CSSParserFunction;
        f->name = $1;
        f->args = nullptr;
        $$.id = CSSValueInvalid;
        $$.unit = CSSParserValue::Function;
        $$.function = f;
  }
  ;

calc_func_term:
  unary_term
  | unary_operator unary_term { $$ = $2; $$.fValue *= $1; }
  ;

/*
 * The grammar requires spaces around binary ‘+’ and ‘-’ operators.
 * The '*' and '/' operators do not require spaces.
 * http://www.w3.org/TR/css3-values/#calc-syntax
 */
calc_func_operator:
    space '+' space {
        $$ = '+';
    }
    | space '-' space {
        $$ = '-';
    }
    | calc_maybe_space '*' maybe_space {
        $$ = '*';
    }
    | calc_maybe_space '/' maybe_space {
        $$ = '/';
    }
  ;

calc_maybe_space: /* empty */ | WHITESPACE ;

calc_func_paren_expr:
    '(' maybe_space calc_func_expr calc_maybe_space closing_parenthesis {
        $$ = nullptr;
        if ($3) {
            $$ = $3;
            CSSParserValue v;
            v.id = CSSValueInvalid;
            v.unit = CSSParserValue::Operator;
            v.iValue = '(';
            $$->insertValueAt(0, v);
            v.iValue = ')';
            $$->addValue(v);
        }
    }
  ;

calc_func_expr: valid_calc_func_expr | valid_calc_func_expr expr_recovery { $$ = nullptr; delete $1; } ;

valid_calc_func_expr:
    calc_func_term {
        $$ = new CSSParserValueList;
        $$->addValue($1);
    }
    | calc_func_expr calc_func_operator calc_func_term {
        std::unique_ptr<CSSParserValueList> expression($1);
        $$ = nullptr;
        if (expression && $2) {
            $$ = expression.release();
            CSSParserValue v;
            v.id = CSSValueInvalid;
            v.unit = CSSParserValue::Operator;
            v.iValue = $2;
            $$->addValue(v);
            $$->addValue($3);
        } else {
            destroy($3);
        }

    }
    | calc_func_expr calc_func_operator calc_func_paren_expr {
        std::unique_ptr<CSSParserValueList> left($1);
        std::unique_ptr<CSSParserValueList> right($3);
        $$ = nullptr;
        if (left && $2 && right) {
            CSSParserValue v;
            v.id = CSSValueInvalid;
            v.unit = CSSParserValue::Operator;
            v.iValue = $2;
            left->addValue(v);
            left->extend(*right);
            $$ = left.release();
        }
    }
    | calc_func_paren_expr
  ;

calc_func_expr_list:
    calc_func_expr calc_maybe_space
    | calc_func_expr_list ',' maybe_space calc_func_expr calc_maybe_space {
        std::unique_ptr<CSSParserValueList> list($1);
        std::unique_ptr<CSSParserValueList> expression($4);
        $$ = nullptr;
        if (list && expression) {
            $$ = list.release();
            CSSParserValue v;
            v.id = CSSValueInvalid;
            v.unit = CSSParserValue::Operator;
            v.iValue = ',';
            $$->addValue(v);
            $$->extend(*expression);
        }
    }
  ;

calc_function:
    CALCFUNCTION maybe_space calc_func_expr calc_maybe_space closing_parenthesis {
        CSSParserFunction* f = new CSSParserFunction;
        f->name = $1;
        f->args = std::unique_ptr<CSSParserValueList>($3);
        $$.id = CSSValueInvalid;
        $$.unit = CSSParserValue::Function;
        $$.function = f;
    }
    | CALCFUNCTION maybe_space expr_recovery closing_parenthesis {
        $$.id = CSSValueInvalid;
        $$.unit = 0;
        YYERROR;
    }
    ;


min_or_max: MINFUNCTION | MAXFUNCTION ;

min_or_max_function:
    min_or_max maybe_space calc_func_expr_list closing_parenthesis {
        CSSParserFunction* f = new CSSParserFunction;
        f->name = $1;
        f->args = std::unique_ptr<CSSParserValueList>($3);
        $$.id = CSSValueInvalid;
        $$.unit = CSSParserValue::Function;
        $$.function = f;
    } 
    | min_or_max maybe_space expr_recovery closing_parenthesis {
        $$.id = CSSValueInvalid;
        $$.unit = 0;
        YYERROR;
    }
    ;

/* error handling rules */

save_block: closing_brace | error closing_brace ;

invalid_at: ATKEYWORD error invalid_block | ATKEYWORD error ';' ;

invalid_rule: error invalid_block ;

invalid_block: '{' error_recovery closing_brace { parser->invalidBlockHit(); } ;

invalid_square_brackets_block: '[' error_recovery ']' | '[' error_recovery TOKEN_EOF ;

invalid_parentheses_block: opening_parenthesis error_recovery closing_parenthesis;

opening_parenthesis:
    '(' | FUNCTION | CALCFUNCTION | MINFUNCTION | MAXFUNCTION | ANYFUNCTION | NOTFUNCTION
#if ENABLE_VIDEO_TRACK
    | CUEFUNCTION
#endif
    ;

error_location: { $$ = parser->currentLocation(); } ;

error_recovery:
    /* empty */
  | error_recovery error
  | error_recovery invalid_block
  | error_recovery invalid_square_brackets_block
  | error_recovery invalid_parentheses_block
    ;

%%