/*

* CSS parser based on the grammar described at http://www.w3.org/TR/CSS2/grammar.html.
*
* The parser builds a tree representing the parsed CSS, composed of basic
* JavaScript values, arrays and objects (basically JSON). It can be easily
* used by various CSS processors, transformers, etc.
*
* Note that the parser does not handle errors in CSS according to the
* specification -- many errors which it should recover from (e.g. malformed
* declarations or unexpected end of stylesheet) are simply fatal. This is a
* result of straightforward rewrite of the CSS grammar to PEG.js and it should
* be fixed sometimes.
*/

/* ===== Syntactical Elements ===== */

start

= stylesheet:stylesheet comment* { return stylesheet; }

stylesheet

= charset:(CHARSET_SYM STRING ";")? (S / CDO / CDC)*
  imports:(import (CDO S* / CDC S*)*)*
  rules:((ruleset / media / page) (CDO S* / CDC S*)*)* {
    var importsConverted = [];
    for (var i = 0; i < imports.length; i++) {
      importsConverted.push(imports[i][0]);
    }

    var rulesConverted = [];
    for (i = 0; i < rules.length; i++) {
      rulesConverted.push(rules[i][0]);
    }

    return {
      type:    "stylesheet",
      charset: charset !== null ? charset[1] : null,
      imports: importsConverted,
      rules:   rulesConverted
    };
  }

import

= IMPORT_SYM S* href:(STRING / URI) S* media:media_list? ";" S* {
    return {
      type:  "import_rule",
      href:  href,
      media: media !== null ? media : []
    };
  }

media

= MEDIA_SYM S* media:media_list "{" S* rules:ruleset* "}" S* {
    return {
      type:  "media_rule",
      media: media,
      rules: rules
    };
  }

media_list

= head:medium tail:("," S* medium)* {
    var result = [head];
    for (var i = 0; i < tail.length; i++) {
      result.push(tail[i][2]);
    }
    return result;
  }

medium

= ident:IDENT S* { return ident; }

page

= PAGE_SYM S* qualifier:pseudo_page?
  "{" S*
  declarationsHead:declaration?
  declarationsTail:(";" S* declaration?)*
  "}" S* {
    var declarations = declarationsHead !== null ? [declarationsHead] : [];
    for (var i = 0; i < declarationsTail.length; i++) {
      if (declarationsTail[i][2] !== null) {
        declarations.push(declarationsTail[i][2]);
      }
    }

    return {
      type:         "page_rule",
      qualifier:    qualifier,
      declarations: declarations
    };
  }

pseudo_page

= ":" ident:IDENT S* { return ident; }

operator

= "/" S* { return "/"; }
/ "," S* { return ","; }

combinator

= "+" S* { return "+"; }
/ ">" S* { return ">"; }

unary_operator

= "+"
/ "-"

property

= ident:IDENT S* { return ident; }

ruleset

= selectorsHead:selector
  selectorsTail:("," S* selector)*
  "{" S*
  declarationsHead:declaration?
  declarationsTail:(";" S* declaration?)*
  "}" S* {
    var selectors = [selectorsHead];
    for (var i = 0; i < selectorsTail.length; i++) {
      selectors.push(selectorsTail[i][2]);
    }

    var declarations = declarationsHead !== null ? [declarationsHead] : [];
    for (i = 0; i < declarationsTail.length; i++) {
      if (declarationsTail[i][2] !== null) {
        declarations.push(declarationsTail[i][2]);
      }
    }

    return {
      type:         "ruleset",
      selectors:    selectors,
      declarations: declarations
    };
  }

selector

= left:simple_selector S* combinator:combinator right:selector {
    return {
      type:       "selector",
      combinator: combinator,
      left:       left,
      right:      right
    };
  }
/ left:simple_selector S* right:selector {
    return {
      type:       "selector",
      combinator: " ",
      left:       left,
      right:      right
    };
  }
/ selector:simple_selector S* { return selector; }

simple_selector

= element:element_name
  qualifiers:(
      id:HASH { return { type: "ID selector", id: id.substr(1) }; }
    / class
    / attrib
    / pseudo
  )* {
    return {
      type:       "simple_selector",
      element:    element,
      qualifiers: qualifiers
    };
  }
