memgraph/src/lisp/reader.lisp
Lovro Lugovic 0b2240e6e4 LCP: Split up PROCESS-FILE
Summary:
Split up `process-file` so that looking at the generated code for an LCP form is
easier from the REPL.

`process-lcp`, `generate-hpp` and `generate-cpp` now perform the generation of
C++ code, but take a list of "C++ elements" (the results of LCP forms) as input
and write their output to streams. They do no reading/evaluating of LCP forms of
their own.

`read-lcp` and `read-lcp-file` are used to read and evaluate a stream of LCP
forms. The latter is a specialized version for file streams which also reports
the position of the form within the file when an error happens.

`process-lcp-string` and `process-lcp-file` are convenient wrappers around the
main functionality that take a string (file) and output to strings (files).
Using `read-lcp` and `read-lcp-file` they process LCP forms and pass them off to
`process-lcp` for code generation.

Reviewers: mtomic, teon.banek

Reviewed By: teon.banek

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D2097
2019-06-12 09:38:41 +02:00

96 lines
4.1 KiB
Common Lisp

(in-package #:lcp)
(defstruct raw-cpp
"Represents a raw character string of C++ code."
(string "" :type string :read-only t))
(defvar +whitespace-chars+
'(#\Newline #\Space #\Return #\Linefeed #\Tab #\Page))
(defun read-expecting (string &optional (stream *standard-input*)
(eof-error-p t) recursivep)
"Tries to read from STREAM a character sequence that matches a possibly empty
STRING and reports whether it was successful.
If EOF-ERROR-P is T and EOF is reached before all of the characters are matched,
an END-OF-FILE error is signaled.
Otherwise, returns 2 values, SUCCESSP and COUNT. SUCCESSP is a boolean denoting
whether it was able to match all of the characters. COUNT is the number of
characters that were read from STREAM, i.e. successfully matched.
If STRING is empty then it is automatically treated as a successful
match (returning T and 0), whether or not STREAM has reached EOF.
RECURSIVEP should be treated as in standard reader functions such as READ."
(loop :for count :from 0
:for c1 :across string
:for c2 := (peek-char nil stream eof-error-p nil recursivep)
:while (and c2 (char= c1 c2))
:do (read-char stream t nil recursivep)
:finally (return (values (= count (length string)) count))))
(defun |#>-reader| (stream sub-char numarg)
"Reads the #>cpp ... cpp<# block into an instance of RAW-CPP.
The block supports string interpolation of variables by using the syntax similar
to shell interpolation. For example, ${variable} will interpolate (be replaced
with) the value of VARIABLE."
(declare (ignore sub-char numarg))
(let ((output
(make-array 0 :element-type 'character :adjustable t :fill-pointer 0))
(begin-cpp "cpp")
(end-cpp "cpp<#")
(interpolated-args nil))
(unless (read-expecting begin-cpp stream nil t)
(error "Expected a C++ block to start with \"#>cpp\""))
(flet ((interpolate-argument ()
"Parse argument for interpolation after $."
(when (char= #\$ (peek-char nil stream t nil t))
;; $$ is just $
(vector-push-extend (read-char stream t nil t) output)
(return-from interpolate-argument))
(unless (char= #\{ (peek-char nil stream t nil t))
(error "Expected { after $"))
(read-char stream t nil t) ;; consume {
(let ((form (let ((*readtable* (copy-readtable)))
;; Read form to }
(set-macro-character #\} (get-macro-character #\)))
(read-delimited-list #\} stream t))))
(when (and (not *read-suppress*)
(or (null form)
(not (symbolp (car form)))
(cdr form)))
(error "Expected a variable inside ${...}, got ~S" form))
;; Push the variable symbol
(push (car form) interpolated-args))
;; Push the format directive
(vector-push-extend #\~ output)
(vector-push-extend #\A output)))
(handler-case
(do (curr
(pos 0))
((= pos (length end-cpp)))
(setf curr (read-char stream t nil t))
(if (and (< pos (length end-cpp))
(char= (char-downcase curr) (aref end-cpp pos)))
(incf pos)
(setf pos 0))
(if (char= #\$ curr)
(interpolate-argument)
(vector-push-extend curr output)))
(end-of-file ()
(error "Missing a closing \"cpp<#\" delimiter for a C++ block"))))
(let ((trimmed-string
(string-trim +whitespace-chars+
(subseq output
0 (- (length output) (length end-cpp))))))
`(make-raw-cpp
:string ,(if interpolated-args
`(format nil ,trimmed-string ,@(reverse interpolated-args))
trimmed-string)))))
(named-readtables:defreadtable lcp-syntax
(:merge :standard)
(:dispatch-macro-char #\# #\> #'|#>-reader|))