summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/color.rs9
-rw-r--r--src/parse/mod.rs14
-rw-r--r--src/parse/tokens.rs94
-rw-r--r--src/syntax/token.rs47
4 files changed, 85 insertions, 79 deletions
diff --git a/src/color.rs b/src/color.rs
index 11cc5b3b..66810537 100644
--- a/src/color.rs
+++ b/src/color.rs
@@ -49,9 +49,14 @@ impl RgbaColor {
impl FromStr for RgbaColor {
type Err = ParseRgbaError;
- /// Constructs a new color from a hex string like `7a03c2`. Do not specify a
- /// leading `#`.
+ /// Constructs a new color from hex strings like the following:
+ /// - `#aef` (shorthand, with leading hashtag),
+ /// - `7a03c2` (without alpha),
+ /// - `abcdefff` (with alpha).
+ ///
+ /// Both lower and upper case is fine.
fn from_str(hex_str: &str) -> Result<Self, Self::Err> {
+ let hex_str = hex_str.strip_prefix('#').unwrap_or(hex_str);
if !hex_str.is_ascii() {
return Err(ParseRgbaError);
}
diff --git a/src/parse/mod.rs b/src/parse/mod.rs
index 9fe8e62e..a3a38775 100644
--- a/src/parse/mod.rs
+++ b/src/parse/mod.rs
@@ -13,9 +13,6 @@ pub use resolve::*;
pub use scanner::*;
pub use tokens::*;
-use std::str::FromStr;
-
-use crate::color::RgbaColor;
use crate::diag::Pass;
use crate::syntax::*;
@@ -314,7 +311,7 @@ fn primary(p: &mut Parser) -> Option<Expr> {
Some(Token::Length(val, unit)) => Expr::Length(val, unit),
Some(Token::Angle(val, unit)) => Expr::Angle(val, unit),
Some(Token::Percent(p)) => Expr::Percent(p),
- Some(Token::Hex(hex)) => Expr::Color(color(p, hex)),
+ Some(Token::Color(color)) => Expr::Color(color),
Some(Token::Str(token)) => Expr::Str(string(p, token)),
// No value.
@@ -357,15 +354,6 @@ fn paren_call(p: &mut Parser, name: Spanned<Ident>) -> Expr {
})
}
-/// Parse a color.
-fn color(p: &mut Parser, hex: &str) -> RgbaColor {
- RgbaColor::from_str(hex).unwrap_or_else(|_| {
- // Replace color with black.
- p.diag(error!(p.peek_span(), "invalid color"));
- RgbaColor::new(0, 0, 0, 255)
- })
-}
-
/// Parse a string.
fn string(p: &mut Parser, token: TokenStr) -> String {
if !token.terminated {
diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs
index f5e5baaf..056bbbbb 100644
--- a/src/parse/tokens.rs
+++ b/src/parse/tokens.rs
@@ -1,6 +1,8 @@
use std::fmt::{self, Debug, Formatter};
+use std::str::FromStr;
use super::{is_newline, Scanner};
+use crate::color::RgbaColor;
use crate::geom::{AngularUnit, LengthUnit};
use crate::syntax::*;
@@ -139,7 +141,7 @@ impl<'s> Iterator for Tokens<'s> {
}
// Hex values and strings.
- '#' => self.hex(),
+ '#' => self.hex(start),
'"' => self.string(),
_ => Token::Invalid(self.s.eaten_from(start)),
@@ -200,16 +202,11 @@ impl<'s> Tokens<'s> {
if self.s.check(is_id_start) {
self.s.eat();
self.s.eat_while(is_id_continue);
- match self.s.eaten_from(start) {
- "#let" => Token::Let,
- "#if" => Token::If,
- "#else" => Token::Else,
- "#for" => Token::For,
- "#while" => Token::While,
- "#break" => Token::Break,
- "#continue" => Token::Continue,
- "#return" => Token::Return,
- s => Token::Invalid(s),
+ let read = self.s.eaten_from(start);
+ if let Some(keyword) = keyword(read) {
+ keyword
+ } else {
+ Token::Invalid(read)
}
} else {
Token::Hash
@@ -310,15 +307,6 @@ impl<'s> Tokens<'s> {
"not" => Token::Not,
"and" => Token::And,
"or" => Token::Or,
- "let" => Token::Let,
- "if" => Token::If,
- "else" => Token::Else,
- "for" => Token::For,
- "in" => Token::In,
- "while" => Token::While,
- "break" => Token::Break,
- "continue" => Token::Continue,
- "return" => Token::Return,
"none" => Token::None,
"true" => Token::Bool(true),
"false" => Token::Bool(false),
@@ -379,9 +367,16 @@ impl<'s> Tokens<'s> {
}
}
- fn hex(&mut self) -> Token<'s> {
- // Allow more than `ascii_hexdigit` for better error recovery.
- Token::Hex(self.s.eat_while(|c| c.is_ascii_alphanumeric()))
+ fn hex(&mut self, start: usize) -> Token<'s> {
+ self.s.eat_while(is_id_continue);
+ let read = self.s.eaten_from(start);
+ if let Some(keyword) = keyword(read) {
+ keyword
+ } else if let Ok(color) = RgbaColor::from_str(read) {
+ Token::Color(color)
+ } else {
+ Token::Invalid(read)
+ }
}
fn string(&mut self) -> Token<'s> {
@@ -440,6 +435,21 @@ impl Debug for Tokens<'_> {
}
}
+fn keyword(id: &str) -> Option<Token<'static>> {
+ Some(match id {
+ "#let" => Token::Let,
+ "#if" => Token::If,
+ "#else" => Token::Else,
+ "#for" => Token::For,
+ "#in" => Token::In,
+ "#while" => Token::While,
+ "#break" => Token::Break,
+ "#continue" => Token::Continue,
+ "#return" => Token::Return,
+ _ => return None,
+ })
+}
+
#[cfg(test)]
#[allow(non_snake_case)]
mod tests {
@@ -465,6 +475,10 @@ mod tests {
Token::Str(TokenStr { string, terminated })
}
+ const fn Color(r: u8, g: u8, b: u8, a: u8) -> Token<'static> {
+ Token::Color(RgbaColor { r, g, b, a })
+ }
+
/// Building blocks for suffix testing.
///
/// We extend each test case with a collection of different suffixes to make
@@ -495,7 +509,6 @@ mod tests {
// Letter suffixes.
('a', Some(Markup), "hello", Text("hello")),
('a', Some(Markup), "πŸ’š", Text("πŸ’š")),
- ('a', Some(Code), "if", If),
('a', Some(Code), "val", Ident("val")),
('a', Some(Code), "Ξ±", Ident("Ξ±")),
('a', Some(Code), "_", Ident("_")),
@@ -510,10 +523,11 @@ mod tests {
('/', Some(Markup), "$ $", Math(" ", true, true)),
('/', Some(Markup), r"\\", Text(r"\")),
('/', Some(Markup), "#let", Let),
+ ('/', Some(Code), "#if", If),
('/', Some(Code), "(", LeftParen),
('/', Some(Code), ":", Colon),
('/', Some(Code), "+=", PlusEq),
- ('/', Some(Code), "#123", Hex("123")),
+ ('/', Some(Code), "#123", Color(0x11, 0x22, 0x33, 0xff)),
];
macro_rules! t {
@@ -633,6 +647,7 @@ mod tests {
("if", If),
("else", Else),
("for", For),
+ ("in", In),
("while", While),
("break", Break),
("continue", Continue),
@@ -640,7 +655,7 @@ mod tests {
];
for &(s, t) in &both {
- t!(Code[" "]: s => t);
+ t!(Code[" "]: format!("#{}", s) => t);
t!(Markup[" "]: format!("#{}", s) => t);
t!(Markup[" "]: format!("#{0}#{0}", s) => t, t);
t!(Markup[" /"]: format!("# {}", s) => Hash, Space(0), Text(s));
@@ -650,7 +665,6 @@ mod tests {
("not", Not),
("and", And),
("or", Or),
- ("in", In),
("none", Token::None),
("false", Bool(false)),
("true", Bool(true)),
@@ -854,13 +868,10 @@ mod tests {
}
#[test]
- fn test_tokenize_hex() {
- // Test basic hex expressions.
- t!(Code[" /"]: "#6ae6dd" => Hex("6ae6dd"));
- t!(Code[" /"]: "#8A083c" => Hex("8A083c"));
-
- // Test with non-hex letters.
- t!(Code[" /"]: "#PQ" => Hex("PQ"));
+ fn test_tokenize_color() {
+ t!(Code[" /"]: "#ABC" => Color(0xAA, 0xBB, 0xCC, 0xff));
+ t!(Code[" /"]: "#6ae6dd" => Color(0x6a, 0xe6, 0xdd, 0xff));
+ t!(Code[" /"]: "#8A083caf" => Color(0x8A, 0x08, 0x3c, 0xaf));
}
#[test]
@@ -924,11 +935,11 @@ mod tests {
t!(Both: "/**/*/" => BlockComment(""), Token::Invalid("*/"));
// Test invalid expressions.
- t!(Code: r"\" => Invalid(r"\"));
- t!(Code: "πŸŒ“" => Invalid("πŸŒ“"));
- t!(Code: r"\:" => Invalid(r"\"), Colon);
- t!(Code: "meal⌚" => Ident("meal"), Invalid("⌚"));
- t!(Code[" /"]: r"\a" => Invalid(r"\"), Ident("a"));
+ t!(Code: r"\" => Invalid(r"\"));
+ t!(Code: "πŸŒ“" => Invalid("πŸŒ“"));
+ t!(Code: r"\:" => Invalid(r"\"), Colon);
+ t!(Code: "meal⌚" => Ident("meal"), Invalid("⌚"));
+ t!(Code[" /"]: r"\a" => Invalid(r"\"), Ident("a"));
// Test invalid number suffixes.
t!(Code[" /"]: "1foo" => Invalid("1foo"));
@@ -936,7 +947,8 @@ mod tests {
t!(Code: "1%%" => Percent(1.0), Invalid("%"));
// Test invalid keyword.
- t!(Markup[" /"]: "#-" => Hash, Text("-"));
- t!(Markup[" "]: "#do" => Invalid("#do"))
+ t!(Markup[" /"]: "#-" => Hash, Text("-"));
+ t!(Markup[" /"]: "#do" => Invalid("#do"));
+ t!(Code[" /"]: r"#letter" => Invalid(r"#letter"));
}
}
diff --git a/src/syntax/token.rs b/src/syntax/token.rs
index 432b4dc5..43797f75 100644
--- a/src/syntax/token.rs
+++ b/src/syntax/token.rs
@@ -1,3 +1,4 @@
+use crate::color::RgbaColor;
use crate::geom::{AngularUnit, LengthUnit};
/// A minimal semantic entity of source code.
@@ -71,26 +72,26 @@ pub enum Token<'s> {
And,
/// The `or` operator.
Or,
- /// The `let` / `#let` keyword.
+ /// The none literal: `none`.
+ None,
+ /// The `#let` keyword.
Let,
- /// The `if` / `#if` keyword.
+ /// The `#if` keyword.
If,
- /// The `else` / `#else` keyword.
+ /// The `#else` keyword.
Else,
- /// The `for` / `#for` keyword.
+ /// The `#for` keyword.
For,
- /// The `in` / `#in` keyword.
+ /// The `#in` keyword.
In,
- /// The `while` / `#while` keyword.
+ /// The `#while` keyword.
While,
- /// The `break` / `#break` keyword.
+ /// The `#break` keyword.
Break,
- /// The `continue` / `#continue` keyword.
+ /// The `#continue` keyword.
Continue,
- /// The `return` / `#return` keyword.
+ /// The `#return` keyword.
Return,
- /// The none literal: `none`.
- None,
/// One or more whitespace characters.
///
/// The contained `usize` denotes the number of newlines that were contained
@@ -124,8 +125,8 @@ pub enum Token<'s> {
/// _Note_: `50%` is stored as `50.0` here, as in the corresponding
/// [literal](super::Expr::Percent).
Percent(f64),
- /// A hex value: `#20d82a`.
- Hex(&'s str),
+ /// A color value: `#20d82a`.
+ Color(RgbaColor),
/// A quoted string: `"..."`.
Str(TokenStr<'s>),
/// Two slashes followed by inner contents, terminated with a newline:
@@ -223,16 +224,16 @@ impl<'s> Token<'s> {
Self::Not => "operator `not`",
Self::And => "operator `and`",
Self::Or => "operator `or`",
- Self::Let => "keyword `let`",
- Self::If => "keyword `if`",
- Self::Else => "keyword `else`",
- Self::For => "keyword `for`",
- Self::In => "keyword `in`",
- Self::While => "keyword `while`",
- Self::Break => "keyword `break`",
- Self::Continue => "keyword `continue`",
- Self::Return => "keyword `return`",
Self::None => "`none`",
+ Self::Let => "keyword `#let`",
+ Self::If => "keyword `#if`",
+ Self::Else => "keyword `#else`",
+ Self::For => "keyword `#for`",
+ Self::In => "keyword `#in`",
+ Self::While => "keyword `#while`",
+ Self::Break => "keyword `#break`",
+ Self::Continue => "keyword `#continue`",
+ Self::Return => "keyword `#return`",
Self::Space(_) => "space",
Self::Text(_) => "text",
Self::Raw(_) => "raw block",
@@ -245,7 +246,7 @@ impl<'s> Token<'s> {
Self::Length(..) => "length",
Self::Angle(..) => "angle",
Self::Percent(_) => "percentage",
- Self::Hex(_) => "hex value",
+ Self::Color(_) => "color",
Self::Str(_) => "string",
Self::LineComment(_) => "line comment",
Self::BlockComment(_) => "block comment",