summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/eval/mod.rs1
-rw-r--r--src/eval/ops.rs8
-rw-r--r--src/parse/scanner.rs10
-rw-r--r--src/parse/tokens.rs10
-rw-r--r--src/syntax/expr.rs12
-rw-r--r--tests/typ/code/ops-invalid.typ3
-rw-r--r--tests/typ/code/ops.typ12
7 files changed, 46 insertions, 10 deletions
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index 96ac87a9..ba2de8c7 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 2537f4a5..01044842 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<T, A>(mut a: T, b: T) -> T
where
diff --git a/src/parse/scanner.rs b/src/parse/scanner.rs
index d2c2efed..1a0e3045 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<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()
diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs
index 522b3136..c9b7dc21 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 a8a5854a..f5b79122 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 ab53dd97..4090554c 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 a7d5474e..6d788df1 100644
--- a/tests/typ/code/ops.typ
+++ b/tests/typ/code/ops.typ
@@ -147,6 +147,18 @@
{ 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