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::SubAssign => self.assign(ctx, ops::sub),
BinOp::MulAssign => self.assign(ctx, ops::mul), BinOp::MulAssign => self.assign(ctx, ops::mul),
BinOp::DivAssign => self.assign(ctx, ops::div), 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!(gt, Greater);
comparison!(geq, Greater | Equal); 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. /// Concatenate two collections.
fn concat<T, A>(mut a: T, b: T) -> T fn concat<T, A>(mut a: T, b: T) -> T
where where

View File

@ -86,11 +86,6 @@ impl<'s> Scanner<'s> {
self.rest().chars().next() 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. /// Checks whether the next char fulfills a condition.
/// ///
/// Returns `false` if there is no next char. /// Returns `false` if there is no next char.
@ -101,6 +96,11 @@ impl<'s> Scanner<'s> {
self.peek().map(f).unwrap_or(false) 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. /// The previous index in the source string.
pub fn last_index(&self) -> usize { pub fn last_index(&self) -> usize {
self.eaten() self.eaten()

View File

@ -216,7 +216,7 @@ impl<'s> Tokens<'s> {
self.s.eat_assert(c); self.s.eat_assert(c);
Token::Text(&self.s.eaten_from(start)) 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('u');
self.s.eat_assert('{'); self.s.eat_assert('{');
Token::UnicodeEscape(UnicodeEscapeToken { Token::UnicodeEscape(UnicodeEscapeToken {
@ -366,7 +366,8 @@ impl<'s> Tokens<'s> {
self.s.eat_while(|c| c.is_ascii_digit()); self.s.eat_while(|c| c.is_ascii_digit());
// Read the fractional part if not already done. // 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()); self.s.eat_while(|c| c.is_ascii_digit());
} }
@ -905,6 +906,11 @@ mod tests {
t!(Code[" /"]: format!("{}{}", s, suffix) => build(v)); 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] #[test]

View File

@ -278,6 +278,8 @@ pub enum BinOp {
MulAssign, MulAssign,
/// The divide-assign operator: `/=`. /// The divide-assign operator: `/=`.
DivAssign, DivAssign,
/// The inclusive range operator: `..`.
Range,
} }
impl BinOp { impl BinOp {
@ -301,6 +303,7 @@ impl BinOp {
Token::HyphEq => Self::SubAssign, Token::HyphEq => Self::SubAssign,
Token::StarEq => Self::MulAssign, Token::StarEq => Self::MulAssign,
Token::SlashEq => Self::DivAssign, Token::SlashEq => Self::DivAssign,
Token::Dots => Self::Range,
_ => return None, _ => return None,
}) })
} }
@ -311,8 +314,9 @@ impl BinOp {
Self::Mul | Self::Div => 7, Self::Mul | Self::Div => 7,
Self::Add | Self::Sub => 6, Self::Add | Self::Sub => 6,
Self::Eq | Self::Neq | Self::Lt | Self::Leq | Self::Gt | Self::Geq => 5, Self::Eq | Self::Neq | Self::Lt | Self::Leq | Self::Gt | Self::Geq => 5,
Self::And => 3, Self::And => 4,
Self::Or => 2, Self::Or => 3,
Self::Range => 2,
Self::Assign Self::Assign
| Self::AddAssign | Self::AddAssign
| Self::SubAssign | Self::SubAssign
@ -335,7 +339,8 @@ impl BinOp {
| Self::Lt | Self::Lt
| Self::Leq | Self::Leq
| Self::Gt | Self::Gt
| Self::Geq => Associativity::Left, | Self::Geq
| Self::Range => Associativity::Left,
Self::Assign Self::Assign
| Self::AddAssign | Self::AddAssign
| Self::SubAssign | Self::SubAssign
@ -364,6 +369,7 @@ impl BinOp {
Self::SubAssign => "-=", Self::SubAssign => "-=",
Self::MulAssign => "*=", Self::MulAssign => "*=",
Self::DivAssign => "/=", Self::DivAssign => "/=",
Self::Range => "..",
} }
} }
} }

View File

@ -44,6 +44,9 @@
// Error: 3-4 expected function, found integer // Error: 3-4 expected function, found integer
{ 1 with () } { 1 with () }
// Error: 3-10 cannot apply '..' to integer and string
{ 1 .. "" }
--- ---
// Bad left-hand sides of assignment. // Bad left-hand sides of assignment.

View File

@ -146,6 +146,18 @@
{ x = "some" } #test(x, "some") { x = "some" } #test(x, "some")
{ x += "thing" } #test(x, "something") { 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. // Test with operator.
// Ref: true // Ref: true