From 0b9385874be037962d1144f2c3267577cd08edce Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Sun, 25 Oct 2026 21:30:51 -0400 Subject: [PATCH] refactor: split complex render, CSS and WGSL checker functions (S3776, S107) Extract helpers from curves_graph, draw_group, parse_css and the WGSL bitwise checker so each stays under the cognitive complexity limit. curves_graph now takes its pencil and natural flags as one CurveMode value, which brings it to seven parameters. --- crates/gpu/src/tests_wgsl_grammar.rs | 180 +++++++++++-------- crates/svg/src/lib.rs | 62 ++++--- crates/svg/src/raster.rs | 170 ++++++++++-------- crates/ui-egui/src/panels/effect_controls.rs | 2 +- crates/ui-egui/src/panels/fx_editors.rs | 4 +- crates/ui-egui/src/panels/fx_widgets.rs | 145 +++++++++------ 6 files changed, 327 insertions(+), 236 deletions(-) diff --git a/crates/gpu/src/tests_wgsl_grammar.rs b/crates/gpu/src/tests_wgsl_grammar.rs index 18fef7a0f..7153e4010 100644 --- a/crates/gpu/src/tests_wgsl_grammar.rs +++ b/crates/gpu/src/tests_wgsl_grammar.rs @@ -22,9 +22,22 @@ enum Tok<'a> { const BITWISE: [&str; 3] = ["&", "|", "^"]; +const OPS: [&str; 18] = ["&&", "||", "==", "!=", "<=", ">=", "<<", ">>", "<", ">", "+", "-", "*", "/", "%", "&", "|", "^"]; + /// Drop comments and template argument lists (`vec3`, `array>`), whose /// angle brackets are not relational operators. fn strip(src: &str) -> String { + let mut out = drop_comments(src); + loop { + let (next, changed) = drop_template_lists_once(&out); + out = next; + if !changed { + return out; + } + } +} + +fn drop_comments(src: &str) -> String { let mut out = String::with_capacity(src.len()); let b = src.as_bytes(); let mut i = 0; @@ -45,31 +58,98 @@ fn strip(src: &str) -> String { i += 1; } } - loop { - let mut changed = false; - let mut next = String::with_capacity(out.len()); - let c: Vec = out.chars().collect(); - let mut i = 0; - while i < c.len() { - if c[i] == '<' && i > 0 && (c[i - 1].is_alphanumeric() || c[i - 1] == '_') { - let mut j = i + 1; - while j < c.len() && (c[j].is_alphanumeric() || c[j] == '_' || c[j] == ',' || c[j] == ' ') { - j += 1; - } - if j < c.len() && c[j] == '>' { - i = j + 1; - changed = true; - continue; - } + out +} + +/// One pass over `out` dropping every template argument list. Returns whether one was dropped. +fn drop_template_lists_once(out: &str) -> (String, bool) { + let mut changed = false; + let mut next = String::with_capacity(out.len()); + let c: Vec = out.chars().collect(); + let mut i = 0; + while i < c.len() { + if let Some(end) = template_list_end(&c, i) { + i = end; + changed = true; + continue; + } + next.push(c[i]); + i += 1; + } + (next, changed) +} + +/// If `c[i]` opens a template argument list after an identifier, the index just past its `>`. +fn template_list_end(c: &[char], i: usize) -> Option { + if c[i] != '<' || i == 0 || !(c[i - 1].is_alphanumeric() || c[i - 1] == '_') { + return None; + } + let mut j = i + 1; + while j < c.len() && (c[j].is_alphanumeric() || c[j] == '_' || c[j] == ',' || c[j] == ' ') { + j += 1; + } + (j < c.len() && c[j] == '>').then_some(j + 1) +} + +/// Scans the token starting at `*at` (not whitespace) and advances `*at` past it. +fn next_token<'a>(s: &'a str, at: &mut usize) -> Tok<'a> { + let b = s.as_bytes(); + let ch = b[*at]; + let mut i = *at; + let tok = if ch.is_ascii_alphanumeric() || ch == b'_' || ch == b'.' { + // Identifier, member access or number (`1e-6`, `0x9E3779B9u`). + let start = i; + while i < b.len() && (b[i].is_ascii_alphanumeric() || b[i] == b'_' || b[i] == b'.') { + if (b[i] == b'e' || b[i] == b'E') + && b[start].is_ascii_digit() + && !s[start..i].starts_with("0x") + && i + 1 < b.len() + && (b[i + 1] == b'-' || b[i + 1] == b'+') + { + i += 1; } - next.push(c[i]); i += 1; } - out = next; - if !changed { - return out; + Tok::Operand + } else if ch == b'(' || ch == b'[' { + i += 1; + Tok::Open + } else if ch == b')' || ch == b']' { + i += 1; + Tok::Close + } else if b[i..].starts_with(b"->") || b[i..].starts_with(b"++") || b[i..].starts_with(b"--") { + i += 2; + Tok::Sep + } else if matches!(ch, b',' | b';' | b'{' | b'}' | b':' | b'@') { + i += 1; + Tok::Sep + } else if let Some(op) = OPS.iter().find(|op| b[i..].starts_with(op.as_bytes())) { + i += op.len(); + if b.get(i) == Some(&b'=') && !matches!(*op, "==" | "!=" | "<=" | ">=") { + i += 1; + Tok::Sep // compound assignment + } else { + Tok::Op(op) } + } else if ch == b'=' { + i += 1; + Tok::Sep + } else { + i += 1; + Tok::Prefix + }; + *at = i; + tok +} + +/// Records a binary operator at the current parenthesis level, reporting it if it mixes with +/// one already seen there. +fn note_operator<'a>(level: &mut Vec<&'a str>, op: &'a str, line: usize, out: &mut Vec<(usize, String)>) { + let mixes = |a: &str, b: &str| a != b && (BITWISE.contains(&a) || BITWISE.contains(&b)); + if let Some(other) = level.iter().find(|o| mixes(o, op)) { + out.push((line, format!("mixing '{other}' and '{op}' requires parentheses"))); } + level.push(op); } /// `(line, message)` for every bitwise operator mixed with another binary operator at @@ -82,7 +162,6 @@ fn violations(src: &str) -> Vec<(usize, String)> { let mut levels: Vec> = vec![vec![]]; let mut prev = Tok::Sep; let (mut i, mut line) = (0, 1); - const OPS: [&str; 18] = ["&&", "||", "==", "!=", "<=", ">=", "<<", ">>", "<", ">", "+", "-", "*", "/", "%", "&", "|", "^"]; while i < b.len() { let ch = b[i]; if ch == b'\n' { @@ -94,53 +173,14 @@ fn violations(src: &str) -> Vec<(usize, String)> { i += 1; continue; } - let tok = if ch.is_ascii_alphanumeric() || ch == b'_' || ch == b'.' { - // Identifier, member access or number (`1e-6`, `0x9E3779B9u`). - let start = i; - while i < b.len() && (b[i].is_ascii_alphanumeric() || b[i] == b'_' || b[i] == b'.') { - if (b[i] == b'e' || b[i] == b'E') - && b[start].is_ascii_digit() - && !s[start..i].starts_with("0x") - && i + 1 < b.len() - && (b[i + 1] == b'-' || b[i + 1] == b'+') - { - i += 1; - } - i += 1; - } - Tok::Operand - } else if ch == b'(' || ch == b'[' { - i += 1; - levels.push(vec![]); - Tok::Open - } else if ch == b')' || ch == b']' { - i += 1; - if levels.len() > 1 { - levels.pop(); - } - Tok::Close - } else if b[i..].starts_with(b"->") || b[i..].starts_with(b"++") || b[i..].starts_with(b"--") { - i += 2; - Tok::Sep - } else if matches!(ch, b',' | b';' | b'{' | b'}' | b':' | b'@') { - i += 1; - Tok::Sep - } else if let Some(op) = OPS.iter().find(|op| b[i..].starts_with(op.as_bytes())) { - i += op.len(); - if b.get(i) == Some(&b'=') && !matches!(*op, "==" | "!=" | "<=" | ">=") { - i += 1; - Tok::Sep // compound assignment - } else { - Tok::Op(op) - } - } else if ch == b'=' { - i += 1; - Tok::Sep - } else { - i += 1; - Tok::Prefix - }; + let tok = next_token(&s, &mut i); match tok { + Tok::Open => levels.push(vec![]), + Tok::Close => { + if levels.len() > 1 { + levels.pop(); + } + } Tok::Sep => { if let Some(l) = levels.last_mut() { l.clear(); @@ -148,11 +188,7 @@ fn violations(src: &str) -> Vec<(usize, String)> { } Tok::Op(op) if matches!(prev, Tok::Operand | Tok::Close) => { let level = levels.last_mut().expect("level"); - let mixes = |a: &str, b: &str| a != b && (BITWISE.contains(&a) || BITWISE.contains(&b)); - if let Some(other) = level.iter().find(|o| mixes(o, op)) { - out.push((line, format!("mixing '{other}' and '{op}' requires parentheses"))); - } - level.push(op); + note_operator(level, op, line, &mut out); } _ => {} } diff --git a/crates/svg/src/lib.rs b/crates/svg/src/lib.rs index 60f39adc6..fe686dd6b 100644 --- a/crates/svg/src/lib.rs +++ b/crates/svg/src/lib.rs @@ -470,6 +470,39 @@ fn strip_comments(s: &str) -> String { out } +/// Splits one simple selector (`tag.class#id`) into its tag, classes and id. +fn parse_compound(one: &str) -> (Option, Vec, Option) { + let mut tag = None; + let mut classes = vec![]; + let mut id = None; + let mut cur = String::new(); + let mut kind = 't'; + let flush = |kind: char, cur: &mut String, tag: &mut Option, classes: &mut Vec, id: &mut Option| { + if !cur.is_empty() { + match kind { + '.' => classes.push(std::mem::take(cur)), + '#' => *id = Some(std::mem::take(cur)), + _ => { + if cur != "*" { + *tag = Some(std::mem::take(cur)); + } + cur.clear(); + } + } + } + }; + for c in one.chars() { + if c == '.' || c == '#' { + flush(kind, &mut cur, &mut tag, &mut classes, &mut id); + kind = c; + } else { + cur.push(c); + } + } + flush(kind, &mut cur, &mut tag, &mut classes, &mut id); + (tag, classes, id) +} + /// Add the rules of one `