~kingcons/rascal

545e577baeb79a49cb3018e0130b1af666a03910 — Brit Butler 11 months ago 4aa5f24
Add basic interpreter for lang-int.
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)))