Feat: lots of convenience stuff
This commit is contained in:
@@ -143,6 +143,19 @@ fn compile(tokens: &[Token], dict: &Dictionary) -> Result<Vec<Op>, String> {
|
||||
ops.push(Op::Quotation(Arc::from(quote_ops), Some(body_span)));
|
||||
} else if word == "}" {
|
||||
return Err("unexpected }".into());
|
||||
} else if word == "[" {
|
||||
let (bracket_ops, consumed, end_span) =
|
||||
compile_bracket(&tokens[i + 1..], dict)?;
|
||||
i += consumed;
|
||||
ops.push(Op::Mark);
|
||||
ops.extend(bracket_ops);
|
||||
let count_span = SourceSpan {
|
||||
start: span.start,
|
||||
end: end_span.end,
|
||||
};
|
||||
ops.push(Op::Count(Some(count_span)));
|
||||
} else if word == "]" {
|
||||
return Err("unexpected ]".into());
|
||||
} else if word == ":" {
|
||||
let (consumed, name, body) = compile_colon_def(&tokens[i + 1..], dict)?;
|
||||
i += consumed;
|
||||
@@ -211,6 +224,38 @@ fn compile_quotation(
|
||||
Ok((quote_ops, end_idx + 1, end_span))
|
||||
}
|
||||
|
||||
fn compile_bracket(
|
||||
tokens: &[Token],
|
||||
dict: &Dictionary,
|
||||
) -> Result<(Vec<Op>, usize, SourceSpan), String> {
|
||||
let mut depth = 1;
|
||||
let mut end_idx = None;
|
||||
|
||||
for (i, tok) in tokens.iter().enumerate() {
|
||||
if let Token::Word(w, _) = tok {
|
||||
match w.as_str() {
|
||||
"[" => depth += 1,
|
||||
"]" => {
|
||||
depth -= 1;
|
||||
if depth == 0 {
|
||||
end_idx = Some(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let end_idx = end_idx.ok_or("missing ]")?;
|
||||
let end_span = match &tokens[end_idx] {
|
||||
Token::Word(_, span) => *span,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let body_ops = compile(&tokens[..end_idx], dict)?;
|
||||
Ok((body_ops, end_idx + 1, end_span))
|
||||
}
|
||||
|
||||
fn token_span(tok: &Token) -> Option<SourceSpan> {
|
||||
match tok {
|
||||
Token::Int(_, s) | Token::Float(_, s) | Token::Str(_, s) | Token::Word(_, s) => Some(*s),
|
||||
|
||||
@@ -136,4 +136,8 @@ pub enum Op {
|
||||
MidiStart,
|
||||
MidiStop,
|
||||
MidiContinue,
|
||||
// Bracket syntax (mark/count for auto-counting)
|
||||
Mark,
|
||||
Count(Option<SourceSpan>),
|
||||
Index(Option<SourceSpan>),
|
||||
}
|
||||
|
||||
@@ -140,6 +140,7 @@ impl Forth {
|
||||
var_writes: &mut HashMap<String, Value>,
|
||||
) -> Result<(), String> {
|
||||
let mut pc = 0;
|
||||
let mut marks: Vec<usize> = Vec::new();
|
||||
let trace_cell = std::cell::RefCell::new(trace);
|
||||
let var_writes_cell = std::cell::RefCell::new(Some(var_writes));
|
||||
|
||||
@@ -1541,6 +1542,29 @@ impl Forth {
|
||||
.unwrap_or(0);
|
||||
stack.push(Value::Int(val as i64, None));
|
||||
}
|
||||
Op::Mark => {
|
||||
marks.push(stack.len());
|
||||
}
|
||||
Op::Count(span) => {
|
||||
let mark = marks.pop().ok_or("count without mark")?;
|
||||
stack.push(Value::Int((stack.len() - mark) as i64, *span));
|
||||
}
|
||||
Op::Index(word_span) => {
|
||||
let idx = pop_int(stack)?;
|
||||
let count = pop_int(stack)? as usize;
|
||||
if count == 0 {
|
||||
return Err("index count must be > 0".into());
|
||||
}
|
||||
let resolved_idx = ((idx % count as i64 + count as i64) % count as i64) as usize;
|
||||
if let Some(span) = word_span {
|
||||
if stack.len() >= count {
|
||||
let start = stack.len() - count;
|
||||
let selected = &stack[start + resolved_idx];
|
||||
record_resolved_from_value(&trace_cell, Some(*span), selected);
|
||||
}
|
||||
}
|
||||
drain_select_run(count, resolved_idx, stack, outputs, cmd)?;
|
||||
}
|
||||
Op::Forget => {
|
||||
let name = pop(stack)?;
|
||||
self.dict.lock().remove(name.as_str()?);
|
||||
|
||||
@@ -110,6 +110,7 @@ pub(super) fn simple_op(name: &str) -> Option<Op> {
|
||||
"mstop" => Op::MidiStop,
|
||||
"mcont" => Op::MidiContinue,
|
||||
"forget" => Op::Forget,
|
||||
"index" => Op::Index(None),
|
||||
"key!" => Op::SetKey,
|
||||
"tp" => Op::Transpose,
|
||||
"inv" => Op::Invert,
|
||||
@@ -205,7 +206,8 @@ fn attach_span(op: &mut Op, span: SourceSpan) {
|
||||
| Op::Choose(s) | Op::WChoose(s) | Op::Cycle(s) | Op::PCycle(s)
|
||||
| Op::Bounce(s) | Op::ChanceExec(s) | Op::ProbExec(s)
|
||||
| Op::Every(s)
|
||||
| Op::Bjork(s) | Op::PBjork(s) => *s = Some(span),
|
||||
| Op::Bjork(s) | Op::PBjork(s)
|
||||
| Op::Count(s) | Op::Index(s) => *s = Some(span),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +113,16 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "index",
|
||||
aliases: &[],
|
||||
category: "Probability",
|
||||
stack: "(v1..vn n idx -- selected)",
|
||||
desc: "Select item at explicit index",
|
||||
example: "[ c4 e4 g4 ] step index",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "wchoose",
|
||||
aliases: &[],
|
||||
|
||||
39
crates/ratatui/src/theme/everforest.rs
Normal file
39
crates/ratatui/src/theme/everforest.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use super::palette::Palette;
|
||||
|
||||
pub fn palette() -> Palette {
|
||||
Palette {
|
||||
bg: (45, 53, 59),
|
||||
surface: (52, 62, 68),
|
||||
surface2: (68, 80, 86),
|
||||
fg: (211, 198, 170),
|
||||
fg_dim: (135, 131, 116),
|
||||
fg_muted: (80, 80, 68),
|
||||
accent: (167, 192, 128),
|
||||
red: (230, 126, 128),
|
||||
green: (167, 192, 128),
|
||||
yellow: (219, 188, 127),
|
||||
blue: (127, 187, 179),
|
||||
purple: (214, 153, 182),
|
||||
cyan: (131, 192, 146),
|
||||
orange: (230, 152, 117),
|
||||
tempo_color: (214, 153, 182),
|
||||
bank_color: (127, 187, 179),
|
||||
pattern_color: (131, 192, 146),
|
||||
title_accent: (167, 192, 128),
|
||||
title_author: (127, 187, 179),
|
||||
secondary: (230, 152, 117),
|
||||
link_bright: [
|
||||
(167, 192, 128), (214, 153, 182), (230, 152, 117),
|
||||
(127, 187, 179), (219, 188, 127),
|
||||
],
|
||||
link_dim: [
|
||||
(56, 66, 46), (70, 52, 62), (72, 52, 42),
|
||||
(44, 64, 60), (70, 62, 44),
|
||||
],
|
||||
sparkle: [
|
||||
(167, 192, 128), (230, 152, 117), (131, 192, 146),
|
||||
(214, 153, 182), (219, 188, 127),
|
||||
],
|
||||
meter: [(148, 172, 110), (200, 170, 108), (210, 108, 110)],
|
||||
}
|
||||
}
|
||||
39
crates/ratatui/src/theme/fauve.rs
Normal file
39
crates/ratatui/src/theme/fauve.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use super::palette::Palette;
|
||||
|
||||
pub fn palette() -> Palette {
|
||||
Palette {
|
||||
bg: (28, 22, 18),
|
||||
surface: (42, 33, 26),
|
||||
surface2: (58, 46, 36),
|
||||
fg: (240, 228, 210),
|
||||
fg_dim: (170, 150, 130),
|
||||
fg_muted: (100, 82, 66),
|
||||
accent: (230, 60, 20),
|
||||
red: (220, 38, 32),
|
||||
green: (30, 170, 80),
|
||||
yellow: (255, 210, 0),
|
||||
blue: (20, 80, 200),
|
||||
purple: (170, 40, 150),
|
||||
cyan: (0, 150, 180),
|
||||
orange: (240, 120, 0),
|
||||
tempo_color: (230, 60, 20),
|
||||
bank_color: (20, 80, 200),
|
||||
pattern_color: (0, 150, 180),
|
||||
title_accent: (230, 60, 20),
|
||||
title_author: (20, 80, 200),
|
||||
secondary: (170, 40, 150),
|
||||
link_bright: [
|
||||
(230, 60, 20), (20, 80, 200), (240, 120, 0),
|
||||
(0, 150, 180), (30, 170, 80),
|
||||
],
|
||||
link_dim: [
|
||||
(72, 24, 10), (10, 28, 65), (76, 40, 6),
|
||||
(6, 48, 58), (14, 54, 28),
|
||||
],
|
||||
sparkle: [
|
||||
(230, 60, 20), (255, 210, 0), (30, 170, 80),
|
||||
(20, 80, 200), (170, 40, 150),
|
||||
],
|
||||
meter: [(26, 152, 72), (235, 190, 0), (200, 34, 28)],
|
||||
}
|
||||
}
|
||||
39
crates/ratatui/src/theme/iceberg.rs
Normal file
39
crates/ratatui/src/theme/iceberg.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use super::palette::Palette;
|
||||
|
||||
pub fn palette() -> Palette {
|
||||
Palette {
|
||||
bg: (22, 24, 33),
|
||||
surface: (30, 33, 46),
|
||||
surface2: (45, 48, 64),
|
||||
fg: (198, 200, 209),
|
||||
fg_dim: (109, 112, 126),
|
||||
fg_muted: (64, 66, 78),
|
||||
accent: (132, 160, 198),
|
||||
red: (226, 120, 120),
|
||||
green: (180, 190, 130),
|
||||
yellow: (226, 164, 120),
|
||||
blue: (132, 160, 198),
|
||||
purple: (160, 147, 199),
|
||||
cyan: (137, 184, 194),
|
||||
orange: (226, 164, 120),
|
||||
tempo_color: (160, 147, 199),
|
||||
bank_color: (132, 160, 198),
|
||||
pattern_color: (137, 184, 194),
|
||||
title_accent: (132, 160, 198),
|
||||
title_author: (160, 147, 199),
|
||||
secondary: (226, 164, 120),
|
||||
link_bright: [
|
||||
(132, 160, 198), (160, 147, 199), (226, 164, 120),
|
||||
(137, 184, 194), (180, 190, 130),
|
||||
],
|
||||
link_dim: [
|
||||
(45, 55, 70), (55, 50, 68), (70, 55, 42),
|
||||
(46, 62, 66), (58, 62, 44),
|
||||
],
|
||||
sparkle: [
|
||||
(132, 160, 198), (226, 164, 120), (180, 190, 130),
|
||||
(160, 147, 199), (226, 120, 120),
|
||||
],
|
||||
meter: [(160, 175, 115), (210, 150, 105), (200, 105, 105)],
|
||||
}
|
||||
}
|
||||
39
crates/ratatui/src/theme/jaipur.rs
Normal file
39
crates/ratatui/src/theme/jaipur.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use super::palette::Palette;
|
||||
|
||||
pub fn palette() -> Palette {
|
||||
Palette {
|
||||
bg: (30, 24, 22),
|
||||
surface: (44, 36, 32),
|
||||
surface2: (60, 48, 42),
|
||||
fg: (238, 222, 200),
|
||||
fg_dim: (165, 145, 125),
|
||||
fg_muted: (95, 78, 65),
|
||||
accent: (210, 90, 100),
|
||||
red: (200, 44, 52),
|
||||
green: (30, 160, 120),
|
||||
yellow: (240, 180, 20),
|
||||
blue: (60, 60, 180),
|
||||
purple: (150, 50, 120),
|
||||
cyan: (0, 155, 155),
|
||||
orange: (220, 120, 50),
|
||||
tempo_color: (210, 90, 100),
|
||||
bank_color: (60, 60, 180),
|
||||
pattern_color: (0, 155, 155),
|
||||
title_accent: (210, 90, 100),
|
||||
title_author: (60, 60, 180),
|
||||
secondary: (220, 120, 50),
|
||||
link_bright: [
|
||||
(210, 90, 100), (60, 60, 180), (220, 120, 50),
|
||||
(0, 155, 155), (30, 160, 120),
|
||||
],
|
||||
link_dim: [
|
||||
(66, 30, 34), (22, 22, 58), (70, 40, 18),
|
||||
(6, 48, 48), (12, 50, 38),
|
||||
],
|
||||
sparkle: [
|
||||
(210, 90, 100), (240, 180, 20), (30, 160, 120),
|
||||
(60, 60, 180), (150, 50, 120),
|
||||
],
|
||||
meter: [(26, 144, 106), (222, 164, 18), (184, 40, 46)],
|
||||
}
|
||||
}
|
||||
@@ -8,17 +8,22 @@ mod catppuccin_mocha;
|
||||
mod dracula;
|
||||
mod eden;
|
||||
mod ember;
|
||||
mod everforest;
|
||||
mod georges;
|
||||
mod fairyfloss;
|
||||
mod gruvbox_dark;
|
||||
mod hot_dog_stand;
|
||||
mod iceberg;
|
||||
mod jaipur;
|
||||
mod kanagawa;
|
||||
mod letz_light;
|
||||
mod monochrome_black;
|
||||
mod monochrome_white;
|
||||
mod monokai;
|
||||
mod nord;
|
||||
mod fauve;
|
||||
mod pitch_black;
|
||||
mod tropicalia;
|
||||
mod rose_pine;
|
||||
mod tokyo_night;
|
||||
pub mod transform;
|
||||
@@ -51,6 +56,11 @@ pub const THEMES: &[ThemeEntry] = &[
|
||||
ThemeEntry { id: "Ember", label: "Ember", palette: ember::palette },
|
||||
ThemeEntry { id: "Eden", label: "Eden", palette: eden::palette },
|
||||
ThemeEntry { id: "Georges", label: "Georges", palette: georges::palette },
|
||||
ThemeEntry { id: "Iceberg", label: "Iceberg", palette: iceberg::palette },
|
||||
ThemeEntry { id: "Everforest", label: "Everforest", palette: everforest::palette },
|
||||
ThemeEntry { id: "Fauve", label: "Fauve", palette: fauve::palette },
|
||||
ThemeEntry { id: "Tropicalia", label: "Tropicalia", palette: tropicalia::palette },
|
||||
ThemeEntry { id: "Jaipur", label: "Jaipur", palette: jaipur::palette },
|
||||
];
|
||||
|
||||
thread_local! {
|
||||
|
||||
39
crates/ratatui/src/theme/tropicalia.rs
Normal file
39
crates/ratatui/src/theme/tropicalia.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use super::palette::Palette;
|
||||
|
||||
pub fn palette() -> Palette {
|
||||
Palette {
|
||||
bg: (20, 26, 22),
|
||||
surface: (30, 40, 34),
|
||||
surface2: (44, 56, 48),
|
||||
fg: (235, 225, 200),
|
||||
fg_dim: (155, 145, 120),
|
||||
fg_muted: (85, 80, 62),
|
||||
accent: (230, 50, 120),
|
||||
red: (240, 70, 70),
|
||||
green: (80, 200, 50),
|
||||
yellow: (255, 195, 0),
|
||||
blue: (0, 160, 200),
|
||||
purple: (180, 60, 180),
|
||||
cyan: (0, 200, 170),
|
||||
orange: (255, 140, 30),
|
||||
tempo_color: (230, 50, 120),
|
||||
bank_color: (0, 160, 200),
|
||||
pattern_color: (0, 200, 170),
|
||||
title_accent: (230, 50, 120),
|
||||
title_author: (0, 160, 200),
|
||||
secondary: (255, 140, 30),
|
||||
link_bright: [
|
||||
(230, 50, 120), (0, 160, 200), (255, 140, 30),
|
||||
(0, 200, 170), (80, 200, 50),
|
||||
],
|
||||
link_dim: [
|
||||
(72, 20, 40), (6, 50, 64), (80, 44, 12),
|
||||
(6, 62, 54), (26, 62, 18),
|
||||
],
|
||||
sparkle: [
|
||||
(230, 50, 120), (255, 195, 0), (80, 200, 50),
|
||||
(0, 160, 200), (180, 60, 180),
|
||||
],
|
||||
meter: [(70, 182, 44), (236, 178, 0), (220, 62, 62)],
|
||||
}
|
||||
}
|
||||
@@ -68,8 +68,10 @@ pub fn handle_key(ctx: &mut InputContext, key: KeyEvent) -> InputResult {
|
||||
|
||||
if ctx.app.ui.show_title {
|
||||
ctx.dispatch(AppCommand::HideTitle);
|
||||
if matches!(key.code, KeyCode::Char('q') | KeyCode::Esc) {
|
||||
return InputResult::Continue;
|
||||
}
|
||||
}
|
||||
|
||||
ctx.dispatch(AppCommand::ClearStatus);
|
||||
|
||||
|
||||
@@ -60,15 +60,13 @@ pub fn convert_egui_events(ctx: &egui::Context) -> Vec<KeyEvent> {
|
||||
let mut events = Vec::new();
|
||||
|
||||
for event in &ctx.input(|i| i.events.clone()) {
|
||||
if let Some(key_event) = convert_event(event) {
|
||||
events.push(key_event);
|
||||
}
|
||||
convert_event(event, &mut events);
|
||||
}
|
||||
|
||||
events
|
||||
}
|
||||
|
||||
fn convert_event(event: &egui::Event) -> Option<KeyEvent> {
|
||||
fn convert_event(event: &egui::Event, events: &mut Vec<KeyEvent>) {
|
||||
match event {
|
||||
egui::Event::Key {
|
||||
key,
|
||||
@@ -77,33 +75,39 @@ fn convert_event(event: &egui::Event) -> Option<KeyEvent> {
|
||||
..
|
||||
} => {
|
||||
if !*pressed {
|
||||
return None;
|
||||
return;
|
||||
}
|
||||
let mods = convert_modifiers(*modifiers);
|
||||
// For character keys without ctrl/alt, let Event::Text handle it
|
||||
if is_character_key(*key) && !mods.intersects(KeyModifiers::CONTROL | KeyModifiers::ALT)
|
||||
{
|
||||
return None;
|
||||
// For character keys, only handle Ctrl+key (without Alt) as shortcuts.
|
||||
// All other character input (bare, Shift, Alt/Option, AltGr=Ctrl+Alt)
|
||||
// defers to Event::Text which respects the active keyboard layout.
|
||||
if is_character_key(*key) {
|
||||
let ctrl_without_alt =
|
||||
mods.contains(KeyModifiers::CONTROL) && !mods.contains(KeyModifiers::ALT);
|
||||
if !ctrl_without_alt {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if let Some(code) = convert_key(*key) {
|
||||
events.push(KeyEvent::new(code, mods));
|
||||
}
|
||||
let code = convert_key(*key)?;
|
||||
Some(KeyEvent::new(code, mods))
|
||||
}
|
||||
egui::Event::Text(text) => {
|
||||
if text.len() == 1 {
|
||||
let c = text.chars().next()?;
|
||||
for c in text.chars() {
|
||||
if !c.is_control() {
|
||||
return Some(KeyEvent::new(KeyCode::Char(c), KeyModifiers::empty()));
|
||||
events.push(KeyEvent::new(KeyCode::Char(c), KeyModifiers::empty()));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
// egui intercepts Ctrl+C/V/X and converts them to these high-level events
|
||||
// instead of passing through raw Key events (see egui issue #4065).
|
||||
// Synthesize the equivalent KeyEvent so the application's input handler receives them.
|
||||
egui::Event::Copy => Some(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL)),
|
||||
egui::Event::Cut => Some(KeyEvent::new(KeyCode::Char('x'), KeyModifiers::CONTROL)),
|
||||
egui::Event::Paste(_) => Some(KeyEvent::new(KeyCode::Char('v'), KeyModifiers::CONTROL)),
|
||||
_ => None,
|
||||
egui::Event::Copy => events.push(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL)),
|
||||
egui::Event::Cut => events.push(KeyEvent::new(KeyCode::Char('x'), KeyModifiers::CONTROL)),
|
||||
egui::Event::Paste(_) => {
|
||||
events.push(KeyEvent::new(KeyCode::Char('v'), KeyModifiers::CONTROL));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,6 +140,14 @@ fn convert_key(key: egui::Key) -> Option<KeyCode> {
|
||||
egui::Key::F10 => KeyCode::F(10),
|
||||
egui::Key::F11 => KeyCode::F(11),
|
||||
egui::Key::F12 => KeyCode::F(12),
|
||||
egui::Key::F13 => KeyCode::F(13),
|
||||
egui::Key::F14 => KeyCode::F(14),
|
||||
egui::Key::F15 => KeyCode::F(15),
|
||||
egui::Key::F16 => KeyCode::F(16),
|
||||
egui::Key::F17 => KeyCode::F(17),
|
||||
egui::Key::F18 => KeyCode::F(18),
|
||||
egui::Key::F19 => KeyCode::F(19),
|
||||
egui::Key::F20 => KeyCode::F(20),
|
||||
egui::Key::A => KeyCode::Char('a'),
|
||||
egui::Key::B => KeyCode::Char('b'),
|
||||
egui::Key::C => KeyCode::Char('c'),
|
||||
@@ -183,6 +195,13 @@ fn convert_key(key: egui::Key) -> Option<KeyCode> {
|
||||
egui::Key::Backslash => KeyCode::Char('\\'),
|
||||
egui::Key::Backtick => KeyCode::Char('`'),
|
||||
egui::Key::Quote => KeyCode::Char('\''),
|
||||
egui::Key::Colon => KeyCode::Char(':'),
|
||||
egui::Key::Pipe => KeyCode::Char('|'),
|
||||
egui::Key::Questionmark => KeyCode::Char('?'),
|
||||
egui::Key::Exclamationmark => KeyCode::Char('!'),
|
||||
egui::Key::OpenCurlyBracket => KeyCode::Char('{'),
|
||||
egui::Key::CloseCurlyBracket => KeyCode::Char('}'),
|
||||
egui::Key::Plus => KeyCode::Char('+'),
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
@@ -252,5 +271,12 @@ fn is_character_key(key: egui::Key) -> bool {
|
||||
| egui::Key::Backslash
|
||||
| egui::Key::Backtick
|
||||
| egui::Key::Quote
|
||||
| egui::Key::Colon
|
||||
| egui::Key::Pipe
|
||||
| egui::Key::Questionmark
|
||||
| egui::Key::Exclamationmark
|
||||
| egui::Key::OpenCurlyBracket
|
||||
| egui::Key::CloseCurlyBracket
|
||||
| egui::Key::Plus
|
||||
)
|
||||
}
|
||||
|
||||
@@ -465,7 +465,7 @@ fn render_footer(frame: &mut Frame, app: &App, area: Rect) {
|
||||
Page::Main => vec![
|
||||
("Space", "Play"),
|
||||
("Enter", "Edit"),
|
||||
("t", "Toggle"),
|
||||
("t", "On/Off"),
|
||||
("Tab", "Samples"),
|
||||
("?", "Keys"),
|
||||
],
|
||||
|
||||
@@ -83,3 +83,99 @@ fn cycle_zero_count_error() {
|
||||
fn choose_zero_count_error() {
|
||||
expect_error("1 2 3 0 choose", "choose count must be > 0");
|
||||
}
|
||||
|
||||
// Bracket syntax tests
|
||||
|
||||
#[test]
|
||||
fn bracket_cycle() {
|
||||
let ctx = ctx_with(|c| c.runs = 0);
|
||||
let f = run_ctx("[ 10 20 30 ] cycle", &ctx);
|
||||
assert_eq!(stack_int(&f), 10);
|
||||
|
||||
let ctx = ctx_with(|c| c.runs = 1);
|
||||
let f = run_ctx("[ 10 20 30 ] cycle", &ctx);
|
||||
assert_eq!(stack_int(&f), 20);
|
||||
|
||||
let ctx = ctx_with(|c| c.runs = 2);
|
||||
let f = run_ctx("[ 10 20 30 ] cycle", &ctx);
|
||||
assert_eq!(stack_int(&f), 30);
|
||||
|
||||
let ctx = ctx_with(|c| c.runs = 3);
|
||||
let f = run_ctx("[ 10 20 30 ] cycle", &ctx);
|
||||
assert_eq!(stack_int(&f), 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bracket_with_quotations() {
|
||||
let ctx = ctx_with(|c| c.runs = 0);
|
||||
let f = run_ctx("5 [ { 3 + } { 5 + } ] cycle", &ctx);
|
||||
assert_eq!(stack_int(&f), 8);
|
||||
|
||||
let ctx = ctx_with(|c| c.runs = 1);
|
||||
let f = run_ctx("5 [ { 3 + } { 5 + } ] cycle", &ctx);
|
||||
assert_eq!(stack_int(&f), 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bracket_nested() {
|
||||
let ctx = ctx_with(|c| { c.runs = 0; c.iter = 0; });
|
||||
let f = run_ctx("[ [ 10 20 ] cycle [ 30 40 ] cycle ] pcycle", &ctx);
|
||||
assert_eq!(stack_int(&f), 10);
|
||||
|
||||
let ctx = ctx_with(|c| { c.runs = 0; c.iter = 1; });
|
||||
let f = run_ctx("[ [ 10 20 ] cycle [ 30 40 ] cycle ] pcycle", &ctx);
|
||||
assert_eq!(stack_int(&f), 30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bracket_with_generator() {
|
||||
let ctx = ctx_with(|c| c.runs = 0);
|
||||
let f = run_ctx("[ 1 4 .. ] cycle", &ctx);
|
||||
assert_eq!(stack_int(&f), 1);
|
||||
|
||||
let ctx = ctx_with(|c| c.runs = 3);
|
||||
let f = run_ctx("[ 1 4 .. ] cycle", &ctx);
|
||||
assert_eq!(stack_int(&f), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stray_bracket_error() {
|
||||
expect_error("10 ] cycle", "unexpected ]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unclosed_bracket_error() {
|
||||
expect_error("[ 10 20", "missing ]");
|
||||
}
|
||||
|
||||
// Index tests
|
||||
|
||||
#[test]
|
||||
fn index_basic() {
|
||||
expect_int("10 20 30 3 1 index", 20);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn index_with_brackets() {
|
||||
expect_int("[ 10 20 30 ] 1 index", 20);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn index_modulo_wraps() {
|
||||
expect_int("[ 10 20 30 ] 5 index", 30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn index_negative_wraps() {
|
||||
expect_int("[ 10 20 30 ] -1 index", 30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn index_with_quotation() {
|
||||
expect_int("5 [ { 3 + } { 5 + } ] 0 index", 8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn index_zero_count_error() {
|
||||
expect_error("0 0 index", "index count must be > 0");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user