Skip to content
99 changes: 57 additions & 42 deletions src/css/rules/property.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,11 @@ impl PropertyRule {

dest.write_str("initial-value:")?;
dest.whitespace()?;
initial_value.to_css(dest)?;
// Printed without the targets: they lower `light-dark()` to `var()`, not valid here.
let targets = core::mem::take(&mut dest.targets);
let result = initial_value.to_css(dest);
dest.targets = targets;
result?;

if !dest.minify {
dest.write_char(b';')?;
Expand Down Expand Up @@ -80,8 +84,8 @@ impl PropertyRule {
input: &mut css::Parser,
loc: Location,
) -> css::Result<PropertyRule> {
use css::css_parser::{ParserOpts, RuleBodyParser};
use css::{ParserError, ParserInput, TokenList};
use css::css_parser::RuleBodyParser;
use css::{Delimiters, ParserError};
let mut p = PropertyRuleDeclarationParser {
syntax: None,
inherits: None,
Expand All @@ -108,38 +112,23 @@ impl PropertyRule {
None => return Err(input.new_custom_error(ParserError::at_rule_body_invalid)),
};

// SAFETY: `Tokenizer<'a>` owns `arena: &'a Bump`; the arena outlives
// the sub-`ParserInput` constructed below. `'static` is the crate-wide
// erasure (PORTING.md §AST crates).
let bump: &'static bun_alloc::Arena =
unsafe { &*std::ptr::from_ref::<bun_alloc::Arena>(input.arena()) };

// `initial-value` is required unless the syntax is a universal definition.
let initial_value = match syntax {
SyntaxString::Universal => {
if let Some(val) = p.initial_value {
let mut i = ParserInput::new(val, bump);
let mut p2 = css::Parser::new(&mut i, None, ParserOpts::default(), None);

if p2.is_exhausted() {
Some(ParsedComponent::TokenList(TokenList {
v: Default::default(),
}))
} else {
Some(syntax.parse_value(&mut p2)?)
}
} else {
None
}
}
_ => {
let Some(val) = p.initial_value else {
return Err(input.new_custom_error(ParserError::at_rule_body_invalid));
};
let mut i = ParserInput::new(val, bump);
let mut p2 = css::Parser::new(&mut i, None, ParserOpts::default(), None);
Some(syntax.parse_value(&mut p2)?)
let initial_value = match p.initial_value {
Some(InitialValue {
value: Some(value), ..
}) => Some(value?),
Some(InitialValue { start, value: None }) => {
let mut end = input.state();
input.reset(&start);
let value =
input.parse_until_before(Delimiters::SEMICOLON, |i| syntax.parse_value(i))?;
// `Parser::reset` drops the import records after the count of its state.
end.import_record_count = input.state().import_record_count;
input.reset(&end);
Some(value)
}
None if matches!(syntax, SyntaxString::Universal) => None,
None => return Err(input.new_custom_error(ParserError::at_rule_body_invalid)),
};

Ok(PropertyRule {
Expand All @@ -152,13 +141,18 @@ impl PropertyRule {
}
}

// Borrows the parser input buffer for `initial_value` (arena-backed in the
// CSS crate). Kept as `&'static [u8]` per the rules/mod.rs lifetime-erasure
// note; TODO(refactor): re-thread `'i`.
pub(crate) struct PropertyRuleDeclarationParser {
pub syntax: Option<SyntaxString>,
pub inherits: Option<bool>,
pub initial_value: Option<&'static [u8]>,
pub initial_value: Option<InitialValue>,
}

/// The last `initial-value` descriptor of the body.
pub(crate) struct InitialValue {
/// Where the value starts.
start: css::ParserState,
/// `None` until `syntax` is known: `PropertyRule::parse` then reads the value from `start`.
value: Option<css::Result<ParsedComponent>>,
}

// Parser trait impls for `@property` rule bodies.
Expand All @@ -179,6 +173,10 @@ const _: () = {
crate::match_ignore_ascii_case! { name, {
b"syntax" => {
this.syntax = Some(SyntaxString::parse(input)?);
// A value that was read under an earlier `syntax` is read again.
if let Some(initial_value) = &mut this.initial_value {
initial_value.value = None;
}
},
b"inherits" => {
let location = input.current_source_location();
Expand All @@ -190,11 +188,28 @@ const _: () = {
}});
},
b"initial-value" => {
// Buffer the value into a string. We will parse it later.
let start = input.position();
while input.next().is_ok() {}
let initial_value = input.slice_from_cloned(start);
this.initial_value = Some(initial_value);
let mut start = input.state();
if let Some(previous) = &this.initial_value {
// Drop the replaced value's import records. No other descriptor adds any.
start.import_record_count = previous.start.import_record_count;
input.reset(&start);
}
let value = match &this.syntax {
Some(syntax) => {
let result = syntax.parse_value(input);
if result.is_err() {
// The error counts only when no later value replaces this one.
input.reset(&start);
while input.next().is_ok() {}
}
Some(result)
}
None => {
while input.next().is_ok() {}
None
}
};
this.initial_value = Some(InitialValue { start, value });
},
_ => return Err(input.new_custom_error(ParserError::invalid_declaration)),
}}
Expand Down
44 changes: 34 additions & 10 deletions src/css/values/syntax.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::values::length::{Length, LengthPercentage};
use crate::values::number::{CSSInteger, CSSIntegerFns, CSSNumber, CSSNumberFns};
use crate::values::percentage::Percentage;
use crate::values::resolution::Resolution;
use crate::values::string::{CssString, CssStringFns};
use crate::values::time::Time;
use crate::values::url::Url;

Expand Down Expand Up @@ -65,7 +66,7 @@ impl SyntaxString {
/// Parses a syntax string.
fn parse_string(input: &[u8]) -> Result<SyntaxString, ()> {
// https://drafts.css-houdini.org/css-properties-values-api/#parsing-syntax
let mut trimmed_input = strings::trim_left(input, SPACE_CHARACTERS);
let mut trimmed_input = strings::trim(input, SPACE_CHARACTERS);
if trimmed_input.is_empty() {
return Err(());
}
Expand Down Expand Up @@ -97,16 +98,14 @@ impl SyntaxString {
Ok(SyntaxString::Components(components))
}

/// Parses a value according to the syntax grammar.
/// Parses a value as the first component that matches all of it, else as tokens to print as written.
pub(crate) fn parse_value(&self, input: &mut css::Parser) -> CssResult<ParsedComponent> {
// A value of only white space is empty. `TokenList::parse` would keep it as one token.
input.skip_whitespace();

match self {
SyntaxString::Universal => Ok(ParsedComponent::TokenList(TokenList::parse(
input,
&ParserOptions::default(None),
0,
)?)),
SyntaxString::Universal => {}
SyntaxString::Components(components) => {
// Loop through each component, and return the first one that parses successfully.
for component in components.iter() {
let state = input.state();
// PERF: deinit this on error
Expand Down Expand Up @@ -153,6 +152,9 @@ impl SyntaxString {
SyntaxComponentKind::CustomIdent => {
ParsedComponent::CustomIdent(CustomIdentFns::parse(i)?)
}
SyntaxComponentKind::String => ParsedComponent::String(
std::ptr::from_ref::<[u8]>(i.expect_string()?),
),
SyntaxComponentKind::Literal(value) => {
let location = i.current_source_location();
let ident = i.expect_ident_cloned()?;
Expand All @@ -170,7 +172,12 @@ impl SyntaxString {

if let Ok(value) = value_result {
match component.multiplier {
Multiplier::None => return Ok(value),
Multiplier::None => {
if input.is_exhausted() {
return Ok(value);
}
break;
}
Multiplier::Space => {
parsed.push(value);
if input.is_exhausted() {
Expand Down Expand Up @@ -206,9 +213,18 @@ impl SyntaxString {
input.reset(&state);
}

Err(input.new_error_for_next_token())
// An empty value is a missing one, which is an error for a typed syntax.
if input.is_exhausted() {
return Err(input.new_error_for_next_token());
}
}
}

Ok(ParsedComponent::TokenList(TokenList::parse(
input,
&ParserOptions::default(None),
0,
)?))
Comment thread
robobun marked this conversation as resolved.
Resolved
}
}

Expand Down Expand Up @@ -288,6 +304,8 @@ pub enum SyntaxComponentKind {
TransformList,
/// A `<custom-ident>` component.
CustomIdent,
/// A `<string>` component.
String,
/// A literal component.
// PORTING.md §Forbidden bans laundering a parser-borrowed slice to
// `&'static`; that would need a `'bump` lifetime threaded through
Expand Down Expand Up @@ -323,6 +341,7 @@ impl SyntaxComponentKind {
b"transform-function" => SyntaxComponentKind::TransformFunction,
b"transform-list" => SyntaxComponentKind::TransformList,
b"custom-ident" => SyntaxComponentKind::CustomIdent,
b"string" => SyntaxComponentKind::String,
_ => return Err(()),
}};

Expand Down Expand Up @@ -363,6 +382,7 @@ impl SyntaxComponentKind {
SyntaxComponentKind::TransformFunction => dest.write_str("<transform-function>"),
SyntaxComponentKind::TransformList => dest.write_str("<transform-list>"),
SyntaxComponentKind::CustomIdent => dest.write_str("<custom-ident>"),
SyntaxComponentKind::String => dest.write_str("<string>"),
SyntaxComponentKind::Literal(l) => dest.write_str(&l[..]),
}
}
Expand Down Expand Up @@ -416,6 +436,8 @@ pub enum ParsedComponent {
TransformList(TransformList),
/// A `<custom-ident>` value.
CustomIdent(CustomIdent),
/// A `<string>` value.
String(CssString),
/// A literal value.
Literal(Ident),
/// A repeated component value.
Expand Down Expand Up @@ -461,6 +483,7 @@ impl ParsedComponent {
ParsedComponent::TransformFunction(v) => v.to_css(dest),
ParsedComponent::TransformList(v) => v.to_css(dest),
ParsedComponent::CustomIdent(v) => CustomIdentFns::to_css(v, dest),
ParsedComponent::String(v) => CssStringFns::to_css(v, dest),
ParsedComponent::Literal(v) => dest.serialize_identifier(v.v()),
ParsedComponent::Repeated(r) => dest.write_separated(
r.components.iter(),
Expand Down Expand Up @@ -498,6 +521,7 @@ impl ParsedComponent {
}
ParsedComponent::TransformList(v) => ParsedComponent::TransformList(v.deep_clone(bump)),
ParsedComponent::CustomIdent(v) => ParsedComponent::CustomIdent(*v),
ParsedComponent::String(v) => ParsedComponent::String(*v),
ParsedComponent::Literal(v) => ParsedComponent::Literal(*v),
ParsedComponent::Repeated(r) => ParsedComponent::Repeated(r.deep_clone(bump)),
ParsedComponent::TokenList(t) => {
Expand Down
Loading
Loading