parsing.lisp 22 KB

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