Skip to content

Commit

Permalink
flesh out grammar
Browse files Browse the repository at this point in the history
  • Loading branch information
carderne committed Jan 24, 2024
1 parent 88d5de0 commit 76a702c
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 52 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ Planned features:
- [ ] Currency conversions
- [ ] Price/cost and FIFO

## (Deliberate*) differences from beancount
- Permitted transaction flags are limited to `*` `!` `txn`
- Postings can't omit the currency

## Usage
### Install
```bash
Expand Down
87 changes: 47 additions & 40 deletions grammar.pest
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ root = { SOI ~ (NEWLINE* ~ entry ~ NEWLINE*)* ~ EOI }
entry = _{
heading
| option
| query
| custom
| commodity
| open
Expand All @@ -11,60 +12,66 @@ entry = _{
| pad
| price
| document
| note
| transaction
| COMMENT
| space+
| badline
| badline // catch-all so parsing continues
}

heading = _{ "*" ~ anyline }
option = { "option" ~ (space+ ~ quoted){2} }
custom = { date ~ space ~ "custom" ~ (space+ ~ quoted){3} }
heading = _{ "*" ~ anyline }
option = { "option" ~ (space+ ~ quoted){2} }
custom = { date ~ space+ ~ "custom" ~ (space+ ~ (quoted | account | number | ccy)){2,} }
query = { date ~ space+ ~ "query" ~ space+ ~ quoted ~ space+ ~ quoted }

commodity = { date ~ space+ ~ "commodity" ~ space+ ~ ccy ~ metadata_added }
open = { date ~ space+ ~ "open" ~ space+ ~ account ~ currencies? ~ (space+ ~ quoted)? ~ metadata_added* }
currencies = _{ space+ ~ ccy ~ ("," ~ space+ ~ ccy)* }
close = { date ~ space+ ~ "close" ~ space+ ~ account }
commodity = { date ~ space+ ~ "commodity" ~ space+ ~ ccy ~ metadata_added* }
open = { date ~ space+ ~ "open" ~ space+ ~ account ~ currencies? ~ (space+ ~ quoted)? ~ metadata_added* }
close = { date ~ space+ ~ "close" ~ space+ ~ account ~ metadata_added* }
currencies = _{ space+ ~ ccy ~ ("," ~ space+ ~ ccy)* }

price = { date ~ space+ ~ "price" ~ space+ ~ ccy ~ space+ ~ amount }
balance = { date ~ space+ ~ "balance" ~ space+ ~ account ~ space+ ~ amount }
pad = { date ~ space+ ~ "pad" ~ space+ ~ account ~ space+ ~ account }
price = { date ~ space+ ~ "price" ~ space+ ~ ccy ~ space+ ~ amount ~ metadata_added* }
balance = { date ~ space+ ~ "balance" ~ space+ ~ account ~ space+ ~ amount ~ metadata_added* }
pad = { date ~ space+ ~ "pad" ~ space+ ~ account ~ space+ ~ account ~ metadata_added* }

document = { date ~ space+ ~ "document" ~ account ~ space+ ~ path }
document = { date ~ space+ ~ "document" ~ account ~ space+ ~ path ~ metadata_added* }
note = { date ~ space+ ~ "note" ~ space+ ~ account ~ space+ ~ quoted ~ metadata_added* }

transaction = { date ~ space+ ~ txn_type ~ space+ ~ payee ~ (space+ ~ narration)? ~ (space+ ~ tag)? ~ (space+ ~ link)? ~ (posting_added | metadata_added)* }
payee = { quoted }
narration = { quoted }
posting_added = _{ (NEWLINE ~ posting) }
posting = { space+ ~ account ~ (space+ ~ amount)? }
transaction = { date ~ space+ ~ txn_type ~ space+ ~ payee ~ (space* ~ narration)? ~ (space+ ~ (tag | link))* ~ (posting_added | metadata_added)* }
payee = { quoted }
narration = { quoted }
tag = @{ "#" ~ tagkey }
link = @{ "^" ~ tagkey }

posting_added = _{ (NEWLINE ~ posting) }
posting = { space+ ~ account ~ (space+ ~ amount)? ~ space* ~ at_cost? ~ space* ~ at_price? ~ metadata_added* }
at_cost = { "{" ~ (number ~ space+ ~ ccy)? ~ "}" }
at_price = { "@" ~ (space+ ~ number)? ~ space+ ~ ccy }

metadata_added = _{ (NEWLINE ~ metadata) }
metadata = { space+ ~ key ~ ":" ~ space* ~ val }

txn_type = { "*" | "!" | "txn" }
key = @{ ASCII_ALPHA_LOWER+ }
val = @{ quoted }
path = @{ quoted }
space = _{ " " | "\t" }
quoted = _{ quote ~ inner_quoted ~ quote }
inner_quoted = { (!quote ~ ANY)* }
quote = _{ "\"" }

date = @{ year ~ "-" ~ month ~ "-" ~ day }
year = @{ '1'..'2' ~ ASCII_DIGIT ~ ASCII_DIGIT ~ ASCII_DIGIT }
month = @{ '0'..'1' ~ ASCII_DIGIT }
day = @{ '0'..'3' ~ ASCII_DIGIT }
txn_type = { "*" | "!" | "txn" }
key = @{ ASCII_ALPHA_LOWER ~ (ASCII_ALPHANUMERIC | "-" ~ ASCII_ALPHANUMERIC)* }
tagkey = @{ ASCII_ALPHANUMERIC ~ (ASCII_ALPHANUMERIC | "-" ~ ASCII_ALPHANUMERIC)* }
val = @{ quoted | ASCII_ALPHA* }
path = @{ quoted }
space = _{ " " | "\t" }
quoted = _{ quote ~ inner_quoted ~ quote }
inner_quoted = { (!quote ~ ANY)* }
quote = _{ "\"" }

amount = { number ~ space+ ~ ccy }
number = @{ "-"? ~ ASCII_DIGIT ~ (ASCII_DIGIT | ".")* }
ccy = @{ ASCII_ALPHA_UPPER{3, 9} }
date = @{ year ~ "-" ~ month ~ "-" ~ day }
year = @{ '1'..'2' ~ ASCII_DIGIT ~ ASCII_DIGIT ~ ASCII_DIGIT }
month = @{ '0'..'1' ~ ASCII_DIGIT }
day = @{ '0'..'3' ~ ASCII_DIGIT }

account = @{ account_root ~ (":" ~ ASCII_ALPHA_UPPER ~ ASCII_ALPHA_LOWER+)+ }
account_root = _{ "Assets" | "Liabilities" | "Income" | "Expenses" | "Equity" }
amount = { number ~ space+ ~ ccy }
number = @{ "-"? ~ ASCII_DIGIT ~ (ASCII_DIGIT | "." | ",")* }
ccy = @{ ASCII_ALPHA_UPPER{3, 9} }

tag = @{ "#" ~ ASCII_ALPHA_LOWER+ }
link = @{ "^" ~ ASCII_ALPHA_LOWER+ }
account = @{ account_root ~ (":" ~ ASCII_ALPHA_UPPER ~ ASCII_ALPHANUMERIC+)+ }
account_root = _{ "Assets" | "Liabilities" | "Income" | "Expenses" | "Equity" }

COMMENT = _{ NEWLINE? ~ space* ~ ";" ~ anyline }
badline = { (!NEWLINE ~ ANY)+ }
anyline = _{ (!NEWLINE ~ ANY)* }
COMMENT = _{ NEWLINE? ~ space* ~ ";" ~ anyline }
badline = { (!NEWLINE ~ ANY)+ }
anyline = _{ (!NEWLINE ~ ANY)* }
29 changes: 21 additions & 8 deletions src/directives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,17 @@ impl Amount {
}
pub fn from_entry(entry: Pair<Rule>) -> Self {
let mut pairs = entry.clone().into_inner();
let number: Decimal = pairs.next().unwrap().as_str().parse().unwrap();
let mut number: String = pairs.next().unwrap().as_str().to_string();
if number.contains(',') {
number = number.replace(',', "");
}
let number: Decimal = match number.parse() {
Ok(num) => num,
Err(_) => {
let (line, _) = entry.line_col();
panic!("Un-parseable decimal at line:{line}");
}
};
let ccy = pairs.next().unwrap().as_str().to_string();
Self { number, ccy }
}
Expand Down Expand Up @@ -131,6 +141,7 @@ pub struct Metadata {
key: String,
val: String,
debug: DebugLine,
// TODO: parse metadata in all directives
}

impl Metadata {
Expand Down Expand Up @@ -501,21 +512,23 @@ pub struct Transaction {
ty: String,
payee: Option<String>,
narration: String,
tag: Option<String>,
link: Option<String>,
tag: Option<String>, // TODO can have multiple
link: Option<String>, // TODO can have multiple
pub postings: Vec<Posting>,
meta: Vec<Metadata>,
pub debug: DebugLine,
// TODO add at_cost, at_price
}

fn get_payee_narration(pairs: &mut Pairs<Rule>) -> (Option<String>, String) {
let first_val = pairs.next().unwrap().as_str().to_string();
if pairs.peek().unwrap().as_rule() == Rule::narration {
let narration = pairs.next().unwrap().as_str().to_string();
(Some(first_val), narration)
} else {
(None, first_val)
if let Some(pair) = pairs.peek() {
if pair.as_rule() == Rule::narration {
let narration = pairs.next().unwrap().as_str().to_string();
return (Some(first_val), narration);
}
}
(None, first_val)
}

impl Transaction {
Expand Down
6 changes: 4 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@ fn load(text: String) -> (Vec<Directive>, Vec<BeanError>) {
}

/// Load the file at `path` and print the balance
pub fn balance(path: &String) {
pub fn balance(path: &String, print_bals: bool) {
let text = std::fs::read_to_string(path).expect("cannot read file");
let (mut dirs, mut errs) = load(text);
let (bals, book_errs) = book::get_balances(&mut dirs);
errs.extend(book_errs);
utils::print_errors(errs);
utils::print_bals(bals);
if print_bals {
utils::print_bals(bals);
}
}
8 changes: 6 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use clap::{Parser, Subcommand};
use bean_rs::balance;
use clap::{Parser, Subcommand};

#[derive(Parser)]
#[command(author, version, about, long_about = None)]
Expand All @@ -12,14 +12,18 @@ struct Cli {
#[derive(Subcommand)]
enum Commands {
Balance { path: String },
Check { path: String },
}

fn main() {
env_logger::init();
let cli = Cli::parse();
match &cli.command {
Commands::Balance { path } => {
balance(path);
balance(path, true);
}
Commands::Check { path } => {
balance(path, false);
}
}
}
12 changes: 12 additions & 0 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ pub fn consume(entries: Pairs<'_, Rule>) -> (Vec<Directive>, Vec<BeanError>) {
directives::ConfigCustom::from_entry(entry),
));
}
Rule::query => {
// TODO do something with queries
let (line, _) = entry.line_col();
let debug = DebugLine::new(line);
debug!("Ignoring query {debug}");
}
Rule::commodity => {
dirs.push(Directive::Commodity(directives::Commodity::from_entry(
entry,
Expand All @@ -78,6 +84,12 @@ pub fn consume(entries: Pairs<'_, Rule>) -> (Vec<Directive>, Vec<BeanError>) {
Rule::document => {
dirs.push(Directive::Document(directives::Document::from_entry(entry)));
}
Rule::note => {
// TODO do something with notes
let (line, _) = entry.line_col();
let debug = DebugLine::new(line);
debug!("Ignoring note {debug}");
}
Rule::transaction => {
dirs.push(Directive::Transaction(directives::Transaction::from_entry(
entry,
Expand Down

0 comments on commit 76a702c

Please sign in to comment.