Selaa lähdekoodia

Added support for @font-face, @namespace, @keyframes + some general lexer/parser improvements

Taddeus Kroes 11 vuotta sitten
vanhempi
sitoutus
8bc10a4826
5 muutettua tiedostoa jossa 129 lisäystä ja 52 poistoa
  1. 44 10
      lexer.mll
  2. 6 4
      main.ml
  3. 46 26
      parser.mly
  4. 25 9
      stringify.ml
  5. 8 3
      types.ml

+ 44 - 10
lexer.mll

@@ -44,6 +44,34 @@ let name        = nmchar+
 let num         = ['0'-'9']+ | ['0'-'9']*'.'['0'-'9']+
 let url         = (['!' '#' '$' '%' '&' '*'-'~'] | nonascii | escape)*
 
+let A = ['a' 'A']
+let B = ['b' 'B']
+let C = ['c' 'C']
+let D = ['d' 'D']
+let E = ['e' 'E']
+let F = ['f' 'F']
+let G = ['g' 'G']
+let H = ['h' 'H']
+let I = ['i' 'I']
+let J = ['j' 'J']
+let K = ['k' 'K']
+let L = ['l' 'L']
+let M = ['m' 'M']
+let N = ['n' 'N']
+let O = ['o' 'O']
+let P = ['p' 'P']
+let Q = ['q' 'Q']
+let R = ['r' 'R']
+let S = ['s' 'S']
+let T = ['t' 'T']
+let U = ['u' 'U']
+let V = ['v' 'V']
+let W = ['w' 'W']
+let X = ['x' 'X']
+let Y = ['y' 'Y']
+let Z = ['z' 'Z']
+
+
 rule token = parse
   | s                   { S }
 
@@ -60,21 +88,27 @@ rule token = parse
 
   | '#' (name as nm)    { HASH nm }
 
-  | "@import"           { IMPORT_SYM }
-  | "@page"             { PAGE_SYM }
-  | "@media"            { MEDIA_SYM }
-  | "@charset"          { CHARSET_SYM }
+  | '@' I M P O R T          { IMPORT_SYM }
+  | '@' P A G E              { PAGE_SYM }
+  | '@' M E D I A            { MEDIA_SYM }
+  | "@charset "              { CHARSET_SYM }
+  | '@' F O N T '-' F A C E  { FONT_FACE_SYM }
+  | '@' N A M E S P A C E    { NAMESPACE_SYM }
+  | '@' K E Y F R A M E S    { KEYFRAMES_SYM }
 
-  | "only"              { ONLY }
-  | "not"               { NOT }
-  | "and"               { AND }
+  | O N L Y             { ONLY }
+  | N O T               { NOT }
+  | A N D               { AND }
+  | F R O M             { FROM }
+  | T O                 { TO }
 
   | ident as id         { IDENT id }
 
-  | '!' (w | comment)* "important"  { IMPORTANT_SYM }
+  | '!' (w | comment)* I M P O R T A N T  { IMPORTANT_SYM }
 
-  | (num as n) ("em"|"ex"|"px"|"cm"|"mm"|"in"|"pt"|"pc"|"deg"|"rad"|"grad"|
-                "ms"|"s"|"hz"|"khz"|"%"|"dpi"|"dpcm"|ident as u)
+  |  (num as n) '%'     { PERCENTAGE (float_of_string n) }
+  |  (num as n) (E M | E X | P X | C M | M M | I N | P T | P C | D E G |
+                 G? R A D | M? S | K? H Z | D P (I | C M) | ident as u)
   { UNIT_VALUE (float_of_string n, u) }
   | num as n            { NUMBER (float_of_string n) }
 

+ 6 - 4
main.ml

@@ -32,7 +32,7 @@ let parse_args () =
 let main () =
   let args = parse_args () in
   try
-    let css =
+    let stylesheet =
       match args.infiles with
       | [] ->
         let input = Util.input_buffered stdin 512 in
@@ -42,12 +42,14 @@ let main () =
           | [] -> []
           | filename :: tl ->
             let input = Util.input_all (open_in filename) in
-            let css = Parse.parse_input filename input in
-            css @ loop tl
+            let stylesheet = Parse.parse_input filename input in
+            stylesheet @ loop tl
         in
         loop files
     in
-    Util.print_css css;
+    print_endline (Stringify.string_of_stylesheet stylesheet);
+    print_endline "\n";
+    print_endline (Stringify.minify_stylesheet stylesheet);
     exit 0
   with
   | LocError (loc, msg) ->

+ 46 - 26
parser.mly

@@ -43,13 +43,13 @@
 %}
 
 (* Tokens *)
-%token S CDO CDC IMPORT_SYM PAGE_SYM MEDIA_SYM CHARSET_SYM
-%token IMPORTANT_SYM
-%token <float> NUMBER
+%token S CDO CDC IMPORT_SYM PAGE_SYM MEDIA_SYM CHARSET_SYM FONT_FACE_SYM
+%token NAMESPACE_SYM KEYFRAMES_SYM IMPORTANT_SYM
+%token <float> PERCENTAGE NUMBER
 %token <float * string> UNIT_VALUE
 %token <string> COMBINATOR RELATION STRING IDENT HASH URI FUNCTION
 %token LPAREN RPAREN LBRACE RBRACE LBRACK RBRACK SEMICOL COLON COMMA DOT PLUS
