Skip to content

Commit

Permalink
opt: closing parenthesis optimization
Browse files Browse the repository at this point in the history
  • Loading branch information
rpitasky committed Sep 6, 2024
1 parent f9496bd commit 0a2e5e8
Show file tree
Hide file tree
Showing 19 changed files with 199 additions and 25 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sin(cos(|LABC(X)))*~(1+2)+3
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
C and |LSH(A)=N
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
(1+1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
(2+(2+2
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"(333)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
("4444
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
(5+5)+(5+5
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
("6"+6
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
(1+1)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
(2+(2+2))
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"(333)"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
("4444")
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
(5+5)+(5+5)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
("6"+6)

This file was deleted.

15 changes: 10 additions & 5 deletions ti-basic-optimizer/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,16 @@ fn main() {
Priority::Neutral
};

if let Ok(program) = loaded {
if cfg!(feature = "round-trip") {
let version = titokens::version::LATEST.clone();
let version = titokens::version::LATEST.clone();
let config = Config {
mrov: version.clone(),
priority,
};
};
let tokenizer = Tokenizer::new(version.clone(), "en");

if let Ok(mut program) = loaded {
if cfg!(feature = "round-trip") {

let tokenizer = Tokenizer::new(version.clone(), "en");
let a = program.reconstruct(&config);
let a_program = Program::from_tokens(
&mut Tokens::from_vec(a.clone(), Some(version.clone())),
Expand All @@ -108,6 +109,10 @@ fn main() {
println!("{}", tokenizer.stringify(&b));
} else {
println!("Loaded program successfully!");
program.optimize(&config);

let tokens = program.reconstruct(&config);
println!("{}", tokenizer.stringify(&tokens));
}
} else {
loaded.unwrap();
Expand Down
159 changes: 148 additions & 11 deletions ti-basic-optimizer/src/optimize/expressions/parenthesis_optimization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
use std::mem;
use titokens::Token;

use crate::parse::components::{ListIndex, MatrixIndex};
use crate::parse::commands::{Command, ControlFlow, Generic};
use crate::parse::components::{ListIndex, MatrixIndex, StoreTarget};
use crate::parse::{
components::{Operand, Operator},
expression::Expression,
Expand All @@ -16,6 +17,28 @@ impl Expression {
///
/// Returns the number of parentheses that can be removed.
pub fn optimize_parentheses(&mut self) -> u16 {
/* there's some extra weirdness with lists/Ans & implicit mul that I'm not yet considering.
* `Ans(1+X)` is a list access if Ans is a list. We avoid this with `(1+X)Ans` or `Ans*(1+X)`
* If X has parentheses to be removed *and those parentheses get removed*, `Ans*(1+X)` is
* shorter. Otherwise, `(1+X)Ans` is shorter or equivalent.
*
* One more note...
* If `Ans(1+X)` is a list access or ambiguously written, we conservatively default to
* parsing it as a list access- if it is actually an implicit multiplication then it will
* always execute the same way as it did in the input. Defaulting to parsing as an
* implicit multiplication may not result in the same code, because several passes depend on
* multiplication being commutative.
*/

/* more opt ideas: consider functions where changing the arguments does not change the result
* (eg. min, max), and some functions on list literals where changing the order of the
* list elements will not change the output.
*/

/* even more opt ideas: stuff like /2 or /5 or /10 can be .5*, .2*, .1*
* also x^^-1* if x is not 2, 5, 10, etc
*/

match self {
Expression::Operator(Operator::Binary(binop)) => {
let mut right = binop.right.optimize_parentheses();
Expand Down Expand Up @@ -81,25 +104,139 @@ impl Expression {
1 + col.optimize_parentheses()
}

_=> 0,
Expression::Operand(Operand::StringLiteral(_) | Operand::ListLiteral(_)) => 1,

_ => 0,
}
}

/// Removes closing parenthesis, braces, and quotes from the provided reconstructed expression.
///
/// Returns true if the expression ends in an unclosed string.
pub fn strip_closing_parenthesis(expr: &mut Vec<Token>) -> bool {
// a little tricky; `")))` should not have anything removed
let mut length_guess = 0;
let mut in_string = false;

let mut unclosed_string = false;
// 123"123)")

for (idx, tok) in expr.iter().enumerate() {
match tok {
Token::OneByte(0x2A) => {
// "
in_string = !in_string;

if !in_string {
length_guess = idx - 1;
unclosed_string = true;
}
}

Token::OneByte(0x11 | 0x07 | 0x09) if !in_string => {
// ) ] }
// nothing
}

_ => {
length_guess = idx;

unclosed_string = in_string;
}
}
}

expr.truncate(length_guess + 1);

unclosed_string
}
}

impl Command {
pub fn optimize_parentheses(&mut self) {
match self {
Command::Generic(Generic { arguments, .. }) => {
if let Some(last) = arguments.last_mut() {
last.optimize_parentheses();
}
}

Command::ControlFlow(control_flow) => {
match control_flow {
ControlFlow::If(expr) | ControlFlow::While(expr) | ControlFlow::Repeat(expr) => {
expr.optimize_parentheses();
}
ControlFlow::For(for_loop) => {
if let Some(step) = &mut for_loop.step {
step.optimize_parentheses();
} else {
for_loop.end.optimize_parentheses();
}
}
ControlFlow::IsGt(is_ds) | ControlFlow::DsLt(is_ds) => {
is_ds.condition.optimize_parentheses();
}

_ => { /* no closing parentheses savings possible */ }
}
}

Command::Expression(expr) => {
expr.optimize_parentheses();
}
Command::Store(expr, target) => {
expr.optimize_parentheses();
match target {
StoreTarget::ListIndex(index) => {
index.index.optimize_parentheses();
}
StoreTarget::MatrixIndex(index) => {
index.col.optimize_parentheses();
}

_ => { /* no closing parentheses possible */ }
}
}

_ => { /* no closing parentheses possible */ }
}
}
}

#[cfg(test)]
mod tests {
use crate::parse::Parse;
use test_files::load_test_data;

use crate::parse::{Parse, Reconstruct};
use test_files::{load_test_data, test_version};
use super::*;

#[test]
fn parenthesis_optimization() {
let mut tokens = load_test_data("/snippets/parsing/formulas/parenthesis-optimization.txt");
let mut expr = Expression::parse(tokens.next().unwrap(), &mut tokens)
.ok()
.flatten()
.unwrap();
let cases = [("1.txt", 3), ("2.txt", 1)];
for (case_name, expected_savings) in cases {
let mut tokens = load_test_data(&("/snippets/optimize/parentheses/maximization/".to_string() + case_name));
let mut expr = Expression::parse(tokens.next().unwrap(), &mut tokens)
.unwrap()
.unwrap();

let savings = expr.optimize_parentheses();
assert_eq!(expected_savings, savings);

let reconstructed = expr.reconstruct(&test_version().into());
let mut optimized = reconstructed.clone();
Expression::strip_closing_parenthesis(&mut optimized);

assert_eq!(expr.optimize_parentheses(), 2);
assert_eq!(expected_savings, (reconstructed.len() - optimized.len()) as u16);
}
}

#[test]
fn strip_closing_parentheses() {
for case in ["1.txt", "2.txt", "3.txt", "4.txt", "5.txt", "6.txt"] {
let mut actual = load_test_data(&("/snippets/optimize/parentheses/stripping/before/".to_string() + case)).collect::<Vec<_>>();
let expected = load_test_data(&("/snippets/optimize/parentheses/stripping/after/".to_string() + case)).collect::<Vec<_>>();
Expression::strip_closing_parenthesis(&mut actual);

assert_eq!(actual, expected);
}
}
}
12 changes: 12 additions & 0 deletions ti-basic-optimizer/src/optimize/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
use crate::Config;
use crate::parse::Program;

mod expressions;
mod strategies;
mod control_flow;

#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
pub enum Priority {
Expand All @@ -11,3 +15,11 @@ pub enum Priority {
/// Disables optimizations which would increase the program's size.
Size,
}

impl Program {
pub fn optimize(&mut self, config: &Config) {
for command in self.lines.iter_mut() {
command.optimize_parentheses();
}
}
}
23 changes: 15 additions & 8 deletions ti-basic-optimizer/src/parse/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,20 +69,27 @@ impl Parse for Command {

impl Reconstruct for Command {
fn reconstruct(&self, config: &Config) -> Vec<Token> {
match self {
let mut line = match self {
Command::ControlFlow(x) => x.reconstruct(config),
Command::Generic(x) => x.reconstruct(config),
Command::DelVarChain(x) => x.reconstruct(config),
Command::SetUpEditor(x) => x.reconstruct(config),
Command::Expression(x) => x.reconstruct(config),
Command::ProgramInvocation(x) => x.reconstruct(config),
Command::Store(x, target) => x
.reconstruct(config)
.into_iter()
.chain(once(Token::OneByte(0x04)))
.chain(target.reconstruct(config))
.collect(),
}
Command::Store(x, target) => {
let mut expr = x
.reconstruct(config);
Expression::strip_closing_parenthesis(&mut expr);
expr.into_iter()
.chain(once(Token::OneByte(0x04)))
.chain(target.reconstruct(config))
.collect()
},
};

Expression::strip_closing_parenthesis(&mut line);

line
}
}

Expand Down

0 comments on commit 0a2e5e8

Please sign in to comment.