/ qualifiers:(
      id:HASH { return { type: "ID selector", id: id.substr(1) }; }
    / class
    / attrib
    / pseudo
  )+ {
    return {
      type:       "simple_selector",
      element:    "*",
      qualifiers: qualifiers
    };
  }

class

= "." class_:IDENT { return { type: "class_selector", "class": class_ }; }

element_name

= IDENT / '*'

attrib

= "[" S*
  attribute:IDENT S*
  operatorAndValue:(
    ('=' / INCLUDES / DASHMATCH) S*
    (IDENT / STRING) S*
  )?
  "]" {
    return {
      type:      "attribute_selector",
      attribute: attribute,
      operator:  operatorAndValue !== null ? operatorAndValue[0] : null,
      value:     operatorAndValue !== null ? operatorAndValue[2] : null
    };
  }

pseudo

= ":"
  value:(
      name:FUNCTION S* params:(IDENT S*)? ")" {
        return {
          type:   "function",
          name:   name,
          params: params !== null ? [params[0]] : []
        };
      }
    / IDENT
  ) {
    /*
     * The returned object has somewhat vague property names and values because
     * the rule matches both pseudo-classes and pseudo-elements (they look the
     * same at the syntactic level).
     */
    return {
      type:  "pseudo_selector",
      value: value
    };
  }

declaration

= property:property ":" S* expression:expr important:prio? {
    return {
      type:       "declaration",
      property:   property,
      expression: expression,
      important:  important !== null ? true : false
    };
  }

prio

= IMPORTANT_SYM S*

expr

= head:term tail:(operator? term)* {
    var result = head;
    for (var i = 0; i < tail.length; i++) {
      result = {
        type:     "expression",
        operator: tail[i][0],
        left:     result,
        right:    tail[i][1]
      };
    }
    return result;
  }

term

= operator:unary_operator?
  value:(
      EMS S*
    / EXS S*
    / LENGTH S*
    / ANGLE S*
    / TIME S*
    / FREQ S*
    / PERCENTAGE S*
    / NUMBER S*
  ) {
    return {
      type: "value",
      value: (operator !== null ? operator : "") + value[0]
    };
  }
/ value:URI S*    { return { type: "uri",    value: value               }; }
/ function
/ hexcolor
/ value:STRING S* { return { type: "string", value: value               }; }
/ value:IDENT S*  { return { type: "ident",  value: value               }; }

function

= name:FUNCTION S* params:expr ")" S* {
    return {
      type:   "function",
      name:   name,
      params: params
    };
  }

hexcolor

= value:HASH S* { return { type: "hexcolor", value: value}; }

/* ===== Lexical Elements ===== */

/* Macros */

h

= [0-9a-fA-F]

nonascii

= [\x80-\xFF]

unicode

= "\\" digits:$(h h? h? h? h? h?) ("\r\n" / [ \t\r\n\f])? {
    return String.fromCharCode(parseInt(digits, 16));
  }

escape

= unicode
/ "\\" char_:[^\r\n\f0-9a-fA-F] { return char_; }

nmstart

= [_a-zA-Z]
/ nonascii
/ escape

nmchar

= [_a-zA-Z0-9-]
/ nonascii
/ escape

integer

= parts:$[0-9]+ { return parts; }

float

= parts:$([0-9]* "." [0-9]+) { return parts; }

string1

