parsing.lisp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. (in-package #:pta-ledger)
  2. (defvar *default-year* nil "Initialized to current year on parsing start. Could be set with directive")
  3. (defvar *default-commodity* "RUB" "Could be set with directive")
  4. (defparameter +day+ (* 60 60 24) "day in seconds")
  5. (defun get-date (universal-time)
  6. (nreverse (subseq (multiple-value-list (decode-universal-time universal-time)) 3 6)))
  7. (defun get-ut (year month day)
  8. (encode-universal-time 0 0 0 day month year))
  9. (defun strip-space (text)
  10. (subseq text (or (position #\Space text :test-not 'eql) 0)
  11. (1+ (or (position #\Space text :test-not 'eql :from-end t) -1))))
  12. (defun .date-delimiter ()
  13. (.is #'member '(#\/ #\- #\.)))
  14. (defun .chars (&rest chars)
  15. (.is #'member chars))
  16. ;; optimized (.first (.map 'list parser at-least))
  17. (defun .while (parser &key result-type (at-least 1))
  18. (lambda (input)
  19. (loop
  20. for inp = input then (input-rest inp)
  21. for count from 0
  22. until (input-empty-p inp)
  23. while (funcall parser inp)
  24. finally (return (when (>= count at-least)
  25. (list (cons (when result-type
  26. (coerce (subseq input 0 count) result-type))
  27. inp)))))))
  28. (defun .until (parser &key result-type (at-least 1))
  29. (.while (.not parser) :result-type result-type :at-least at-least))
  30. (defun .mapcar* (parser &optional skip)
  31. (lambda (input)
  32. (loop
  33. with results
  34. until (input-empty-p input)
  35. for result = (run parser input)
  36. while result
  37. do
  38. (unless skip (push (caar result) results))
  39. (setf input (cdar result))
  40. finally (return (list (cons (unless skip (nreverse results)) input))))))
  41. (defun .list (&rest parsers)
  42. (if (not parsers)
  43. (.fail)
  44. (.let* ((first (first parsers))
  45. (rest (if (rest parsers)
  46. (apply #'.list (rest parsers))
  47. (.identity nil))))
  48. (.identity (cons first rest)))))
  49. (defun .number (&optional (at-least 1))
  50. (.let* ((digits (.while (.is #'digit-char-p) :result-type 'string :at-least at-least)))
  51. (.identity (parse-integer digits))))
  52. (defun .spaces (&key (at-least 1) result-type)
  53. (.while (.char= #\Space) :result-type result-type :at-least at-least))
  54. (defun .whitespace (&key (at-least 1) result-type)
  55. (.while (.chars #\Space #\Tab) :result-type result-type :at-least at-least))
  56. (defun .eol ()
  57. (.or (.char= #\Newline)
  58. (.and (.not (.item))
  59. (.identity '()))))
  60. (defun .line-comment ()
  61. (.let* ((_ (.whitespace :at-least 0))
  62. (_ (.chars #\; #\# #\*))
  63. (text (.until (.char= #\Newline) :at-least 0 :result-type 'string))
  64. (_ (.eol)))
  65. (.identity (strip-space text))))
  66. (defun .multi-line-comment ()
  67. (.let* ((_ (.string= "comment"))
  68. (text (.until (.progn (.eol) (.string= "end comment")) :result-type 'string))
  69. (_ (.optional (.progn (.eol) (.string= "end comment"))))
  70. (_ (.eol)))
  71. (.identity (strip-space text))))
  72. (defun .comment ()
  73. (.let* ((_ (.char= #\;))
  74. (text (.until (.char= #\Newline) :at-least 0 :result-type 'string)))
  75. (.identity (strip-space text))))
  76. (defun .empty-lines ()
  77. (.mapcar*
  78. (.or (.progn (.whitespace :at-least 0) (.eol))
  79. (.line-comment)
  80. (.multi-line-comment))
  81. t))
  82. (defun split (text &optional (delimiter (.char= #\Newline)))
  83. (when text
  84. (parse (.mapcar* (.prog1 (.until delimiter :result-type 'string :at-least 0)
  85. (.optional delimiter))) text)))
  86. (defun .simple-date (&optional default-year)
  87. (.let* ((year (.optional (.prog1 (.number 4) (.date-delimiter))))
  88. (month (.prog1 (.number) (.date-delimiter)))
  89. (day (.number)))
  90. (handler-case
  91. (.identity (get-ut (or year default-year *default-year*) month day))
  92. (error () (.fail)))))
  93. (defun .status ()
  94. (.is 'member '(#\! #\*)))
  95. (defun .code ()
  96. (.prog2 (.char= #\()
  97. (.until (.char= #\)) :result-type 'string :at-least 0)
  98. (.char= #\))))
  99. (defun .description ()
  100. (.let* ((text (.until (.chars #\Newline #\;) :result-type 'string)))
  101. (.identity (strip-space text))))
  102. (defun .account ()
  103. (.let* ((account (.until (.or (.string= " ")
  104. (.char= #\Newline)) :result-type 'string)))
  105. (.identity (strip-space account))))
  106. (defun .money-number (thousands-sep decimal-sep)
  107. (.let* ((first-part (.while (.is 'digit-char-p) :result-type 'string))
  108. (rest-parts (.mapcar* (.progn (.char= thousands-sep)
  109. (.while (.is 'digit-char-p) :result-type 'string))))
  110. (decimals (.optional (.progn (.char= decimal-sep)
  111. (.while (.is 'digit-char-p) :result-type 'list :at-least 0)))))
  112. ;; One thousands separator and no decimals - is other way around, fail for other to succeceed
  113. (if (and (= 1 (length rest-parts))
  114. (null decimals))
  115. (.fail)
  116. (.identity
  117. (+
  118. (parse-integer (apply 'concatenate 'string (cons first-part rest-parts)))
  119. (if decimals
  120. (* (parse-integer (coerce decimals 'string))
  121. (expt 10 (- (length decimals))))
  122. 0))))))
  123. (defun .money ()
  124. (.plus (.money-number #\, #\.)
  125. (.money-number #\. #\,)))
  126. (defun .commodity ()
  127. (.or (.until (.or (.chars #\. #\, #\Space #\" #\; #\Newline #\- #\+ #\@)
  128. (.is 'digit-char-p))
  129. :result-type 'string)
  130. (.prog2 (.char= #\")
  131. (.until (.char= #\") :result-type 'string)
  132. (.char= #\"))))
  133. (defun .amount ()
  134. (.or
  135. (.let* ((sign1 (.optional (.chars #\- #\+)))
  136. (commodity (.prog1 (.commodity) (.optional (.whitespace))))
  137. (sign2 (.optional (.chars #\- #\+)))
  138. (quantity (.money)))
  139. (if (and sign1 sign2)
  140. (.fail)
  141. (.identity
  142. (make-amount :quantity (if (or (equal sign1 #\-)
  143. (equal sign2 #\-))
  144. (- quantity)
  145. quantity)
  146. :commodity commodity))))
  147. (.let* ((sign (.optional (.chars #\- #\+)))
  148. (quantity (.money))
  149. (commodity (.optional (.progn (.optional (.whitespace))
  150. (.commodity)))))
  151. (.identity
  152. (make-amount :quantity (if (equal sign #\-)
  153. (- quantity)
  154. quantity)
  155. :commodity commodity)))))
  156. (defun .posting ()
  157. (.let* ((_ (.whitespace))
  158. (status (.optional (.prog1 (.status) (.optional (.spaces)))))
  159. (account (.account))
  160. (amount (.optional (.prog2 (.whitespace :at-least 2) (.amount) (.optional (.whitespace)))))
  161. (unit-price (.optional (.progn (.char= #\@)
  162. (.not (.char= #\@))
  163. (.optional (.whitespace))
  164. (.amount))))
  165. (_ (.optional (.whitespace)))
  166. (total-price (.optional (.progn (.string= "@@")
  167. (.optional (.whitespace))
  168. (.amount))))
  169. (_ (.optional (.whitespace)))
  170. (comment (.optional (.comment)))
  171. (_ (.eol))
  172. (comments (.mapcar* (.prog2 (.whitespace) (.comment) (.eol)))))
  173. (if (and unit-price total-price) (.fail)
  174. (.identity (make-posting
  175. :status status
  176. :account account
  177. :amount amount
  178. :unit-price unit-price
  179. :total-price total-price
  180. :comment (when (or comment comments)
  181. (format nil "~{~A~^~%~}" (remove nil (list* comment comments)))))))))
  182. (defun .entry ()
  183. (.let* ((date (.simple-date))
  184. (secondary-date (.optional (.progn (.char= #\=)
  185. (.simple-date (car (get-date date))))))
  186. (status (.optional (.progn (.spaces) (.status))))
  187. (code (.optional (.progn (.spaces) (.code))))
  188. (_ (.optional (.whitespace)))
  189. (description (.optional (.description)))
  190. (_ (.optional (.whitespace)))
  191. (comment (.optional (.comment)))
  192. (_ (.eol))
  193. (comments (.mapcar* (.prog2 (.whitespace) (.comment) (.eol))))
  194. (postings (.mapcar* (.posting))))
  195. (.identity (make-entry :date date
  196. :secondary-date secondary-date
  197. :status status
  198. :code code
  199. :description description
  200. :comment (when (or comment comments)
  201. (format nil "~{~A~^~%~}" (remove nil (list* comment comments))))
  202. :postings postings))))
  203. (defun .wrap (prefix parser)
  204. (.let* ((value parser))
  205. (.identity (cons prefix value))))
  206. (defun .journal ()
  207. (.prog1
  208. (.mapcar*
  209. (.progn
  210. (.empty-lines)
  211. (.or
  212. (.wrap :entry (.entry))
  213. (.prog1
  214. (.or
  215. (.wrap :market-price
  216. (.progn (.char= #\P)
  217. (.optional (.whitespace))
  218. (.let* ((date (.simple-date))
  219. (_ (.whitespace))
  220. (commodity (.commodity))
  221. (_ (.whitespace))
  222. (unit-price (.amount)))
  223. (.identity (list date commodity unit-price)))))
  224. (.wrap :commodity
  225. (.progn (.string= "commodity")
  226. (.whitespace)
  227. (.amount)))
  228. (.wrap :default-commodity
  229. (.progn (.char= #\D)
  230. (.optional (.whitespace))
  231. (.amount)))
  232. (.wrap :default-year
  233. (.progn (.char= #\Y)
  234. (.optional (.whitespace))
  235. (.number))))
  236. (.optional (.whitespace))
  237. (.eol)))))
  238. (.empty-lines)))
  239. (defun .query-coloned (type key-parser value-parser)
  240. (.let* ((key key-parser)
  241. (_ (.char= #\:))
  242. (value value-parser))
  243. (.identity (cons type
  244. #'(lambda (entry posting)
  245. (let ((key-value (funcall key entry posting)))
  246. (or (eql key-value :t)
  247. (funcall value key-value))))))))
  248. (defun .query-entryp (prefix key)
  249. (.progn (.string= prefix) (.identity #'(lambda (e p) (declare (ignore p)) (funcall key e)))))
  250. (defun .query-postingp (prefix key)
  251. (.progn (.string= prefix) (.identity #'(lambda (e p) (declare (ignore e)) (funcall key p)))))
  252. (defun .query-value-regex ()
  253. (.let* ((regex (.until (.eol) :result-type 'string)))
  254. (handler-case
  255. (let ((matcher (cl-ppcre:create-scanner regex :case-insensitive-mode t)))
  256. (.identity #'(lambda (value)
  257. (cl-ppcre:scan matcher value))))
  258. (error ()
  259. (.fail)))))
  260. (defun .query-value-amount ()
  261. (.let* ((op (.or (.string= ">=")
  262. (.string= "<=")
  263. (.char= #\=)
  264. (.char= #\<)
  265. (.char= #\>)
  266. (.identity #\=)))
  267. (value (.number)))
  268. (.identity #'(lambda (v)
  269. (funcall (find-symbol (string op)) v value)))))
  270. (defun .month ()
  271. (.or (.progn (.or (.string= "january") (.string= "jan")) (.identity 1))
  272. (.progn (.or (.string= "february") (.string= "feb")) (.identity 2))
  273. (.progn (.or (.string= "march") (.string= "mar")) (.identity 3))
  274. (.progn (.or (.string= "april") (.string= "apr")) (.identity 4))
  275. (.progn (.string= "may") (.identity 5))
  276. (.progn (.or (.string= "june") (.string= "jun")) (.identity 6))
  277. (.progn (.or (.string= "july") (.string= "jul")) (.identity 7))
  278. (.progn (.or (.string= "august") (.string= "aug")) (.identity 8))
  279. (.progn (.or (.string= "september") (.string= "sep")) (.identity 9))
  280. (.progn (.or (.string= "october") (.string= "oct")) (.identity 10))
  281. (.progn (.or (.string= "november") (.string= "nov")) (.identity 11))
  282. (.progn (.or (.string= "december") (.string= "dec")) (.identity 12))))
  283. (defun week-start-date (universal-time &optional offset)
  284. (get-date (- universal-time (* +day+ (+ (nth 6 (multiple-value-list (decode-universal-time universal-time)))
  285. (- (* 7 (or offset 0))))))))
  286. (defun month-start-date (year month &optional offset)
  287. (multiple-value-bind (year-offset month) (floor (+ month (or offset 0) -1) 12)
  288. (list (+ year year-offset) (1+ month) 1)))
  289. (defun .smart-range (parser &optional duration)
  290. (.let* ((date parser))
  291. (destructuring-bind (&optional year month day) date
  292. (handler-case
  293. (let* ((ut (get-ut (or year (car (get-date (get-universal-time))))
  294. (or month 1) (or day 1)))
  295. (date (get-date ut)))
  296. (.identity (cons ut
  297. (if duration (+ ut duration)
  298. (if day (+ ut +day+)
  299. (if month (apply #'get-ut (month-start-date (car date) (cadr date) 1))
  300. (get-ut (1+ year) 1 1)))))))
  301. (error () (.fail))))))
  302. (defun .smart-date ()
  303. (let* ((now (get-universal-time))
  304. (date-now (get-date now)))
  305. (.or (.smart-range (.list (.prog1 (.number 4) (.date-delimiter))
  306. (.prog1 (.number) (.date-delimiter))
  307. (.number)))
  308. (.smart-range (.list (.prog1 (.number 4) (.date-delimiter))
  309. (.number)
  310. (.identity nil)))
  311. (.smart-range (.list (.number 4) (.identity nil) (.identity nil)))
  312. (.smart-range (.list (.identity nil)
  313. (.prog1 (.number) (.date-delimiter))
  314. (.number)))
  315. (.smart-range (.list (.identity nil) (.month) (.identity nil)))
  316. (.smart-range (.progn (.string= "today") (.identity date-now)))
  317. (.smart-range (.progn (.string= "yesterday") (.identity (get-date (- now +day+)))))
  318. (.smart-range (.progn (.string= "tomorrow") (.identity (get-date (+ now +day+)))))
  319. (.let* ((offset (.or (.progn (.string= "this") (.identity 0))
  320. (.progn (.string= "last") (.identity -1))
  321. (.progn (.string= "next") (.identity 1))
  322. (.identity 0)))
  323. (_ (.optional (.whitespace))))
  324. (.or (.smart-range (.progn (.string= "year")
  325. (.identity (list (+ (car date-now) offset) nil nil))))
  326. (.smart-range (.progn (.string= "month")
  327. (.identity (subseq (month-start-date (car date-now) (cadr date-now) offset)
  328. 0 2))))
  329. (.smart-range (.progn (.string= "week")
  330. (.identity (week-start-date now offset)))
  331. (* +day+ 7)))))))
  332. (defun .query-value-period ()
  333. (.let* ((period (.or (.let* ((start (.optional (.smart-date)))
  334. (_ (.optional (.whitespace)))
  335. (_ (.or (.char= #\-) (.string= "to")))
  336. (_ (.optional (.whitespace)))
  337. (end (.optional (.smart-date))))
  338. (if (or start end)
  339. (.identity (cons (car start) (car end)))
  340. (.fail)))
  341. (.smart-date))))
  342. (destructuring-bind (start . end) period
  343. (.identity #'(lambda (value)
  344. (and
  345. (or (not start) (>= value start))
  346. (or (not end) (< value end))))))))
  347. (defun .query-term ()
  348. (.or (.query-coloned :acct (.query-postingp "acct" #'posting-account)
  349. (.query-value-regex))
  350. (.query-coloned :amt (.progn (.string= "amt")
  351. (.identity
  352. #'(lambda (e p)
  353. (let ((amounts (get-amounts p (entry-postings e))))
  354. (if (> (length amounts) 1) :t
  355. (amount-quantity (car amounts)))))))
  356. (.query-value-amount))
  357. (.query-coloned :code (.query-entryp "code" #'entry-code)
  358. (.query-value-regex))
  359. (.query-coloned :cur (.progn (.string= "cur")
  360. (.identity
  361. #'(lambda (e p)
  362. (get-amounts p (entry-postings e)))))
  363. (.let* ((regexp (.query-value-regex)))
  364. (.identity #'(lambda (amounts)
  365. (find-if regexp amounts :key #'amount-commodity)))))
  366. (.query-coloned :desc (.query-entryp "desc" #'entry-description)
  367. (.query-value-regex))
  368. (.query-coloned :date (.query-entryp "date" #'entry-date)
  369. (.query-value-period))
  370. (.let* ((value (.query-value-regex)))
  371. (.identity (cons :acct
  372. #'(lambda (entry posting)
  373. (declare (ignore entry))
  374. (let ((account (posting-account posting)))
  375. (funcall value account))))))))
  376. (defun .arg ()
  377. (.let* ((word (.until (.chars #\' #\" #\Space #\Tab) :result-type 'string :at-least 0))
  378. (quoted (.optional (.let* ((quote (.chars #\' #\"))
  379. (text (.until (.char= quote) :result-type 'string :at-least 0))
  380. (_ (.char= quote)))
  381. (.identity text)))))
  382. (.identity (if quoted (concatenate 'string word quoted)
  383. word))))
  384. (defun make-query-predicate (terms)
  385. (let (desc acct status other)
  386. (loop for (type . f) in terms
  387. do (case type
  388. (:desc (push f desc))
  389. (:acct (push f acct))
  390. (:status (push f status))
  391. (otherwise (push f other))))
  392. #'(lambda (entry posting)
  393. (labels ((any (predicates)
  394. (or (not predicates)
  395. (find-if #'(lambda (p) (funcall p entry posting)) predicates)))
  396. (all (predicates)
  397. (not (find-if-not #'(lambda (p) (funcall p entry posting)) predicates))))
  398. (and (any desc) (any acct) (any status) (all other))))))
  399. (defun .query ()
  400. (.let* ((args (.mapcar* (.prog1 (.arg) (.optional (.whitespace))))))
  401. (loop for arg in args with terms
  402. do (multiple-value-bind (pred left)
  403. (parse (.query-term) arg)
  404. (if (and pred (input-empty-p left))
  405. (push pred terms)
  406. (return (.fail))))
  407. finally (return (.identity (make-query-predicate terms))))))