stringify.ml 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  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 s else "(" ^ s ^ ")"
  23. (*
  24. * Pretty-printing
  25. *)
  26. let rec string_of_expr = function
  27. | Ident id -> id
  28. | Strlit str -> "\"" ^ str ^ "\""
  29. | Uri uri when String.contains uri ')' -> "url(\"" ^ uri ^ "\")"
  30. | Uri uri -> "url(" ^ uri ^ ")"
  31. | Concat values -> cat " " string_of_expr values
  32. | Number (n, None) -> string_of_num n
  33. | Number (n, Some u) -> string_of_num n ^ u
  34. | Function (name, arg) -> name ^ "(" ^ string_of_expr arg ^ ")"
  35. | Hexcolor hex -> "#" ^ hex
  36. | Unary (op, opnd) -> op ^ string_of_expr opnd
  37. | Nary (",", opnds) -> cat ", " string_of_expr opnds
  38. | Nary (op, opnds) -> cat op string_of_expr opnds
  39. let string_of_declaration (name, value, important) =
  40. let imp = if important then " !important" else "" in
  41. name ^ ": " ^ string_of_expr value ^ imp ^ ";"
  42. let rec string_of_selector = function
  43. | Simple simple -> simple
  44. | Combinator (left, " ", right) ->
  45. string_of_selector left ^ " " ^ string_of_selector right
  46. | Combinator (left, com, right) ->
  47. string_of_selector left ^ " " ^ com ^ " " ^ string_of_selector right
  48. let string_of_media_feature = function
  49. | (feature, None) -> "(" ^ feature ^ ")"
  50. | (feature, Some value) -> "(" ^ feature ^ ": " ^ string_of_expr value ^ ")"
  51. let string_of_media_query query =
  52. let features_str = cat " and " string_of_media_feature in
  53. match query with
  54. | (None, None, []) -> ""
  55. | (None, Some mtype, []) -> mtype
  56. | (Some pre, Some mtype, []) -> pre ^ " " ^ mtype
  57. | (None, None, features) -> features_str features
  58. | (None, Some mtype, features) -> mtype ^ " and " ^ features_str features
  59. | (Some pre, Some mtype, features) ->
  60. pre ^ " " ^ mtype ^ " and " ^ features_str features
  61. | (Some pre, None, _) ->
  62. failwith "unexpected media query prefix \"" ^ pre ^ "\""
  63. let stringify_condition w c =
  64. let rec transform =
  65. let p c = `Parens (transform c) in
  66. function
  67. | Not c -> `Not (p c)
  68. | And c -> `And (List.map p c)
  69. | Or c -> `Or (List.map p c)
  70. | Decl (name, value) -> `Decl (name, value)
  71. in
  72. let rec str = function
  73. | `Not c -> "not " ^ str c
  74. | `And c -> cat " and " str c
  75. | `Or c -> cat " or " str c
  76. | `Decl (name, value) -> "(" ^ name ^ ":" ^ w ^ string_of_expr value ^ ")"
  77. | `Parens (`Decl _ as d) -> str d
  78. | `Parens c -> "(" ^ str c ^ ")"
  79. in
  80. str (transform c)
  81. let block = function "" -> " {}" | body -> " {\n" ^ indent body ^ "\n}"
  82. let rec string_of_statement = function
  83. | Ruleset (selectors, decls) ->
  84. cat ", " string_of_selector selectors ^
  85. block (cat "\n" string_of_declaration decls)
  86. | Media (queries, rulesets) ->
  87. "@media" ^ prefix_space (cat ", " string_of_media_query queries) ^
  88. block (cat "\n\n" string_of_statement rulesets)
  89. | Import (target, []) ->
  90. "@import " ^ string_of_expr target ^ ";"
  91. | Import (target, queries) ->
  92. "@import " ^ string_of_expr target ^ " " ^ cat ", " string_of_media_query queries ^ ";"
  93. | Charset charset ->
  94. "@charset \"" ^ charset ^ "\";"
  95. | Page (None, decls) ->
  96. "@page" ^ block (cat "\n" string_of_declaration decls)
  97. | Page (Some pseudo, decls) ->
  98. "@page :" ^ pseudo ^ block (cat "\n" string_of_declaration decls)
  99. | Font_face decls ->
  100. let string_of_descriptor_declaration (name, value) =
  101. name ^ ": " ^ string_of_expr value ^ ";"
  102. in
  103. "@font-face" ^ block (cat "\n" string_of_descriptor_declaration decls)
  104. | Namespace (None, uri) ->
  105. "@namespace " ^ string_of_expr uri ^ ";"
  106. | Namespace (Some prefix, uri) ->
  107. "@namespace " ^ prefix ^ " " ^ string_of_expr uri ^ ";"
  108. | Keyframes (id, rules) ->
  109. let string_of_keyframe_ruleset (expr, decls) =
  110. string_of_expr expr ^ block (cat "\n" string_of_declaration decls)
  111. in
  112. "@keyframes " ^ id ^ block (cat "\n\n" string_of_keyframe_ruleset rules)
  113. | Supports (condition, statements) ->
  114. "@supports " ^ stringify_condition " " condition ^
  115. block (cat "\n\n" string_of_statement statements)
  116. let string_of_stylesheet = cat "\n\n" string_of_statement
  117. (*
  118. * Minified stringification
  119. *)
  120. let rec minify_expr = function
  121. | Concat values -> cat " " minify_expr values
  122. | Function (name, arg) -> name ^ "(" ^ minify_expr arg ^ ")"
  123. | Unary (op, opnd) -> op ^ minify_expr opnd
  124. | Nary (",", opnds) -> cat "," minify_expr opnds
  125. | Nary (op, opnds) -> cat op minify_expr opnds
  126. | expr -> string_of_expr expr
  127. let minify_declaration (name, value, important) =
  128. let imp = if important then "!important" else "" in
  129. name ^ ":" ^ minify_expr value ^ imp
  130. let rec minify_selector = function
  131. | Simple simple -> simple
  132. | Combinator (left, com, right) ->
  133. minify_selector left ^ com ^ minify_selector right
  134. let minify_media_feature = function
  135. | (feature, None) -> "(" ^ feature ^ ")"
  136. | (feature, Some value) -> "(" ^ feature ^ ":" ^ minify_expr value ^ ")"
  137. let minify_media_query query =
  138. let features_str = cat "and " minify_media_feature in
  139. match query with
  140. | (None, None, features) -> features_str features
  141. | (None, Some mtype, features) -> mtype ^ " and " ^ features_str features
  142. | (Some pre, Some mtype, features) ->
  143. pre ^ " " ^ mtype ^ " and " ^ features_str features
  144. | _ -> string_of_media_query query
  145. let rec minify_condition = function
  146. | Not c -> "not " ^ add_parens (minify_condition c)
  147. | And c -> cat " and " (add_parens @@ minify_condition) c
  148. | Or c -> cat " or " (add_parens @@ minify_condition) c
  149. | Decl (name, value) -> "(" ^ name ^ ":" ^ minify_expr value ^ ")"
  150. let rec minify_statement = function
  151. | Ruleset (selectors, decls) ->
  152. cat "," minify_selector selectors ^
  153. "{" ^ cat ";" minify_declaration decls ^ "}"
  154. | Media (queries, rulesets) ->
  155. "@media" ^ prefix_space (cat "," minify_media_query queries) ^
  156. "{" ^ cat "" minify_statement rulesets ^ "}"
  157. | Import (target, []) ->
  158. "@import " ^ string_of_expr target ^ ";"
  159. | Import (target, queries) ->
  160. "@import " ^ string_of_expr target ^ " " ^ cat "," string_of_media_query queries ^ ";"
  161. | Page (None, decls) ->
  162. "@page{" ^ cat "" minify_declaration decls ^ "}"
  163. | Page (Some pseudo, decls) ->
  164. "@page :" ^ pseudo ^ "{" ^ cat "" minify_declaration decls ^ "}"
  165. | Font_face decls ->
  166. let minify_descriptor_declaration (name, value) =
  167. name ^ ":" ^ string_of_expr value
  168. in
  169. "@font-face{" ^ cat ";" minify_descriptor_declaration decls ^ "}"
  170. | Keyframes (id, rules) ->
  171. let minify_keyframe_ruleset (expr, decls) =
  172. minify_expr expr ^ "{" ^ cat ";" minify_declaration decls ^ "}"
  173. in
  174. "@keyframes " ^ id ^ "{" ^ cat "" minify_keyframe_ruleset rules ^ "}"
  175. | Supports (condition, statements) ->
  176. "@supports " ^ stringify_condition "" condition ^
  177. "{" ^ cat "" minify_statement statements ^ "}"
  178. | statement -> string_of_statement statement
  179. let minify_stylesheet = cat "" minify_statement