Pārlūkot izejas kodu

Add support for :nth-child formula syntax

Taddeus Kroes 10 gadi atpakaļ
vecāks
revīzija
f7c7c13cba
8 mainītis faili ar 141 papildinājumiem un 25 dzēšanām
  1. 13 0
      lexer.mll
  2. 1 1
      main.ml
  3. 25 7
      parser.mly
  4. 25 4
      simple.ml
  5. 23 2
      stringify.ml
  6. 31 0
      test/pseudo-class.css
  7. 9 7
      types.ml
  8. 14 4
      util.ml

+ 13 - 0
lexer.mll

@@ -114,12 +114,25 @@ rule token = parse
   | (s | comment)* s comment* O R   comment* s (s | comment)*
   { advance_pos lexbuf; WS_OR }
 
+  | (['-' '+'] as a_sign)? (['0'-'9']* as a) N
+    (w (['-' '+'] as b_sign) w (['0'-'9']+ as b))?
+  {
+    let a = if a = "" then 1 else int_of_string a in
+    let b = match b with None -> 0 | Some n -> int_of_string n in
+    let apply_sign n = function Some '-' -> -n | _ -> n in
+    let a = apply_sign a a_sign in
+    let b = apply_sign b b_sign in
+    FORMULA (a, b)
+  }
+
   | O N L Y             { ONLY }
   | N O T               { NOT }
   | A N D               { AND }
   (*| O R                 { OR } removed in favor of WS_OR *)
   | F R O M             { FROM }
   | T O                 { TO }
+  | O D D               { ODD }
+  | E V E N             { EVEN }
 
   | ident as id         { IDENT id }
 

+ 1 - 1
main.ml

@@ -27,7 +27,7 @@ let parse_args () =
      Optimization flags (if none are specified, all are enabled):\n \
      -w, --whitespace  Eliminate unnecessary whitespaces (has the greatest \
                        effect, omit for pretty-printing)\n \
-     -c, --simple      Shorten colors and font weights\n \
+     -c, --simple      Shorten colors, font weights and nth-child\n \
      -s, --shorthands  Generate shorthand properties\n \
      -d, --duplicates  Prune duplicate properties (WARNING: may affect \
                        cross-browser hacks)\n \

+ 25 - 7
parser.mly

@@ -62,7 +62,8 @@
 %token <string> URI FUNCTION
 %token LPAREN RPAREN LBRACE RBRACE LBRACK RBRACK SEMICOL COLON DOUBLE_COLON
 %token COMMA DOT PLUS MINUS SLASH STAR ONLY AND (*OR*) NOT FROM TO EOF
-%token WS_AND WS_OR
+%token WS_AND WS_OR ODD EVEN
+%token <int * int> FORMULA
 
 (* Start symbol *)
 %type <Types.stylesheet> stylesheet
@@ -241,10 +242,8 @@ simple_selector:
   | addons=element_addon+
   { append_addons No_element addons }
 %inline element_addon:
-  | id=HASH       { `Id id }
-  | addon=cls
-  | addon=attrib
-  | addon=pseudo  { addon }
+  | id=HASH  { `Id id }
+  | addon=cls | addon=attrib | addon=pseudo_class  { addon }
 element_name:
   | tag=IDENT  { Element (String.lowercase tag) }
   | STAR       { All_elements }
@@ -259,13 +258,32 @@ attrib:
 %inline rel_value:
   | S* id=IDENT S*  { Ident id }
   | S* s=STRING S*  { Strlit s }
