Skip to content

Commit

Permalink
analyze: find labels usage/decl
Browse files Browse the repository at this point in the history
  • Loading branch information
rpitasky committed Sep 6, 2024
1 parent 4c95567 commit e095a43
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 6 deletions.
5 changes: 5 additions & 0 deletions test-files/snippets/analysis/labels.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Lbl RE
Menu("MY TERRIBLE GAME","PLAY",PL,"EXIT",0,"PLEASE LEAVE",0,"RESTART",RE
Lbl PL
Goto 0
Lbl 0
87 changes: 87 additions & 0 deletions ti-basic-optimizer/src/analyze/labels.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
use std::collections::BTreeMap;

use crate::parse::commands::{control_flow::Menu, ControlFlow};
use crate::parse::{
commands::{Command, LabelName},
Program,
};

impl Program {
/// Compute a mapping from label names to the index of the line where the label was defined.
pub fn label_declarations(&self) -> BTreeMap<LabelName, usize> {
let mut declarations = BTreeMap::new();

for (idx, line) in self.lines.iter().enumerate() {
if let Command::ControlFlow(ControlFlow::Lbl(name)) = line {
declarations.insert(*name, idx);
}
}

declarations
}

/// Compute a mapping from label names to label usages (namely, `Goto `, `Menu(`)
///
/// If a `Menu(` references the same label name more than once, the line will appear in the
/// usages that many times.
pub fn label_usages(&self) -> BTreeMap<LabelName, Vec<usize>> {
let mut usages: BTreeMap<LabelName, Vec<usize>> = BTreeMap::new();

for (idx, line) in self.lines.iter().enumerate() {
match line {
Command::ControlFlow(ControlFlow::Goto(label)) => {
usages.entry(*label).or_default().push(idx);
}

Command::ControlFlow(ControlFlow::Menu(Menu { option_labels, .. })) => {
for label in option_labels {
usages.entry(*label).or_default().push(idx);
}
}

_ => {}
}
}

usages
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::label_name;
use test_files::{load_test_data, test_version};
use titokens::Tokenizer;

fn program() -> Program {
let mut tokens = load_test_data("/snippets/analysis/labels.txt");
let tokenizer = Tokenizer::new(test_version(), "en");

Program::from_tokens(&mut tokens, &tokenizer)
}

#[test]
fn label_usages() {
let test_program = program();

let mut expected = BTreeMap::new();
expected.insert(label_name!('R' 'E'), vec![1]);
expected.insert(label_name!('P' 'L'), vec![1]);
expected.insert(label_name!('0'), vec![1, 1, 3]);

assert_eq!(test_program.label_usages(), expected)
}

#[test]
fn label_declarations() {
let test_program = program();

let mut expected = BTreeMap::new();
expected.insert(label_name!('R' 'E'), 0usize);
expected.insert(label_name!('P' 'L'), 2);
expected.insert(label_name!('0'), 4);

assert_eq!(test_program.label_declarations(), expected)
}
}
1 change: 1 addition & 0 deletions ti-basic-optimizer/src/analyze/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mod labels;
60 changes: 55 additions & 5 deletions ti-basic-optimizer/src/parse/commands/control_flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,68 @@ mod for_loop;
mod isds;
mod menu;

pub use {for_loop::ForLoop, isds::IsDs, menu::Menu};

use crate::error_reporting::{expect_some, next_or_err, LineReport};
use crate::parse::{
commands::control_flow::{for_loop::ForLoop, isds::IsDs, menu::Menu},
expression::Expression,
Parse, Reconstruct,
};
use crate::parse::{expression::Expression, Parse, Reconstruct};
use crate::Config;
use std::iter::once;
use titokens::{Token, Tokens};

#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
pub struct LabelName(u16);
#[macro_export]
/// Call with one or two char literals for the letters of the label, or "theta" for theta.
///
/// Example:
/// ```
/// use ti_basic_optimizer::label_name;
/// let lbl_12 = label_name!('1' '2');
/// let lbl_thetatheta = label_name!("theta" "theta");
macro_rules! label_name {
($first: tt $second: tt) => {
$crate::parse::commands::LabelName::new(label_name!(<internal> $first), Some(label_name!(<internal> $second)))
};

($first: tt) => {
$crate::parse::commands::LabelName::new(label_name!(<internal> $first), None)
};

(<internal> "theta") => { 0x5B };
(<internal> $x: literal) => {$x as u8};
}

impl LabelName {
/// Construct a label with the given name.
///
/// Example:
/// ```
/// # use ti_basic_optimizer::parse::commands::LabelName;
/// // corresponds to the label defined by `Lbl PL`
/// let lbl_pl = LabelName::new('P' as u8, Some('L' as u8));
/// // corresponds to the label defined by `Lbl theta`
/// let lbl_theta = LabelName::new(1 + 'Z' as u8, None);
/// assert_ne!(lbl_theta, lbl_pl);
/// ```
///
/// Consider using the [`label_name`] macro:
/// ```
/// use ti_basic_optimizer::label_name;
/// # use ti_basic_optimizer::parse::commands::LabelName;
/// # let lbl_pl = LabelName::new('P' as u8, Some('L' as u8));
/// # let lbl_theta = LabelName::new(1 + 'Z' as u8, None);
/// assert_eq!(lbl_pl, label_name!('P' 'L'));
/// assert_eq!(lbl_theta, label_name!("theta"));
/// ```
pub fn new(first: u8, second: Option<u8>) -> Self {
assert!(matches!(first, 0x41..=0x5B | 0x30..=0x39));
if let Some(second) = second {
assert!(matches!(second, 0x41..=0x5B | 0x30..=0x39))
}

LabelName((first as u16) << 8 | second.unwrap_or(0) as u16)
}
}

impl Parse for LabelName {
fn parse(token: Token, more: &mut Tokens) -> Result<Option<Self>, LineReport> {
Expand Down
2 changes: 1 addition & 1 deletion ti-basic-optimizer/src/parse/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
mod control_flow;
pub mod control_flow;
mod delvar_chain;
mod generic;
mod prgm;
Expand Down
1 change: 1 addition & 0 deletions ti-basic-optimizer/src/parse/components/numeric_literal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ mod tests {

mod reconstruct {
use super::*;
use crate::parse::Reconstruct;
macro_rules! reconstruct_test_case {
($name:ident, $path:expr) => {
#[test]
Expand Down

0 comments on commit e095a43

Please sign in to comment.