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
139
140
141
142
143
144
//! Rule selectors and the shared `|type#id|` identity parse.
use super::*;
impl<'a> Parser<'a> {
// ───────────────────────── Rules & defines ─────────────────────────
/// `selector { decls }` [SPEC 4] — `|box| { }`, `.hot { }`, `#hero { }`,
/// `|table| |box| { }`.
pub(super) fn parse_rule(&mut self) -> Result<Rule, Error> {
let start = self.span();
let selector = self.parse_selector()?;
let (decls, _) = self.parse_style()?;
Ok(Rule {
selector,
decls,
span: self.span_from(start),
})
}
/// A run of space-separated selector units up to the `{` [SPEC 4]: a type
/// `|box|` / `|table#main|`, a class `.hot`, or an id `#hero`. The space is
/// the descendant combinator; each unit keeps its sigil.
pub(super) fn parse_selector(&mut self) -> Result<Selector, Error> {
let mut units = Vec::new();
loop {
match self.kind() {
// `|-|` — the link type [SPEC 9]: every link, styled like a node.
Some(TokKind::Pipe) if self.at_link_bars() => {
self.pos += 3;
units.push(SelUnit::Link);
}
// `(-)` — the dimension type [SPEC 4, 15.6]: the `|-|` subtype, one
// token, matching every dimension. In selector position (not after
// endpoints) it never means the linear operator.
Some(TokKind::DrawOp(DrawOp::Linear)) => {
self.pos += 1;
units.push(SelUnit::Dimension);
}
Some(TokKind::Pipe) => {
let (ty, id) = self.parse_identity(BarsCtx::Selector)?;
units.push(match ty {
Some(name) => SelUnit::Type { name, id },
None => SelUnit::Id(id.expect("identity yields a type or an id")),
});
}
Some(TokKind::Dot) => {
self.pos += 1;
units.push(SelUnit::Class(self.expect_ident()?.0));
}
Some(TokKind::Hash(_)) => units.push(SelUnit::Id(self.parse_hash_id()?)),
_ => break,
}
}
if units.is_empty() {
return Err(self.err("a rule needs a selector"));
}
Ok(Selector { units })
}
/// The bars — `|type|`, `|type#id|`, or `|#id|` [SPEC 3]: the optional type
/// and id, at least one present. Shared by an instance and a selector unit;
/// `ctx` only picks the glued-class error wording.
pub(super) fn parse_identity(
&mut self,
ctx: BarsCtx,
) -> Result<(Option<String>, Option<String>), Error> {
// `|-|` is the link selector, not an identity: reachable here only as an
// instance (a selector peels it first), so it draws nothing [SPEC 9].
if ctx == BarsCtx::Instance && self.at_link_bars() {
return Err(self
.err("a link is drawn by an operator — '|-|' only styles links (write 'a -> b')"));
}
self.expect(&TokKind::Pipe, "'|'")?;
let (ty, id) = match self.kind() {
// `|#id|` — an id alone, the default box type.
Some(TokKind::Hash(_)) => (None, Some(self.parse_hash_id()?)),
Some(TokKind::Ident(_)) => {
let name = self.expect_ident()?.0;
if matches!(self.kind(), Some(TokKind::DColon)) {
return Err(self.err("a define belongs in the stylesheet"));
}
// A glued `#id` after the type (`|box#cat|`).
let id = if matches!(self.kind(), Some(TokKind::Hash(_))) && self.glued_at(0) {
Some(self.parse_hash_id()?)
} else {
None
};
// A glued `.class` is worn styling, not identity — rejected.
if matches!(self.kind(), Some(TokKind::Dot)) && self.glued_at(0) {
return Err(self.glued_class_err(ctx));
}
(Some(name), id)
}
Some(TokKind::Dot) => return Err(self.glued_class_err(ctx)),
_ => return Err(self.err("'| |' needs a type or an '#id'")),
};
if let Some(t) = &ty {
if t == "link" {
return Err(self.err("links are drawn by operators, not the '|link|' type"));
}
if t == "node" {
return Err(
self.err("'node' is the umbrella concept — write '|block|' for the bare box")
);
}
}
self.expect(&TokKind::Pipe, "'|'")?;
Ok((ty, id))
}
/// Consume a `#name` token as an id, validating the run is a real ident
/// (`#cat`, not `#123`).
pub(super) fn parse_hash_id(&mut self) -> Result<String, Error> {
let (run, span) = match self.kind() {
Some(TokKind::Hash(s)) => (s.clone(), self.span()),
_ => return Err(self.err("expected an '#id'")),
};
if !is_ident(&run) {
return Err(Error::at(
span,
format!("'#{run}' is not a valid id — an id starts with a letter or '_'"),
));
}
self.pos += 1;
Ok(run)
}
pub(super) fn glued_class_err(&self, ctx: BarsCtx) -> Error {
self.err(match ctx {
BarsCtx::Instance => "a class follows the bars — write '|box| .hot', not '|box.hot|'",
BarsCtx::Selector => {
"a selector unit can't glue a type and a class — space them (descendant) or style '.hot'"
}
})
}
}
/// A valid id / ident: starts with a letter or `_`, then ident chars.
fn is_ident(s: &str) -> bool {
let mut bytes = s.bytes();
matches!(bytes.next(), Some(c) if c.is_ascii_alphabetic() || c == b'_')
&& bytes.all(|c| c.is_ascii_alphanumeric() || c == b'_' || c == b'-')
}