-pseudo:
+pseudo_class:
   | COLON id=IDENT
   { `Pseudo_class (String.lowercase id, None) }
-  | COLON f=FUNCTION args=wslist(COMMA, simple_selector) RPAREN
+  | COLON f=FUNCTION args=wslist(COMMA, function_arg) RPAREN
   { `Pseudo_class (String.lowercase f, Some args) }
   | DOUBLE_COLON id=IDENT
   { `Pseudo_element (String.lowercase id) }
+function_arg:
+  | s=selector
+  { Nested_selector s }
+  | EVEN
+  { Nth Even }
+  | ODD
+  { Nth Odd }
+  | f=FORMULA
+  { let a, b = f in Nth (Formula (a, b)) }
+  | sign=sign? n=NUMBER
+  {
+    if is_int n then begin
+      let b = int_of_float (match sign with Some MINUS -> -.n | _ -> n) in
+      Nth (Formula (0, b))
+    end else
+      raise (Syntax_error ("unexpected float '" ^ string_of_float n ^
+                           "', expected int"))
+  }
+%inline sign: PLUS { PLUS } | MINUS { MINUS }
 
 declaration:
   | name=property S* COLON S* value=expr important=boption(ig2(IMPORTANT_SYM, S*))

+ 25 - 4
simple.ml

@@ -1,6 +1,7 @@
+open Str
 open Types
 
-let hex6 = Str.regexp "\\([0-9a-f]\\)\\1\\([0-9a-f]\\)\\2\\([0-9a-f]\\)\\3"
+let hex6 = regexp "\\([0-9a-f]\\)\\1\\([0-9a-f]\\)\\2\\([0-9a-f]\\)\\3"
 
 let is_num = function
   | Number (n, (None | Some "%")) -> true
@@ -14,8 +15,8 @@ let clip = function
 
 let rec shorten_expr = function
   (* #aabbcc -> #abc *)
-  | Hexcolor h when Str.string_match hex6 h 0 ->
-    let gr n = Str.matched_group n h in
+  | Hexcolor h when string_match hex6 h 0 ->
+    let gr n = matched_group n h in
     shorten_expr (Hexcolor (gr 1 ^ gr 2 ^ gr 3))
 
   (* rgb(r,g,b) -> #rrggbb *)
@@ -50,12 +51,32 @@ let shorten_font_weight = function
   | Ident "bold"   -> Number (700.0, None)
   | v -> v
 
+let shorten_nth = function
+  (* even -> 2n *)
+  | Even                  -> Formula (2, 0)
+  (* 2n+1 | 2n-1 -> odd *)
+  | Formula (2, (1 | -1)) -> Odd
+  (* -n+1 -> 1 *)
+  | Formula (-1, 1)       -> Formula (0, 1)
+  | v -> v
+
 let compress =
   Util.transform_stylesheet begin function
-    | Expr value -> Expr (shorten_expr value)
+    | Expr value ->
+      Expr (shorten_expr value)
     | Declaration ("font-weight", value, imp) ->
       Declaration ("font-weight", shorten_font_weight value, imp)
     | Declaration (("border" | "outline") as name, Ident "none", imp) ->
       Declaration (name, Number (0., None), imp)
+    | Pseudo_class_arg (Nth nth) ->
+      Pseudo_class_arg (Nth (shorten_nth nth))
+    (* Remove rulesets with :nth-child(0) selector *)
+    | Selector (Pseudo_class (_, cls, Some [Nth (Formula (0, 0))]))
+      when string_match (regexp "nth-") cls 0 ->
+      Clear
+    (* Remove rulesets with no selectors or declarations *)
+    | Statement (Ruleset ([], _))
+    | Statement (Ruleset (_, [])) ->
+      Clear
     | v -> v
   end

+ 23 - 2
stringify.ml

@@ -17,7 +17,7 @@ let rec cat sep fn = function
  *)
 
 let string_of_num n =
-  if float_of_int (int_of_float n) = n
+  if is_int n
     then string_of_int (int_of_float n)
     else string_of_float n
 
@@ -57,7 +57,7 @@ let rec stringify_selector w selector =
   | Pseudo_class (base, cls, None) ->
     str base ^ ":" ^ cls
   | Pseudo_class (base, fn, Some args) ->
-    str base ^ ":" ^ fn ^ "(" ^ cat ("," ^ w) str args ^ ")"
+    str base ^ ":" ^ fn ^ "(" ^ cat ("," ^ w) (stringify_arg w) args ^ ")"
   | Pseudo_element (base, elem) ->
     str base ^ "::" ^ elem
   | Combinator (left, " ", right) ->
@@ -65,6 +65,27 @@ let rec stringify_selector w selector =
   | Combinator (left, com, right) ->
     str left ^ w ^ com ^ w ^ str right
 
+and stringify_arg w = function
+  | Nested_selector s -> stringify_selector w s
+  | Nth nth -> stringify_nth w nth
+
+and stringify_nth w = function
+  | Even           -> "even"
+  | Odd            -> "odd"
+  | Formula (0, b) -> string_of_int b
+  | Formula (a, b) ->
+    begin
+      match a with
+      | 1  -> "n"
+      | -1 -> "-n"
+      | a  -> string_of_int a ^ "n"
+    end ^ begin
+      match b with
+      | 0            -> ""
+      | b when b < 0 -> w ^ "-" ^ w ^ string_of_int (-b)
+      | b            -> w ^ "+" ^ w ^ string_of_int b
+    end
+
 let string_of_selector = stringify_selector " "
 
 let string_of_media_expr = function

+ 31 - 0
test/pseudo-class.css

@@ -0,0 +1,31 @@
+a:link {
+    margin: 0;
+}
+
+div:nth-child(even) {    /* div:nth-child(2n) */
+    margin: 0;
+}
+div:nth-child(2n + 1) {  /* div:nth-child(odd) */
+    margin: 1;
+}
+div:nth-child(-1n +2) {  /* div:nth-child(-n+2) */
+    margin: 2;
+}
+div:nth-child(0n+ 1) {   /* div:nth-child(1) */
+    margin: 3;
+}
+div:nth-child(0) {       /* removed */
+    margin: 4;
+}
+div:nth-child(2n) {      /* div:nth-child(2n) */
+    margin: 5;
+}
+div:nth-child(-n+0) {    /* div:nth-child(-n) */
+    margin: 6;
+}
+div:nth-child(+3n) {     /* div:nth-child(3n) */
+    margin: 7;
+}
+div:nth-child(-n+1) {    /* div:nth-child(1) */
+    margin: 8;
+}

+ 9 - 7
types.ml

@@ -18,16 +18,17 @@ type selector =
   | Element of string
   | Id of selector * string
   | Class of selector * string
-  | Pseudo_class of selector * string * selector list option
+  | Pseudo_class of selector * string * pseudo_class_arg list option
   | Pseudo_element of selector * string
   | Attribute of selector * string * (string * expr) option
   | Combinator of selector * string * selector
-
-  (*
-type selector =
-  | Simple of string
-  | Combinator of selector * string * selector
-  *)
+and pseudo_class_arg =
+  | Nested_selector of selector
+  | Nth of nth
+and nth =
+  | Even | Odd
+  (* a and b in an+b *)
+  | Formula of int * int
 
 type media_expr = string * expr option
 type media_query = string option * string option * media_expr list
@@ -73,6 +74,7 @@ type box =
   | Expr of expr
   | Declaration of declaration
   | Selector of selector
+  | Pseudo_class_arg of pseudo_class_arg
   | Media_expr of media_expr
   | Media_query of media_query
   | Descriptor_declaration of descriptor_declaration

+ 14 - 4
util.ml

@@ -142,7 +142,7 @@ let transform_stylesheet f stylesheet =
     | Pseudo_class (base, cls, None) ->
       f (Selector (Pseudo_class (expect_selector base, cls, None)))
     | Pseudo_class (base, fn, Some args) ->
-      let args = trav_all_selector args in
+      let args = trav_all_pseudo_class_arg args in
       f (Selector (Pseudo_class (expect_selector base, fn, Some args)))
     | Pseudo_element (base, elem) ->
       f (Selector (Pseudo_element (expect_selector base, elem)))
@@ -151,7 +151,14 @@ let transform_stylesheet f stylesheet =
       let right = expect_selector right in
       f (Selector (Combinator (left, com, right)))
   and EXPECT(selector, Selector)
-  and TRAV_ALL(selector, Selector) in
+  and TRAV_ALL(selector, Selector)
+
+  and trav_pseudo_class_arg = function
+    | Nested_selector s ->
+      f (Pseudo_class_arg (Nested_selector (expect_selector s)))
+    | Nth _ as elem ->
+      f (Pseudo_class_arg elem)
+  and TRAV_ALL(pseudo_class_arg, Pseudo_class_arg) in
 
   let trav_media_expr = function
     | (_, None) as value ->
@@ -230,11 +237,11 @@ let transform_stylesheet f stylesheet =
 
   trav_all_statement stylesheet
 
-(* Expression identification *)
+(** Expression identification *)
 
 let is_color = Color_names.is_color
 
-(* Sorting declarations *)
+(** Sorting declarations *)
 
 let sort_stylesheet =
   transform_stylesheet begin function
@@ -247,3 +254,6 @@ let sort_stylesheet =
       Statement (Ruleset (selectors, List.stable_sort cmp decls))
     | v -> v
   end
+
+(** Misc *)
+let is_int n = float_of_int (int_of_float n) = n