utx-file.lisp 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. ;; This software is Copyright (c) cage, 2012.
  2. ;; cage grants you the rights to distribute
  3. ;; and use this software as governed by the terms
  4. ;; of the Lisp Lesser GNU Public License
  5. ;; (http://opensource.franz.com/preamble.html),
  6. ;; known as the LLGPL
  7. (in-package :utx-file)
  8. (alexandria:define-constant +column+ "[^\\t\\n]+(\\t|\\n)" :test 'equalp)
  9. (alexandria:define-constant +column-id+ "[0-9]+\\t" :test 'equalp)
  10. (alexandria:define-constant +utx-ignored-line+ "(#.*\\r\\n)|(^\\p{white_space}+\\r\\n)|(^\\r\\n)" :test 'string=)
  11. (alexandria:define-constant +line-stopper+ #\NewLine :test 'char=)
  12. (alexandria:define-constant +status-forbidden+ "forbidden" :test 'string=)
  13. (alexandria:define-constant +plural-field+ "src:plural" :test 'string=)
  14. (alexandria:define-constant +status-field+ "term status" :test 'string=)
  15. (defparameter *fields-position* (make-hash-table :test 'equal))
  16. (defclass utx-parsed-file (cl-i18n:parsed-file) ())
  17. (defgeneric last-comment-line-p (object))
  18. (defmethod initialize-instance :after ((object utx-parsed-file) &key &allow-other-keys)
  19. (with-slots (comment-line) object
  20. (setf utx-file:comment-line +utx-ignored-line+)))
  21. (defmethod peek-token ((object utx-parsed-file) &optional (test #'identity))
  22. (if (cl-i18n:peek-valid-stream)
  23. (multiple-value-bind (token start-token)
  24. (cl-i18n:next-token object)
  25. (prog1
  26. (funcall test token)
  27. (cl-i18n:seek cl-i18n:*file* start-token)))
  28. nil))
  29. (defmethod last-comment-line-p ((object utx-parsed-file))
  30. (cl-i18n:with-no-errors
  31. (multiple-value-bind (line length start)
  32. (cl-i18n:get-line cl-i18n:*file*)
  33. (declare (ignore length))
  34. (unwind-protect
  35. (not (cl-i18n:is-comment-line-p object line))
  36. (cl-i18n:seek cl-i18n:*file* start)))))
  37. (defmethod parse-comment-line ((object utx-parsed-file))
  38. (cl-i18n:with-no-errors
  39. (multiple-value-bind (line length start)
  40. (cl-i18n:get-line cl-i18n:*file*)
  41. (declare (ignore length))
  42. (if (and
  43. (cl-i18n:is-comment-line-p object line)
  44. (not (last-comment-line-p object)))
  45. (progn
  46. (parse-comment-line object))
  47. (progn
  48. (cl-i18n:seek cl-i18n:*file* start)
  49. nil)))))
  50. (cl-i18n:define-parser-skeleton utx utx-parsed-file
  51. (*fields-position* (make-hash-table :test 'equal)))
  52. (cl-i18n:define-tokenizer (utx-file:utx-parsed-file +column+))
  53. (defun last-column-p (col)
  54. (char= (char col (1- (length col))) +line-stopper+))
  55. (defun row-src (row)
  56. (first row))
  57. (defun row-target (row)
  58. (second row))
  59. (defmacro get-field (key row)
  60. (alexandria:with-gensyms (pos)
  61. `(let ((,pos (gethash ,key *fields-position*)))
  62. (if ,pos
  63. (nth ,pos ,row)
  64. nil))))
  65. (defun row-status (row)
  66. (get-field +status-field+ row))
  67. (defun row-plural (row)
  68. (get-field +plural-field+ row))
  69. (defun status-forbidden-p (row)
  70. (string= +status-forbidden+ (row-status row)))
  71. (cl-i18n:defnocfun parse-utx-file ()
  72. (cl-i18n:with-no-errors
  73. (if (cl-i18n:peek-valid-stream)
  74. (progn
  75. (parse-utx-column-description)
  76. (values (parse-utx-lines)
  77. #'cl-i18n:english-plural-form
  78. cl-i18n:*has-errors*
  79. cl-i18n:*parsing-errors*))
  80. (values nil
  81. #'cl-i18n:english-plural-form
  82. cl-i18n:*has-errors*
  83. cl-i18n:*parsing-errors*))))
  84. (defun parse-utx-column-description ()
  85. (let ((fields (trim-rows (parse-utx-line))))
  86. (loop for i from 0 below (length fields) do
  87. (setf (gethash (nth i fields) *fields-position*) i))))
  88. (defun min-column-number-p (row)
  89. (if (and row
  90. (< (length row) 3))
  91. (progn
  92. (setf cl-i18n:*has-errors* t)
  93. (push (format nil "Error: utx row has less than 3 field: ~{~s~}" row)
  94. cl-i18n:*parsing-errors*)
  95. nil)
  96. row))
  97. (defun trim-rows (rows &optional (bag (format nil "~a~a~a" #\Tab #\Newline #\Return)))
  98. (mapcar #'(lambda (c) (string-trim bag c))
  99. rows))
  100. (defun parse-utx-lines (&optional (current-line '()) (entries (make-hash-table :test 'equal)))
  101. (declare (optimize (speed 3) (safety 0) (debug 0)))
  102. (labels ((get-row ()
  103. (cl-i18n:with-no-errors
  104. (let ((row (parse-utx-line)))
  105. (if (min-column-number-p row)
  106. (trim-rows row)
  107. nil)))))
  108. #+sbcl
  109. (if (cl-i18n:peek-valid-stream)
  110. (progn
  111. (setf current-line (get-row))
  112. (when current-line
  113. (setf (gethash (row-src current-line) entries)
  114. (cl-i18n:make-translation (row-target current-line)
  115. cl-i18n:+translated-flag+
  116. ""
  117. (list (row-plural current-line)))))
  118. (parse-utx-lines current-line entries))
  119. entries)
  120. #-sbcl
  121. (do ((row (get-row) (get-row)))
  122. ((not (cl-i18n:peek-valid-stream)) entries)
  123. (setf (gethash (row-src row) entries)
  124. (cl-i18n:make-translation (row-target row)
  125. cl-i18n:+translated-flag+
  126. ""
  127. (list (row-plural row)))))))
  128. (defun parse-utx-line (&key (look-for-comment t))
  129. (when (and look-for-comment
  130. (cl-i18n:peek-valid-stream))
  131. (parse-comment-line cl-i18n:*file*))
  132. (cl-i18n:with-no-errors
  133. (if (cl-i18n:peek-valid-stream)
  134. (let ((col (parse-utx-column)))
  135. (if col
  136. (if (not (last-column-p col))
  137. (append (list col) (parse-utx-line :look-for-comment nil))
  138. (list col))))
  139. nil)))
  140. (defun parse-utx-column ()
  141. (cl-i18n:with-no-errors
  142. (if (cl-i18n:peek-valid-stream)
  143. (cl-i18n:next-token cl-i18n:*file*)
  144. nil)))