initial commit

This commit is contained in:
2025-11-03 19:18:59 -05:00
commit 44e145d1b1
6 changed files with 348 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

7
Cargo.lock generated Normal file
View File

@@ -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"

6
Cargo.toml Normal file
View File

@@ -0,0 +1,6 @@
[package]
name = "scott"
version = "0.1.0"
edition = "2024"
[dependencies]

39
data Normal file
View File

@@ -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

74
src/main.rs Normal file
View File

@@ -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);
}
}

221
src/parse.rs Normal file
View File

@@ -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<Term>,
pub result: String,
}
#[derive(Debug)]
pub struct System {
pub equations: Vec<Equation>,
pub order: Vec<String>,
pub map: Map,
}
enum Token {
Sign(Sign),
Var(String),
Coeff(String),
Equals,
}
impl System {
pub fn parse(lines: impl Iterator<Item = Result<String, std::io::Error>>) -> 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<String> {
line.split(' ')
.map(|s| {
let mut s = s.to_string();
reorder(&mut s);
s
})
.collect()
}
pub type Map = HashMap<String, String>;
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<Chars>) -> Option<Term> {
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<String> = 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<Chars>) -> Option<Token> {
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<Chars>) -> 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<char> = 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,
}
}
}