stringify.ml 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. open Types
  2. let tab = " "
  3. let indent = Str.global_replace (Str.regexp "^\\(.\\)") (tab ^ "\\1")
  4. let prefix_space = function "" -> "" | s -> " " ^ s
  5. let rec cat sep fn = function
  6. | [] -> ""
  7. | [hd] -> fn hd
  8. | hd :: tl -> fn hd ^ sep ^ cat sep fn tl
  9. let string_of_num n =
  10. if float_of_int (int_of_float n) = n
  11. then string_of_int (int_of_float n)
  12. else string_of_float n
  13. (* TODO: move this to utils *)
  14. let (@@) f g x = f (g x)
  15. let rec filter_none = function
  16. | [] -> []
  17. | None :: tl -> filter_none tl
  18. | Some hd :: tl -> hd :: filter_none tl
  19. let add_parens s =
  20. let l = String.length s in
  21. if l > 0 & s.[0] = '(' & s.[l - 1] = ')'
  22. then String.sub s 1 (l - 2)
  23. else s
  24. (*
  25. * Pretty-printing
  26. *)
  27. let rec string_of_expr = function
  28. | Ident id -> id
  29. | Strlit str -> "\"" ^ str ^ "\""
  30. | Uri uri when String.contains uri ')' -> "url(\"" ^ uri ^ "\")"
  31. | Uri uri -> "url(" ^ uri ^ ")"
  32. | Concat values -> cat " " string_of_expr values
  33. | Number (n, None) -> string_of_num n
  34. | Number (n, Some u) -> string_of_num n ^ u
  35. | Function (name, arg) -> name ^ "(" ^ string_of_expr arg ^ ")"
  36. | Hexcolor hex -> "#" ^ hex
  37. | Unary (op, opnd) -> op ^ string_of_expr opnd
  38. | Nary (",", opnds) -> cat ", " string_of_expr opnds
  39. | Nary (op, opnds) -> cat op string_of_expr opnds
  40. let string_of_declaration (name, value, important) =
  41. let imp = if important then " !important" else "" in
  42. name ^ ": " ^ string_of_expr value ^ imp ^ ";"
  43. let rec string_of_selector = function
  44. | Simple simple -> simple
  45. | Combinator (left, " ", right) ->
  46. string_of_selector left ^ " " ^ string_of_selector right
  47. | Combinator (left, com, right) ->
  48. string_of_selector left ^ " " ^ com ^ " " ^ string_of_selector right
  49. let string_of_media_feature = function
  50. | (feature, None) -> "(" ^ feature ^ ")"
  51. | (feature, Some value) -> "(" ^ feature ^ ": " ^ string_of_expr value ^ ")"
  52. let string_of_media_query query =
  53. let features_str = cat " and " string_of_media_feature in
  54. match query with
  55. | (None, None, []) -> ""
  56. | (None, Some mtype, []) -> mtype
  57. | (Some pre, Some mtype, []) -> pre ^ " " ^ mtype
  58. | (None, None, features) -> features_str features
  59. | (None, Some mtype, features) -> mtype ^ " and " ^ features_str features
  60. | (Some pre, Some mtype, features) ->
  61. pre ^ " " ^ mtype ^ " and " ^ features_str features
  62. | (Some pre, None, _) ->
  63. failwith "unexpected media query prefix \"" ^ pre ^ "\""
  64. let rec string_of_condition = function
  65. | Not c -> "not " ^ add_parens (string_of_condition c)
  66. | And c -> cat " and " (add_parens @@ string_of_condition) c
  67. | Or c -> cat " or " (add_parens @@ string_of_condition) c
  68. | Decl (name, value, false) -> "(" ^ name ^ ": " ^ string_of_expr value ^ ")"
  69. | Decl (_, _, true) -> failwith "unexpected \"!important\""
  70. let block = function "" -> " {}" | body -> " {\n" ^ indent body ^ "\n}"
  71. let rec string_of_statement = function
  72. | Ruleset (selectors, decls) ->
  73. cat ", " string_of_selector selectors ^
  74. block (cat "\n" string_of_declaration decls)
  75. | Media (queries, rulesets) ->
  76. "@media" ^ prefix_space (cat ", " string_of_media_query queries) ^
  77. block (cat "\n\n" string_of_statement rulesets)
  78. | Import (target, []) ->
  79. "@import " ^ string_of_expr target ^ ";"
  80. | Import (target, queries) ->
  81. "@import " ^ string_of_expr target ^ " " ^ cat ", " string_of_media_query queries ^ ";"
  82. | Charset charset ->
  83. "@charset \"" ^ charset ^ "\";"
  84. | Page (None, decls) ->
  85. "@page" ^ block (cat "\n" string_of_declaration decls)
  86. | Page (Some pseudo, decls) ->
  87. "@page :" ^ pseudo ^ block (cat "\n" string_of_declaration decls)
  88. | Font_face decls ->
  89. let string_of_descriptor_declaration (name, value) =
  90. name ^ ": " ^ string_of_expr value ^ ";"
  91. in
  92. "@font-face" ^ block (cat "\n" string_of_descriptor_declaration decls)
  93. | Namespace (None, uri) ->
  94. "@namespace " ^ string_of_expr uri ^ ";"
  95. | Namespace (Some prefix, uri) ->
  96. "@namespace " ^ prefix ^ " " ^ string_of_expr uri ^ ";"
  97. | Keyframes (id, rules) ->
  98. let string_of_keyframe_ruleset (expr, decls) =
  99. string_of_expr expr ^ block (cat "\n" string_of_declaration decls)
  100. in
  101. "@keyframes " ^ id ^ block (cat "\n\n" string_of_keyframe_ruleset rules)
  102. | Supports (condition, statements) ->
  103. "@supports " ^ string_of_condition condition ^
  104. block (cat "\n\n" string_of_statement statements)
  105. let string_of_stylesheet = cat "\n\n" string_of_statement
  106. (*
  107. * Minified stringification
  108. *)
  109. let rec minify_expr = function
  110. | Concat values -> cat " " minify_expr values
  111. | Function (name, arg) -> name ^ "(" ^ minify_expr arg ^ ")"
  112. | Unary (op, opnd) -> op ^ minify_expr opnd
  113. | Nary (",", opnds) -> cat "," minify_expr opnds
  114. | Nary (op, opnds) -> cat op minify_expr opnds
  115. | expr -> string_of_expr expr
  116. let minify_declaration (name, value, important) =
  117. let imp = if important then "!important" else "" in
  118. name ^ ":" ^ minify_expr value ^ imp
  119. let rec minify_selector = function
  120. | Simple simple -> simple
  121. | Combinator (left, com, right) ->
  122. minify_selector left ^ com ^ minify_selector right
  123. let minify_media_feature = function
  124. | (feature, None) -> "(" ^ feature ^ ")"
  125. | (feature, Some value) -> "(" ^ feature ^ ":" ^ minify_expr value ^ ")"
  126. let minify_media_query query =
  127. let features_str = cat "and " minify_media_feature in
  128. match query with
  129. | (None, None, features) -> features_str features
  130. | (None, Some mtype, features) -> mtype ^ " and " ^ features_str features
  131. | (Some pre, Some mtype, features) ->
  132. pre ^ " " ^ mtype ^ " and " ^ features_str features
  133. | _ -> string_of_media_query query
  134. let rec minify_condition = function
  135. | Not c -> "not " ^ add_parens (minify_condition c)
  136. | And c -> cat "and " (add_parens @@ minify_condition) c
  137. | Or c -> cat "or " (add_parens @@ minify_condition) c
  138. | Decl (name, value, false) -> "(" ^ name ^ ":" ^ minify_expr value ^ ")"
  139. | Decl (_, _, true) -> failwith "unexpected \"!important\""
  140. let rec minify_statement = function
  141. | Ruleset (selectors, decls) ->
  142. cat "," minify_selector selectors ^
  143. "{" ^ cat ";" minify_declaration decls ^ "}"
  144. | Media (queries, rulesets) ->
  145. "@media" ^ prefix_space (cat "," minify_media_query queries) ^
  146. "{" ^ cat "" minify_statement rulesets ^ "}"
  147. | Import (target, []) ->
  148. "@import " ^ string_of_expr target ^ ";"
  149. | Import (target, queries) ->
  150. "@import " ^ string_of_expr target ^ " " ^ cat "," string_of_media_query queries ^ ";"
  151. | Page (None, decls) ->
  152. "@page{" ^ cat "" minify_declaration decls ^ "}"
  153. | Page (Some pseudo, decls) ->
  154. "@page :" ^ pseudo ^ "{" ^ cat "" minify_declaration decls ^ "}"
  155. | Font_face decls ->
  156. let minify_descriptor_declaration (name, value) =
  157. name ^ ":" ^ string_of_expr value
  158. in
  159. "@font-face{" ^ cat ";" minify_descriptor_declaration decls ^ "}"
  160. | Keyframes (id, rules) ->
  161. let minify_keyframe_ruleset (expr, decls) =
  162. minify_expr expr ^ "{" ^ cat ";" minify_declaration decls ^ "}"
  163. in
  164. "@keyframes " ^ id ^ "{" ^ cat "" minify_keyframe_ruleset rules ^ "}"
  165. | Supports (condition, statements) ->
  166. "@supports " ^ minify_condition condition ^
  167. "{" ^ cat "" minify_statement statements ^ "}"
  168. | statement -> string_of_statement statement
  169. let minify_stylesheet = cat "" minify_statement