aboutsummaryrefslogtreecommitdiff
path: root/src/parser.lisp
diff options
context:
space:
mode:
Diffstat (limited to 'src/parser.lisp')
-rw-r--r--src/parser.lisp144
1 files changed, 144 insertions, 0 deletions
diff --git a/src/parser.lisp b/src/parser.lisp
new file mode 100644
index 0000000..60e7ac4
--- /dev/null
+++ b/src/parser.lisp
@@ -0,0 +1,144 @@
+(in-package #:chains)
+
+(defconstant +special-characters+
+ '(#\\ #\% #\# #\@ #\$ #\{ #\} #\[ #\] #\Newline)
+ "")
+
+(defconstant +whitespace+ '(#\Tab #\Newline #\Return #\Space)
+ "")
+
+;; TODO: (read-char stream nil)
+(defun read-if (predicate stream)
+ "Read characters from STREAM into a string as long as PREDICATE is true"
+ (let ((string (with-output-to-string (value)
+ (loop :for c = (read-char stream nil)
+ :while (and c (funcall predicate c))
+ :do (write-char c value)
+ :finally
+ (when c (unread-char c stream))))))
+ (if (emptyp string) nil string)))
+
+(defun read-if-not (predicate stream)
+ "Read characters from STREAM into a string as long as PREDICATE is false"
+ (read-if (complement predicate) stream))
+
+(defun read-text (stream start-of-line)
+ "Read text from STREAM"
+ (let* ((text (read-if-not #'special-char-p stream))
+ (peek (peek-char nil stream nil)))
+ (when text (setf start-of-line nil))
+ (cond ((and (null text) (null peek))
+ nil)
+ ((and (not start-of-line) (newlinep peek))
+ (read-char stream nil)
+ (let ((rest (read-text stream t)))
+ (when (eq rest 'empty-line)
+ (setf rest nil)
+ (unread-char #\Newline stream))
+ (list* text (string #\Newline) rest)))
+ ((newlinep peek)
+ (read-char stream nil)
+ 'empty-line)
+ (t
+ (ensure-list text)))))
+
+(defun atom-char-p (char)
+ (or (alphanumericp char) (eql char #\-)))
+
+(defun next-token (stream start-of-line &aux c)
+ "Read the next token inside STREAM"
+ (case (setf c (read-char stream nil))
+ ('nil nil)
+ (#\\
+ (concat-text (make-text (string (read-char stream nil)))
+ (make-text (read-if-not #'special-char-p stream))))
+ (#\%
+ (peek-char #\Newline stream)
+ (read-char stream nil)
+ ;;(next-token stream nil)
+ 'separator)
+ ((#\@ #\$)
+ (let* ((text (read-if #'atom-char-p stream))
+ (value (read-from-string text))
+ (type (cond ((symbolp value) 'symbol)
+ ((integerp value) 'integer)
+ (t (error "Unknown type of object @~A~%" text)))))
+ ;; (unless (or (symbolp value) (integerp value))
+ ;; (error "Unknown type of object @~A~%" text))
+ (cons type value)))
+ (#\#
+ (case (read-char stream nil)
+ (#\@ (cons 'raw-function (read-if #'alphanumericp stream)))
+ (#\$ (cons 'inline-raw-function (read-if #'alphanumericp stream)))))
+ (#\{ 'link-start)
+ (#\} 'link-end)
+ (#\[ 'raw-link-start)
+ (#\] 'raw-link-end)
+ (t
+ (unread-char c stream)
+ (let ((text (read-text stream start-of-line)))
+ (cond ((null text) nil)
+ ((symbolp text) text)
+ (t (make-text (apply #'concatenate 'string text))))))))
+
+(defun build-tree (tokens)
+ "Groups a list of tokens into a tree. Returns a subtree and a list of unused
+tokens."
+ (flet ((build-chain (tokens)
+ (loop :with tokens = tokens
+ :for (link rest) = (multiple-value-list (build-tree tokens))
+ :collect link :into links
+ :while (member (first rest) '(raw-link-start link-start))
+ :do (setf tokens (rest rest))
+ :finally
+ (setf tokens rest)
+ (return (values links tokens)))))
+ (destructuring-bind (&optional head &rest tail) tokens
+ (cond ((null head)
+ (values nil '()))
+ ((or (eq head 'link-end) (eq head 'raw-link-end))
+ (values '() (rest tokens)))
+ ((eq head 'separator)
+ (multiple-value-bind (tree rest) (build-tree tail)
+ (values tree rest)))
+ ((eq head 'link-start)
+ (multiple-value-bind (links rest) (build-chain tail)
+ (multiple-value-bind (tree rest) (build-tree rest)
+ (values (cons (cons 'chain links) tree) rest))))
+ (t
+ (multiple-value-bind (tree rest) (build-tree tail)
+ (values (cons head tree) rest)))))))
+
+(defun read-raw-link (stream)
+ "Read argument of a raw function"
+ (flet ((squarep (x) (member x '(#\]))))
+ ;; Text omits closing ], may therefore be nil, for example when reading ]]
+ (loop :for text = (read-if-not #'squarep stream)
+ :for c = (peek-char nil stream nil)
+ :when text :collect text :into text-list
+ :while (and c text (eql (last-elt text) #\\))
+ :collect (string (read-char stream nil)) :into text-list
+ :finally
+ ;; Keep ] inside stream
+ (return (list (make-text (apply #'concat text-list)))))))
+
+(defun start-of-line-p (last-token)
+ ;; Keep track if new line starts a paragraph
+ (or (eq last-token 'empty-line)
+ (and (textp last-token)
+ (newlinep (last-elt (content last-token))))))
+
+(defun read-tree (stream)
+ "Build a syntax tree from the contents of STREAM"
+ (loop :with start-of-line = t
+ :for token = (next-token stream start-of-line)
+ :while token
+ :if (eq token 'raw-link-start)
+ :append (cons 'raw-link-start (read-raw-link stream)) :into tokens
+ :and :do
+ (setf start-of-line nil)
+ :else
+ :collect token :into tokens :and :do
+ (setf start-of-line (start-of-line-p token))
+ :finally ;; (build-tree)
+ (return tokens)))