package cascade

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file component.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
(** Stage 3 IR: CSS Syntax 3 (ED) sec. 5.1 component values and rules. *)

type 'a node = { node : 'a; loc : Loc.t }

type t = Preserved of Token.t | Block of block node | Func of func node

and block = {
  opening : Token.bracket;
  value : t list;
  closed : bool;
      (** [false] when the lexer reached EOF before the matching closer (CSS
          Syntax 3 (ED) sec. 5.5.9 parse error). Typed validators inspect this
          to reject values that the syntax level only forgives. *)
}

and func = {
  name : string;
  arguments : t list;
  terminated : bool;
      (** [false] when the lexer reached EOF before the matching [)] (CSS Syntax
          3 (ED) sec. 5.4.6 parse error). The serializer still emits the
          synthetic [)] so reserialised output round-trips through the lexer;
          typed validators can inspect this flag to reject values that the
          syntax level only forgives. *)
}

type at_rule_body = {
  name : string;
  prelude : t list;
  block : block node option;
}

type at_rule = at_rule_body node
type qualified_rule_body = { prelude : t list; block : block node }
type qualified_rule = qualified_rule_body node
type rule = Qualified of qualified_rule | At of at_rule
type declaration_body = { name : string; value : t list; important : bool }
type declaration = declaration_body node

(* A {!Loc.t} records where a node was read, not what it holds, so two nodes
   that spell the same component value are the same value however far apart the
   source wrote them. *)
let rank = function Preserved _ -> 0 | Block _ -> 1 | Func _ -> 2

let rec compare (a : t) (b : t) =
  match (a, b) with
  | Preserved t1, Preserved t2 -> Token.compare_kind t1.kind t2.kind
  | Block b1, Block b2 -> compare_block b1.node b2.node
  | Func f1, Func f2 -> compare_func f1.node f2.node
  | (Preserved _ | Block _ | Func _), _ -> Int.compare (rank a) (rank b)

and compare_block (b1 : block) (b2 : block) =
  let c = Token.compare_bracket b1.opening b2.opening in
  if c <> 0 then c
  else
    let c = Bool.compare b1.closed b2.closed in
    if c <> 0 then c else List.compare compare b1.value b2.value

and compare_func (f1 : func) (f2 : func) =
  let c = String.compare f1.name f2.name in
  if c <> 0 then c
  else
    let c = Bool.compare f1.terminated f2.terminated in
    if c <> 0 then c else List.compare compare f1.arguments f2.arguments

let equal a b = compare a b = 0

let source_loc : t -> Loc.t = function
  | Preserved tok -> tok.Token.loc
  | Block b -> b.loc
  | Func f -> f.loc

let rule_loc : rule -> Loc.t = function Qualified r -> r.loc | At r -> r.loc

let rec is_any_value components =
  List.for_all
    (function
      | Preserved { kind = Token.Bad_string | Token.Bad_url | Token.Close _; _ }
        ->
          false
      | Block { node = { value; closed; _ }; _ } -> closed && is_any_value value
      | Func { node = { arguments; terminated; _ }; _ } ->
          terminated && is_any_value arguments
      | Preserved _ -> true)
    components

let is_whitespace = function
  | Preserved { kind = Token.Whitespace _; _ } -> true
  | Preserved _ | Block _ | Func _ -> false

(* A real [var()] function anywhere in the components, recursing into function
   arguments and bracketed blocks. A [var(] inside a string or a url() is an
   atomic [Preserved] token, never a [Func], so it is data, not a reference. *)
let rec has_var components =
  List.exists
    (fun (c : t) ->
      match c with
      | Func { node = { name; arguments; _ }; _ } ->
          String.lowercase_ascii name = "var" || has_var arguments
      | Block { node = { value; _ }; _ } -> has_var value
      | Preserved _ -> false)
    components

let opening_char : Token.bracket -> char = function
  | Curly -> '{'
  | Paren -> '('
  | Square -> '['

let closing_char : Token.bracket -> char = function
  | Curly -> '}'
  | Paren -> ')'
  | Square -> ']'

(* A debug dump, not source text: every node shows its own location and the CSS
   Syntax 3 (ED) sec. 5.5.9 and 5.5.10 flags [compare] separates values on.
   [Pp.space] rather than [Pp.sp] because a dump has no minified form, and
   layout whitespace would drop out and run the children together. *)
let rec pp : t Pp.t =
 fun ctx cv ->
  match cv with
  | Preserved tok -> Token.pp ctx tok
  | Block { node = { opening; value; closed }; loc } ->
      Pp.char ctx (opening_char opening);
      Pp.list ~sep:Pp.space pp ctx value;
      Pp.char ctx (closing_char opening);
      if not closed then Pp.string ctx "<unclosed>";
      Pp.char ctx '@';
      Loc.pp ctx loc
  | Func { node = { name; arguments; terminated }; loc } ->
      Pp.string ctx name;
      Pp.char ctx '(';
      Pp.list ~sep:Pp.space pp ctx arguments;
      Pp.char ctx ')';
      if not terminated then Pp.string ctx "<unterminated>";
      Pp.char ctx '@';
      Loc.pp ctx loc

let to_string t = Pp.to_string pp t