mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Require hashtag for all keywords 💂♀️
This commit is contained in:
parent
7ced99bcd7
commit
aaa48403cd
@ -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);
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
Let,
|
||||
/// The `if` / `#if` keyword.
|
||||
If,
|
||||
/// The `else` / `#else` keyword.
|
||||
Else,
|
||||
/// The `for` / `#for` keyword.
|
||||
For,
|
||||
/// The `in` / `#in` keyword.
|
||||
In,
|
||||
/// The `while` / `#while` keyword.
|
||||
While,
|
||||
/// The `break` / `#break` keyword.
|
||||
Break,
|
||||
/// The `continue` / `#continue` keyword.
|
||||
Continue,
|
||||
/// The `return` / `#return` keyword.
|
||||
Return,
|
||||
/// The none literal: `none`.
|
||||
None,
|
||||
/// The `#let` keyword.
|
||||
Let,
|
||||
/// The `#if` keyword.
|
||||
If,
|
||||
/// The `#else` keyword.
|
||||
Else,
|
||||
/// The `#for` keyword.
|
||||
For,
|
||||
/// The `#in` keyword.
|
||||
In,
|
||||
/// The `#while` keyword.
|
||||
While,
|
||||
/// The `#break` keyword.
|
||||
Break,
|
||||
/// The `#continue` keyword.
|
||||
Continue,
|
||||
/// The `#return` keyword.
|
||||
Return,
|
||||
/// 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",
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 799 B After Width: | Height: | Size: 640 B |
Binary file not shown.
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8.0 KiB |
@ -19,8 +19,3 @@
|
||||
// Missing closing bracket in template expression.
|
||||
// Error: 1:11-1:11 expected closing bracket
|
||||
{[_] + [4_}
|
||||
|
||||
// Opening brace is ignored after one expression is parsed.
|
||||
// Error: 2:4-2:5 unexpected hex value
|
||||
// Error: 1:5-1:6 unexpected opening brace
|
||||
{5 #{}*.*
|
||||
|
@ -80,7 +80,7 @@
|
||||
#let x = "string"
|
||||
[x]
|
||||
|
||||
// Error: 1:2-1:3 expected function name, found hex value
|
||||
// Error: 1:2-1:3 expected function name, found invalid token
|
||||
[# 1]
|
||||
|
||||
// Error: 4:1-4:1 expected function name
|
||||
|
@ -53,7 +53,7 @@ a#if true {}
|
||||
a#if true [b] #else c
|
||||
|
||||
// Lone else.
|
||||
// Error: 2:1-2:6 unexpected keyword `else`
|
||||
// Error: 2:1-2:6 unexpected keyword `#else`
|
||||
// Error: 1:8-1:8 expected function name
|
||||
#else []
|
||||
|
||||
|
@ -32,9 +32,6 @@
|
||||
// Colors.
|
||||
{#f7a20500} \
|
||||
|
||||
// Error: 1:2-1:5 invalid color
|
||||
{#a5}
|
||||
|
||||
// Strings and escaping.
|
||||
{"hi"} \
|
||||
{"a\n[]\"\u{1F680}string"} \
|
||||
|
Loading…
x
Reference in New Issue
Block a user