-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrepeat.rs
152 lines (143 loc) · 4.7 KB
/
repeat.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;
use crate::stores::note::Line;
const REPEAT: u8 = b'|';
const DELIM: u8 = b':';
const SEP: u8 = b'.';
#[derive(PartialEq, Debug)]
pub enum Rep {
/// |:
RepeatStart,
/// e.g. |1.3.
VoltaStart(Vec<usize>),
/// :|
RepeatEnd,
/// |
VoltaEnd,
}
/// check if a line should be parsed as repeat based on the first token
pub fn should_be_rep(token: &str) -> bool {
let bytes = token.as_bytes();
bytes.contains(&REPEAT) && bytes.iter().all(
|&b| b.is_ascii_digit() || matches!(b, REPEAT | DELIM | SEP)
)
}
/// return volta not found error to be handled
fn not_found(v: usize, action: &str) -> Result<(), String> {
let volta = match v {
0 => "pre-volta".to_string(),
v if v == !0 => "post-volta".to_string(),
_ => format!("volta no. {}", v),
};
Err(format!("{} is not found while trying to {}", volta, action))
}
fn parse_volta_start(bytes: &[u8]) -> Option<Vec<usize>> {
match bytes.strip_prefix(&[REPEAT]) {
Some(voltas) => Some(voltas.iter().filter_map(
|&b| if b == SEP { None } else { Some((b - b'0') as usize) }
).collect()),
None => None
}
}
#[derive(Default)]
pub struct RepeatParser {
/// 0 for pre-volta, MAX for post-volta
voltas: BTreeMap<usize, Rc<RefCell<Vec<Line>>>>,
/// indices of one of the voltas to record
current: usize,
/// should trigger repeat on RepeatEnd
on_rep_end: bool,
}
impl RepeatParser {
pub fn new() -> Self {
Self {
voltas: BTreeMap::new(),
current: 0,
on_rep_end: true,
}
}
/// parse token as repeat
pub fn parse(&self, token: &str) -> Result<Rep, String> {
let bytes = token.as_bytes();
match bytes {
&[REPEAT] => Ok(Rep::VoltaEnd),
&[DELIM, REPEAT] => Ok(Rep::RepeatEnd),
&[REPEAT, DELIM] => Ok(Rep::RepeatStart),
// parse as volta start or die
_ => if let Some(voltas) = parse_volta_start(bytes) {
Ok(Rep::VoltaStart(voltas))
} else {
Err(format!("invalid token as repeat: {}", token))
}
}
}
/// return if Repeat is currently recording
pub fn on_rec(&self) -> bool {
!self.voltas.is_empty()
}
/// return the Rep token that will trigger a repeat
pub fn get_trigger(&self) -> Rep {
if self.on_rep_end {
Rep::RepeatEnd
} else {
Rep::VoltaEnd
}
}
/// change the Rep token that will trigger a repeat
pub fn set_trigger(&mut self, trigger: Rep) -> Result<(), String> {
match trigger {
Rep::VoltaEnd => Ok(self.on_rep_end = false),
Rep::RepeatEnd => Ok(self.on_rep_end = true),
_ => Err(format!("invalid trigger token, expected VoltaEnd | RepeatEnd, found {:?}", trigger)),
}
}
/// init new voltas to store if empty
pub fn start(&mut self, indices: &[usize]) {
let volta = Rc::new(RefCell::new(Vec::new()));
for &i in indices.iter() {
self.voltas.entry(i).or_insert(Rc::clone(&volta));
}
self.current = indices[0];
}
/// add new line to current voltas
pub fn push(&mut self, line: Line) -> Result<(), String> {
if line.size() == 0 {
return Err(format!("attempt to push empty line"));
}
match self.voltas.get(&self.current) {
Some(volta) => Ok(volta.borrow_mut().push(line)),
None => not_found(self.current, "push new line"),
}
}
/// repeat voltas and reset self
pub fn repeat(&self, mut write: impl FnMut(&Line) -> Result<(), String>) -> Result<(), String> {
if self.voltas.len() > 2 {
for &k in self.voltas.keys().filter(|&&k| 0 < k && k < !0) {
// write pre-volta volta post-volta
self.write(0, &mut write)?;
self.write(k, &mut write)?;
self.write(!0, &mut write)?;
}
} else {
// no voltas, only pre-volta
self.write(0, &mut write)?;
self.write(0, &mut write)?;
}
Ok(())
}
/// free data
pub fn clear(&mut self) {
self.voltas.clear();
self.current = 0;
self.on_rep_end = true;
}
/// write a volta
fn write(&self, v: usize, write: &mut impl FnMut(&Line) -> Result<(), String>) -> Result<(), String> {
match self.voltas.get(&v) {
// didn't know that you can put a for loop inside Ok()
Some(volta) => Ok(for line in volta.borrow().iter() { write(line)?; }),
None => not_found(v, "write line"),
}
}
}