278 lines
5.9 KiB
Rust
278 lines
5.9 KiB
Rust
use ratatui::style::{Color, Style};
|
|
|
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
|
pub enum TokenKind {
|
|
Number,
|
|
String,
|
|
Comment,
|
|
Keyword,
|
|
StackOp,
|
|
Operator,
|
|
Sound,
|
|
Param,
|
|
Context,
|
|
Default,
|
|
}
|
|
|
|
impl TokenKind {
|
|
pub fn style(self) -> Style {
|
|
match self {
|
|
TokenKind::Number => Style::default().fg(Color::Rgb(255, 180, 100)),
|
|
TokenKind::String => Style::default().fg(Color::Rgb(150, 220, 150)),
|
|
TokenKind::Comment => Style::default().fg(Color::Rgb(100, 100, 100)),
|
|
TokenKind::Keyword => Style::default().fg(Color::Rgb(220, 120, 220)),
|
|
TokenKind::StackOp => Style::default().fg(Color::Rgb(120, 180, 220)),
|
|
TokenKind::Operator => Style::default().fg(Color::Rgb(200, 200, 130)),
|
|
TokenKind::Sound => Style::default().fg(Color::Rgb(100, 220, 200)),
|
|
TokenKind::Param => Style::default().fg(Color::Rgb(180, 150, 220)),
|
|
TokenKind::Context => Style::default().fg(Color::Rgb(220, 180, 120)),
|
|
TokenKind::Default => Style::default().fg(Color::Rgb(200, 200, 200)),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct Token {
|
|
pub start: usize,
|
|
pub end: usize,
|
|
pub kind: TokenKind,
|
|
}
|
|
|
|
const STACK_OPS: &[&str] = &["dup", "drop", "swap", "over", "rot", "nip", "tuck"];
|
|
const OPERATORS: &[&str] = &[
|
|
"+", "-", "*", "/", "mod", "neg", "abs", "min", "max", "=", "<>", "<", ">", "<=", ">=", "and",
|
|
"or", "not",
|
|
];
|
|
const KEYWORDS: &[&str] = &[
|
|
"if", "else", "then", "emit", "get", "set", "rand", "rrand", "seed", "cycle", "choose",
|
|
"chance", "[", "]",
|
|
];
|
|
const SOUND: &[&str] = &["sound", "s"];
|
|
const CONTEXT: &[&str] = &[
|
|
"step", "beat", "bank", "pattern", "tempo", "phase", "slot", "runs",
|
|
];
|
|
const PARAMS: &[&str] = &[
|
|
"time",
|
|
"repeat",
|
|
"dur",
|
|
"gate",
|
|
"freq",
|
|
"detune",
|
|
"speed",
|
|
"glide",
|
|
"pw",
|
|
"spread",
|
|
"mult",
|
|
"warp",
|
|
"mirror",
|
|
"harmonics",
|
|
"timbre",
|
|
"morph",
|
|
"begin",
|
|
"end",
|
|
"gain",
|
|
"postgain",
|
|
"velocity",
|
|
"pan",
|
|
"attack",
|
|
"decay",
|
|
"sustain",
|
|
"release",
|
|
"lpf",
|
|
"lpq",
|
|
"lpe",
|
|
"lpa",
|
|
"lpd",
|
|
"lps",
|
|
"lpr",
|
|
"hpf",
|
|
"hpq",
|
|
"hpe",
|
|
"hpa",
|
|
"hpd",
|
|
"hps",
|
|
"hpr",
|
|
"bpf",
|
|
"bpq",
|
|
"bpe",
|
|
"bpa",
|
|
"bpd",
|
|
"bps",
|
|
"bpr",
|
|
"ftype",
|
|
"penv",
|
|
"patt",
|
|
"pdec",
|
|
"psus",
|
|
"prel",
|
|
"vib",
|
|
"vibmod",
|
|
"vibshape",
|
|
"fm",
|
|
"fmh",
|
|
"fmshape",
|
|
"fme",
|
|
"fma",
|
|
"fmd",
|
|
"fms",
|
|
"fmr",
|
|
"am",
|
|
"amdepth",
|
|
"amshape",
|
|
"rm",
|
|
"rmdepth",
|
|
"rmshape",
|
|
"phaser",
|
|
"phaserdepth",
|
|
"phasersweep",
|
|
"phasercenter",
|
|
"flanger",
|
|
"flangerdepth",
|
|
"flangerfeedback",
|
|
"chorus",
|
|
"chorusdepth",
|
|
"chorusdelay",
|
|
"comb",
|
|
"combfreq",
|
|
"combfeedback",
|
|
"combdamp",
|
|
"coarse",
|
|
"crush",
|
|
"fold",
|
|
"wrap",
|
|
"distort",
|
|
"distortvol",
|
|
"delay",
|
|
"delaytime",
|
|
"delayfeedback",
|
|
"delaytype",
|
|
"verb",
|
|
"verbdecay",
|
|
"verbdamp",
|
|
"verbpredelay",
|
|
"verbdiff",
|
|
"voice",
|
|
"orbit",
|
|
"note",
|
|
"size",
|
|
"n",
|
|
"cut",
|
|
"reset",
|
|
];
|
|
|
|
pub fn tokenize_line(line: &str) -> Vec<Token> {
|
|
let mut tokens = Vec::new();
|
|
let mut chars = line.char_indices().peekable();
|
|
|
|
while let Some((start, c)) = chars.next() {
|
|
if c.is_whitespace() {
|
|
continue;
|
|
}
|
|
|
|
if c == '(' {
|
|
let end = line.len();
|
|
let comment_end = line[start..]
|
|
.find(')')
|
|
.map(|i| start + i + 1)
|
|
.unwrap_or(end);
|
|
tokens.push(Token {
|
|
start,
|
|
end: comment_end,
|
|
kind: TokenKind::Comment,
|
|
});
|
|
while let Some((i, _)) = chars.peek() {
|
|
if *i >= comment_end {
|
|
break;
|
|
}
|
|
chars.next();
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if c == '"' {
|
|
let mut end = start + 1;
|
|
while let Some((i, ch)) = chars.next() {
|
|
end = i + ch.len_utf8();
|
|
if ch == '"' {
|
|
break;
|
|
}
|
|
}
|
|
tokens.push(Token {
|
|
start,
|
|
end,
|
|
kind: TokenKind::String,
|
|
});
|
|
continue;
|
|
}
|
|
|
|
let mut end = start + c.len_utf8();
|
|
while let Some((i, ch)) = chars.peek() {
|
|
if ch.is_whitespace() {
|
|
break;
|
|
}
|
|
end = *i + ch.len_utf8();
|
|
chars.next();
|
|
}
|
|
|
|
let word = &line[start..end];
|
|
let kind = classify_word(word);
|
|
tokens.push(Token { start, end, kind });
|
|
}
|
|
|
|
tokens
|
|
}
|
|
|
|
fn classify_word(word: &str) -> TokenKind {
|
|
if word.parse::<f64>().is_ok() || word.parse::<i64>().is_ok() {
|
|
return TokenKind::Number;
|
|
}
|
|
|
|
if STACK_OPS.contains(&word) {
|
|
return TokenKind::StackOp;
|
|
}
|
|
|
|
if OPERATORS.contains(&word) {
|
|
return TokenKind::Operator;
|
|
}
|
|
|
|
if KEYWORDS.contains(&word) {
|
|
return TokenKind::Keyword;
|
|
}
|
|
|
|
if SOUND.contains(&word) {
|
|
return TokenKind::Sound;
|
|
}
|
|
|
|
if CONTEXT.contains(&word) {
|
|
return TokenKind::Context;
|
|
}
|
|
|
|
if PARAMS.contains(&word) {
|
|
return TokenKind::Param;
|
|
}
|
|
|
|
TokenKind::Default
|
|
}
|
|
|
|
pub fn highlight_line(line: &str) -> Vec<(Style, String)> {
|
|
let tokens = tokenize_line(line);
|
|
let mut result = Vec::new();
|
|
let mut last_end = 0;
|
|
|
|
for token in tokens {
|
|
if token.start > last_end {
|
|
result.push((
|
|
TokenKind::Default.style(),
|
|
line[last_end..token.start].to_string(),
|
|
));
|
|
}
|
|
result.push((token.kind.style(), line[token.start..token.end].to_string()));
|
|
last_end = token.end;
|
|
}
|
|
|
|
if last_end < line.len() {
|
|
result.push((TokenKind::Default.style(), line[last_end..].to_string()));
|
|
}
|
|
|
|
result
|
|
}
|