use crate::models::*;
use chrono::{NaiveDate};
use crate::error::AvaroError;
use lalrpop_util::ParseError;
use snailquote::unescape;
use std::str::FromStr;
use bigdecimal::BigDecimal;
grammar;
extern {
type Location = usize;
type Error = AvaroError;
}
match {
// Ignore C++-style comments
r"///[^\n\r]*[\n\r]*" => { },
r"\d{4}-\d{1,2}-\d{1,2}",
r"[A-Z][A-Z0-9_\-'\.]{0,22}[A-Z0-9]"
} else {
// Other things you want to match
}
else {
_ // Everything else
}
Comma<T>: Vec<T> = { // (1)
<v:(<T> "," SPACE*)*> <e:T> => { // (2)
let mut v = v;
v.push(e);
v
}
};
pub Entry: Vec<Directive> = {
<v: ("\n"* <(<DirectiveExpression> "\n"+)*>)> => v
}
pub DirectiveExpression: Directive = {
OptionExpression,
OpenExpression,
NoteExpression,
CloseExpression,
CommodityExpression,
<TransactionExpression> => Directive::Transaction(<>),
PadExpression,
BalanceExpression,
DocumentExpression,
PriceExpression,
EventExpression,
PluginExpression,
IncludeExpression,
CustomExpression,
CommentExpression,
}
pub OptionExpression: Directive = {
"option" SPACE+ <key: StringExpression> SPACE+ <value: StringExpression> => Directive::Option{key, value}
}
pub PluginExpression: Directive = {
"plugin" SPACE+ <module: StringExpression> <value: (SPACE+ <StringExpression>)?> => Directive::Plugin{module, value}
}
pub IncludeExpression: Directive = {
"include" SPACE+ <file: StringExpression> => Directive::Include{file}
}
pub OpenExpression: Directive = {
<date: DateExpression> SPACE+ "open" SPACE+ <account: AccountExpression> <commodities: (SPACE+ <Comma<Commodity>>)?> SPACE* => Directive::Open{date, account, commodities}
}
pub NoteExpression: Directive = {
<date: DateExpression> SPACE+ "note" SPACE+ <account: AccountExpression> SPACE+ <description: StringExpression> SPACE* => Directive::Note{date, account, description}
}
pub CloseExpression: Directive = {
<date: DateExpression> SPACE+ "close" SPACE+ <account: AccountExpression> SPACE* => Directive::Close{date, account}
}
pub CommodityExpression: Directive = {
<date: DateExpression> SPACE+ "commodity" SPACE+ <name: Commodity> SPACE*
<v:("\n " <CommodityLine>)*>
=> Directive::Commodity{date, name, metas:v.into_iter().collect()}
}
CommodityLine: (String, String) = {
SPACE* <AttributeKey> SPACE* ":" SPACE* <StringExpression>
}
pub TransactionExpression: Transaction = {
<date: DateExpression> SPACE+ <flag: FlagExpression> SPACE+ <options: TransactionHeadOptions>
<v:("\n " <TransactionLineExpression> )*>
=> {
let lines = v.into_iter().map(|line|TransactionLine::from_parser(line.0, line.1, line.2)).collect();
Transaction::from_parser(date, flag, options.0, options.1, options.2, lines)
}
}
TransactionHeadOptions: (Option<(String, Option<String>)>, Vec<String>, Vec<String>) = { <PnExpression?> <TagsExpression> <LinksExpression> }
PnExpression: (String, Option<String>) = {
<first: StringExpression> SPACE* <second: NarrationExpression?> => (first, second)
}
NarrationExpression: String = { ("|" SPACE+)? <StringExpression> SPACE* }
TagsExpression: Vec<String> = { <TagExpression*> }
TagExpression: String = { "#" <AttributeKey> SPACE* }
LinksExpression: Vec<String> = { <LinkExpression*> }
LinkExpression: String = { "^" <AttributeKey> SPACE* }
TransactionLineExpression: (Option<Flag>, Account, Option<(Amount, Option<(Amount, Option<String>)>, Option<Amount>, Option<Amount>)>) = {
SPACE* <(<FlagExpression> SPACE*)?> <AccountExpression> <TLOptions>
}
TLOptions:Option<(Amount, Option<(Amount, Option<String>)>, Option<Amount>, Option<Amount>)> = {
(
SPACE+ <Amount> SPACE*
<CostWithComment?>
<SinglePrice?>
<TotalPrice?>
)?
}
CostWithComment: (Amount, Option<String>) = { "{" <Amount> SPACE* <Cost?> "}" SPACE* }
Cost: String = { "," SPACE* <StringExpression> SPACE* }
SinglePrice: Amount = { "@" SPACE* <Amount> SPACE* }
TotalPrice: Amount = { "@@" SPACE* <Amount> }
pub PadExpression: Directive = {
<date: DateExpression> SPACE+ "pad" SPACE+ <from: AccountExpression> SPACE+ <to: AccountExpression> SPACE* => Directive::Pad{date, from, to}
}
pub BalanceExpression: Directive = {
<date: DateExpression> SPACE+ "balance" SPACE+ <account: AccountExpression> SPACE+ <amount: Amount> SPACE* => Directive::Balance{date, account, amount}
}
pub DocumentExpression: Directive = {
<date: DateExpression> SPACE+ "document" SPACE+ <account: AccountExpression> SPACE+ <path: StringExpression> SPACE* => Directive::Document{date, account, path}
}
pub PriceExpression: Directive = {
<date: DateExpression> SPACE+ "price" SPACE+ <commodity: Commodity> SPACE+ <amount: Amount> SPACE* => Directive::Price{date, commodity, amount}
}
pub EventExpression: Directive = {
<date: DateExpression> SPACE+ "event" SPACE+ <name: StringExpression> SPACE+ <value: StringExpression> SPACE* => Directive::Event{date, name, value}
}
pub CustomExpression: Directive = {
<date: DateExpression> SPACE+ "custom" SPACE+ <type_name: StringExpression> <values: (SPACE+ <CustomConfigItem>)+> => Directive::Custom{date, type_name, values}
}
pub CustomConfigItem: String = {
StringExpression,
AccountExpression => <>.to_string(),
Commodity,
AttributeKey,
}
pub CommentExpression: Directive = {
r";[^\n]*" => Directive::Comment{content:<>.to_owned()}
}
pub FlagExpression: Flag = {
"!" => Flag::from_str(<>).unwrap(),
"*" => Flag::from_str(<>).unwrap()
}
pub Amount: (BigDecimal, String) = {
r"[+-]?\d+(\.\d+)? +[A-Z][A-Z0-9_\-'\.]{0,22}[A-Z0-9]" => amount_parse(<>)
}
pub AttributeKey: String = {
<r"([^\x00-\x9F]|[A-Za-z0-9-_])+"> => <>.to_owned()
}
pub StringExpression: String = {
<r#""(\\(["\\/bfnrt]|u[a-fA-F0-9]{4})|[^"\\\x00-\x1F\x7F]+)*""#> => unescape(<>).unwrap()
}
pub AccountExpression: Account = {
<account_type: AccountType> <v:AccountName+> => Account::new(AccountType::from_str(&account_type).unwrap(), v)
};
AccountType: String = {
<"Assets"> => <>.to_owned(),
<"Liabilities"> => <>.to_owned(),
<"Equity"> => <>.to_owned(),
<"Income"> => <>.to_owned(),
<"Expenses"> => <>.to_owned()
}
pub AccountName: String = {
<r":([^\x00-\x9F]|[A-Za-z0-9-])+"> => <>[1..<>.len()].to_owned()
}
pub Commodity: String = {
<r"[A-Z][A-Z0-9_\-'\.]{0,22}[A-Z0-9]"> => <>.to_owned()
}
pub DateExpression: NaiveDate = {
<date:r"\d{4}-\d{1,2}-\d{1,2}"> =>? NaiveDate::parse_from_str(date, "%Y-%m-%d").map_err(|_|ParseError::User {error: AvaroError::InvalidDate})
}
pub SPACE = {
" ",
r"\t"
}