Skip to content

Commit 10b0a17

Browse files
authored
Merge pull request #138 from togglebyte/feature/ranges
Feature/ranges
2 parents 48654a0 + 6bc85dc commit 10b0a17

File tree

20 files changed

+244
-447
lines changed

20 files changed

+244
-447
lines changed

CHANGES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
* 0.2.11
2+
* FEATURE: ranges
3+
* `padding` function
4+
* `width` function
15
* 0.2.10
26
* BUGFIX: `on_mount` is now called after the children are generated
37
* BUGFIX: `on_tick` is now run before the cycle call

Cargo.toml

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "anathema"
33
edition = "2024"
4-
version = "0.2.10"
4+
version = "0.2.11"
55
license = "MIT"
66
description = "Create beautiful, easily customisable terminal applications"
77
keywords = ["tui", "terminal", "widgets", "ui", "layout"]
@@ -40,24 +40,24 @@ workspace = true
4040

4141
[workspace.package]
4242
edition = "2024"
43-
version = "0.2.10"
43+
version = "0.2.11"
4444

4545
[workspace.dependencies]
4646
bitflags = "2.4.1"
4747
crossterm = "0.28.1"
4848
unicode-width = "0.1.11"
4949
flume = "0.11.0"
5050
notify = "6.1.1"
51-
anathema-default-widgets = { path = "./anathema-default-widgets", version = "0.2.10" }
52-
anathema-backend = { path = "./anathema-backend", version = "0.2.10" }
53-
anathema-runtime = { path = "./anathema-runtime", version = "0.2.10" }
54-
anathema-state = { path = "./anathema-state", version = "0.2.10" }
55-
anathema-state-derive = { path = "./anathema-state-derive", version = "0.2.10" }
56-
anathema-store = { path = "./anathema-store", version = "0.2.10" }
57-
anathema-templates = { path = "./anathema-templates", version = "0.2.10" }
58-
anathema-widgets = { path = "./anathema-widgets", version = "0.2.10" }
59-
anathema-geometry = { path = "./anathema-geometry", version = "0.2.10" }
60-
anathema-value-resolver = { path = "./anathema-value-resolver", version = "0.2.10" }
51+
anathema-default-widgets = { path = "./anathema-default-widgets", version = "0.2.11" }
52+
anathema-backend = { path = "./anathema-backend", version = "0.2.11" }
53+
anathema-runtime = { path = "./anathema-runtime", version = "0.2.11" }
54+
anathema-state = { path = "./anathema-state", version = "0.2.11" }
55+
anathema-state-derive = { path = "./anathema-state-derive", version = "0.2.11" }
56+
anathema-store = { path = "./anathema-store", version = "0.2.11" }
57+
anathema-templates = { path = "./anathema-templates", version = "0.2.11" }
58+
anathema-widgets = { path = "./anathema-widgets", version = "0.2.11" }
59+
anathema-geometry = { path = "./anathema-geometry", version = "0.2.11" }
60+
anathema-value-resolver = { path = "./anathema-value-resolver", version = "0.2.11" }
6161