= '"' chars:([^\n\r\f\\"] / "\\" nl:nl { return nl } / escape)* '"' {
    return chars.join("");
  }

string2

= "'" chars:([^\n\r\f\\'] / "\\" nl:nl { return nl } / escape)* "'" {
    return chars.join("");
  }

comment

= "/*" [^*]* "*"+ ([^/*] [^*]* "*"+)* "/"

ident

= dash:"-"? nmstart:nmstart nmchars:nmchar* {
    return (dash !== null ? dash : "") + nmstart + nmchars.join("");
  }

name

= nmchars:nmchar+ { return nmchars.join(""); }

num

= float
/ integer

string

= string1
/ string2

url

= chars:([!#$%&*-~] / nonascii / escape)* { return chars.join(""); }

s

= [ \t\r\n\f]+

w

= s?

nl

= "\n"
/ "\r\n"
/ "\r"
/ "\f"

A

= [aA]
/ "\\" "0"? "0"? "0"? "0"? "41" ("\r\n" / [ \t\r\n\f])? { return "A"; }
/ "\\" "0"? "0"? "0"? "0"? "61" ("\r\n" / [ \t\r\n\f])? { return "a"; }

C

= [cC]
/ "\\" "0"? "0"? "0"? "0"? "43" ("\r\n" / [ \t\r\n\f])? { return "C"; }
/ "\\" "0"? "0"? "0"? "0"? "63" ("\r\n" / [ \t\r\n\f])? { return "c"; }

D

= [dD]
/ "\\" "0"? "0"? "0"? "0"? "44" ("\r\n" / [ \t\r\n\f])? { return "D"; }
/ "\\" "0"? "0"? "0"? "0"? "64" ("\r\n" / [ \t\r\n\f])? { return "d"; }

E

= [eE]
/ "\\" "0"? "0"? "0"? "0"? "45" ("\r\n" / [ \t\r\n\f])? { return "E"; }
/ "\\" "0"? "0"? "0"? "0"? "65" ("\r\n" / [ \t\r\n\f])? { return "e"; }

G

= [gG]
/ "\\" "0"? "0"? "0"? "0"? "47" ("\r\n" / [ \t\r\n\f])? { return "G"; }
/ "\\" "0"? "0"? "0"? "0"? "67" ("\r\n" / [ \t\r\n\f])? { return "g"; }
/ "\\" char_:[gG] { return char_; }

H

= h:[hH]
/ "\\" "0"? "0"? "0"? "0"? "48" ("\r\n" / [ \t\r\n\f])? { return "H"; }
/ "\\" "0"? "0"? "0"? "0"? "68" ("\r\n" / [ \t\r\n\f])? { return "h"; }
/ "\\" char_:[hH] { return char_; }

I

= i:[iI]
/ "\\" "0"? "0"? "0"? "0"? "49" ("\r\n" / [ \t\r\n\f])? { return "I"; }
/ "\\" "0"? "0"? "0"? "0"? "69" ("\r\n" / [ \t\r\n\f])? { return "i"; }
/ "\\" char_:[iI] { return char_; }

K

= [kK]
/ "\\" "0"? "0"? "0"? "0"? "4" [bB] ("\r\n" / [ \t\r\n\f])? { return "K"; }
/ "\\" "0"? "0"? "0"? "0"? "6" [bB] ("\r\n" / [ \t\r\n\f])? { return "k"; }
/ "\\" char_:[kK] { return char_; }

L

= [lL]
/ "\\" "0"? "0"? "0"? "0"? "4" [cC] ("\r\n" / [ \t\r\n\f])? { return "L"; }
/ "\\" "0"? "0"? "0"? "0"? "6" [cC] ("\r\n" / [ \t\r\n\f])? { return "l"; }
/ "\\" char_:[lL] { return char_; }

M

= [mM]
/ "\\" "0"? "0"? "0"? "0"? "4" [dD] ("\r\n" / [ \t\r\n\f])? { return "M"; }
/ "\\" "0"? "0"? "0"? "0"? "6" [dD] ("\r\n" / [ \t\r\n\f])? { return "m"; }
/ "\\" char_:[mM] { return char_; }

N

= [nN]
/ "\\" "0"? "0"? "0"? "0"? "4" [eE] ("\r\n" / [ \t\r\n\f])? { return "N"; }
/ "\\" "0"? "0"? "0"? "0"? "6" [eE] ("\r\n" / [ \t\r\n\f])? { return "n"; }
/ "\\" char_:[nN] { return char_; }

O

= [oO]
/ "\\" "0"? "0"? "0"? "0"? "4" [fF] ("\r\n" / [ \t\r\n\f])? { return "O"; }
/ "\\" "0"? "0"? "0"? "0"? "6" [fF] ("\r\n" / [ \t\r\n\f])? { return "o"; }
/ "\\" char_:[oO] { return char_; }

P

= [pP]
/ "\\" "0"? "0"? "0"? "0"? "50" ("\r\n" / [ \t\r\n\f])? { return "P"; }
/ "\\" "0"? "0"? "0"? "0"? "70" ("\r\n" / [ \t\r\n\f])? { return "p"; }
/ "\\" char_:[pP] { return char_; }

R

= [rR]
/ "\\" "0"? "0"? "0"? "0"? "52" ("\r\n" / [ \t\r\n\f])? { return "R"; }
/ "\\" "0"? "0"? "0"? "0"? "72" ("\r\n" / [ \t\r\n\f])? { return "r"; }
/ "\\" char_:[rR] { return char_; }

S_

= [sS]
/ "\\" "0"? "0"? "0"? "0"? "53" ("\r\n" / [ \t\r\n\f])? { return "S"; }
/ "\\" "0"? "0"? "0"? "0"? "73" ("\r\n" / [ \t\r\n\f])? { return "s"; }
/ "\\" char_:[sS] { return char_; }

T

= [tT]
/ "\\" "0"? "0"? "0"? "0"? "54" ("\r\n" / [ \t\r\n\f])? { return "T"; }
/ "\\" "0"? "0"? "0"? "0"? "74" ("\r\n" / [ \t\r\n\f])? { return "t"; }
/ "\\" char_:[tT] { return char_; }

U

= [uU]
/ "\\" "0"? "0"? "0"? "0"? "55" ("\r\n" / [ \t\r\n\f])? { return "U"; }
/ "\\" "0"? "0"? "0"? "0"? "75" ("\r\n" / [ \t\r\n\f])? { return "u"; }
/ "\\" char_:[uU] { return char_; }

X

= [xX]
/ "\\" "0"? "0"? "0"? "0"? "58" ("\r\n" / [ \t\r\n\f])? { return "X"; }
/ "\\" "0"? "0"? "0"? "0"? "78" ("\r\n" / [ \t\r\n\f])? { return "x"; }
/ "\\" char_:[xX] { return char_; }

Z

= [zZ]
/ "\\" "0"? "0"? "0"? "0"? "5" [aA] ("\r\n" / [ \t\r\n\f])? { return "Z"; }
/ "\\" "0"? "0"? "0"? "0"? "7" [aA] ("\r\n" / [ \t\r\n\f])? { return "z"; }
/ "\\" char_:[zZ] { return char_; }

/* Tokens */

S “whitespace”

= comment* s

CDO “<!–”

= comment* "<!--"

CDC “–>”

= comment* "-->"

INCLUDES “~=”

= comment* "~="

DASHMATCH “|=”

= comment* "|="

STRING “string”

= comment* string:string { return string; }

IDENT “identifier”

= comment* ident:ident { return ident; }

HASH “hash”

= comment* "#" name:name { return "#" + name; }

IMPORT_SYM “@import”

= comment* "@" I M P O R T

PAGE_SYM “@page”

= comment* "@" P A G E

MEDIA_SYM “@media”

= comment* "@" M E D I A

CHARSET_SYM “@charset”

= comment* "@charset "

/* Note: We replace “w” with “s” here to avoid infinite recursion. */ IMPORTANT_SYM “!important”

= comment* "!" (s / comment)* I M P O R T A N T { return "!important"; }

EMS “length”

= comment* num:num e:E m:M { return num + e + m; }

EXS “length”

= comment* num:num e:E x:X { return num + e + x; }

LENGTH “length”

= comment* num:num unit:(P X / C M / M M / I N / P T / P C) {
    return num + unit.join("");
  }

ANGLE “angle”

= comment* num:num unit:(D E G / R A D / G R A D) {
    return num + unit.join("");
  }

TIME “time”

= comment* num:num unit:(m:M s:S_ { return m + s; } / S_) {
    return num + unit;
  }

FREQ “frequency”

= comment* num:num unit:(H Z / K H Z) { return num + unit.join(""); }

DIMENSION “dimension”

= comment* num:num unit:ident { return num + unit; }

PERCENTAGE “percentage”

= comment* parts:$(num "%") { return parts; }

NUMBER “number”

= comment* num:num { return num; }

URI “uri”

= comment* U R L "(" w value:(string / url) w ")" { return value; }

FUNCTION “function”

= comment* name:ident "(" { return name; }