Initial commit for git.lain.church
This commit is contained in:
commit
33635b26db
18
cl-forth.asd
Normal file
18
cl-forth.asd
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
;;;; cl-forth.asd
|
||||||
|
|
||||||
|
(asdf:defsystem cl-forth
|
||||||
|
:description "FORTH interpreter written in Common Lisp"
|
||||||
|
:author ("Bubblegumdrop <staticsunn@gmail.com>")
|
||||||
|
:license "GPLv3+"
|
||||||
|
:version "0.1.0"
|
||||||
|
:depends-on (:deploy)
|
||||||
|
:components ((:file "package")
|
||||||
|
(:file "utils")
|
||||||
|
(:file "stack")
|
||||||
|
(:file "interpreter")
|
||||||
|
(:file "methods")
|
||||||
|
(:file "cl-forth"))
|
||||||
|
:defsystem-depends-on (:deploy)
|
||||||
|
:build-operation "deploy-op"
|
||||||
|
:build-pathname "cl-forth.run"
|
||||||
|
:entry-point "cl-forth::main")
|
26
cl-forth.lisp
Normal file
26
cl-forth.lisp
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
(in-package #:cl-forth)
|
||||||
|
|
||||||
|
;; For debugging purposes.
|
||||||
|
(defparameter *interpreter*
|
||||||
|
(make-instance 'interpreter))
|
||||||
|
|
||||||
|
(defun main ()
|
||||||
|
(let ((interp *interpreter*)
|
||||||
|
(input ""))
|
||||||
|
(format t "Welcome to CL-Forth! Type your commands below (type 'exit' to quit).~%")
|
||||||
|
(loop
|
||||||
|
(format t "CL-FORTH) ")
|
||||||
|
(finish-output)
|
||||||
|
(setf input (read-line))
|
||||||
|
(if (string= input "exit")
|
||||||
|
(progn
|
||||||
|
(format t "Exiting CL-Forth. Goodbye!~%")
|
||||||
|
(return))
|
||||||
|
(progn
|
||||||
|
(handler-case
|
||||||
|
(interpret interp input)
|
||||||
|
(unknown-word-error (e)
|
||||||
|
(format t "Error: Unknown word '~a'~%" (token-of e)))
|
||||||
|
(stack-underflow (e)
|
||||||
|
(declare (ignore e))
|
||||||
|
(format t "Error: Stack underflow - cannot pop from an empty stack.~%"))))))))
|
60
define.lisp
Normal file
60
define.lisp
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
(in-package #:cl-forth)
|
||||||
|
|
||||||
|
(defun interpret (input)
|
||||||
|
(let ((tokens (split-sequence #\Space input))) ;; Assuming you have a tokenizing function
|
||||||
|
(dolist (token tokens)
|
||||||
|
(cond
|
||||||
|
((equal token ":")
|
||||||
|
(setf *compile-mode* t))
|
||||||
|
|
||||||
|
((equal token ";")
|
||||||
|
(setf *compile-mode* nil)
|
||||||
|
;; Store the definition in the dictionary
|
||||||
|
(setf (gethash (car *current-definition*) *dictionary*)
|
||||||
|
(cdr *current-definition*))
|
||||||
|
(setf *current-definition* nil))
|
||||||
|
|
||||||
|
(*compile-mode*
|
||||||
|
(if (null *current-definition*)
|
||||||
|
;; First token is the name of the new word
|
||||||
|
(setf *current-definition* (list token))
|
||||||
|
;; Subsequent tokens are the body of the word
|
||||||
|
(push token (cdr *current-definition*))))
|
||||||
|
(t
|
||||||
|
(execute-word token))))))
|
||||||
|
|
||||||
|
(defun execute-word (token)
|
||||||
|
(cond
|
||||||
|
((digit-char-p (elt token 0))
|
||||||
|
;; Push numbers onto the stack
|
||||||
|
(push (parse-integer token) *stack*))
|
||||||
|
((gethash token *dictionary*)
|
||||||
|
;; If token is in dictionary, execute it
|
||||||
|
(let ((definition (gethash token *dictionary*)))
|
||||||
|
(if (functionp definition)
|
||||||
|
;; If it's a primitive word, just execute it
|
||||||
|
(funcall definition)
|
||||||
|
;; If it's a user-defined word, interpret each token in it
|
||||||
|
(dolist (word definition)
|
||||||
|
(execute-word word)))))
|
||||||
|
(t
|
||||||
|
(error "Unknown word: ~A" token))))
|
||||||
|
|
||||||
|
;; Please incorporate the above interpret and execute-word with evaluate-token and evaluate-line below:
|
||||||
|
(defmethod evaluate-line ((obj interpreter))
|
||||||
|
(let ((tokens (split-string #\Space (line-of obj))))
|
||||||
|
(dolist (token tokens)
|
||||||
|
(setf (current-token-of obj) token)
|
||||||
|
(evaluate-token obj))))
|
||||||
|
|
||||||
|
(defmethod evaluate-token ((obj interpreter))
|
||||||
|
(let* ((token (current-token-of obj))
|
||||||
|
(words (words-of (state-of obj)))
|
||||||
|
(dict-entry (gethash token words)))
|
||||||
|
(if dict-entry
|
||||||
|
(funcall dict-entry obj)
|
||||||
|
(let ((parsed (ignore-errors (parse-integer token))))
|
||||||
|
(if parsed
|
||||||
|
(push-to-stack (stack-of obj) parsed)
|
||||||
|
(signal 'unknown-word-error :token token))))))
|
||||||
|
|
81
interpreter.lisp
Normal file
81
interpreter.lisp
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
(in-package #:cl-forth)
|
||||||
|
|
||||||
|
(defparameter *compile-mode* nil)
|
||||||
|
(defparameter *current-definition* nil)
|
||||||
|
|
||||||
|
(define-condition unknown-word-error (error)
|
||||||
|
((token :initarg :token
|
||||||
|
:accessor token-of))
|
||||||
|
(:report (lambda (condition stream)
|
||||||
|
(format stream "Unknown word: ~a.~&" (token-of condition)))))
|
||||||
|
|
||||||
|
(defclass interpreter ()
|
||||||
|
((current-token :accessor current-token-of)
|
||||||
|
(stack :initform (make-instance 'stack)
|
||||||
|
:accessor stack-of)
|
||||||
|
(words :initform (make-hash-table :test 'equal)
|
||||||
|
:accessor words-of)
|
||||||
|
(line :accessor line-of)))
|
||||||
|
|
||||||
|
(defmethod initialize-instance :after ((interp interpreter) &key &allow-other-keys)
|
||||||
|
"Initialize a new interpreter with basic operations."
|
||||||
|
(setf (gethash "+" (words-of interp)) #'add
|
||||||
|
(gethash "-" (words-of interp)) #'subtract
|
||||||
|
(gethash "*" (words-of interp)) #'multiply
|
||||||
|
(gethash "/" (words-of interp)) #'divide
|
||||||
|
(gethash "mod" (words-of interp)) #'mod
|
||||||
|
(gethash "dup" (words-of interp)) #'dup
|
||||||
|
(gethash "swap" (words-of interp)) #'swap
|
||||||
|
(gethash "negate" (words-of interp)) #'negate
|
||||||
|
(gethash "drop" (words-of interp)) #'drop
|
||||||
|
(gethash "over" (words-of interp)) #'over
|
||||||
|
(gethash "rot" (words-of interp)) #'rot)
|
||||||
|
interp)
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
(defmethod evaluate-line ((obj interpreter))
|
||||||
|
(let ((tokens (split-string #\Space (line-of obj))))
|
||||||
|
(dolist (token tokens)
|
||||||
|
(cond
|
||||||
|
((equal token ":")
|
||||||
|
(setf *compile-mode* t))
|
||||||
|
((equal token ";")
|
||||||
|
(setf *compile-mode* nil)
|
||||||
|
;; save the definition
|
||||||
|
(setf (gethash (car *current-definition*) (words-of obj))
|
||||||
|
(reverse (cdr *current-definition*))
|
||||||
|
*current-definition* nil))
|
||||||
|
(*compile-mode*
|
||||||
|
(if (null *current-definition*)
|
||||||
|
;; First token is the name of the new word
|
||||||
|
(setf *current-definition* (list token))
|
||||||
|
;; Subsequent tokens are the body of the word
|
||||||
|
(push token (cdr *current-definition*))))
|
||||||
|
(t
|
||||||
|
(setf (current-token-of obj) token)
|
||||||
|
(execute-word obj))))))
|
||||||
|
|
||||||
|
(defmethod execute-word ((obj interpreter))
|
||||||
|
(let ((token (current-token-of obj)))
|
||||||
|
(cond
|
||||||
|
((digit-char-p (elt token 0))
|
||||||
|
;; Push numbers onto the stack
|
||||||
|
(push-to-stack (stack-of obj) (parse-integer token)))
|
||||||
|
((gethash token (words-of obj))
|
||||||
|
;; If token is in dictionary, execute it
|
||||||
|
(let ((definition (gethash token (words-of obj))))
|
||||||
|
(if (functionp definition)
|
||||||
|
;; If it's a primitive word, just execute it
|
||||||
|
(funcall definition obj)
|
||||||
|
;; If it's a user-defined word, interpret each token in it
|
||||||
|
(dolist (word definition)
|
||||||
|
(setf (current-token-of obj) word)
|
||||||
|
(execute-word obj)))))
|
||||||
|
(t
|
||||||
|
(error 'unknown-word-error :token token)))))
|
||||||
|
|
||||||
|
(defmethod interpret ((obj interpreter) program)
|
||||||
|
"Interpret a PROGRAM using the INTERP."
|
||||||
|
(setf (line-of obj) program)
|
||||||
|
(evaluate-line obj)
|
||||||
|
(format t "Stack after execution: ~a~%" (stack-of obj)))
|
79
methods.lisp
Normal file
79
methods.lisp
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
(in-package #:cl-forth)
|
||||||
|
|
||||||
|
(defun with-stack-args (interpreter func)
|
||||||
|
"Pop two values from the stack and apply FUNC to them."
|
||||||
|
(let ((b (pop-from-stack (stack-of interpreter)))
|
||||||
|
(a (pop-from-stack (stack-of interpreter))))
|
||||||
|
(funcall func a b)))
|
||||||
|
|
||||||
|
(defun dup (interpreter)
|
||||||
|
"Duplicate the top number on the stack."
|
||||||
|
(let ((top (peek-stack (stack-of interpreter))))
|
||||||
|
(push-to-stack (stack-of interpreter) top)))
|
||||||
|
|
||||||
|
(defun swap (interpreter)
|
||||||
|
"Swap the top two numbers on the stack."
|
||||||
|
(let ((a (pop-from-stack (stack-of interpreter)))
|
||||||
|
(b (pop-from-stack (stack-of interpreter))))
|
||||||
|
(push-to-stack (stack-of interpreter) a)
|
||||||
|
(push-to-stack (stack-of interpreter) b)))
|
||||||
|
|
||||||
|
(defun add (interpreter)
|
||||||
|
"Add the top two numbers on the stack."
|
||||||
|
(with-stack-args interpreter
|
||||||
|
(lambda (a b)
|
||||||
|
(push-to-stack (stack-of interpreter) (+ a b)))))
|
||||||
|
|
||||||
|
(defun subtract (interpreter)
|
||||||
|
"Subtract the top two numbers on the stack."
|
||||||
|
(with-stack-args interpreter
|
||||||
|
(lambda (a b)
|
||||||
|
(push-to-stack (stack-of interpreter) (- a b)))))
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
(defun multiply (interpreter)
|
||||||
|
"Multiply the top two numbers on the stack."
|
||||||
|
(with-stack-args interpreter
|
||||||
|
(lambda (a b)
|
||||||
|
(push-to-stack (stack-of interpreter) (* a b)))))
|
||||||
|
|
||||||
|
(defun divide (interpreter)
|
||||||
|
"Divide the second top number by the top number on the stack."
|
||||||
|
(with-stack-args interpreter
|
||||||
|
(lambda (a b)
|
||||||
|
(if (zerop a)
|
||||||
|
(error "Division by zero")
|
||||||
|
(push-to-stack (stack-of interpreter) (/ b a))))))
|
||||||
|
|
||||||
|
(defun my-mod (interpreter)
|
||||||
|
"Compute the modulus of the second top number by the top number on the stack."
|
||||||
|
(with-stack-args interpreter
|
||||||
|
(lambda (a b)
|
||||||
|
(if (zerop a)
|
||||||
|
(error "Division by zero")
|
||||||
|
(push-to-stack (stack-of interpreter) (mod b a))))))
|
||||||
|
|
||||||
|
(defun negate (interpreter)
|
||||||
|
"Negate the top number on the stack."
|
||||||
|
(let ((top (pop-from-stack (stack-of interpreter))))
|
||||||
|
(push-to-stack (stack-of interpreter) (- top))))
|
||||||
|
|
||||||
|
(defun drop (interpreter)
|
||||||
|
"Remove the top number from the stack."
|
||||||
|
(pop-from-stack (stack-of interpreter)))
|
||||||
|
|
||||||
|
(defun over (interpreter)
|
||||||
|
"Copy the second top number to the top of the stack."
|
||||||
|
(let ((a (pop-from-stack (stack-of interpreter)))
|
||||||
|
(b (peek-stack (stack-of interpreter))))
|
||||||
|
(push-to-stack (stack-of interpreter) a)
|
||||||
|
(push-to-stack (stack-of interpreter) b)))
|
||||||
|
|
||||||
|
(defun rot (interpreter)
|
||||||
|
"Rotate the top three numbers on the stack."
|
||||||
|
(let ((a (pop-from-stack (stack-of interpreter)))
|
||||||
|
(b (pop-from-stack (stack-of interpreter)))
|
||||||
|
(c (pop-from-stack (stack-of interpreter))))
|
||||||
|
(push-to-stack (stack-of interpreter) b)
|
||||||
|
(push-to-stack (stack-of interpreter) a)
|
||||||
|
(push-to-stack (stack-of interpreter) c)))
|
7
package.lisp
Normal file
7
package.lisp
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
(in-package #:cl-user)
|
||||||
|
|
||||||
|
(defpackage #:cl-forth
|
||||||
|
(:use #:cl)
|
||||||
|
(:export #:main))
|
||||||
|
|
||||||
|
(in-package #:cl-forth)
|
12
readme.org
Normal file
12
readme.org
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
* CL-FORTH
|
||||||
|
|
||||||
|
I'm not sure what good another FORTH interpreter is. Why not.
|
||||||
|
|
||||||
|
* License
|
||||||
|
|
||||||
|
GPLv3+
|
||||||
|
|
||||||
|
* TODO
|
||||||
|
|
||||||
|
- Saving/Loading an image
|
||||||
|
- Colon Definitions (WIP)
|
44
stack.lisp
Normal file
44
stack.lisp
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
(in-package #:cl-forth)
|
||||||
|
|
||||||
|
(define-condition stack-error (error)
|
||||||
|
((message :initarg :message
|
||||||
|
:accessor error-message)))
|
||||||
|
|
||||||
|
(define-condition stack-underflow (stack-error)
|
||||||
|
((message :initarg :message
|
||||||
|
:accessor underflow-message)))
|
||||||
|
|
||||||
|
(defclass stack ()
|
||||||
|
((contents :initform '()
|
||||||
|
:initarg :initial-contents
|
||||||
|
:accessor contents)))
|
||||||
|
|
||||||
|
(defmethod initialize-instance :after ((obj stack) &key initial-element &allow-other-keys)
|
||||||
|
(when initial-element
|
||||||
|
(setf (contents obj) (list initial-element))))
|
||||||
|
|
||||||
|
(defmethod push-to-stack ((obj stack) value)
|
||||||
|
"Push VALUE onto the stack in the STACK."
|
||||||
|
(push value (contents obj)))
|
||||||
|
|
||||||
|
(defmethod pop-from-stack ((obj stack))
|
||||||
|
"Pop a value from the stack in the STACK."
|
||||||
|
(pop (contents obj)))
|
||||||
|
|
||||||
|
(defmethod peek-stack ((obj stack))
|
||||||
|
"Peek at the top value of the stack in the STACK."
|
||||||
|
(car (contents obj)))
|
||||||
|
|
||||||
|
(defmethod print-object ((obj stack) stream)
|
||||||
|
(print-unreadable-object (obj stream :type t)
|
||||||
|
(format stream "(~{~a~^ ~})" (contents obj))))
|
||||||
|
|
||||||
|
;; Example usage:
|
||||||
|
(defun stack-test ()
|
||||||
|
(let ((my-stack (make-instance 'stack)))
|
||||||
|
(format t "Empty Stack: ~a~%" my-stack)
|
||||||
|
(push-to-stack my-stack 10)
|
||||||
|
(push-to-stack my-stack 20)
|
||||||
|
(format t "Top element: ~a~%" (peek-stack my-stack))
|
||||||
|
(format t "Popped element: ~a~%" (pop-from-stack my-stack))
|
||||||
|
(format t "Stack after pop: ~a~%" my-stack)))
|
16
utils.lisp
Normal file
16
utils.lisp
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
(in-package #:cl-forth)
|
||||||
|
|
||||||
|
(defun split-string (delimiter input-string)
|
||||||
|
"Splits INPUT-STRING into a list of tokens, using DELIMITER as the delimiter."
|
||||||
|
(let ((start 0)
|
||||||
|
(tokens '()))
|
||||||
|
(loop for i from 0 below (length input-string) ; Keep it below
|
||||||
|
for char = (char input-string i)
|
||||||
|
do (if (eql char delimiter)
|
||||||
|
(when (> i start)
|
||||||
|
(push (subseq input-string start i) tokens)
|
||||||
|
(setf start (1+ i)))))
|
||||||
|
;; Handle the last token if there is any
|
||||||
|
(when (> (length input-string) start)
|
||||||
|
(push (subseq input-string start (length input-string)) tokens))
|
||||||
|
(nreverse tokens)))
|
Loading…
Reference in New Issue
Block a user