6262
[workspace]
6363
members = [

anathema-state/src/store/subscriber.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -214,18 +214,21 @@ impl Subscribers {
214214
fn insert(&mut self, sub: Subscriber) {
215215
match self {
216216
Self::Empty => *self = Self::One(sub),
217-
Self::One(key) => *self = Self::Arr([*key, sub, Subscriber::MAX], KeyIndex::TWO),
217+
Self::One(key) if *key != sub => *self = Self::Arr([*key, sub, Subscriber::MAX], KeyIndex::TWO),
218218
Self::Arr(arr_keys, index) if *index == KeyIndex::MAX => {
219219
let mut keys = Vec::with_capacity(KeyIndex::max() + 1);
220220
keys.extend_from_slice(arr_keys);
221221
keys.push(sub);
222222
*self = Self::Heap(keys);
223223
}
224-
Self::Arr(keys, index) => {
224+
Self::Arr(keys, index) if !keys.contains(&sub) => {
225225
keys[index.0 as usize] = sub;
226226
index.add();
227227
}
228-
Self::Heap(keys) => keys.push(sub),
228+
Self::Heap(keys) if !keys.contains(&sub) => keys.push(sub),
229+
230+
// The sub is already registered
231+
Self::Arr(..) | Self::One(_) | Self::Heap(_) => (),
229232
}
230233
}
231234

anathema-templates/src/expressions/eval.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ pub(super) fn eval(expr: Expr, strings: &Strings) -> Result<Expression, ParseErr
3737
let rhs = strings.get_unchecked(string_id);
3838
Expression::Index(lhs, Expression::Str(rhs).into())
3939
}
40+
Operator::DotDot => {
41+
let (lhs, rhs) = (eval(*lhs, strings)?.into(), eval(*rhs, strings)?.into());
42+
Expression::Range(lhs, rhs)
43+
}
4044
Operator::Mul | Operator::Plus | Operator::Minus | Operator::Div | Operator::Mod => {
4145
let (lhs, rhs) = (eval(*lhs, strings)?.into(), eval(*rhs, strings)?.into());
4246
let op = match op {

anathema-templates/src/expressions/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ pub enum Expression {
7878
// Either
7979
Either(Box<Self>, Box<Self>),
8080

81+
// Range
82+
Range(Box<Self>, Box<Self>),
83+
8184
// Function call
8285
Call { fun: Box<Self>, args: Vec<Self> },
8386
}
@@ -121,6 +124,7 @@ impl Display for Expression {
121124
write!(f, "{lhs} {op} {rhs}")
122125
}
123126
Self::Either(lhs, rhs) => write!(f, "{lhs} ? {rhs}"),
127+
Self::Range(lhs, rhs) => write!(f, "{lhs} .. {rhs}"),
124128
Self::List(list) => {
125129
write!(
126130
f,
@@ -192,6 +196,10 @@ pub fn either(lhs: Box<Expression>, rhs: Box<Expression>) -> Box<Expression> {
192196
Expression::Either(lhs, rhs).into()
193197
}
194198

199+
pub fn range(lhs: Box<Expression>, rhs: Box<Expression>) -> Box<Expression> {
200+
Expression::Range(lhs, rhs).into()
201+
}
202+
195203
// -----------------------------------------------------------------------------
196204
// - Maths -
197205
// -----------------------------------------------------------------------------

anathema-templates/src/expressions/parser.rs

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@ use crate::token::{Kind, Operator, Tokens, Value};
1010
pub(crate) mod prec {
1111
pub const INITIAL: u8 = 0;
1212
pub const CONDITIONAL: u8 = 2;
13-
pub const EQUALITY: u8 = 3;
14-
pub const LOGICAL: u8 = 4;
15-
pub const SUM: u8 = 5;
16-
pub const PRODUCT: u8 = 6;
17-
pub const PREFIX: u8 = 8;
18-
pub const CALL: u8 = 10;
19-
pub const SUBCRIPT: u8 = 11;
13+
pub const RANGE: u8 = 3;
14+
pub const EQUALITY: u8 = 4;
15+
pub const LOGICAL: u8 = 5;
16+
pub const SUM: u8 = 6;
17+
pub const PRODUCT: u8 = 7;
18+
pub const PREFIX: u8 = 9;
19+
pub const CALL: u8 = 11;
20+
pub const SUBCRIPT: u8 = 12;
2021
}
2122

2223
fn get_precedence(op: Operator) -> u8 {
@@ -30,6 +31,7 @@ fn get_precedence(op: Operator) -> u8 {
3031
}
3132
Operator::EqualEqual | Operator::NotEqual => prec::EQUALITY,
3233
Operator::Or | Operator::And | Operator::Either => prec::CONDITIONAL,
34+
Operator::DotDot => prec::RANGE,
3335

3436
_ => prec::INITIAL,
3537
}
@@ -384,4 +386,18 @@ mod test {
384386
let actual = parse(input);
385387
assert_eq!(actual, "(? (? <sid 1> <sid 2>) <sid 3>)");
386388
}
389+
390+
#[test]
391+
fn range() {
392+
let input = "a..b";
393+
let actual = parse(input);
394+
assert_eq!(actual, "(.. <sid 1> <sid 2>)");
395+
}
396+
397+
#[test]
398+
fn range_2() {
399+
let input = "1 + 0..1 + 5";
400+
let actual = parse(input);
401+
assert_eq!(actual, "(.. (+ 1 0) (+ 1 5))");
402+
}
387403
}

anathema-templates/src/lexer.rs

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,36 @@ use crate::error::{ParseError, ParseErrorKind, Result};
66
use crate::strings::Strings;
77
use crate::token::{Kind, Operator, Token, Value};
88

9+
// TODO:
10+
// const EOF: char = '\0';
11+
// * change the lexer to use this one instead.
12+
// * split the spans and tokens
13+
// * spans can point to tokens, know their lines and cols
14+
// struct Cursor<'src> {
15+
// chars: Peekable<Chars<'src>>,
16+
// }
17+
18+
// impl<'src> Cursor<'src> {
19+
// fn new(chars: Peekable<Chars<'src>>) -> Self {
20+
// Self { chars }
21+
// }
22+
23+
// fn next(&mut self) -> char {
24+
// self.chars.next().unwrap_or(EOF)
25+
// }
26+
27+
// fn first(&self) -> char {
28+
// let mut iter = self.chars.clone();
29+
// iter.next().unwrap_or(EOF)
30+
// }
31+
32+
// fn second(&self) -> char {
33+
// let mut iter = self.chars.clone();
34+
// iter.next();
35+
// iter.next().unwrap_or(EOF)
36+
// }
37+
// }
38+
939
impl<'src, 'consts> Iterator for Lexer<'src, 'consts> {
1040
type Item = Result<Token>;
1141

@@ -58,6 +88,10 @@ impl<'src, 'strings> Lexer<'src, 'strings> {
5888
let _ = self.chars.next();
5989
Ok(Kind::Op(Operator::And).to_token(index))
6090
}
91+
('.', Some('.')) => {
92+
let _ = self.chars.next();
93+
Ok(Kind::Op(Operator::DotDot).to_token(index))
94+
}
6195
('|', Some('|')) => {
6296
let _ = self.chars.next();
6397
Ok(Kind::Op(Operator::Or).to_token(index))
@@ -179,14 +213,27 @@ impl<'src, 'strings> Lexer<'src, 'strings> {
179213
let mut end = index;
180214
let mut parse_float = &self.src[index..=index] == ".";
181215

182-
let _signed = &self.src[index..=index] == "-" || self.chars.peek().map(|(_, c)| *c == '-').unwrap_or(false);
216+
loop {
217+
if let Some((e, '0'..='9')) = self.chars.peek() {
218+
end = *e;
219+
self.chars.next();
220+
continue;
221+
}
183222

184-
while let Some((e, c @ ('0'..='9' | '.'))) = self.chars.peek() {
185-
if *c == '.' {
186-
parse_float = true;
223+
if let Some((_, '.')) = self.chars.peek() {
224+
let mut chars = self.chars.clone();
225+
_ = chars.next();
226+
// If the next character is a dot, then the following character has to be a number
227+
// or this is not a valid float.
228+
if let Some((e, '0'..='9')) = chars.peek() {
229+
parse_float = true;
230+
end = *e;
231+
self.chars.next();
232+
continue;
233+
}
187234
}
188-
end = *e;
189-
self.chars.next();
235+
236+
break;
190237
}
191238

192239
let input = &self.src[index..=end];
@@ -527,4 +574,10 @@ mod test {
527574
let decl = token_kind("?");
528575
assert_eq!(decl, Kind::Op(Operator::Either));
529576
}
577+
578+
#[test]
579+
fn double_dot() {
580+
let decl = token_kind("..");
581+
assert_eq!(decl, Kind::Op(Operator::DotDot));
582+
}
530583
}

anathema-templates/src/statements/const_eval.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ pub(crate) fn const_eval(expr: impl Into<Expression>, ctx: &Context<'_>) -> Opti
5858
E::Negative(expr) => E::Negative(ce!(*expr)),
5959
E::Equality(lhs, rhs, eq) => E::Equality(ce!(*lhs), ce!(*rhs), eq),
6060
E::LogicalOp(lhs, rhs, op) => E::LogicalOp(ce!(*lhs), ce!(*rhs), op),
61+
E::Range(from, to) => E::Range(ce!(*from), ce!(*to)),
6162

6263
E::Ident(_) | E::Index(..) => eval_path(expr, ctx)?,
6364
E::Variable(_) => unreachable!("const eval is not recursive so this can never happen"),
@@ -100,7 +101,7 @@ pub(crate) fn const_eval(expr: impl Into<Expression>, ctx: &Context<'_>) -> Opti
100101
#[cfg(test)]
101102
mod test {
102103
use super::*;
103-
use crate::expressions::{add, div, either, ident, index, list, mul, num, strlit, sub};
104+
use crate::expressions::{add, div, either, ident, index, list, mul, num, range, strlit, sub};
104105
use crate::statements::with_context;
105106

106107
#[test]
@@ -177,4 +178,14 @@ mod test {
177178
assert_eq!(output, *expr);
178179
});
179180
}
181+
182+
#[test]
183+
fn const_range() {
184+
with_context(|ctx| {
185+
let expr = range(num(1), num(2));
186+
187+
let output = const_eval(expr, &ctx).unwrap();
188+
assert_eq!(output, *range(num(1), num(2)));
189+
});
190+
}
180191
}

anathema-templates/src/statements/parser.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -640,7 +640,7 @@ impl Iterator for Parser<'_, '_, '_> {
640640
mod test {
641641
use super::*;
642642
use crate::error::ErrorKind;
643-
use crate::expressions::{boolean, ident, map, num, strlit, text_segments};
643+
use crate::expressions::{boolean, ident, map, num, range, strlit, text_segments};
644644
use crate::lexer::Lexer;
645645
use crate::statements::test::{
646646
associated_fun, case, component, else_stmt, eof, for_loop, global, if_else, if_stmt, load_attrib, load_value,
@@ -977,6 +977,13 @@ mod test {
977977
);
978978
}
979979

980+
#[test]
981+
fn parse_range() {
982+
let src = "for i in x..y";
983+
let mut statements = parse_ok(src);
984+
assert_eq!(statements.remove(0), for_loop(2, range(ident("x"), ident("y"))));
985+
}
986+
980987
#[test]
981988
fn parse_local_declaration() {
982989
let src = "let x = 1";

anathema-templates/src/token.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ pub enum Operator {
3232
And,
3333
Or,
3434
Dot,
35+
DotDot,
3536
Comma,
3637
Colon,
3738
Semicolon,
@@ -66,6 +67,7 @@ impl Display for Operator {
6667
Self::And => write!(f, "&&"),
6768
Self::Or => write!(f, "||"),
6869
Self::Dot => write!(f, "."),
70+
Self::DotDot => write!(f, ".."),
6971
Self::Comma => write!(f, ","),
7072
Self::Colon => write!(f, ":"),
7173
Self::Semicolon => write!(f, ";"),

0 commit comments

Comments
 (0)