-%token MINUS SLASH STAR ONLY AND NOT EOF
+%token MINUS SLASH STAR ONLY AND NOT FROM TO EOF
 
 (* Start symbol *)
 %type <Types.stylesheet> stylesheet
@@ -66,17 +66,18 @@ cd: CDO S? | CDC S? {}
 stylesheet:
   | charset    = charset? S? cd*
     imports    = terminated(import, cd*)*
+    namespaces = terminated(namespace, cd*)*
     statements = terminated(statement, cd*)*
                  EOF
   { let charset = match charset with None -> [] | Some c -> [c] in
-    charset @ imports @ statements }
+    charset @ imports @ namespaces @ statements }
 
 statement:
-  | s=ruleset | s=media | s=page
+  | s=ruleset | s=media | s=page | s=font_face | s=keyframes
   { s }
 
 charset:
-  | CHARSET_SYM S? name=STRING S? SEMICOL
+  | CHARSET_SYM name=STRING S? SEMICOL
   { Charset name }
 
 import:
@@ -86,6 +87,13 @@ import:
   | str=STRING  { Strlit str }
   | uri=URI     { Uri uri }
 
+namespace:
+  | NAMESPACE_SYM S? prefix=terminated(namespace_prefix, S?)? ns=string_or_uri S? SEMICOL S?
+  { Namespace (prefix, ns) }
+%inline namespace_prefix:
+  | prefix=IDENT
+  { prefix }
+
 media:
   | MEDIA_SYM queries=media_query_list LBRACE S? rulesets=ruleset* RBRACE S?
   { Media (queries, rulesets) }
@@ -113,11 +121,29 @@ media_expr:
 page:
   | PAGE_SYM S? pseudo=pseudo_page? decls=decls_block
   { Page (pseudo, decls) }
-
 pseudo_page:
   | COLON pseudo=IDENT S?
   { pseudo }
 
+font_face:
+  | FONT_FACE_SYM S? LBRACE S? hd=descriptor_declaration?
+    tl=wspreceded(SEMICOL, descriptor_declaration?)* RBRACE S?
+  { Font_face (filter_none (hd :: tl)) }
+descriptor_declaration:
+  | name=property COLON S? value=expr
+  { (name, value) }
+
+keyframes:
+  | KEYFRAMES_SYM S? id=IDENT S? LBRACE S? rules=keyframe_ruleset* RBRACE S?
+  { Keyframes (id, rules) }
+keyframe_ruleset:
+  | selector=keyframe_selector S? decls=decls_block
+  { (selector, decls) }
+keyframe_selector:
+  | FROM          { Ident "from" }
+  | TO            { Ident "to" }
+  | n=PERCENTAGE  { Number (n, Some "%") }
+
 %inline decls_block:
   | LBRACE S? hd=declaration? tl=wspreceded(SEMICOL, declaration?)* RBRACE S?
   { filter_none (hd :: tl) }
@@ -172,8 +198,9 @@ pseudo:
     ":" ^ f ^ "(" ^ arg ^ ")" }
 
 declaration:
-  | name=IDENT S? COLON S? value=expr important=boption(pair(IMPORTANT_SYM, S?))
+  | name=property S? COLON S? value=expr important=boption(pair(IMPORTANT_SYM, S?))
   { (String.lowercase name, value, important) }
+%inline property: name=IDENT  { name }
 
 expr:
   | l=exprl             { concat_terms l }
@@ -187,28 +214,21 @@ expr:
   | COMMA S?            { "," }
 
 term:
-  | op=unary_operator n=NUMBER S?
-  { Unary (op, Number (n, None)) }
-  | op=unary_operator v=UNIT_VALUE S?
-  { let (n, u) = v in Unary (op, Number (n, Some u)) }
-  | n=NUMBER S?
-  { Number (n, None) }
-  | v=UNIT_VALUE S?
-  { let (n, u) = v in Number (n, Some u) }
-  | str=STRING S?
-  { Strlit str }
-  | id=IDENT S?
-  { Ident id }
-  | uri=URI S?
-  { Uri uri }
-  | fn=FUNCTION arg=expr RPAREN S?
-  { Function (fn, arg) }
+  | op=unary_operator v=numval S?   { Unary (op, v) }
+  | v=numval S?                     { v }
+  | str=STRING S?                   { Strlit str }
+  | id=IDENT S?                     { Ident id }
+  | uri=URI S?                      { Uri uri }
+  | fn=FUNCTION arg=expr RPAREN S?  { Function (fn, arg) }
   | hex=HASH S?
   { let h = "[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]" in
     if Str.string_match (Str.regexp ("^" ^ h ^ "\\(" ^ h ^ "\\)?$")) hex 0
       then Hexcolor (String.lowercase hex)
       else raise (SyntaxError ("invalid color #" ^ hex)) }
