From e90d1248920b50e5f8c25ab406a9095e3f6a2358 Mon Sep 17 00:00:00 2001 From: mRnea Date: Fri, 16 Aug 2024 10:09:45 +0300 Subject: changed project name from cl-forth to kurt --- assembly.lisp | 482 ----------------------------------------------------- build.sh | 6 +- cl-forth.asd | 17 -- cl-forth.lisp | 2 +- codegen.lisp | 482 +++++++++++++++++++++++++++++++++++++++++++++++++++++ errors.lisp | 26 +++ kurt.asd | 18 ++ main.lisp | 6 +- package.lisp | 2 +- simulation.lisp | 2 +- test/arith.kurt | 3 + test/arith.lorth | 3 - test/bits.kurt | 15 ++ test/bits.lorth | 15 -- test/branchs.kurt | 62 +++++++ test/branchs.lorth | 62 ------- test/include.kurt | 3 + test/loop.kurt | 6 + test/loop.lorth | 6 - test/makro.kurt | 6 + test/makro.lorth | 6 - test/stack.kurt | 5 + test/stack.lorth | 5 - test/std.kurt | 4 + test/syscall.kurt | 15 ++ test/syscall.lorth | 15 -- test/tests.lisp | 4 +- util.lisp | 4 +- 28 files changed, 658 insertions(+), 624 deletions(-) delete mode 100644 assembly.lisp delete mode 100644 cl-forth.asd create mode 100644 codegen.lisp create mode 100644 errors.lisp create mode 100644 kurt.asd create mode 100644 test/arith.kurt delete mode 100644 test/arith.lorth create mode 100644 test/bits.kurt delete mode 100644 test/bits.lorth create mode 100644 test/branchs.kurt delete mode 100644 test/branchs.lorth create mode 100644 test/include.kurt create mode 100644 test/loop.kurt delete mode 100644 test/loop.lorth create mode 100644 test/makro.kurt delete mode 100644 test/makro.lorth create mode 100644 test/stack.kurt delete mode 100644 test/stack.lorth create mode 100644 test/std.kurt create mode 100644 test/syscall.kurt delete mode 100644 test/syscall.lorth diff --git a/assembly.lisp b/assembly.lisp deleted file mode 100644 index 16183f7..0000000 --- a/assembly.lisp +++ /dev/null @@ -1,482 +0,0 @@ -(in-package :cl-forth) - -(defparameter *psuedo-identifiers* - '(syscall-1 syscall-2 syscall-3 syscall-4 syscall-5 syscall-6 makro son kütüphane) - "These do not map to operations that generate code directly, but are valid to lexer and parser") - -(defparameter *identifiers* ()) -;; '(+ - dump = ise yoksa yap eş push değiş üst rot düş döngü iken < > -;; bel oku yaz >> << & "|") - -(defun is-identifier (sym) - (or (find sym *identifiers* :test #'string=) - (find sym *psuedo-identifiers* :test #'string=))) - -(eval-always - (defparameter *targets* '(:nasm :c)) - - (defun normalize-op-list (asm-list) - (cons 'list - (mapcar (lambda (el) (cond ((stringp el) el) - ((listp el) `(format nil ,@el)))) - asm-list))) - - (defun defop-format (str space-num asm-list) - (format str - (format nil "~~{~a~~a~~%~~}" - (make-string space-num :initial-element #\Space)) - asm-list)) - - (defun replace-write (out-stream indent forms) - (if (consp forms) - (if (eq :write (car forms)) - `(defop-format ,out-stream ,indent - ,(normalize-op-list (cdr forms))) - (cons (replace-write out-stream indent (car forms)) - (replace-write out-stream indent (cdr forms)))) - forms)) - - (defun add-indent (indent fmt-string) - (format nil "~a~a" - (make-string indent :initial-element #\Space) - fmt-string)) - - (defun split-stack (stack) - (let ((split-num (position '-- stack))) - (values (butlast stack (- (length stack) split-num)) - (nthcdr (+ 1 split-num) stack)))) - - (defun op->string (asm-instruction &key (push? t)) - "asm-instruction is something like (:add rax rbx)" - (destructuring-bind (op arg1 arg2) asm-instruction - (let ((*print-case* :downcase)) - (if (null push?) - (format nil (format nil "~a ~a, ~a" op arg1 arg2)) - (list (format nil "~a ~a, ~a" op arg1 arg2) - (format nil "push ~a" arg1)))))) - - (defun stack->string (stack) - (multiple-value-bind (prev next) - (split-stack stack) - (let ((*print-case* :downcase)) - (append (iter (for sym in (reverse prev)) - (collect (format nil "pop ~a" sym))) - (iter (for form in next) - (cond ((symbolp form) - (appending (list (format nil "push ~a" form)))) - ((listp form) - (appending (op->string form))))))))) - - (defparameter *stack-fn-assoc* '((:= :cmove) - (:> :cmovg) - (:< :cmovl))) - - (defun stack-unextend (stack) - "Turns an extended stack to body of a defop, second part of an extended stack is in the form of (:op arg1 arg2 :then val1 :else val2) which is asserted." - (multiple-value-bind (fst snd) (split-stack stack) - (assert (= 1 (length snd))) - (append (iter (for x in (reverse fst)) - (let ((*print-case* :downcase)) - (collect (format nil "pop ~a" x)))) - (let* ((push-part (car snd)) - (ifs (nthcdr 3 push-part))) - (list `(:mov rdx ,(getf ifs :ise)) - `(:mov rcx ,(getf ifs :değilse)) - `(:cmp ,(second push-part) ,(third push-part)) - `(,(cadr (assoc (car push-part) *stack-fn-assoc*)) - rcx rdx) - (format nil "push rcx")))))) - - (defun syntax-of (form) - (cond ((or (stringp form) - (and (consp form) (stringp (car form)))) - :string) - ((and (listp form) (find '-- form)) - (if (multiple-value-bind (fst snd) (split-stack form) - (declare (ignore fst)) - (and (consp snd) - (consp (car snd)) - (find (caar snd) *stack-fn-assoc* - :key #'car))) - :stack-extended - :stack)) - ((and (listp form) (keywordp (car form))) - :op) - (t :general))) - - (defun group-by-syntax (forms &optional (syntax nil) (cur ()) (acc ())) - (when (null forms) - (return-from group-by-syntax - (cdr (reverse (append (list (cons (syntax-of (car cur)) - (reverse cur))) - acc))))) - (let* ((form (car forms)) - (form-syntax (syntax-of form))) - (cond ((eq syntax form-syntax) - (group-by-syntax (cdr forms) syntax - (cons form cur) acc)) - (t (group-by-syntax (cdr forms) form-syntax - (list form) (append (list (cons syntax - (reverse cur))) - acc)))))) - - (defun expand-nasm-group (group out-stream &key (indent 4)) - (destructuring-bind (syntax-type . forms) group - (case syntax-type - (:stack `((defop-format ,out-stream ,indent - ,(cons 'list (mapcan (lambda (form) (stack->string form)) - forms))))) - (:stack-extended (expand-nasm out-stream indent - (stack-unextend (car forms)))) - (:op `((defop-format ,out-stream ,indent - ,(cons 'list (mapcar (lambda (form) (op->string form :push? nil)) - forms))))) - (:string `((defop-format ,out-stream ,indent - ,(normalize-op-list forms)))) - (:general `((progn ,@(mapcar (lambda (form) (replace-write out-stream indent - form)) - forms))))))) - - (defun expand-nasm (out-stream indent body) - (mapcan #'(lambda (group) (expand-nasm-group group out-stream - :indent indent)) - (group-by-syntax body))) - - (defun expand-c-group (group out-stream &key (indent 4)) - (destructuring-bind (syntax-type . forms) group - (case syntax-type - ((:stack :stack-extended) `((defop-format ,out-stream ,indent - ,(cons 'list - (c-stack->string (car forms)))))) - (:string `((defop-format ,out-stream ,indent - ,(normalize-op-list forms)))) - (:general `((progn ,@(mapcar (lambda (form) (replace-write out-stream indent - form)) - forms))))))) - - (defun expand-c (out-stream indent body) - (mapcan #'(lambda (group) (expand-c-group group out-stream :indent indent)) - (group-by-syntax body))) - - (defun expand-for-target (target out-stream body &optional (indent 4)) - (case target - (:nasm (expand-nasm out-stream indent body)) - (:c (expand-c out-stream indent body)))) - - (defun expand-method (target out-stream indent op-name args body) - (with-gensyms (_op _args _target) - (declare (ignorable _args)) - `(defmethod write-op - ((,_target (eql ,target)) ,out-stream - (,_op (eql ,(intern (string op-name) "KEYWORD"))) - ,_args) - ,@(if (null args) - (expand-for-target target out-stream body indent) - `((destructuring-bind ,args ,_args - ,@(expand-for-target target out-stream body indent))))))) - - ;;hack, because there is no cl in C only nasm - (defun not-cl (sym) - (if (eq 'cl sym) - 'rcx - sym)) - - (defparameter *c-ops* '((:add "+") - (:sub "-") - (:shl "<<") - (:shr ">>") - (:or "|") - (:and "&") - (:< "<") - (:> ">") - (:= "=="))) - - (defun c-stack->string (stack) - (multiple-value-bind (prev next) (split-stack stack) - (let ((*print-case* :downcase)) - (append (iter (for x in (reverse prev)) - (collect (format nil "~a = pop();" x))) - (iter (for x in next) - (if (and (consp x) (assoc (car x) *c-ops*)) - (destructuring-bind (op arg1 arg2 . conds) x - (let ((opstr (cadr (assoc op *c-ops*)))) - (if (null conds) - (collect (format nil "push(~a ~a ~a);" - arg1 opstr (not-cl arg2))) - (collect (format nil - "push((~a ~a ~a) ? ~a : ~a);" - arg1 opstr (not-cl arg2) - (getf conds :ise) - (getf conds :değilse)))))) - (collect (format nil "push(~a);" x))))))))) - -(defun comment-safe-str (str) - "Handle newlines for comment" - (with-output-to-string (new-str) - (iter (for ch in-string str with-index i) - (cond ((> i 10) - (princ "..." new-str) - (finish)) - ((char= #\Newline ch) - (princ "\\n" new-str)) - (t (write-char ch new-str)))))) - -(defgeneric write-op (target stream op args) - (:documentation "Generate code for OP and ARGS and write it to STREAM according to the TARGET. WRITE-OP methods are defined by the DEFOP macro.") - (:method :before ((target (eql :nasm)) stream op args) - (format stream " ;; -- ~s --~%" - (mapcar (lambda (x) (if (stringp x) (comment-safe-str x) x)) - (cons op args)))) - (:method :before ((target (eql :c)) stream op args) - (format stream "~% /* ~s */~%" - ;; comment-safe is probably not necessary with multiline comments - (mapcar (lambda (x) (if (stringp x) (comment-safe-str x) x)) - (cons op args))))) - -(defmacro defop (op-name+args (&key (indent 4) (lex t) (targets *targets*) - (as nil)) - &body body) - (declare (ignorable indent)) - (with-gensyms (out-stream) - (destructuring-bind (op-name . args) (mklist op-name+args) - `(progn - ,@(unless (null lex) - `((push ',op-name *identifiers*))) - ,@(iter (for target in (mklist targets)) - (collect (expand-method target out-stream indent - (if (null as) op-name as) - args body))))))) - -;;; TODO: Turn stack operation comments to DEFOP option, -;;; which then can be used by the user as a documentation -;;; DONE: Better yet, generate the asm code directly from -;;; the stack op documentation (this seems easily doable) -;;; Hopefully these two are done, need testing... - -(defop + () - (rbx rax -- (:add rax rbx))) - -(defop - () - (rbx rax -- (:sub rbx rax))) - -(defop = () - (rbx rax -- (:= rbx rax :ise 1 :değilse 0))) - -(defop eş () - (rax -- rax rax)) - -(defop düş () - (rax -- )) - -(defop < () - (rax rbx -- (:< rax rbx :ise 1 :değilse 0))) - -(defop > () - (rax rbx -- (:> rax rbx :ise 1 :değilse 0))) - -(defop üst () - (rbx rax -- rbx rax rbx)) - -(defop rot () - (rcx rbx rax -- rbx rax rcx)) - -(defop değiş () - (rbx rax -- rax rbx)) - -(defop << () - (rbx rcx -- (:shl rbx cl))) - -(defop >> () - (rbx rcx -- (:shr rbx cl))) - -(defop "|" (:as pipe) - (rbx rax -- (:or rbx rax))) - -(defop & () - (rbx rax -- (:and rbx rax))) - - - -;;; NASM operations -(defop bel (:targets :nasm) - ( -- bel)) - -(defop oku (:targets :nasm) - (rax -- ) - (:xor rbx rbx) - (:mov bl [rax]) - ( -- rbx)) - -(defop yaz (:targets :nasm) - (rax rbx -- ) - (:mov [rax] bl)) - -;; ( -- a) -(defop (push-int a) (:lex nil :targets :nasm) - ("push ~d" a)) - -(defop (push-str len addr str) (:lex nil :targets :nasm) - (progn (:write ("push ~d" len) - ("push str_~d" addr)) - (list :string addr str))) - -(defop dump (:targets :nasm) - "pop rdi" - "call dump") - -(defop (exit code) (:lex nil :targets :nasm) - "mov rax, 60" - ("mov rdi, ~a" code) - "syscall") - -(defop (ise label-num) (:targets :nasm) - "pop rax" - "test rax, rax" - ("jz et_~a" label-num)) - -(defop (yoksa yap-num ise-num) (:indent 0 :targets :nasm) - (" jmp et_~a" yap-num) - ("et_~a:" ise-num)) - -(defop (yap label-num &optional döngü-num) (:indent 0 :targets :nasm) - (if (null döngü-num) - (:write ("et_~a:" label-num)) - (:write (" jmp et_~a" döngü-num) - ("et_~a:" label-num)))) - -(defop (iken label-num) (:targets :nasm) - "pop rax" - "test rax, rax" - ("jz et_~a" label-num)) - -(defop (döngü label-num) (:indent 0 :targets :nasm) - ("et_~a:" label-num)) - -(defop (syscall num) (:lex nil :targets :nasm) - (iter (with call-regs = #("rdi" "rsi" "rdx" "r10" "r8" "r9")) - (initially (:write "pop rax")) - (for i from (- num 1) downto 0) - (:write ("pop ~a" (aref call-regs i))) - (finally (:write "syscall")))) - -(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"))) - - -;;; C operations -(defop (push-int a) (:lex nil :targets :c) - ("push(~d);" a)) - -;; (defop (push-str a) (:lex nil :targets :c) -;; ("push(&stack, ~d);" a)) - -(defop dump (:targets :c) - ("printf(\"%d\\n\", pop());")) - -(defop (ise label-num) (:targets :c) - "rax = pop();" - ("if(!rax){ goto et_~a; }" label-num)) - -(defop (yoksa yap-num ise-num) (:indent 0 :targets :c) - (" goto et_~a;" yap-num) - ("et_~a:" ise-num)) - -(defop (yap label-num &optional döngü-num) (:indent 0 :targets :c) - (if (null döngü-num) - (:write ("et_~a:" label-num)) - (:write (" goto et_~a;" döngü-num) - ("et_~a:" label-num)))) - -(defop (iken label-num) (:targets :c) - "rax = pop();" - ("if(!rax){ goto et_~a; }" label-num)) - -(defop (döngü label-num) (:indent 0 :targets :c) - ("et_~a:" label-num)) - -(defop bel (:targets :c) - "push((uintptr_t) bel);") - -(defop oku (:targets :c) - "push(*((char*) pop()));") - -(defop yaz (:targets :c) - "rax = pop();" - "*((char*) pop()) = rax;") - -(defop (syscall num) (:lex nil :targets :c) - (iter (with call-regs = #("rdi" "rsi" "rdx" "r10" "r8" "r9")) - (initially (:write "rax = pop();")) - (for i from (- num 1) downto 0) - (:write ("~a = pop();" (aref call-regs i))) - (collect (aref call-regs i) into used-regs) - (finally (:write ("syscall(rax~{, ~a~});" (reverse used-regs)))))) - -(defun gen-c-stack (stream) - (format stream "~{~a~%~}" - '("#include " - "#include " - "" - "struct Stack {" - " uintptr_t content[1000000];" - " int i;" - "};" - "" - "typedef struct Stack Stack;" - "" - "Stack stack = { .i = 0 };" - "" - "void push(uintptr_t val){" - " stack.content[stack.i] = val;" - " stack.i += 1;" - "}" - "" - "uintptr_t pop(){" - " stack.i -= 1;" - " return stack.content[stack.i];" - "}" - "" - "uintptr_t rax, rbx, rcx, rdi, rsi, rdx, r10, r8, r9;" - "char bel[640000];" - ""))) - -(defmacro with-c-fn ((ret name) args out &body body) - `(let ((*print-case* :downcase)) - (format ,out "~a ~a(~{~a ~a~^, ~}){~%" ',ret ',name ',args) - ,@body - (format ,out "~&}~%"))) - - diff --git a/build.sh b/build.sh index 80a559a..3a4f741 100755 --- a/build.sh +++ b/build.sh @@ -1,6 +1,6 @@ #!/bin/sh -sbcl --load cl-forth.asd \ - --eval '(ql:quickload :cl-forth)' \ - --eval '(asdf:make :cl-forth)' \ +sbcl --load kurt.asd \ + --eval '(ql:quickload :kurt)' \ + --eval '(asdf:make :kurt)' \ --eval '(quit)' diff --git a/cl-forth.asd b/cl-forth.asd deleted file mode 100644 index 6702f90..0000000 --- a/cl-forth.asd +++ /dev/null @@ -1,17 +0,0 @@ -(asdf:defsystem "cl-forth" - :description "Stack based language implemented in Common Lisp" - :version "0.1" - :author "Emre Akan" - :licence "MIT" - :depends-on ("iterate" "cl-fad" "clingon") - :serial t - :components ((:file "package") - (:file "util") - (:file "assembly") - (:file "cl-forth") - (:file "simulation") - (:file "main") - (:file "test/tests")) - :build-operation "program-op" - :build-pathname "test/cl-forth" - :entry-point "cl-forth:main") diff --git a/cl-forth.lisp b/cl-forth.lisp index 410b112..ab3af1a 100644 --- a/cl-forth.lisp +++ b/cl-forth.lisp @@ -1,4 +1,4 @@ -(in-package :cl-forth) +(in-package :kurt) (defun make-token (sym? line col &optional (type nil)) (when (null type) diff --git a/codegen.lisp b/codegen.lisp new file mode 100644 index 0000000..1e8c364 --- /dev/null +++ b/codegen.lisp @@ -0,0 +1,482 @@ +(in-package :kurt) + +(defparameter *psuedo-identifiers* + '(syscall-1 syscall-2 syscall-3 syscall-4 syscall-5 syscall-6 makro son kütüphane) + "These do not map to operations that generate code directly, but are valid to lexer and parser") + +(defparameter *identifiers* ()) +;; '(+ - dump = ise yoksa yap eş push değiş üst rot düş döngü iken < > +;; bel oku yaz >> << & "|") + +(defun is-identifier (sym) + (or (find sym *identifiers* :test #'string=) + (find sym *psuedo-identifiers* :test #'string=))) + +(eval-always + (defparameter *targets* '(:nasm :c)) + + (defun normalize-op-list (asm-list) + (cons 'list + (mapcar (lambda (el) (cond ((stringp el) el) + ((listp el) `(format nil ,@el)))) + asm-list))) + + (defun defop-format (str space-num asm-list) + (format str + (format nil "~~{~a~~a~~%~~}" + (make-string space-num :initial-element #\Space)) + asm-list)) + + (defun replace-write (out-stream indent forms) + (if (consp forms) + (if (eq :write (car forms)) + `(defop-format ,out-stream ,indent + ,(normalize-op-list (cdr forms))) + (cons (replace-write out-stream indent (car forms)) + (replace-write out-stream indent (cdr forms)))) + forms)) + + (defun add-indent (indent fmt-string) + (format nil "~a~a" + (make-string indent :initial-element #\Space) + fmt-string)) + + (defun split-stack (stack) + (let ((split-num (position '-- stack))) + (values (butlast stack (- (length stack) split-num)) + (nthcdr (+ 1 split-num) stack)))) + + (defun op->string (asm-instruction &key (push? t)) + "asm-instruction is something like (:add rax rbx)" + (destructuring-bind (op arg1 arg2) asm-instruction + (let ((*print-case* :downcase)) + (if (null push?) + (format nil (format nil "~a ~a, ~a" op arg1 arg2)) + (list (format nil "~a ~a, ~a" op arg1 arg2) + (format nil "push ~a" arg1)))))) + + (defun stack->string (stack) + (multiple-value-bind (prev next) + (split-stack stack) + (let ((*print-case* :downcase)) + (append (iter (for sym in (reverse prev)) + (collect (format nil "pop ~a" sym))) + (iter (for form in next) + (cond ((symbolp form) + (appending (list (format nil "push ~a" form)))) + ((listp form) + (appending (op->string form))))))))) + + (defparameter *stack-fn-assoc* '((:= :cmove) + (:> :cmovg) + (:< :cmovl))) + + (defun stack-unextend (stack) + "Turns an extended stack to body of a defop, second part of an extended stack is in the form of (:op arg1 arg2 :then val1 :else val2) which is asserted." + (multiple-value-bind (fst snd) (split-stack stack) + (assert (= 1 (length snd))) + (append (iter (for x in (reverse fst)) + (let ((*print-case* :downcase)) + (collect (format nil "pop ~a" x)))) + (let* ((push-part (car snd)) + (ifs (nthcdr 3 push-part))) + (list `(:mov rdx ,(getf ifs :ise)) + `(:mov rcx ,(getf ifs :değilse)) + `(:cmp ,(second push-part) ,(third push-part)) + `(,(cadr (assoc (car push-part) *stack-fn-assoc*)) + rcx rdx) + (format nil "push rcx")))))) + + (defun syntax-of (form) + (cond ((or (stringp form) + (and (consp form) (stringp (car form)))) + :string) + ((and (listp form) (find '-- form)) + (if (multiple-value-bind (fst snd) (split-stack form) + (declare (ignore fst)) + (and (consp snd) + (consp (car snd)) + (find (caar snd) *stack-fn-assoc* + :key #'car))) + :stack-extended + :stack)) + ((and (listp form) (keywordp (car form))) + :op) + (t :general))) + + (defun group-by-syntax (forms &optional (syntax nil) (cur ()) (acc ())) + (when (null forms) + (return-from group-by-syntax + (cdr (reverse (append (list (cons (syntax-of (car cur)) + (reverse cur))) + acc))))) + (let* ((form (car forms)) + (form-syntax (syntax-of form))) + (cond ((eq syntax form-syntax) + (group-by-syntax (cdr forms) syntax + (cons form cur) acc)) + (t (group-by-syntax (cdr forms) form-syntax + (list form) (append (list (cons syntax + (reverse cur))) + acc)))))) + + (defun expand-nasm-group (group out-stream &key (indent 4)) + (destructuring-bind (syntax-type . forms) group + (case syntax-type + (:stack `((defop-format ,out-stream ,indent + ,(cons 'list (mapcan (lambda (form) (stack->string form)) + forms))))) + (:stack-extended (expand-nasm out-stream indent + (stack-unextend (car forms)))) + (:op `((defop-format ,out-stream ,indent + ,(cons 'list (mapcar (lambda (form) (op->string form :push? nil)) + forms))))) + (:string `((defop-format ,out-stream ,indent + ,(normalize-op-list forms)))) + (:general `((progn ,@(mapcar (lambda (form) (replace-write out-stream indent + form)) + forms))))))) + + (defun expand-nasm (out-stream indent body) + (mapcan #'(lambda (group) (expand-nasm-group group out-stream + :indent indent)) + (group-by-syntax body))) + + (defun expand-c-group (group out-stream &key (indent 4)) + (destructuring-bind (syntax-type . forms) group + (case syntax-type + ((:stack :stack-extended) `((defop-format ,out-stream ,indent + ,(cons 'list + (c-stack->string (car forms)))))) + (:string `((defop-format ,out-stream ,indent + ,(normalize-op-list forms)))) + (:general `((progn ,@(mapcar (lambda (form) (replace-write out-stream indent + form)) + forms))))))) + + (defun expand-c (out-stream indent body) + (mapcan #'(lambda (group) (expand-c-group group out-stream :indent indent)) + (group-by-syntax body))) + + (defun expand-for-target (target out-stream body &optional (indent 4)) + (case target + (:nasm (expand-nasm out-stream indent body)) + (:c (expand-c out-stream indent body)))) + + (defun expand-method (target out-stream indent op-name args body) + (with-gensyms (_op _args _target) + (declare (ignorable _args)) + `(defmethod write-op + ((,_target (eql ,target)) ,out-stream + (,_op (eql ,(intern (string op-name) "KEYWORD"))) + ,_args) + ,@(if (null args) + (expand-for-target target out-stream body indent) + `((destructuring-bind ,args ,_args + ,@(expand-for-target target out-stream body indent))))))) + + ;;hack, because there is no cl in C only nasm + (defun not-cl (sym) + (if (eq 'cl sym) + 'rcx + sym)) + + (defparameter *c-ops* '((:add "+") + (:sub "-") + (:shl "<<") + (:shr ">>") + (:or "|") + (:and "&") + (:< "<") + (:> ">") + (:= "=="))) + + (defun c-stack->string (stack) + (multiple-value-bind (prev next) (split-stack stack) + (let ((*print-case* :downcase)) + (append (iter (for x in (reverse prev)) + (collect (format nil "~a = pop();" x))) + (iter (for x in next) + (if (and (consp x) (assoc (car x) *c-ops*)) + (destructuring-bind (op arg1 arg2 . conds) x + (let ((opstr (cadr (assoc op *c-ops*)))) + (if (null conds) + (collect (format nil "push(~a ~a ~a);" + arg1 opstr (not-cl arg2))) + (collect (format nil + "push((~a ~a ~a) ? ~a : ~a);" + arg1 opstr (not-cl arg2) + (getf conds :ise) + (getf conds :değilse)))))) + (collect (format nil "push(~a);" x))))))))) + +(defun comment-safe-str (str) + "Handle newlines for comment" + (with-output-to-string (new-str) + (iter (for ch in-string str with-index i) + (cond ((> i 10) + (princ "..." new-str) + (finish)) + ((char= #\Newline ch) + (princ "\\n" new-str)) + (t (write-char ch new-str)))))) + +(defgeneric write-op (target stream op args) + (:documentation "Generate code for OP and ARGS and write it to STREAM according to the TARGET. WRITE-OP methods are defined by the DEFOP macro.") + (:method :before ((target (eql :nasm)) stream op args) + (format stream " ;; -- ~s --~%" + (mapcar (lambda (x) (if (stringp x) (comment-safe-str x) x)) + (cons op args)))) + (:method :before ((target (eql :c)) stream op args) + (format stream "~% /* ~s */~%" + ;; comment-safe is probably not necessary with multiline comments + (mapcar (lambda (x) (if (stringp x) (comment-safe-str x) x)) + (cons op args))))) + +(defmacro defop (op-name+args (&key (indent 4) (lex t) (targets *targets*) + (as nil)) + &body body) + (declare (ignorable indent)) + (with-gensyms (out-stream) + (destructuring-bind (op-name . args) (mklist op-name+args) + `(progn + ,@(unless (null lex) + `((push ',op-name *identifiers*))) + ,@(iter (for target in (mklist targets)) + (collect (expand-method target out-stream indent + (if (null as) op-name as) + args body))))))) + +;;; TODO: Turn stack operation comments to DEFOP option, +;;; which then can be used by the user as a documentation +;;; DONE: Better yet, generate the asm code directly from +;;; the stack op documentation (this seems easily doable) +;;; Hopefully these two are done, need testing... + +(defop + () + (rbx rax -- (:add rax rbx))) + +(defop - () + (rbx rax -- (:sub rbx rax))) + +(defop = () + (rbx rax -- (:= rbx rax :ise 1 :değilse 0))) + +(defop eş () + (rax -- rax rax)) + +(defop düş () + (rax -- )) + +(defop < () + (rax rbx -- (:< rax rbx :ise 1 :değilse 0))) + +(defop > () + (rax rbx -- (:> rax rbx :ise 1 :değilse 0))) + +(defop üst () + (rbx rax -- rbx rax rbx)) + +(defop rot () + (rcx rbx rax -- rbx rax rcx)) + +(defop değiş () + (rbx rax -- rax rbx)) + +(defop << () + (rbx rcx -- (:shl rbx cl))) + +(defop >> () + (rbx rcx -- (:shr rbx cl))) + +(defop "|" (:as pipe) + (rbx rax -- (:or rbx rax))) + +(defop & () + (rbx rax -- (:and rbx rax))) + + + +;;; NASM operations +(defop bel (:targets :nasm) + ( -- bel)) + +(defop oku (:targets :nasm) + (rax -- ) + (:xor rbx rbx) + (:mov bl [rax]) + ( -- rbx)) + +(defop yaz (:targets :nasm) + (rax rbx -- ) + (:mov [rax] bl)) + +;; ( -- a) +(defop (push-int a) (:lex nil :targets :nasm) + ("push ~d" a)) + +(defop (push-str len addr str) (:lex nil :targets :nasm) + (progn (:write ("push ~d" len) + ("push str_~d" addr)) + (list :string addr str))) + +(defop dump (:targets :nasm) + "pop rdi" + "call dump") + +(defop (exit code) (:lex nil :targets :nasm) + "mov rax, 60" + ("mov rdi, ~a" code) + "syscall") + +(defop (ise label-num) (:targets :nasm) + "pop rax" + "test rax, rax" + ("jz et_~a" label-num)) + +(defop (yoksa yap-num ise-num) (:indent 0 :targets :nasm) + (" jmp et_~a" yap-num) + ("et_~a:" ise-num)) + +(defop (yap label-num &optional döngü-num) (:indent 0 :targets :nasm) + (if (null döngü-num) + (:write ("et_~a:" label-num)) + (:write (" jmp et_~a" döngü-num) + ("et_~a:" label-num)))) + +(defop (iken label-num) (:targets :nasm) + "pop rax" + "test rax, rax" + ("jz et_~a" label-num)) + +(defop (döngü label-num) (:indent 0 :targets :nasm) + ("et_~a:" label-num)) + +(defop (syscall num) (:lex nil :targets :nasm) + (iter (with call-regs = #("rdi" "rsi" "rdx" "r10" "r8" "r9")) + (initially (:write "pop rax")) + (for i from (- num 1) downto 0) + (:write ("pop ~a" (aref call-regs i))) + (finally (:write "syscall")))) + +(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"))) + + +;;; C operations +(defop (push-int a) (:lex nil :targets :c) + ("push(~d);" a)) + +;; (defop (push-str a) (:lex nil :targets :c) +;; ("push(&stack, ~d);" a)) + +(defop dump (:targets :c) + ("printf(\"%d\\n\", pop());")) + +(defop (ise label-num) (:targets :c) + "rax = pop();" + ("if(!rax){ goto et_~a; }" label-num)) + +(defop (yoksa yap-num ise-num) (:indent 0 :targets :c) + (" goto et_~a;" yap-num) + ("et_~a:" ise-num)) + +(defop (yap label-num &optional döngü-num) (:indent 0 :targets :c) + (if (null döngü-num) + (:write ("et_~a:" label-num)) + (:write (" goto et_~a;" döngü-num) + ("et_~a:" label-num)))) + +(defop (iken label-num) (:targets :c) + "rax = pop();" + ("if(!rax){ goto et_~a; }" label-num)) + +(defop (döngü label-num) (:indent 0 :targets :c) + ("et_~a:" label-num)) + +(defop bel (:targets :c) + "push((uintptr_t) bel);") + +(defop oku (:targets :c) + "push(*((char*) pop()));") + +(defop yaz (:targets :c) + "rax = pop();" + "*((char*) pop()) = rax;") + +(defop (syscall num) (:lex nil :targets :c) + (iter (with call-regs = #("rdi" "rsi" "rdx" "r10" "r8" "r9")) + (initially (:write "rax = pop();")) + (for i from (- num 1) downto 0) + (:write ("~a = pop();" (aref call-regs i))) + (collect (aref call-regs i) into used-regs) + (finally (:write ("syscall(rax~{, ~a~});" (reverse used-regs)))))) + +(defun gen-c-stack (stream) + (format stream "~{~a~%~}" + '("#include " + "#include " + "" + "struct Stack {" + " uintptr_t content[1000000];" + " int i;" + "};" + "" + "typedef struct Stack Stack;" + "" + "Stack stack = { .i = 0 };" + "" + "void push(uintptr_t val){" + " stack.content[stack.i] = val;" + " stack.i += 1;" + "}" + "" + "uintptr_t pop(){" + " stack.i -= 1;" + " return stack.content[stack.i];" + "}" + "" + "uintptr_t rax, rbx, rcx, rdi, rsi, rdx, r10, r8, r9;" + "char bel[640000];" + ""))) + +(defmacro with-c-fn ((ret name) args out &body body) + `(let ((*print-case* :downcase)) + (format ,out "~a ~a(~{~a ~a~^, ~}){~%" ',ret ',name ',args) + ,@body + (format ,out "~&}~%"))) + + diff --git a/errors.lisp b/errors.lisp new file mode 100644 index 0000000..2f955ca --- /dev/null +++ b/errors.lisp @@ -0,0 +1,26 @@ +(in-package :kurt) + +(defun report-line (line-num line col? &optional (stream t)) + (format stream "~5a:~a~%" line-num line) + (iter (for i from 0 below (+ 6 (if (consp col?) + (getf (cdr col?) :col) ;; if token get col + col?))) + (write-char #\Space stream)) + (format stream "^~%")) + +(define-condition char-not-closed () + ((line :initarg :line :reader line) + (col :initarg :col :reader col) + (line-num :initarg :line-num :reader line-num)) + (:report (lambda (condition stream) + (format stream "Karakterin kapanış sembolü ' eksik.~%") + (report-line (line-num condition) (line condition) (col condition) stream)))) + +(defun handle-char-not-closed (line-num line token-or-col) + (make-condition 'char-not-closed :line-num line-num :line line :col token-or-col)) + +;; (define-condition op-not-implemented (style-warning) +;; ((undef-ops :initarg :ops :reader undef-ops)) +;; (:report (lambda (condition stream) +;; (format stream "These ops are not defined in op-case: ~s" +;; (undef-ops condition))))) diff --git a/kurt.asd b/kurt.asd new file mode 100644 index 0000000..517c5bb --- /dev/null +++ b/kurt.asd @@ -0,0 +1,18 @@ +(asdf:defsystem "kurt" + :description "Stack based language implemented in Common Lisp" + :version "0.1" + :author "Emre Akan" + :licence "MIT" + :depends-on ("iterate" "cl-fad" "clingon") + :serial t + :components ((:file "package") + (:file "util") + (:file "errors") + (:file "codegen") + (:file "cl-forth") + (:file "simulation") + (:file "main") + (:file "test/tests")) + :build-operation "program-op" + :build-pathname "test/kurt" + :entry-point "kurt:main") diff --git a/main.lisp b/main.lisp index 15b33a7..6b0d08c 100644 --- a/main.lisp +++ b/main.lisp @@ -1,4 +1,4 @@ -(in-package :cl-forth) +(in-package :kurt) ;; (defun main () ;; (let ((args (rest sb-ext:*posix-argv*))) @@ -97,8 +97,8 @@ (defun top-level-command () (clingon:make-command - :name "cl-forth" - :description "cl-forth derleyicisi" + :name "kurt" + :description "kurt derleyicisi" :version "0.1.0" :authors '("Emre Akan ") :options (top-level-options) diff --git a/package.lisp b/package.lisp index 76547a0..3880c56 100644 --- a/package.lisp +++ b/package.lisp @@ -1,3 +1,3 @@ -(defpackage cl-forth +(defpackage kurt (:use :common-lisp :iterate) (:export #:main)) diff --git a/simulation.lisp b/simulation.lisp index 2699dc9..b0e317c 100644 --- a/simulation.lisp +++ b/simulation.lisp @@ -1,4 +1,4 @@ -(in-package :cl-forth) +(in-package :kurt) (defvar *stack* nil) (defvar *bel* nil) diff --git a/test/arith.kurt b/test/arith.kurt new file mode 100644 index 0000000..76bef42 --- /dev/null +++ b/test/arith.kurt @@ -0,0 +1,3 @@ +;;; test +;;; (format nil "9~%") +3 6 + dump \ No newline at end of file diff --git a/test/arith.lorth b/test/arith.lorth deleted file mode 100644 index 76bef42..0000000 --- a/test/arith.lorth +++ /dev/null @@ -1,3 +0,0 @@ -;;; test -;;; (format nil "9~%") -3 6 + dump \ No newline at end of file diff --git a/test/bits.kurt b/test/bits.kurt new file mode 100644 index 0000000..77be5b0 --- /dev/null +++ b/test/bits.kurt @@ -0,0 +1,15 @@ +;;; test +;;; (format nil "~{~a~%~}" (append '(0 1 2) +;;; '(7 3 5) +;;; '(4 32 12))) +1 2 & dump +1 1 & dump +3 2 & dump + +1 2 4 | | dump +3 1 | dump +5 5 | dump + +32 3 >> dump +1 5 << dump +3 2 << dump \ No newline at end of file diff --git a/test/bits.lorth b/test/bits.lorth deleted file mode 100644 index 77be5b0..0000000 --- a/test/bits.lorth +++ /dev/null @@ -1,15 +0,0 @@ -;;; test -;;; (format nil "~{~a~%~}" (append '(0 1 2) -;;; '(7 3 5) -;;; '(4 32 12))) -1 2 & dump -1 1 & dump -3 2 & dump - -1 2 4 | | dump -3 1 | dump -5 5 | dump - -32 3 >> dump -1 5 << dump -3 2 << dump \ No newline at end of file diff --git a/test/branchs.kurt b/test/branchs.kurt new file mode 100644 index 0000000..41eaf96 --- /dev/null +++ b/test/branchs.kurt @@ -0,0 +1,62 @@ +;;; test +;;; (format nil "~{~a~%~}" '(1 2 3 4 5)) + +34 36 + +eş 70 = ise 1 dump +yoksa eş 68 = ise + 1 ise + 2 dump + yoksa + 3 dump + yap +yoksa eş 69 = ise 4 dump +yoksa 5 dump +yap yap yap + +34 34 + +eş 70 = ise 1 dump +yoksa eş 68 = ise + 1 ise + 2 dump + yoksa + 3 dump + yap +yoksa eş 69 = ise 4 dump +yoksa 5 dump +yap yap yap + +34 34 + +eş 70 = ise 1 dump +yoksa eş 68 = ise + 0 ise + 2 dump + yoksa + 3 dump + yap +yoksa eş 69 = ise 4 dump +yoksa 5 dump +yap yap yap + +34 35 + +eş 70 = ise 1 dump +yoksa eş 68 = ise + 1 ise + 2 dump + yoksa + 3 dump + yap +yoksa eş 69 = ise 4 dump +yoksa 5 dump +yap yap yap + +34 420 + +eş 70 = ise 1 dump +yoksa eş 68 = ise + 1 ise + 2 dump + yoksa + 3 dump + yap +yoksa eş 69 = ise 4 dump +yoksa 5 dump +yap yap yap \ No newline at end of file diff --git a/test/branchs.lorth b/test/branchs.lorth deleted file mode 100644 index 41eaf96..0000000 --- a/test/branchs.lorth +++ /dev/null @@ -1,62 +0,0 @@ -;;; test -;;; (format nil "~{~a~%~}" '(1 2 3 4 5)) - -34 36 + -eş 70 = ise 1 dump -yoksa eş 68 = ise - 1 ise - 2 dump - yoksa - 3 dump - yap -yoksa eş 69 = ise 4 dump -yoksa 5 dump -yap yap yap - -34 34 + -eş 70 = ise 1 dump -yoksa eş 68 = ise - 1 ise - 2 dump - yoksa - 3 dump - yap -yoksa eş 69 = ise 4 dump -yoksa 5 dump -yap yap yap - -34 34 + -eş 70 = ise 1 dump -yoksa eş 68 = ise - 0 ise - 2 dump - yoksa - 3 dump - yap -yoksa eş 69 = ise 4 dump -yoksa 5 dump -yap yap yap - -34 35 + -eş 70 = ise 1 dump -yoksa eş 68 = ise - 1 ise - 2 dump - yoksa - 3 dump - yap -yoksa eş 69 = ise 4 dump -yoksa 5 dump -yap yap yap - -34 420 + -eş 70 = ise 1 dump -yoksa eş 68 = ise - 1 ise - 2 dump - yoksa - 3 dump - yap -yoksa eş 69 = ise 4 dump -yoksa 5 dump -yap yap yap \ No newline at end of file diff --git a/test/include.kurt b/test/include.kurt new file mode 100644 index 0000000..80b69d2 --- /dev/null +++ b/test/include.kurt @@ -0,0 +1,3 @@ +kütüphane "std.lorth" + +stdout "Merhaba Dünya!\n" write \ No newline at end of file diff --git a/test/loop.kurt b/test/loop.kurt new file mode 100644 index 0000000..8234010 --- /dev/null +++ b/test/loop.kurt @@ -0,0 +1,6 @@ +;;; test +;;; (format nil "~{~a~%~}" (list 0 1 2 3)) +0 döngü eş 4 < iken + eş dump + 1 + +yap \ No newline at end of file diff --git a/test/loop.lorth b/test/loop.lorth deleted file mode 100644 index 8234010..0000000 --- a/test/loop.lorth +++ /dev/null @@ -1,6 +0,0 @@ -;;; test -;;; (format nil "~{~a~%~}" (list 0 1 2 3)) -0 döngü eş 4 < iken - eş dump - 1 + -yap \ No newline at end of file diff --git a/test/makro.kurt b/test/makro.kurt new file mode 100644 index 0000000..405f98b --- /dev/null +++ b/test/makro.kurt @@ -0,0 +1,6 @@ +;;; test +;;; (format nil "Hello world!..~%") +makro write değiş 1 syscall-3 son +makro stdout 1 son + +stdout "Hello world!..\n" write diff --git a/test/makro.lorth b/test/makro.lorth deleted file mode 100644 index 405f98b..0000000 --- a/test/makro.lorth +++ /dev/null @@ -1,6 +0,0 @@ -;;; test -;;; (format nil "Hello world!..~%") -makro write değiş 1 syscall-3 son -makro stdout 1 son - -stdout "Hello world!..\n" write diff --git a/test/stack.kurt b/test/stack.kurt new file mode 100644 index 0000000..318d9be --- /dev/null +++ b/test/stack.kurt @@ -0,0 +1,5 @@ +;;; test +;;; (format nil "~{~a~%~}" '(1 5 4 4)) +1 dump +4 5 dump +eş dump dump diff --git a/test/stack.lorth b/test/stack.lorth deleted file mode 100644 index 318d9be..0000000 --- a/test/stack.lorth +++ /dev/null @@ -1,5 +0,0 @@ -;;; test -;;; (format nil "~{~a~%~}" '(1 5 4 4)) -1 dump -4 5 dump -eş dump dump diff --git a/test/std.kurt b/test/std.kurt new file mode 100644 index 0000000..cf7e684 --- /dev/null +++ b/test/std.kurt @@ -0,0 +1,4 @@ +makro sys-write 1 son +makro write (fd string -- ) + değiş sys-write syscall-3 son +makro stdout 1 son \ No newline at end of file diff --git a/test/syscall.kurt b/test/syscall.kurt new file mode 100644 index 0000000..a58a9ec --- /dev/null +++ b/test/syscall.kurt @@ -0,0 +1,15 @@ +;;; test +;;; (format nil "abc~%bcd~%") +bel 0 + 97 yaz +bel 1 + 98 yaz +bel 2 + 99 yaz +bel 3 + 10 yaz + +1 bel 4 1 syscall-3 + +bel 0 + eş oku 1 + yaz +bel 1 + eş oku 1 + yaz +bel 2 + eş oku 1 + yaz + +1 bel 4 1 syscall-3 +0 60 syscall-1 diff --git a/test/syscall.lorth b/test/syscall.lorth deleted file mode 100644 index a58a9ec..0000000 --- a/test/syscall.lorth +++ /dev/null @@ -1,15 +0,0 @@ -;;; test -;;; (format nil "abc~%bcd~%") -bel 0 + 97 yaz -bel 1 + 98 yaz -bel 2 + 99 yaz -bel 3 + 10 yaz - -1 bel 4 1 syscall-3 - -bel 0 + eş oku 1 + yaz -bel 1 + eş oku 1 + yaz -bel 2 + eş oku 1 + yaz - -1 bel 4 1 syscall-3 -0 60 syscall-1 diff --git a/test/tests.lisp b/test/tests.lisp index 8b00817..0907a58 100644 --- a/test/tests.lisp +++ b/test/tests.lisp @@ -1,4 +1,4 @@ -(in-package :cl-forth) +(in-package :kurt) (defun drop-file-type (file &key (returns :string)) (let* ((file-str (namestring file)) @@ -73,7 +73,7 @@ (ignore-errors (run-test file :target target)))) (remove-if-not (lambda (file) - (string= "lorth" (pathname-type file))) + (string= "kurt" (pathname-type file))) (cl-fad:list-directory (from-root "test")))) counting (eq t success?) into succs diff --git a/util.lisp b/util.lisp index 9f80adb..0eee717 100644 --- a/util.lisp +++ b/util.lisp @@ -1,4 +1,4 @@ -(in-package :cl-forth) +(in-package :kurt) (eval-when (:compile-toplevel :load-toplevel :execute) (defmacro eval-always (&body body) @@ -26,7 +26,7 @@ (apply #'uiop:run-program args options)) (defun from-root (path) - (merge-pathnames path (asdf:system-source-directory :cl-forth))) + (merge-pathnames path (asdf:system-source-directory :kurt))) ;; ,(file-namestring ;; (make-pathname :name (pathname-name path) -- cgit v1.2.3