M TODO.org => TODO.org +5 -1
@@ 1,2 1,6 @@
* rascal Tasks
-** TODO - Figure out how to parse code (from files initially)
+** DONE - Figure out how to parse code (from files initially)
+** TODO - Write an interpreter for lang-int
+** TODO - Add a debugging aid for viewing the AST
+** TODO - Wire up the interpreter and debugging aid to the CLI?
+** TODO - Consider supporting a #!lang directive in parser/program metadata.
M rascal.asd => rascal.asd +4 -1
@@ 23,6 23,7 @@
:components
((:file "protocol")
(:file "parse")
+ (:file "lang-int")
(:file "debug")
(:file "shell")
(:file "docs")
@@ 41,7 42,9 @@
:components ((:module "test"
:serial t
:components
- ((:file "parse")
+ ((:file "utils")
+ (:file "parse")
+ (:file "lang-int")
(:file "tests"))))
:perform (test-op (o s)
(uiop:symbol-call '#:rascal.test '#:test)))
M src/docs.lisp => src/docs.lisp +2 -1
@@ 10,7 10,8 @@
(@overview section)
(rascal.shell:@command section)
(rascal.protocol:@protocol section)
- (rascal.parse:@parse section))
+ (rascal.parse:@parse section)
+ (rascal.lang-int:@lang-int section))
(defsection @links (:title "Links")
"[repo]: https://git.sr.ht/~kingcons/rascal
A src/lang-int.lisp => src/lang-int.lisp +44 -0
@@ 0,0 1,44 @@
+(mgl-pax:define-package :rascal.lang-int
+ (:use :cl :alexandria :mgl-pax :trivia :rascal.parse)
+ (:import-from :rascal.protocol
+ #:program
+ #:body
+ #:lint
+ #:interpret))
+
+(in-package :rascal.lang-int)
+
+(defsection @lang-int (:title "Lang-Int")
+ "To start with, we will support a very simple language consisting
+of only integers, addition, subtraction, negation, and simplified input."
+ (*read-handler* variable)
+ (stubbed-read function))
+
+(defvar *read-handler* :cl
+ "The handler to use for READ calls. Should be `:cl` to use cl's READ or
+`:stub` to use STUBBED-READ. Primarily used to ease testing the interpreter.")
+
+(let ((value 0))
+ (defun stubbed-read (&optional new-value)
+ "A substitute for READ. Always returns the value it has closed over, which
+defaults to 0, but can be updated via passing the optional NEW-VALUE arg."
+ (if new-value
+ (setf value new-value)
+ value)))
+
+(defmethod interpret ((program program) (language (eql :lang-int)))
+ (labels ((evaluate (expr)
+ (match expr
+ ((num :value-of value)
+ value)
+ ((primitive :operator 'read)
+ (let ((result (if (eql *read-handler* :cl)
+ (read)
+ (stubbed-read))))
+ (unless (integerp result)
+ (error 'invalid-value "Expected an integer: ~A" result))
+ result))
+ ((primitive :operator op :args arguments)
+ (let ((args (mapcar #'evaluate arguments)))
+ (apply op args))))))
+ (evaluate (body program))))
M src/protocol.lisp => src/protocol.lisp +1 -1
@@ 21,7 21,7 @@ and a method INTERPRET to execute the program via a simple interpreter."
(interpret generic-function))
(defvar *languages*
- '(:lang-ints)
+ '(:lang-int)
"A list of symbols naming all language subsets that rascal supports.")
(defclass program ()
A test/lang-int.lisp => test/lang-int.lisp +19 -0
@@ 0,0 1,19 @@
+(defpackage :rascal.test.lang-int
+ (:use :cl :try :rascal.test.utils :rascal.lang-int)
+ (:import-from :rascal.protocol #:parse-program)
+ (:import-from :rascal.protocol #:interpret)
+ (:export #:test-lang-int))
+
+(in-package :rascal.test.lang-int)
+
+(deftest test-lang-int ()
+ (test-example-1))
+
+(deftest test-example-1 ()
+ (let ((example-1 (parse-program (example "lang-int1.ras")))
+ (*read-handler* :stub))
+ (stubbed-read 50)
+ (is (= 42 (interpret example-1 :lang-int)))))
+
+#+nil
+(try 'test-lang-int)
M test/parse.lisp => test/parse.lisp +5 -6
@@ 1,14 1,13 @@
(defpackage :rascal.test.parse
- (:use :cl :rascal.protocol :rascal.parse :try)
- (:import-from :serapeum #:concat)
+ (:use :cl :try :rascal.test.utils :rascal.parse)
+ (:import-from :rascal.protocol
+ #:parse-program
+ #:program
+ #:body)
(:export #:test-parse))
(in-package :rascal.test.parse)
-(defun example (name)
- (let ((relpath (concat "examples/" name)))
- (asdf:system-relative-pathname :rascal relpath)))
-
(deftest test-parse ()
(test-lang-int-examples))
M test/tests.lisp => test/tests.lisp +2 -1
@@ 5,7 5,8 @@
(in-package :rascal.test)
(deftest test-all ()
- (rascal.test.parse:test-parse))
+ (rascal.test.parse:test-parse)
+ (rascal.test.lang-int:test-lang-int))
(defun test (&key (debug nil) (print 'unexpected) (describe 'unexpected))
(warn-on-tests-not-run ((find-package :rascal-test))
A test/utils.lisp => test/utils.lisp +10 -0
@@ 0,0 1,10 @@
+(defpackage :rascal.test.utils
+ (:use :cl :try)
+ (:export #:example))
+
+(in-package :rascal.test.utils)
+
+(defun example (name)
+ "Return the pathname for the rascal program in `examples/` matching NAME."
+ (let ((relpath (serapeum:concat "examples/" name)))
+ (asdf:system-relative-pathname :rascal relpath)))