1use crate::error::Error;
2use crate::error::InvalidArgument;
3use crate::error::ParseError;
4use crate::time::TimeStamp;
5use crate::track::Disc;
6use crate::track::Index;
7use crate::track::Track;
8use crate::utils;
9use crate::Cuna;
10use std::fmt;
11use std::iter::Enumerate;
12use std::str::Lines;
13
14pub type Parser<'a> = Parna<Enumerate<Lines<'a>>>;
15
16macro_rules! fail {
17 (token $token: expr) => {
18 return Err($crate::error::ParseError::unexpected_token($token))
19 };
20 (syntax $cmd: expr, $msg: expr) => {
21 return Err($crate::error::ParseError::syntax_error($cmd, $msg))
22 }
23}
24
25#[derive(Debug, Clone, PartialEq, Eq, Hash)]
26pub enum Command<'a> {
27 Rem(&'a str),
28 Title(&'a str),
29 Performer(&'a str),
30 Songwriter(&'a str),
31 Catalog(u64),
32 Cdtextfile(&'a str),
33 File(&'a str, &'a str),
34 Track(u8, &'a str),
35 Index(u8, TimeStamp),
36 Pregap(&'a str),
37 Postgap(&'a str),
38 Isrc(&'a str),
39 Flags(&'a str),
40 Empty,
41}
42
43#[derive(Debug, Clone)]
51pub struct Parna<I>(I);
52
53impl<'a> Command<'a> {
54 pub fn new(s: &'a str) -> Result<Self, ParseError> {
55 let s = match s.trim() {
56 "" => return Ok(Self::Empty),
57 ts => ts,
58 };
59 let (content, command) = match utils::token(s) {
60 Ok(ok) => ok,
61 Err(_) => fail!(syntax s, "missing arguments"),
62 };
63 match command.to_ascii_lowercase().as_ref() {
64 "rem" => Ok(Self::Rem(content)),
65 "title" => Ok(Self::Title(trimq(content))),
66 "performer" => Ok(Self::Performer(trimq(content))),
67 "songwriter" => Ok(Self::Songwriter(trimq(content))),
68 "catalog" => match utils::number(13)(content) {
69 Ok((_, catalog)) => Ok(Self::Catalog(catalog)),
70 Err(_) => fail!(syntax content, "invaild catalog"),
71 },
72 "cdtextfile" => Ok(Self::Cdtextfile(trimq(content))),
73 "file" => match utils::quote_opt(content) {
74 Ok(("", _)) | Err(_) => Err(InvalidArgument::MissingArgument.into()),
75 Ok((format, path)) => Ok(Self::File(trimq(path), format.trim())),
76 },
77 "track" => match utils::token(content) {
78 Ok((format, id)) => Ok(Self::Track(parse_id(id)?, format)),
79 Err(_) => Err(InvalidArgument::MissingArgument.into()),
80 },
81 "index" => match utils::token(content) {
82 Ok((timestamp, id)) => Ok(Self::Index(parse_id(id)?, timestamp.parse()?)),
83 Err(_) => Err(InvalidArgument::MissingArgument.into()),
84 },
85 "pregap" => Ok(Self::Pregap(trimq(content))),
86 "postgap" => Ok(Self::Postgap(trimq(content))),
87 "isrc" => Ok(Self::Isrc(trimq(content))),
88 "flags" => Ok(Self::Flags(trimq(content))),
89 _ => Err(ParseError::unexpected_token(command)),
90 }
91 }
92 pub fn parse(&self, sheet: &mut Cuna) -> Result<(), ParseError> {
93 match *self {
94 Self::Empty => {}
95 Self::Rem(s) => sheet.comments.push(s.to_owned()),
96 Self::Title(s) => match sheet.last_track_mut() {
97 Some(tk) => tk.push_title(s.to_owned()),
98 None => sheet.header.push_title(s.to_owned()),
99 },
100 Self::Performer(s) => match sheet.last_track_mut() {
101 Some(tk) => tk.push_performer(s.to_owned()),
102 _ => sheet.header.push_performer(s.to_owned()),
103 },
104 Self::Songwriter(s) => match sheet.last_track_mut() {
105 Some(tk) => tk.push_songwriter(s.to_owned()),
106 _ => sheet.header.push_songwriter(s.to_owned()),
107 },
108 Self::Catalog(s) => match sheet.header.catalog {
109 None => sheet.header.catalog = Some(s),
110 _ => fail!(syntax self, "multiple `CATALOG` commands is not allowed"),
111 },
112 Self::Cdtextfile(s) => {
113 sheet.header.set_cdtextfile(s.to_owned());
114 }
115 Self::File(name, format) => {
116 sheet.push_file(Disc::new(name.to_owned(), format.to_owned()));
117 }
118 Self::Track(id, format) => match sheet.last_file_mut() {
119 Some(tk) => tk.push_track(Track::new_unchecked(id, format.to_owned())),
120 None => fail!(token "TRACK"),
121 },
122 Self::Index(id, timestamp) => match sheet.last_track_mut() {
123 Some(tk) if tk.postgap.is_none() => {
124 tk.push_index(Index::new_unchecked(id, timestamp))
125 }
126 Some(_) => fail!(syntax self, "Command `INDEX` should be before `POSTGAP`"),
127 None => fail!(token "INDEX"),
128 },
129 Self::Pregap(timestamp) => match sheet.last_track_mut() {
130 Some(tk) if tk.index.is_empty() && tk.pregap.is_none() => {
131 tk.set_pregep(timestamp.parse()?);
132 }
133 Some(tk) if tk.pregap.is_some() => {
134 fail!(syntax self, "Multiple `PREGAP` commands are not allowed in one `TRACK` scope")
135 }
136 Some(_) => fail!(syntax self, "Command `PREGAP` should be before `INDEX`"),
137 _ => fail!(token "PREGAP"),
138 },
139 Self::Postgap(timestamp) => match sheet.last_track_mut() {
140 Some(tk) if tk.postgap.is_none() => {
141 tk.set_postgep(timestamp.parse()?);
142 }
143 Some(_) => {
144 fail!(syntax self, "Multiple `POSTGAP` commands are not allowed in one `TRACK` scope")
145 }
146 None => fail!(token "POSTGAP"),
147 },
148 Self::Isrc(s) => match sheet.last_track_mut() {
149 Some(tk) if tk.isrc.is_none() => {
150 tk.set_isrc(s.to_owned());
151 }
152 Some(_) => {
153 fail!(syntax self, "Multiple `ISRC` commands are not allowed in one `TRACK` scope")
154 }
155 None => fail!(token "ISRC"),
156 },
157 Self::Flags(s) => match sheet.last_track_mut() {
158 Some(tk) if tk.flags.is_empty() => tk.push_flags(s.split(' ')),
159 Some(_) => {
160 fail!(syntax self, "Multiple `FLAGS` commands are not allowed in one `TRACK` scope")
161 }
162 None => fail!(token "FLAGS"),
163 },
164 }
165 Ok(())
166 }
167}
168impl fmt::Display for Command<'_> {
169 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
170 match *self {
171 Self::Rem(c) => write!(formatter, "REM {}", c),
172 Self::Title(c) => write!(formatter, r#"TITLE "{}""#, c),
173 Self::Performer(c) => write!(formatter, r#"PERFORMER "{}""#, c),
174 Self::Songwriter(c) => write!(formatter, r#"SONGWRITER "{}""#, c),
175 Self::Catalog(c) => write!(formatter, "CATALOG {}", c),
176 Self::Cdtextfile(c) => write!(formatter, r#"CDTEXTFILE "{}""#, c),
177 Self::File(name, tp) => write!(formatter, r#"FILE "{}" {}"#, name, tp),
178 Self::Track(id, format) => write!(formatter, "TRACK {} {}", id, format),
179 Self::Index(id, timestamp) => write!(formatter, "INDEX {} {}", id, timestamp),
180 Self::Pregap(c) => write!(formatter, "PREGAP {}", c),
181 Self::Postgap(c) => write!(formatter, "POSTGAP {}", c),
182 Self::Isrc(c) => write!(formatter, "ISRC {}", c),
183 Self::Flags(c) => write!(formatter, "FLAG {}", c),
184 Self::Empty => Ok(()),
185 }
186 }
187}
188impl<'a> Parna<Enumerate<Lines<'a>>> {
189 pub fn new(s: &'a str) -> Self {
191 Self(s.lines().enumerate())
192 }
193}
194impl<'a, I: Iterator<Item = &'a str>> Parna<Enumerate<I>> {
195 pub fn from_lines(lines: I) -> Self {
196 Self(lines.enumerate())
197 }
198 #[deprecated]
199 pub fn set_lines(&mut self, lines: I) {
200 self.0 = lines.enumerate();
201 }
202}
203impl<'a, I: Iterator<Item = (usize, &'a str)>> Parna<I> {
204 pub fn with_iter(it: I) -> Self {
209 Self(it)
210 }
211 pub fn data(&mut self) -> &mut I {
213 self.0.by_ref()
214 }
215 pub fn parse_next_line(&mut self, state: &mut Cuna) -> Result<(), Error> {
217 self.parse_next_n_lines(1, state)
218 }
219 pub fn parse_next_n_lines(&mut self, n: usize, state: &mut Cuna) -> Result<(), Error> {
223 for (at, line) in self.0.by_ref().take(n) {
224 let to_error = |e| Error::new(e, at + 1);
225 Command::new(line)
226 .map_err(to_error)?
227 .parse(state)
228 .map_err(to_error)?;
229 }
230 Ok(())
231 }
232 pub fn parse(&mut self, state: &mut Cuna) -> Result<(), Error> {
238 for (at, line) in self.0.by_ref() {
239 let to_error = |e| Error::new(e, at + 1);
240 Command::new(line)
241 .map_err(to_error)?
242 .parse(state)
243 .map_err(to_error)?;
244 }
245 Ok(())
246 }
247}
248impl<'a, I: Iterator<Item = (usize, &'a str)> + Clone> Parna<I> {
249 pub fn current_line(&self) -> Option<&'a str> {
257 self.current().map(|(_, s)| s)
258 }
259 pub fn current(&self) -> Option<(usize, &'a str)> {
261 self.0.clone().next()
262 }
263}
264
265#[inline(always)]
266fn parse_id(s: &str) -> Result<u8, InvalidArgument> {
267 Ok(utils::number(2)(s)
268 .map_err(|_| InvalidArgument::InvalidId)?
269 .1)
270}
271#[inline(always)]
272fn trimq(s: &str) -> &str {
273 s.trim_matches('"')
274}