başlangıç, push pop + - . vb.

This commit is contained in:
2024-07-18 13:50:14 +03:00
commit a0fb419b1b
8 changed files with 327 additions and 0 deletions

72
assembly.lisp Normal file
View File

@@ -0,0 +1,72 @@
(in-package :cl-forth)
(defparameter *operations* (make-hash-table))
(eval-always
(defun normalize-op-list (lst)
(cons 'list
(mapcar (lambda (el) (cond ((stringp el) el)
((listp el) `(format nil ,@el))))
lst))))
(defmacro defop (op-name args &body asm-strings)
`(setf (gethash ',op-name *operations*)
(lambda (out-stream ,@args)
(format out-stream "~{ ~a~%~}"
,(normalize-op-list asm-strings)))))
(defop push (a)
("push ~d" a))
(defop + ()
"pop rax"
"pop rbx"
"add rax, rbx"
"push rax")
(defop - ()
"pop rax"
"pop rbx"
"sub rbx, rax"
"push rbx")
(defop |.| ()
"pop rdi"
"call dump")
(defun gen-dump (str)
(format str "~{~a~%~}"
'("dump:"
" mov r9, -3689348814741910323"
" sub rsp, 40"
" mov BYTE [rsp+31], 10"
" lea rcx, [rsp+30]"
".L2:"
" mov rax, rdi"
" lea r8, [rsp+32]"
" mul r9"
" mov rax, rdi"
" sub r8, rcx"
" shr rdx, 3"
" lea rsi, [rdx+rdx*4]"
" add rsi, rsi"
" sub rax, rsi"
" add eax, 48"
" mov BYTE [rcx], al"
" mov rax, rdi"
" mov rdi, rdx"
" mov rdx, rcx"
" sub rcx, 1"
" cmp rax, 9"
" ja .L2"
" lea rax, [rsp+32]"
" mov edi, 1"
" sub rdx, rax"
" xor eax, eax"
" lea rsi, [rsp+32+rdx]"
" mov rdx, r8"
" mov rax, 1"
" syscall"
" add rsp, 40"
" ret")))

6
build.sh Executable file
View File

@@ -0,0 +1,6 @@
#!/bin/sh
sbcl --load cl-forth.asd \
--eval '(ql:quickload :cl-forth)' \
--eval '(asdf:make :cl-forth)' \
--eval '(quit)'

14
cl-forth.asd Normal file
View File

@@ -0,0 +1,14 @@
(asdf:defsystem "cl-forth"
:description "Stack based language implemented in Common Lisp"
:version "0.1"
:author "Emre Akan"
:licence "MIT"
:depends-on ("iterate")
:components ((:file "package")
(:file "util")
(:file "assembly")
(:file "cl-forth")
(:file "main"))
:build-operation "program-op"
:build-pathname "test/cl-forth"
:entry-point "cl-forth:main")

140
cl-forth.lisp Normal file
View File

@@ -0,0 +1,140 @@
(in-package :cl-forth)
(defparameter *identifiers* '(+ - |.| =))
(defun is-identifier (sym)
(find sym *identifiers*))
(defun make-token (sym? line col)
(if (or (is-identifier sym?) (numberp sym?))
(values (list sym? :line line :col col) nil)
(values (list sym? :line line :col col :error t) t)))
(defun token-op (token)
(car token))
(defun lex-line (line-stream line-num)
(iter (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)
(collect (make-token '|.| line-num col) into tokens)
(read-char line-stream))
((char= #\Space next-char) (read-char line-stream))
((char= #\; next-char) ;; and not in string
(finish))
(t (setf flag nil)))
(when flag
(incf col)
(next-iteration)))
(for next-sym in-stream line-stream
using #'read-preserving-whitespace)
(multiple-value-bind (token err)
(make-token next-sym line-num col)
(collect token into tokens)
(when err ;; skip line on error and continue lexing
(setf has-err t)
(finish))
(incf col (length (princ-to-string next-sym))))
(finally (return (values tokens has-err)))))
(defun lex-file (file-name &optional report-errors)
(let ((has-error nil))
(values
(with-open-file (str file-name)
(iter outer
(for line = (read-line str nil nil))
(until (null line))
(for line-num from 1)
(multiple-value-bind (tokens has-err)
(lex-line (make-string-input-stream line) line-num)
(when has-err
(setf has-error t)
(when report-errors
(format t "~a~%" line)
(let ((err-token (find-if (lambda (tok) (find :error tok))
tokens)))
(format t "~a^"
(make-string (getf (cdr err-token) :col)
:initial-element #\Space)))))
(appending tokens))))
has-error)))
;; (defun prog-from-tokens (tokens)
;; (iter (for token in tokens)
;; (let ((op (token-op token)))
;; (cond ((numberp op)
;; (collect `(push ,op) result-type 'vector))
;; (t (collect (list op) result-type 'vector))))))
(defun parse-tokens (tokens)
(iter (with ops = (make-array (length tokens) :fill-pointer 0
:adjustable t))
(for i from 0)
(for token in tokens)
(let ((op (token-op token)))
(cond ((numberp op)
(vector-push-extend `(push ,op) ops))
(t (vector-push-extend (list op) ops))))
(finally (return ops))))
(defun make-program (file-name)
(multiple-value-bind (tokens has-error)
(lex-file file-name t)
(when has-error
(error "Can't generate program due to error during lexing"))
(parse-tokens tokens)))
;; (defun *ops* '(push pop minus dump))
(defun interpret-program (program)
(iter (with stack = (make-array 100 :fill-pointer 0 :adjustable t))
(for op in-sequence program)
(case (first op)
(push (vector-push-extend (second op) stack))
(+ (vector-push-extend (+ (vector-pop stack)
(vector-pop stack))
stack))
(- (vector-push-extend (let ((top (vector-pop stack)))
(- (vector-pop stack) top))
stack))
(|.| (print (vector-pop stack)))
(= (vector-push-extend (= (vector-pop stack)
(vector-pop stack))
stack))
(otherwise (error "op: ~a -- Not implemented yet" (first op))))))
(defun gen-header (op str)
(format str " ;; -- ~s --~%" op))
;; (defun not-implemented (str)
;; (format str " ;; -- TODO: not implemented --~%"))
(defun generate-program (program &key (path "output.asm") (compile nil))
(with-open-file (out path :direction :output
:if-exists :supersede)
(format out "~a~%" "segment .text")
(gen-dump out)
(format out "~{~a~%~}" '("global _start"
"_start:"))
(iter (for op in-sequence program)
(gen-header op out)
(let ((op-fn (gethash (car op) *operations*)))
(if (null op-fn)
(format t "~s is not an op" (car op))
(apply op-fn out (cdr op)))))
(format out "~{~a~%~}" '(" mov rax, 60"
" mov rdi, 0"
" syscall")))
(when compile
(run `("nasm" "-felf64" ,path))
(let ((name (first (uiop:split-string path :separator '(#\.)))))
(run `("ld" "-o" ,name ,(concatenate 'string name ".o"))))))
(defun compile-program (path)
(generate-program (make-program path) :compile t))

39
main.lisp Normal file
View File

@@ -0,0 +1,39 @@
(in-package :cl-forth)
(defun main ()
(let ((args (rest sb-ext:*posix-argv*)))
(let ((flag (first args)))
(cond ((string= flag "-c")
;; (iter (for (k v) in-hashtable *operations*)
;; (for i from 0)
;; (format t "~s: ~s~%" i k))
;; (let ((tokens (lex-file (second args))))
;; (format t "~s~%" tokens)
;; (let ((program (prog-from-tokens tokens)))
;; (format t "~s~%" program)
;; (generate-program program :compile t)))
(compile-program (second args)))
((string= flag "-i")
(format t "Interpret program WIP~%"))
(t (format t "~a is not a valid flag~%" flag))))))
;; (defun make-exe ()
;; (sb-ext:save-lisp-and-die #P"cl-forth"
;; :toplevel #'main
;; :executable t))
(defparameter *example-path* "./test/prog.lorth")
(defun example-lex ()
(lex-file *example-path* t))
(defun example-prog ()
(make-program *example-path*))
(defun example-compile ()
(generate-program (make-program *example-path*) :path "test/output.asm"
:compile t))
(defun example-run ()
(example-compile)
(run '("test/output")))

3
package.lisp Normal file
View File

@@ -0,0 +1,3 @@
(defpackage cl-forth
(:use :common-lisp :iterate)
(:export #:main))

6
test/prog.lorth Normal file
View File

@@ -0,0 +1,6 @@
2 3 + . ;; asddsaj 46
10 6 - . ;; I am a comment
34 35 + .
;;34 35 + 69 = .

47
util.lisp Normal file
View File

@@ -0,0 +1,47 @@
(in-package :cl-forth)
(eval-when (:compile-toplevel :load-toplevel :execute)
(defmacro eval-always (&body body)
`(eval-when (:compile-toplevel :load-toplevel :execute)
,@body)))
(eval-always
(defmacro with-gensyms (syms &body body)
`(let ,(mapcar (lambda (sym) `(,sym (gensym ,(string sym)))) syms)
,@body)))
(defmacro init-hash (&body body)
(with-gensyms (table)
`(let ((,table (make-hash-table)))
,@(iter (for (k v) in body)
(collect `(setf (gethash ',k ,table) ,v)))
,table)))
(defun mklist (form)
(if (listp form) form (list form)))
;; (defmacro run (args)
;; (let ((sym (gensym)))
;; `(let ((,sym ,args))
;; (format t "~{~a~^ ~}~%" ,sym)
;; (uiop:run-program ,sym))))
(defun run (args)
(format t "~{~a~^ ~}~%" args)
(uiop:run-program args :output *standard-output*))
;; ,(file-namestring
;; (make-pathname :name (pathname-name path)
;; :type "o"))
(defparameter *test-program*
'((push 34)
(push 35)
(plus)
(dump)
(push 500)
(push 80)
(minus)
(dump)))