From 7e4f8d0e4692a31c994f0caa115eaed862f48417 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Forment?= Date: Thu, 29 Jan 2026 01:10:53 +0100 Subject: [PATCH] Cleaning language --- crates/forth/src/compiler.rs | 81 ++++++++++++++++-------------------- crates/forth/src/vm.rs | 2 +- crates/forth/src/words.rs | 4 +- src/engine/sequencer.rs | 1 + src/views/engine_view.rs | 1 + src/views/help_view.rs | 2 +- src/views/highlight.rs | 18 ++------ tests/forth/definitions.rs | 4 +- tests/forth/errors.rs | 8 ++-- tests/forth/harness.rs | 4 ++ 10 files changed, 57 insertions(+), 68 deletions(-) diff --git a/crates/forth/src/compiler.rs b/crates/forth/src/compiler.rs index 91c0625..29e68c0 100644 --- a/crates/forth/src/compiler.rs +++ b/crates/forth/src/compiler.rs @@ -8,8 +8,6 @@ enum Token { Float(f64, SourceSpan), Str(String, SourceSpan), Word(String, SourceSpan), - QuoteStart(usize), - QuoteEnd(usize), } pub(super) fn compile_script(input: &str, dict: &Dictionary) -> Result, String> { @@ -44,25 +42,21 @@ fn tokenize(input: &str) -> Vec { continue; } - if c == '(' { - while let Some(&(_, ch)) = chars.peek() { - chars.next(); - if ch == ')' { - break; + if c == ';' { + chars.next(); // consume first ; + if let Some(&(_, ';')) = chars.peek() { + // ;; starts a comment to end of line + chars.next(); // consume second ; + while let Some(&(_, ch)) = chars.peek() { + if ch == '\n' { + break; + } + chars.next(); } + continue; } - continue; - } - - if c == '{' { - chars.next(); - tokens.push(Token::QuoteStart(pos)); - continue; - } - - if c == '}' { - chars.next(); - tokens.push(Token::QuoteEnd(pos)); + // single ; is a word, create token + tokens.push(Token::Word(";".to_string(), SourceSpan { start: pos, end: pos + 1 })); continue; } @@ -70,7 +64,7 @@ fn tokenize(input: &str) -> Vec { let mut word = String::new(); let mut end = start; while let Some(&(i, ch)) = chars.peek() { - if ch.is_whitespace() || ch == '{' || ch == '}' { + if ch.is_whitespace() { break; } end = i + ch.len_utf8(); @@ -101,18 +95,16 @@ fn compile(tokens: &[Token], dict: &Dictionary) -> Result, String> { Token::Int(n, span) => ops.push(Op::PushInt(*n, Some(*span))), Token::Float(f, span) => ops.push(Op::PushFloat(*f, Some(*span))), Token::Str(s, span) => ops.push(Op::PushStr(s.clone(), Some(*span))), - Token::QuoteStart(start_pos) => { - let (quote_ops, consumed, end_pos) = compile_quotation(&tokens[i + 1..], dict)?; - i += consumed; - let body_span = SourceSpan { start: *start_pos, end: end_pos + 1 }; - ops.push(Op::Quotation(quote_ops, Some(body_span))); - } - Token::QuoteEnd(_) => { - return Err("unexpected }".into()); - } Token::Word(w, span) => { let word = w.as_str(); - if word == ":" { + if word == "{" { + let (quote_ops, consumed, end_span) = compile_quotation(&tokens[i + 1..], dict)?; + i += consumed; + let body_span = SourceSpan { start: span.start, end: end_span.end }; + ops.push(Op::Quotation(quote_ops, Some(body_span))); + } else if word == "}" { + return Err("unexpected }".into()); + } else if word == ":" { let (consumed, name, body) = compile_colon_def(&tokens[i + 1..], dict)?; i += consumed; dict.lock().unwrap().insert(name, body); @@ -163,38 +155,38 @@ fn is_list_end(word: &str) -> bool { matches!(word, "]" | ">" | ">>") } -fn compile_quotation(tokens: &[Token], dict: &Dictionary) -> Result<(Vec, usize, usize), String> { +fn compile_quotation(tokens: &[Token], dict: &Dictionary) -> Result<(Vec, usize, SourceSpan), String> { let mut depth = 1; let mut end_idx = None; for (i, tok) in tokens.iter().enumerate() { - match tok { - Token::QuoteStart(_) => depth += 1, - Token::QuoteEnd(_) => { - depth -= 1; - if depth == 0 { - end_idx = Some(i); - break; + 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 byte_pos = match &tokens[end_idx] { - Token::QuoteEnd(pos) => *pos, + let end_span = match &tokens[end_idx] { + Token::Word(_, span) => *span, _ => unreachable!(), }; let quote_ops = compile(&tokens[..end_idx], dict)?; - Ok((quote_ops, end_idx + 1, byte_pos)) + Ok((quote_ops, end_idx + 1, end_span)) } fn token_span(tok: &Token) -> Option { match tok { Token::Int(_, s) | Token::Float(_, s) | Token::Str(_, s) | Token::Word(_, s) => Some(*s), - Token::QuoteStart(p) => Some(SourceSpan { start: *p, end: *p + 1 }), - Token::QuoteEnd(p) => Some(SourceSpan { start: *p, end: *p + 1 }), } } @@ -218,7 +210,6 @@ fn compile_colon_def(tokens: &[Token], dict: &Dictionary) -> Result<(usize, Stri let semi_pos = semi_pos.ok_or("missing ';' in word definition")?; let body_tokens = &tokens[1..semi_pos]; let body_ops = compile(body_tokens, dict)?; - // consumed = name + body + semicolon Ok((semi_pos + 1, name, body_ops)) } diff --git a/crates/forth/src/vm.rs b/crates/forth/src/vm.rs index 53eae43..08c9057 100644 --- a/crates/forth/src/vm.rs +++ b/crates/forth/src/vm.rs @@ -559,7 +559,7 @@ impl Forth { Op::Pick => { let idx_i = stack.pop().ok_or("stack underflow")?.as_int()?; if idx_i < 0 { - return Err(format!("pick index must be >= 0, got {}", idx_i)); + return Err(format!("pick index must be >= 0, got {idx_i}")); } let idx = idx_i as usize; let mut quots: Vec = Vec::new(); diff --git a/crates/forth/src/words.rs b/crates/forth/src/words.rs index 0db7b37..6392109 100644 --- a/crates/forth/src/words.rs +++ b/crates/forth/src/words.rs @@ -2300,5 +2300,7 @@ pub(super) fn compile_word( return true; } - false + // Unrecognized token becomes a string + ops.push(Op::PushStr(name.to_string(), span)); + true } diff --git a/src/engine/sequencer.rs b/src/engine/sequencer.rs index dfd048d..dbc100b 100644 --- a/src/engine/sequencer.rs +++ b/src/engine/sequencer.rs @@ -197,6 +197,7 @@ impl AudioState { } } +#[allow(clippy::too_many_arguments)] pub fn spawn_sequencer( link: Arc, playing: Arc, diff --git a/src/views/engine_view.rs b/src/views/engine_view.rs index ffedcaf..b78646a 100644 --- a/src/views/engine_view.rs +++ b/src/views/engine_view.rs @@ -187,6 +187,7 @@ fn render_devices(frame: &mut Frame, app: &App, area: Rect) { ); } +#[allow(clippy::too_many_arguments)] fn render_device_column( frame: &mut Frame, area: Rect, diff --git a/src/views/help_view.rs b/src/views/help_view.rs index 1cb7f03..9f0b1db 100644 --- a/src/views/help_view.rs +++ b/src/views/help_view.rs @@ -242,7 +242,7 @@ fn parse_markdown(md: &str) -> Vec> { match line { Line::Normal(composite) if composite.style == CompositeStyle::Code => { code_line_nr += 1; - let raw: String = composite.compounds.iter().map(|c| &*c.src).collect(); + let raw: String = composite.compounds.iter().map(|c: &minimad::Compound| c.src).collect(); let mut spans = vec![ Span::styled(format!(" {code_line_nr:>2} "), code_border_style()), Span::styled("│ ", code_border_style()), diff --git a/src/views/highlight.rs b/src/views/highlight.rs index 2c16887..d178c81 100644 --- a/src/views/highlight.rs +++ b/src/views/highlight.rs @@ -97,24 +97,14 @@ pub fn tokenize_line(line: &str) -> Vec { continue; } - if c == '(' { - let end = line.len(); - let comment_end = line[start..] - .find(')') - .map(|i| start + i + 1) - .unwrap_or(end); + if c == ';' && chars.peek().map(|(_, ch)| *ch) == Some(';') { + // ;; starts a comment to end of line tokens.push(Token { start, - end: comment_end, + end: line.len(), kind: TokenKind::Comment, }); - while let Some((i, _)) = chars.peek() { - if *i >= comment_end { - break; - } - chars.next(); - } - continue; + break; } if c == '"' { diff --git a/tests/forth/definitions.rs b/tests/forth/definitions.rs index 5637aee..5c58382 100644 --- a/tests/forth/definitions.rs +++ b/tests/forth/definitions.rs @@ -46,8 +46,8 @@ fn word_defined_in_one_forth_available_in_same() { } #[test] -fn unknown_word_errors() { - expect_error("nosuchword", "unknown word"); +fn unknown_word_becomes_string() { + expect_str("nosuchword", "nosuchword"); } #[test] diff --git a/tests/forth/errors.rs b/tests/forth/errors.rs index 6748644..7a22f42 100644 --- a/tests/forth/errors.rs +++ b/tests/forth/errors.rs @@ -11,8 +11,8 @@ fn whitespace_only() { } #[test] -fn unknown_word() { - expect_error("foobar", "unknown word"); +fn unknown_word_becomes_string() { + expect_str("foobar", "foobar"); } #[test] @@ -22,12 +22,12 @@ fn string_not_number() { #[test] fn comment_ignored() { - expect_int("1 (this is a comment) 2 +", 3); + expect_int("1 ;; this is a comment\n2 +", 3); } #[test] fn multiline_comment() { - expect_int("1 (multi\nline\ncomment) 2 +", 3); + expect_int("1 ;; first comment\n;; entire line comment\n2 +", 3); } #[test] diff --git a/tests/forth/harness.rs b/tests/forth/harness.rs index 0c0fcec..6a209de 100644 --- a/tests/forth/harness.rs +++ b/tests/forth/harness.rs @@ -88,6 +88,10 @@ pub fn expect_int(script: &str, expected: i64) { expect_stack(script, &[Value::Int(expected, None)]); } +pub fn expect_str(script: &str, expected: &str) { + expect_stack(script, &[Value::Str(expected.to_string(), None)]); +} + pub fn expect_float(script: &str, expected: f64) { let f = run(script); let stack = f.stack();