-
 unary_operator:
   | MINUS  { "-" }
   | PLUS   { "+" }
+%inline numval:
+  | n=NUMBER      { Number (n, None) }
+  | v=UNIT_VALUE  { let n, u = v in Number (n, Some u) }
+  | n=PERCENTAGE  { Number (n, Some "%") }

+ 25 - 9
stringify.ml

@@ -68,7 +68,7 @@ let string_of_media_query query =
   | (Some pre, None, _) ->
     failwith "unexpected media query prefix \"" ^ pre ^ "\""
 
-let block body = " {\n" ^ indent body ^ "\n}"
+let block = function "" -> " {}" | body -> " {\n" ^ indent body ^ "\n}"
 
 let rec string_of_statement = function
   | Ruleset (selectors, decls) ->
@@ -87,12 +87,20 @@ let rec string_of_statement = function
     "@page" ^ block (cat "\n" string_of_declaration decls)
   | Page (Some pseudo, decls) ->
     "@page :" ^ pseudo ^ block (cat "\n" string_of_declaration decls)
-  | Fontface decls ->
-    "@font-face " ^ block (cat "\n" string_of_declaration decls)
+  | Font_face decls ->
+    let string_of_descriptor_declaration (name, value) =
+      name ^ ": " ^ string_of_expr value ^ ";"
+    in
+    "@font-face" ^ block (cat "\n" string_of_descriptor_declaration decls)
   | Namespace (None, uri) ->
-    "@namespace \"" ^ uri ^ "\";"
+    "@namespace " ^ string_of_expr uri ^ ";"
   | Namespace (Some prefix, uri) ->
-    "@namespace " ^ prefix ^ " \"" ^ uri ^ "\";"
+    "@namespace " ^ prefix ^ " " ^ string_of_expr uri ^ ";"
+  | Keyframes (id, rules) ->
+    let string_of_keyframe_ruleset (expr, decls) =
+      string_of_expr expr ^ block (cat "\n" string_of_declaration decls)
+    in
+    "@keyframes " ^ id ^ block (cat "\n\n" string_of_keyframe_ruleset rules)
 
 let string_of_stylesheet = cat "\n\n" string_of_statement
 
@@ -133,10 +141,10 @@ let minify_media_query query =
 let rec minify_statement = function
   | Ruleset (selectors, decls) ->
     cat "," minify_selector selectors ^
-    "{" ^ (cat ";" minify_declaration decls) ^ "}"
+    "{" ^ cat ";" minify_declaration decls ^ "}"
   | Media (queries, rulesets) ->
     "@media" ^ prefix_space (cat "," minify_media_query queries) ^
-    "{" ^ (cat "" minify_statement rulesets) ^ "}"
+    "{" ^ cat "" minify_statement rulesets ^ "}"
   | Import (target, []) ->
     "@import " ^ string_of_expr target ^ ";"
   | Import (target, queries) ->
@@ -145,8 +153,16 @@ let rec minify_statement = function
     "@page{" ^ cat "" minify_declaration decls ^ "}"
   | Page (Some pseudo, decls) ->
     "@page :" ^ pseudo ^ "{" ^ cat "" minify_declaration decls ^ "}"
-  | Fontface decls ->
-    "@font-face{" ^ cat "" minify_declaration decls ^ "}"
+  | Font_face decls ->
+    let minify_descriptor_declaration (name, value) =
+      name ^ ":" ^ string_of_expr value
+    in
+    "@font-face{" ^ cat ";" minify_descriptor_declaration decls ^ "}"
+  | Keyframes (id, rules) ->
+    let minify_keyframe_ruleset (expr, decls) =
+      minify_expr expr ^ "{" ^ cat ";" minify_declaration decls ^ "}"
+    in
+    "@keyframes " ^ id ^ "{" ^ cat "" minify_keyframe_ruleset rules ^ "}"
   | statement -> string_of_statement statement
 
 let minify_stylesheet = cat "" minify_statement

+ 8 - 3
types.ml

@@ -18,6 +18,10 @@ type selector =
 type media_expr = string * expr option
 type media_query = string option * string option * media_expr list
 
+type descriptor_declaration = string * expr
+
+type keyframe_ruleset = expr * declaration list
+
 type statement =
   | Ruleset of selector list * declaration list
   (* <selectors> { <declarations> } *)
@@ -29,11 +33,12 @@ type statement =
   (* @charset "<charset>"; *)
   | Page of string option * declaration list
   (* @page [<pseudo_page>] { <declarations> } *)
-  | Fontface of declaration list
+  | Font_face of descriptor_declaration list
   (* @font-face { <declarations> } *)
-  | Namespace of string option * string
+  | Namespace of string option * expr
   (* @namespace [<prefix>] "<uri>"; *)
-  (* TODO: @document, @keyframes, @supports *)
+  | Keyframes of string * keyframe_ruleset list
+  (* TODO: @document, @supports *)
 
 type stylesheet = statement list