Feat: UI / UX

This commit is contained in:
2026-02-16 01:22:40 +01:00
parent 23c7abb145
commit 211e71f5a9
37 changed files with 1045 additions and 64 deletions

View File

@@ -160,6 +160,12 @@ fn compile(tokens: &[Token], dict: &Dictionary) -> Result<Vec<Op>, String> {
ops.push(Op::Branch(else_ops.len()));
ops.extend(else_ops);
}
} else if word == "case" {
let (case_ops, consumed) = compile_case(&tokens[i + 1..], dict)?;
i += consumed;
ops.extend(case_ops);
} else if word == "of" || word == "endof" || word == "endcase" {
return Err(format!("unexpected '{word}'"));
} else if !compile_word(word, Some(*span), &mut ops, dict) {
return Err(format!("unknown word: {word}"));
}
@@ -300,3 +306,69 @@ fn compile_if(
Ok((then_ops, else_ops, then_pos + 1, then_span, else_span))
}
fn compile_case(tokens: &[Token], dict: &Dictionary) -> Result<(Vec<Op>, usize), String> {
let mut depth = 1;
let mut endcase_pos = None;
let mut clauses: Vec<(usize, usize)> = Vec::new();
let mut last_of = None;
for (i, tok) in tokens.iter().enumerate() {
if let Token::Word(w, _) = tok {
match w.as_str() {
"case" => depth += 1,
"endcase" => {
depth -= 1;
if depth == 0 {
endcase_pos = Some(i);
break;
}
}
"of" if depth == 1 => last_of = Some(i),
"endof" if depth == 1 => {
let of_pos = last_of.ok_or("'endof' without matching 'of'")?;
clauses.push((of_pos, i));
last_of = None;
}
_ => {}
}
}
}
let endcase_pos = endcase_pos.ok_or("missing 'endcase'")?;
let mut ops = Vec::new();
let mut branch_fixups: Vec<usize> = Vec::new();
let mut clause_start = 0;
for &(of_pos, endof_pos) in &clauses {
let test_ops = compile(&tokens[clause_start..of_pos], dict)?;
let body_ops = compile(&tokens[of_pos + 1..endof_pos], dict)?;
ops.extend(test_ops);
ops.push(Op::Over);
ops.push(Op::Eq);
ops.push(Op::BranchIfZero(body_ops.len() + 2, None, None));
ops.push(Op::Drop);
ops.extend(body_ops);
branch_fixups.push(ops.len());
ops.push(Op::Branch(0));
clause_start = endof_pos + 1;
}
let default_tokens = &tokens[clause_start..endcase_pos];
if !default_tokens.is_empty() {
let default_ops = compile(default_tokens, dict)?;
ops.extend(default_ops);
}
ops.push(Op::Drop);
let end = ops.len();
for pos in branch_fixups {
ops[pos] = Op::Branch(end - pos - 1);
}
Ok((ops, endcase_pos + 1))
}

View File

@@ -64,6 +64,7 @@ pub enum Op {
Emit,
Get,
Set,
SetKeep,
GetContext(&'static str),
Rand(Option<SourceSpan>),
ExpRand(Option<SourceSpan>),

View File

@@ -637,6 +637,16 @@ impl Forth {
.expect("var_writes taken")
.insert(name, val);
}
Op::SetKeep => {
let name = pop(stack)?;
let name = name.as_str()?.to_string();
let val = stack.last().ok_or("Stack underflow")?.clone();
var_writes_cell
.borrow_mut()
.as_mut()
.expect("var_writes taken")
.insert(name, val);
}
Op::GetContext(name) => {
let val = match *name {

View File

@@ -272,6 +272,14 @@ pub(crate) fn compile_word(
}
}
if let Some(var_name) = name.strip_prefix(',') {
if !var_name.is_empty() {
ops.push(Op::PushStr(Arc::from(var_name), span));
ops.push(Op::SetKeep);
return true;
}
}
if let Some(midi) = parse_note_name(name) {
ops.push(Op::PushInt(midi, span));
return true;

View File

@@ -558,6 +558,16 @@ pub(super) const WORDS: &[Word] = &[
compile: Simple,
varargs: false,
},
Word {
name: ",<var>",
aliases: &[],
category: "Variables",
stack: "(val -- val)",
desc: "Store value in variable, keep on stack",
example: "440 ,freq => 440",
compile: Simple,
varargs: false,
},
// Definitions
Word {
name: ":",