From 5a500fb8a7c0ba4b8a59e2622c8cbafdc4ce1fe9 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Thu, 8 Jul 2021 19:12:07 +0200 Subject: [PATCH] Range operator --- src/eval/mod.rs | 1 + src/eval/ops.rs | 8 ++++++++ src/parse/scanner.rs | 10 +++++----- src/parse/tokens.rs | 10 ++++++++-- src/syntax/expr.rs | 12 +++++++++--- tests/typ/code/ops-invalid.typ | 3 +++ tests/typ/code/ops.typ | 12 ++++++++++++ 7 files changed, 46 insertions(+), 10 deletions(-) diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 96ac87a99..ba2de8c7a 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -383,6 +383,7 @@ impl Eval for BinaryExpr { BinOp::SubAssign => self.assign(ctx, ops::sub), BinOp::MulAssign => self.assign(ctx, ops::mul), BinOp::DivAssign => self.assign(ctx, ops::div), + BinOp::Range => self.apply(ctx, ops::range), } } } diff --git a/src/eval/ops.rs b/src/eval/ops.rs index 2537f4a5f..01044842c 100644 --- a/src/eval/ops.rs +++ b/src/eval/ops.rs @@ -250,6 +250,14 @@ comparison!(leq, Less | Equal); comparison!(gt, Greater); comparison!(geq, Greater | Equal); +/// Compute the range from `lhs` to `rhs`. +pub fn range(lhs: Value, rhs: Value) -> Value { + match (lhs, rhs) { + (Int(a), Int(b)) => Array((a ..= b).map(Int).collect()), + _ => Error, + } +} + /// Concatenate two collections. fn concat(mut a: T, b: T) -> T where diff --git a/src/parse/scanner.rs b/src/parse/scanner.rs index d2c2efedc..1a0e3045b 100644 --- a/src/parse/scanner.rs +++ b/src/parse/scanner.rs @@ -86,11 +86,6 @@ impl<'s> Scanner<'s> { self.rest().chars().next() } - /// Peek at the nth-next char without consuming anything. - pub fn peek_nth(&self, n: usize) -> Option { - self.rest().chars().nth(n) - } - /// Checks whether the next char fulfills a condition. /// /// Returns `false` if there is no next char. @@ -101,6 +96,11 @@ impl<'s> Scanner<'s> { self.peek().map(f).unwrap_or(false) } + /// Checks whether the remaining source starts with the given string. + pub fn starts_with(&self, string: &str) -> bool { + self.rest().starts_with(string) + } + /// The previous index in the source string. pub fn last_index(&self) -> usize { self.eaten() diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs index 522b3136d..c9b7dc21e 100644 --- a/src/parse/tokens.rs +++ b/src/parse/tokens.rs @@ -216,7 +216,7 @@ impl<'s> Tokens<'s> { self.s.eat_assert(c); Token::Text(&self.s.eaten_from(start)) } - 'u' if self.s.peek_nth(1) == Some('{') => { + 'u' if self.s.starts_with("u{") => { self.s.eat_assert('u'); self.s.eat_assert('{'); Token::UnicodeEscape(UnicodeEscapeToken { @@ -366,7 +366,8 @@ impl<'s> Tokens<'s> { self.s.eat_while(|c| c.is_ascii_digit()); // Read the fractional part if not already done. - if c != '.' && self.s.eat_if('.') { + // Make sure not to confuse a range for the decimal separator. + if c != '.' && !self.s.starts_with("..") && self.s.eat_if('.') { self.s.eat_while(|c| c.is_ascii_digit()); } @@ -905,6 +906,11 @@ mod tests { t!(Code[" /"]: format!("{}{}", s, suffix) => build(v)); } } + + // Multiple dots close the number. + t!(Code[" /"]: "1..2" => Int(1), Dots, Int(2)); + t!(Code[" /"]: "1..2.3" => Int(1), Dots, Float(2.3)); + t!(Code[" /"]: "1.2..3" => Float(1.2), Dots, Int(3)); } #[test] diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index a8a5854a3..f5b79122d 100644 --- a/src/syntax/expr.rs +++ b/src/syntax/expr.rs @@ -278,6 +278,8 @@ pub enum BinOp { MulAssign, /// The divide-assign operator: `/=`. DivAssign, + /// The inclusive range operator: `..`. + Range, } impl BinOp { @@ -301,6 +303,7 @@ impl BinOp { Token::HyphEq => Self::SubAssign, Token::StarEq => Self::MulAssign, Token::SlashEq => Self::DivAssign, + Token::Dots => Self::Range, _ => return None, }) } @@ -311,8 +314,9 @@ impl BinOp { Self::Mul | Self::Div => 7, Self::Add | Self::Sub => 6, Self::Eq | Self::Neq | Self::Lt | Self::Leq | Self::Gt | Self::Geq => 5, - Self::And => 3, - Self::Or => 2, + Self::And => 4, + Self::Or => 3, + Self::Range => 2, Self::Assign | Self::AddAssign | Self::SubAssign @@ -335,7 +339,8 @@ impl BinOp { | Self::Lt | Self::Leq | Self::Gt - | Self::Geq => Associativity::Left, + | Self::Geq + | Self::Range => Associativity::Left, Self::Assign | Self::AddAssign | Self::SubAssign @@ -364,6 +369,7 @@ impl BinOp { Self::SubAssign => "-=", Self::MulAssign => "*=", Self::DivAssign => "/=", + Self::Range => "..", } } } diff --git a/tests/typ/code/ops-invalid.typ b/tests/typ/code/ops-invalid.typ index ab53dd976..4090554ca 100644 --- a/tests/typ/code/ops-invalid.typ +++ b/tests/typ/code/ops-invalid.typ @@ -44,6 +44,9 @@ // Error: 3-4 expected function, found integer { 1 with () } +// Error: 3-10 cannot apply '..' to integer and string +{ 1 .. "" } + --- // Bad left-hand sides of assignment. diff --git a/tests/typ/code/ops.typ b/tests/typ/code/ops.typ index a7d5474e8..6d788df19 100644 --- a/tests/typ/code/ops.typ +++ b/tests/typ/code/ops.typ @@ -146,6 +146,18 @@ { x = "some" } #test(x, "some") { x += "thing" } #test(x, "something") +--- +// Test range operator. + +#let array = (1, 2, 3) +#test(1..3, array) +#test(1.. 3, array) +#test(1 ..3, array) +#test(1 .. 3, array) + +#test(-4..2, (-4, -3, -2, -1, 0, 1, 2)) +#test(10..5, ()) + --- // Test with operator. // Ref: true