Range operator

This commit is contained in:
Laurenz 2021-07-08 19:12:07 +02:00
parent c5635d8a3f
commit 5a500fb8a7
7 changed files with 46 additions and 10 deletions

View File

@ -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),
}
}
}

View File

@ -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<T, A>(mut a: T, b: T) -> T
where

View File

@ -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<char> {
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()

View File

@ -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]

View File

@ -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 => "..",
}
}
}

View File

@ -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.

View File

@ -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