///|
/// Rules, declarations and values.
///|
/// One rule, at any level.
fn Parser::parse_rule(
self : Parser,
top? : Bool = false,
) -> @ast.CssRule? raise @err.CssError {
match self.peek() {
AtKeyword(name) => self.parse_at_rule(name)
_ => self.parse_qualified_rule(top~)
}
}
///|
/// A style rule: a prelude, then a block.
fn Parser::parse_qualified_rule(
self : Parser,
top? : Bool = false,
) -> @ast.CssRule? raise @err.CssError {
ignore(top)
let start = self.cur().span.start
let prelude_start = self.pos
let mut depth = 0
while !self.at_eof() {
let k = self.peek()
if depth == 0 && k == LBrace {
break
}
if k.is_opener() {
depth = depth + 1
} else if k == RParen || k == RBracket {
if depth > 0 {
depth = depth - 1
}
} else if k == RBrace {
if depth > 0 {
depth = depth - 1
} else {
// A `}` before any `{`: this prelude belongs to nothing.
let b = self.bogus(BadSelector, start, self.cur().span.start)
return Some(Bogus(b))
}
}
self.pos = self.pos + 1
}
if self.at_eof() {
let b = self.bogus(UnexpectedEof, start, self.cur().span.end)
return Some(Bogus(b))
}
let prelude = self.toks[prelude_start:self.pos]
let selectors = self.parse_selector_list(prelude, start)
let body = self.parse_block()
Some(
Style({ selectors, body, span: @span.Span::new(start, self.prev_end()), }),
)
}
///|
fn Parser::prev_end(self : Parser) -> Int {
if self.pos > 0 {
self.toks[self.pos - 1].span.end
} else {
0
}
}
///|
/// The contents of a `{ ... }`, positioned on the `{`.
///
/// Declarations and nested rules interleave, and telling them apart is the one
/// genuine ambiguity: `a:hover { }` and `color: red;` both begin with an
/// identifier and a colon. The decision is made by lookahead, not by guessing
/// from the identifier -- scan forward at depth zero for the first of `{`, `;`
/// or `}`. A `{` first means a rule; anything else means a declaration. That is
/// what real CSS parsers do, and it is right even for a property nobody has
/// heard of.
fn Parser::parse_block(
self : Parser,
) -> Array[@ast.BlockItem] raise @err.CssError {
let items : Array[@ast.BlockItem] = []
if self.peek() != LBrace {
return items
}
let brace_start = self.cur().span.start
self.pos = self.pos + 1
while true {
self.skip_trivia_collecting_block(items)
if self.at_eof() {
self.error(
UnclosedBlock("{"),
@span.Span::new(brace_start, brace_start + 1),
)
return items
}
if self.peek() == RBrace {
self.pos = self.pos + 1
return items
}
if self.peek() == Semicolon {
self.pos = self.pos + 1
continue
}
match self.peek() {
AtKeyword(name) =>
match self.parse_at_rule(name) {
Some(r) => items.push(Rule(r))
None => ()
}
_ =>
if self.next_is_rule() {
match self.parse_qualified_rule() {
Some(r) => items.push(Rule(r))
None => ()
}
} else {
match self.parse_declaration() {
Some(d) => items.push(Decl(d))
None => ()
}
}
}
}
items
}
///|
/// Lookahead: does a `{` come before the `;` or `}` that would end a
/// declaration?
fn Parser::next_is_rule(self : Parser) -> Bool {
let mut i = self.pos
let mut depth = 0
while i < self.toks.length() {
let k = self.toks[i].kind
if k == Eof {
return false
}
if depth == 0 {
if k == LBrace {
return true
}
if k == Semicolon || k == RBrace {
return false
}
}
if k.is_opener() {
depth = depth + 1
} else if k == RParen || k == RBracket || k == RBrace {
if depth > 0 {
depth = depth - 1
}
}
i = i + 1
}
false
}
// ----------------------------------------------------------- declarations
///|
fn Parser::parse_declaration(
self : Parser,
) -> @ast.Declaration? raise @err.CssError {
let start = self.cur().span.start
let name = match self.peek() {
Ident(n) => n
_ => {
let bad_start = self.cur().span.start
self.recover_declaration()
self.error(BadDeclaration, @span.Span::new(bad_start, self.prev_end()))
return None
}
}
self.pos = self.pos + 1
self.skip_trivia()
if self.peek() != Colon {
let here = self.cur().span
self.error(ExpectedColon, here)
self.recover_declaration()
return None
}
self.pos = self.pos + 1
let (value, important) = self.parse_value()
if value.length() == 0 {
self.error(EmptyValue, @span.Span::new(start, self.prev_end()))
}
let property : @ast.PropertyName = if name.has_prefix("--") {
Custom(name)
} else {
Ident(name)
}
Some({
property,
value,
important,
span: @span.Span::new(start, self.prev_end()),
})
}
///|
/// A declaration's value, up to the `;` or `}` that ends it.
///
/// Returns the components and whether `!important` was present. The flag is
/// separated here rather than left in the list because it is not a value: it
/// changes the cascade, and a consumer asking "what colour is this" should not
/// have to filter it out first.
fn Parser::parse_value(
self : Parser,
) -> (Array[@ast.ComponentValue], Bool) raise @err.CssError {
let vs : Array[@ast.ComponentValue] = []
let mut important = false
while !self.at_eof() {
let k = self.peek()
if k == Semicolon {
self.pos = self.pos + 1
break
}
if k == RBrace {
break
}
if self.toks[self.pos].is_trivia() {
self.pos = self.pos + 1
continue
}
// `!important`, and nothing else a `!` may introduce.
if k == Delim('!') {
let bang = self.cur().span
let save = self.pos
self.pos = self.pos + 1
self.skip_trivia()
match self.peek() {
Ident(w) if w.to_lower() == "important" => {
self.pos = self.pos + 1
if important {
self.error(ImportantNotLast, bang)
}
important = true
continue
}
_ => {
self.pos = save
self.error(BadBang, bang)
self.pos = self.pos + 1
vs.push(Delim("!"))
continue
}
}
}
if important {
// Anything after `!important` is not part of the value.
let here = self.cur().span
self.error(ImportantNotLast, here)
}
vs.push(self.parse_component_value())
}
trim_trailing(vs)
(vs, important)
}
///|
/// Drop a trailing separator, which a value cannot end with.
fn trim_trailing(vs : Array[@ast.ComponentValue]) -> Unit {
while vs.length() > 0 {
match vs[vs.length() - 1] {
Comma | Slash => {
let _ = vs.pop()
}
_ => return
}
}
}
///|
/// One component value, recursing into functions and groups.
fn Parser::parse_component_value(
self : Parser,
) -> @ast.ComponentValue raise @err.CssError {
let t = self.cur()
match t.kind {
Ident(s) => {
self.pos = self.pos + 1
Ident(s)
}
Str(s) => {
self.pos = self.pos + 1
Str(s)
}
Url(u) => {
self.pos = self.pos + 1
Url(u)
}
BadUrl => {
self.pos = self.pos + 1
Bogus(self.bogus(BadUrl, t.span.start, t.span.end))
}
BadStr => {
self.pos = self.pos + 1
Bogus(self.bogus(UnterminatedString, t.span.start, t.span.end))
}
Number(r, v, i) => {
self.pos = self.pos + 1
Num({ repr: r, value: v, is_int: i, })
}
Percentage(r, v) => {
self.pos = self.pos + 1
Percentage({ repr: r, value: v, is_int: false, })
}
Dimension(r, v, i, u) => {
self.pos = self.pos + 1
Dimension({ repr: r, value: v, is_int: i, }, u)
}
Hash(d, _) => {
self.pos = self.pos + 1
Hex(d)
}
Comma => {
self.pos = self.pos + 1
Comma
}
Delim('/') => {
self.pos = self.pos + 1
Slash
}
Delim(c) => {
self.pos = self.pos + 1
Delim(c.to_string())
}
Function(name) => {
self.pos = self.pos + 1
// `url("...")` tokenizes as a function; fold it back so that quoted and
// unquoted urls are the same node and a consumer never has to know which
// spelling the source used.
let args = self.parse_group(RParen)
if name.to_lower() == "url" && args.length() == 1 {
match args[0] {
Str(s) => return Url(s)
_ => ()
}
}
Function(name, args)
}
LParen => {
self.pos = self.pos + 1
Paren(self.parse_group(RParen))
}
LBracket => {
self.pos = self.pos + 1
Bracket(self.parse_group(RBracket))
}
Colon => {
self.pos = self.pos + 1
Delim(":")
}
_ => {
self.pos = self.pos + 1
Bogus(self.bogus(Unexpected("a value"), t.span.start, t.span.end))
}
}
}
///|
/// The inside of a `(`, `[` or a function call, up to its closer.
fn Parser::parse_group(
self : Parser,
closer : @token.TokenKind,
) -> Array[@ast.ComponentValue] raise @err.CssError {
let vs : Array[@ast.ComponentValue] = []
while !self.at_eof() {
if self.peek() == closer {
self.pos = self.pos + 1
trim_trailing(vs)
return vs
}
// A `}` or a `;` ends the enclosing rule; a group must not eat it.
if self.peek() == RBrace {
break
}
if self.toks[self.pos].is_trivia() {
self.pos = self.pos + 1
continue
}
vs.push(self.parse_component_value())
}
trim_trailing(vs)
vs
}