commit 44e145d1b159734d907b62f44e56f39e522b0e19 Author: shadow cat Date: Mon Nov 3 19:18:59 2025 -0500 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..34b3ac5 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "scott" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..75b348b --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "scott" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/data b/data new file mode 100644 index 0000000..fec6cd1 --- /dev/null +++ b/data @@ -0,0 +1,39 @@ +RAx = 0 +RAy + REy = L +REy = 1/2 L +RAx - AB - AK cos a = 0 +RAy - AK sin a = 0 +AB + BK cos d - BH cos d - BC = 0 +BK sin d + BH sin d = -1/3 L +BC + CJ cos d - CG cos d - CD = 0 +CJ sin d + CG sin d = -1/3 L +CD + ID cos d - DF cos d - DE = 0 +ID sin d + DF sin d = -1/3 L +DE + FE cos a = 0 +REy - FE sin a = 0 +DF cos d + GF cos b - EF cos a = 0 +DF sin d - GF sin b + EF sin a = 0 +CG cos d + GH cos c - FG cos b = 0 +CG sin d - GH sin c + FG sin b = 0 +IH + BH cos d - HG cos c = 0 +# BH sin d + HG sin c = 0 +# IJ cos c - ID cos d - IH = 0 +JI sin c + ID sin d = 0 +# JK cos b - CJ cos d - IJ cos c = 0 +AK cos a - BK cos d - JK cos b = 0 + +order: RAx RAy REy AB BC CD DE EF FG GH HI IJ JK KA BK CJ DI BH CG DF + +map: cos a = Ca +map: cos b = Cb +map: cos c = Cc +map: cos d = Cd +map: sin a = Sa +map: sin b = Sb +map: sin c = Sc +map: sin d = Sd + +map: -1/3 L = -(1/3)*load +map: 1/2 L = (1/2)*load +map: L = load +map: matrix = Coef diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..c6ea2a0 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,74 @@ +use crate::parse::System; + +mod parse; + +fn main() { + let stdin = std::io::stdin(); + let system = System::parse(stdin.lines()); + print_matlab(&system); +} + +fn print_matlab(system: &System) { + let mut matrix = Vec::new(); + let mut col_len = vec![0; system.order.len()]; + for eq in &system.equations { + for term in &eq.terms { + if !system.order.contains(&term.var) { + panic!("unknown var {}", term.var); + } + } + let coeffs: Vec<_> = system + .order + .iter() + .enumerate() + .map(|(i, var)| { + let res = eq + .terms + .iter() + .find_map(|t| if t.var == *var { Some(t.str()) } else { None }) + .unwrap_or("0".to_string()); + col_len[i] = col_len[i].max(res.len()); + res + }) + .collect(); + matrix.push(coeffs); + } + + let col_len = col_len.into_iter().max().unwrap(); + + print!("% "); + print_row(&system.order, col_len); + println!(); + + println!("{} = [", system.name("matrix")); + for row in &matrix { + print!(" "); + print_row(row, col_len); + println!(";"); + } + println!("];"); + println!(); + + print!("{} = [", system.name("constants")); + for (i, eq) in system.equations.iter().enumerate() { + print!("{};", eq.result); + if i != system.equations.len() - 1 { + print!(" "); + } + } + println!("];"); +} + +fn print_row(row: &[String], col_len: usize) { + let print_col = |str| { + print!("{str:>0$}", col_len); + }; + let mut iter = row.iter(); + if let Some(first) = iter.next() { + print_col(first); + } + for col in iter { + print!(" "); + print_col(col); + } +} diff --git a/src/parse.rs b/src/parse.rs new file mode 100644 index 0000000..75fa4be --- /dev/null +++ b/src/parse.rs @@ -0,0 +1,221 @@ +use std::{ + collections::{HashMap, HashSet}, + iter::Peekable, + str::Chars, +}; + +const TRIG_FNS: [&str; 3] = ["cos", "sin", "tan"]; +const ORDER_PREFIX: &str = "order: "; +const MAP_PREFIX: &str = "map: "; + +#[derive(Debug)] +pub struct Term { + pub sign: Sign, + pub var: String, + pub coeff: String, +} + +#[derive(Clone, Copy, Debug)] +pub enum Sign { + Pos, + Neg, +} + +#[derive(Debug)] +pub struct Equation { + pub terms: Vec, + pub result: String, +} + +#[derive(Debug)] +pub struct System { + pub equations: Vec, + pub order: Vec, + pub map: Map, +} + +enum Token { + Sign(Sign), + Var(String), + Coeff(String), + Equals, +} + +impl System { + pub fn parse(lines: impl Iterator>) -> Self { + let mut equations = Vec::new(); + let mut order = Vec::new(); + let mut map = HashMap::new(); + for line in lines { + let line = line.unwrap(); + let line = line.trim(); + if line.is_empty() || line.starts_with('#') { + continue; + } + if let Some(line) = line.strip_prefix(ORDER_PREFIX) { + order = parse_order(line); + continue; + } + if let Some(line) = line.strip_prefix(MAP_PREFIX) { + let (from, to) = line.split_once('=').expect("bad map"); + map.insert(from.trim().to_string(), to.trim().to_string()); + continue; + } + let eq = parse_equation(line); + equations.push(eq); + } + for eq in &mut equations { + for term in &mut eq.terms { + if let Some(to) = map.get(&term.coeff) { + term.coeff = to.clone(); + } + } + if let Some(to) = map.get(&eq.result) { + eq.result = to.clone(); + } + } + if order.is_empty() { + let mut vars = HashSet::new(); + for eq in &equations { + for term in &eq.terms { + vars.insert(term.var.clone()); + } + } + order = vars.into_iter().collect(); + order.sort(); + } + Self { + equations, + order, + map, + } + } + + pub fn name(&self, name: &'static str) -> &str { + self.map.get(name).map_or(name, |v| v) + } +} + +pub fn parse_order(line: &str) -> Vec { + line.split(' ') + .map(|s| { + let mut s = s.to_string(); + reorder(&mut s); + s + }) + .collect() +} + +pub type Map = HashMap; + +pub fn parse_equation(line: &str) -> Equation { + let mut iter = line.chars().peekable(); + let mut terms = Vec::new(); + let mut next = next_token(&mut iter).unwrap(); + loop { + let term = parse_term(&mut next, &mut iter).expect("no equals"); + terms.push(term); + if let Token::Equals = next { + break; + } + } + let mut result: String = iter.collect(); + result = result.trim().to_string(); + Equation { terms, result } +} + +fn parse_term(next: &mut Token, iter: &mut Peekable) -> Option { + let mut sign = Sign::Pos; + if let Token::Equals = next { + panic!("unexpected equals"); + } + if let &mut Token::Sign(s) = next { + sign = s; + *next = next_token(iter)?; + } + let mut coeff: Option = None; + let mut var = None; + loop { + match next { + Token::Sign(..) => break, + Token::Equals => break, + Token::Var(v) => { + if var.is_some() { + panic!("two vars in term") + } else { + var = Some(v.clone()); + } + } + Token::Coeff(c) => { + if let Some(coeff) = &mut coeff { + *coeff += c; + } else { + coeff = Some(c.clone()); + } + } + } + *next = next_token(iter)?; + } + Some(Term { + sign, + var: var.expect("no var in term"), + coeff: coeff.unwrap_or("1".to_string()), + }) +} + +fn next_token(iter: &mut Peekable) -> Option { + let mut next = *iter.peek()?; + while next.is_whitespace() { + iter.next(); + next = *iter.peek()?; + } + 'outer: { + let token = match next { + '+' => Token::Sign(Sign::Pos), + '-' => Token::Sign(Sign::Neg), + '=' => Token::Equals, + _ => break 'outer, + }; + iter.next(); + return Some(token); + } + let mut word = next_word(iter); + if TRIG_FNS.iter().any(|f| word == *f) { + word.push(' '); + word += &next_word(iter); + } + Some(if word.chars().next()?.is_ascii_uppercase() { + Token::Var(word) + } else { + Token::Coeff(word) + }) +} + +fn next_word(iter: &mut Peekable) -> String { + let mut word = String::new(); + while let Some(&c) = iter.peek() + && !(c.is_whitespace() || c == '-' || c == '+') + { + word.push(c); + iter.next(); + } + reorder(&mut word); + word +} + +pub fn reorder(word: &mut String) { + if word.chars().all(|c| c.is_ascii_uppercase()) { + let mut chars: Vec = word.chars().collect(); + chars.sort(); + *word = chars.into_iter().collect(); + } +} + +impl Term { + pub fn str(&self) -> String { + match self.sign { + Sign::Pos => self.coeff.clone(), + Sign::Neg => "-".to_string() + &self.coeff, + } + } +}