aboutsummaryrefslogtreecommitdiff
path: root/apps/cassiopeia/src/format
diff options
context:
space:
mode:
authorMx Kookie <kookie@spacekookie.de>2020-12-13 12:08:55 +0000
committerMx Kookie <kookie@spacekookie.de>2020-12-21 05:19:49 +0100
commit236cf191b90a428325c8c179d595d4b1cd36f776 (patch)
tree5d6c67cb0b7ef980aad47ee35e264b4dfdb5422d /apps/cassiopeia/src/format
parent4c97f3208a0ba185264a169e01d0b0d922266ea6 (diff)
cassiopeia: changing parser output to more generic IR structure
This allows a few things: a, it's a persistant format that we can mirror to disk again, AND can adapt because all type information is known, and it allows for new entries to be added to the IR more easily, without having to worry about exact formatting, or re-inferring order from the TimeFile abstraction.
Diffstat (limited to 'apps/cassiopeia/src/format')
-rw-r--r--apps/cassiopeia/src/format/ir.rs63
-rw-r--r--apps/cassiopeia/src/format/mod.rs32
-rw-r--r--apps/cassiopeia/src/format/parser.rs3
3 files changed, 91 insertions, 7 deletions
diff --git a/apps/cassiopeia/src/format/ir.rs b/apps/cassiopeia/src/format/ir.rs
new file mode 100644
index 000000000000..32922ec079e7
--- /dev/null
+++ b/apps/cassiopeia/src/format/ir.rs
@@ -0,0 +1,63 @@
+use crate::{format::LineCfg, Date, Time, TimeFile};
+use std::collections::BTreeMap;
+
+/// A set of IR parsed items that makes up a whole cass file
+pub(crate) type IrStream = Vec<IrItem>;
+
+/// Intermediate representation for parsing and generating files
+///
+/// The CASS IR is largely based on the output of the parser's
+/// [`LineCfg`](crate::format::LineCfg), but with concrete types used
+/// in the data layer (namely [`Date`][date] and [`Time`][time]),
+/// while also keeping track of the line numbers to allow idempotent
+/// file changes.
+///
+/// Something not yet implemented is comment pass-through (this needs
+/// to happen in the parser first), but will likely be implemented in
+/// a future version.
+///
+/// [date]: crate::Date
+/// [time]: crate::Time
+#[derive(Debug, Clone)]
+pub(crate) struct IrItem {
+ pub(crate) tt: IrType,
+ pub(crate) lo: usize,
+}
+
+/// Disambiguate between different IR line types with their payload
+#[derive(Debug, Clone)]
+pub(crate) enum IrType {
+ /// A line with parsed header information
+ Header(BTreeMap<String, String>),
+ /// Start a session at a given timestapm
+ Start(Time),
+ /// Stop a session at a given timestamp
+ Stop(Time),
+ /// Invoice a block of previous work
+ Invoice(Date),
+ /// An item that gets ignored
+ Ignore,
+}
+
+/// Generate a stream of IR items from the raw parser output
+pub(crate) fn generate_ir(buf: impl Iterator<Item = LineCfg>) -> IrStream {
+ buf.enumerate().fold(vec![], |mut buf, (lo, item)| {
+ #[cfg_attr(rustfmt, rustfmt_skip)]
+ buf.push(match item {
+ LineCfg::Header(map) => IrItem { tt: IrType::Header(map), lo },
+ LineCfg::Start(Some(time)) => IrItem { tt: IrType::Start(time.into()), lo },
+ LineCfg::Stop(Some(time)) => IrItem { tt: IrType::Stop(time.into()), lo },
+ LineCfg::Invoice(Some(date)) => IrItem { tt: IrType::Invoice(date.into()), lo },
+ LineCfg::Ignore => IrItem { tt: IrType::Ignore, lo },
+ _ => IrItem { tt: IrType::Ignore, lo },
+ });
+
+ buf
+ })
+}
+
+
+pub(crate) trait MakeIr {
+ /// Make a new IR line from an object
+ fn make_ir(&self) -> IrType;
+}
diff --git a/apps/cassiopeia/src/format/mod.rs b/apps/cassiopeia/src/format/mod.rs
index e55658d515ff..814c08656dbe 100644
--- a/apps/cassiopeia/src/format/mod.rs
+++ b/apps/cassiopeia/src/format/mod.rs
@@ -1,17 +1,33 @@
//! cassiopeia file format
+mod ir;
mod lexer;
mod parser;
+pub(crate) use ir::{IrItem, IrStream, IrType, MakeIr};
pub(crate) use lexer::{LineLexer, LineToken, Token};
pub(crate) use parser::LineCfg;
use crate::TimeFile;
use std::{fs::File, io::Read};
+#[derive(Default)]
+pub struct ParseOutput {
+ pub(crate) ir: IrStream,
+ pub(crate) tf: TimeFile,
+}
+
+impl ParseOutput {
+ fn append(mut self, ir: IrItem) -> Self {
+ self.tf.append(ir.clone());
+ self.ir.push(ir);
+ self
+ }
+}
+
/// Load a file from disk and parse it into a
/// [`TimeFile`](crate::TimeFile)
-pub fn load_file(path: &str) -> Option<TimeFile> {
+pub fn load_file(path: &str) -> Option<ParseOutput> {
let mut f = File::open(path).ok()?;
let mut content = String::new();
f.read_to_string(&mut content).ok()?;
@@ -19,11 +35,13 @@ pub fn load_file(path: &str) -> Option<TimeFile> {
let mut lines: Vec<String> = content.split("\n").map(|l| l.to_owned()).collect();
Some(
- lines
- .iter_mut()
- .map(|line| lexer::lex(line))
- .map(|lex| parser::parse(lex))
- .filter(|line| line.valid())
- .fold(TimeFile::default(), |file, line| file.append(line)),
+ ir::generate_ir(
+ lines
+ .iter_mut()
+ .map(|line| lexer::lex(line))
+ .map(|lex| parser::parse(lex)),
+ )
+ .into_iter()
+ .fold(ParseOutput::default(), |output, ir| output.append(ir)),
)
}
diff --git a/apps/cassiopeia/src/format/parser.rs b/apps/cassiopeia/src/format/parser.rs
index 430fee6332a7..d1f0bcdebc68 100644
--- a/apps/cassiopeia/src/format/parser.rs
+++ b/apps/cassiopeia/src/format/parser.rs
@@ -53,6 +53,9 @@ pub(crate) fn parse<'l>(lex: LineLexer<'l>) -> LineCfg {
(Stop(_), LineToken { tt: T::Date, slice }) => Stop(parse_datetime(slice)),
(Invoice(_), LineToken { tt: T::Date, slice }) => Invoice(parse_date(slice)),
+ // Pass empty lines through,
+ (Empty, _) => Empty,
+
// Ignore everything else (which will be filtered)
_ => Ignore,
})