summaryrefslogtreecommitdiff
path: root/cl-forth.lisp
blob: 210b0efe4d90825f8da619f0e626b387978fe88a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
(in-package :cl-forth)

(defun make-token (sym? line col &optional (type nil))
  (when (null type)
    (setf type
          (cond ((numberp sym?) :number)
                ((stringp sym?) :string)
                ((is-identifier sym?) :identifier)
                (t :unknown))))
  (list sym? :line line :col col :type type))

(defun token-op (token)
  (car token))

;;; LEXER
(defun read-string (stream)
  ;; TODO: Handle unmatched " and give proper error message
  (with-output-to-string (str)
    (iter (for ch = (read-char stream))
          (cond ((char= ch #\")
                 (finish))
                ((and (char= ch #\\) (char= (peek-char nil stream) #\n))
                 (read-char stream)
                 (write-char #\Newline str))
                (t (write-char ch str))))))

(defun lex-line (line &optional (line-num 0))
  (let ((*package* (find-package "KEYWORD")))
    (iter (with line-stream = (make-string-input-stream line))
          (with col = 0)
          (with has-err = nil)
          (for next-char = (peek-char nil line-stream nil nil))
          (until (null next-char))
          (let ((flag t))
            (cond ((char= #\|  next-char)
                   (read-char line-stream)
                   (collect (make-token :pipe line-num col :identifier)
                            into tokens))
                  ((char= #\Space next-char) (read-char line-stream))
                  ((char= #\; next-char) ;; and not in string
                   (finish))
                  ((char= #\" next-char)
                   (read-char line-stream)
                   (collect (make-token (read-string line-stream)
                                        line-num col)
                            into tokens))
                  (t (setf flag nil)))
            (when flag
              (incf col)
              (next-iteration)))
          (for next-sym in-stream line-stream
               using #'read-preserving-whitespace)
          (collect (make-token next-sym line-num col) into tokens)
          (incf col (length (princ-to-string next-sym)))
          (finally (return tokens)))))

(defun lex-stream (str)
  (iter (for line = (read-line str nil nil))
        (until (null line))
        (for line-num from 1)
        (appending (lex-line line line-num))))

(defun lex-file (file-name)
  (with-open-file (str file-name :external-format :utf-8)
    (lex-stream str)))

(defun lex-string (string)
  (lex-stream (make-string-input-stream string)))



;;; PARSER
(defmacro add-op (token parser)
  `(progn (vector-push-extend ,token (ops ,parser))
          (incf (index ,parser))))

(defparameter *makro-defs* (make-hash-table))

(defclass parser ()
  ((if-stack :initform ()
             :accessor if-stack)
   (ops :accessor ops)
   (tokens :initarg :tokens
           :accessor tokens)
   (index :initform 0
          :accessor index)))

(defun make-parser (tokens)
  (let ((parser (make-instance 'parser :tokens tokens)))
    (setf (ops parser) (make-array (length tokens) :fill-pointer 0
                                                   :adjustable t))
    parser))

(defmethod peek-token ((parser parser))
  (car (tokens parser)))

(defmethod read-token ((parser parser))
  (pop (tokens parser)))

(defmethod parse-token ((parser parser) (type (eql :number)))
  (add-op `(:push-int ,(car (read-token parser))) parser))

(defmethod parse-token ((parser parser) (type (eql :string)))
  (let ((token (read-token parser)))
    (add-op `(:push-str ,(length (car token))
                        ,(index parser)
                        ,(car token))
            parser)))

(defmethod parse-token ((parser parser) (type (eql :identifier)))
  (let ((token (read-token parser)))
    (parse-op parser token (car token))))

(defmethod parse-token ((parser parser) (type (eql :unknown)))
  (let* ((token (read-token parser))
         (makrodef (gethash (car token) *makro-defs*)))
    (if (not (null makrodef))
        ;; (parser-parse-tokens parser makrodef)
        (setf (tokens parser) (append makrodef (tokens parser)))
        (error "parse-token: token has unknown identifier: ~a"
               (read-token parser)))))

;; (defmethod parser-parse-tokens ((parser parser) tokens)
;;   (iter (for makro-op in-vector)
;;             (add-op makro-op ops))
;;   ())

(defmethod parse-op ((parser parser) token (type (eql :ise)))
  (push (list :ise (index parser)) (if-stack parser))
  (add-op (list :ise nil) parser))

(defmethod parse-op ((parser parser) token (type (eql :yoksa)))
  (let ((top (pop (if-stack parser))))
    (assert (and (string= :ise (car top))
                 (string= :ise (car (aref (ops parser) (cadr top))))))
    (setf (second (aref (ops parser) (cadr top))) (index parser))
    (push (list :yoksa (index parser)) (if-stack parser))
    (add-op (list :yoksa nil (index parser)) parser)))

(defmethod parse-op ((parser parser) token (type (eql :yap)))
  (let ((top (pop (if-stack parser))))
    (unless (and (find (car top) (list :yoksa :ise :iken))
                 (find (car (aref (ops parser) (cadr top)))
                       (list :yoksa :ise :iken)))
      (error "yap cannot close ~a" (aref (ops parser) (cadr top))))
    (cond ((find (car top) (list :yoksa :ise))
           (setf (second (aref (ops parser) (cadr top))) (index parser))
           (add-op (list :yap (index parser)) parser))
          ((string= :iken (car top))
           (setf (second (aref (ops parser) (cadr top))) (index parser))
           (add-op (list :yap (index parser) (third top)) parser))
          (t (error "yap cannot reference: ~a" (car top))))))

(defmethod parse-op ((parser parser) token (type (eql :döngü)))
  (push (list :döngü (index parser)) (if-stack parser))
  (add-op (list :döngü (index parser)) parser))

(defmethod parse-op ((parser parser) token (type (eql :iken)))
  (let ((top (pop (if-stack parser))))
    (assert (string= :döngü (car top)))
    (push (list :iken (index parser) (cadr top)) (if-stack parser))
    (add-op (list :iken nil) parser)))

(defmethod parse-op ((parser parser) token (type (eql :makro)))
  ;; makro name must be undefined before
  (let ((makro-name-tok (read-token parser)))
    (assert (eq :unknown (getf (cdr makro-name-tok) :type))) 
    (setf (gethash (car makro-name-tok) *makro-defs*)
          (do ((tok (read-token parser)
                    (read-token parser))
               (makrodef ()))
              ((eq :son (car tok)) (reverse makrodef))
            (push tok makrodef)))))

(defmethod parse-op ((parser parser) token type)
  (cond ((search "syscall" (string-downcase (string (car token))))
         (let ((syscall-num (parse-integer (subseq (string (car token)) 8))))
           (add-op (list :syscall syscall-num) parser)))
        (t (add-op (list type) parser))))

(defun parse-tokens (tokens)
  (let ((parser (make-parser tokens)))
    (iter (while (not (null (tokens parser))))
          (parse-token parser (getf (cdr (peek-token parser)) :type)))
    (ops parser)))

(defun make-program (file-name)
  (parse-tokens (lex-file file-name)))

;;; COMPILER
;;(defgeneric write-program (target program stream))
(defmethod write-program ((target (eql :nasm)) program out
                          &key (mem-cap 640000))
  (format out "~a~%" "segment .text")
  (gen-dump out)
  (format out "~{~a~%~}" '("global _start"
                           "_start:"))
  (let ((strs nil))
    (iter (for op in-sequence program)
          (let ((gen-val (write-op target out (car op) (cdr op))))
            (when (and (consp gen-val) (eq :string (car gen-val)))
              (push (cdr gen-val) strs))))
    (write-op target out :exit '(0))
    (unless (null strs)
      (format out "segment .data~%")
      (dolist (str strs)
        (format out "str_~a: db ~{0x~x~^,~}~%"
                (first str)
                (map 'list #'char-code (second str))))))
  (format out "~a~%" "segment .bss")
  (format out "~a ~a~%" "bel: resb" mem-cap))

(defmethod write-program ((target (eql :c)) program out &key (mem-cap 640000))
  (declare (ignore mem-cap))
  (gen-c-stack out)
  (with-c-fn (:int main) () out
    (iter (for op in-sequence program)
          (write-op target out (car op) (cdr op)))
    (format out "~%    return 0;~%")))

(defun generate-program (program
                         &key (path "output.asm") (compile nil)
                           (mem-cap 640000) (silence nil) (target :nasm))
  (with-open-file (out path :direction :output :if-exists :supersede)
    (write-program target program out :mem-cap mem-cap))
  (when compile
    (compile-program target path silence)))

(defgeneric compile-program (target path silence))
(setf (documentation #'compile-program 'function)
      (format nil "Produces the executable from source code, targets are ~a"
              *targets*))

(defmethod compile-program ((target (eql :nasm)) path silence)
  (run `("nasm" "-felf64" ,path) :output t :silence silence)
  (let ((name (first (uiop:split-string path :separator '(#\.)))))
    (run `("ld" "-o" ,name ,(concatenate 'string name ".o"))
         :output t :silence silence)))

(defmethod compile-program ((target (eql :c)) path silence)
  (let ((name (first (uiop:split-string path :separator '(#\.)))))
    (run `("gcc" ,path "-o" ,name)
         :output t :silence silence)))