(ns beowulf.mexpr-test
  "These tests are taken generally from the examples on page 10 of
   Lisp 1.5 Programmers Manual"
  (:require [clojure.test :refer :all]
            [beowulf.bootstrap :refer [*options*]]
            [beowulf.read :refer [parse simplify generate gsp]]))

;; These tests are taken generally from the examples on page 10 of
;; Lisp 1.5 Programmers Manual:

;; ## Examples

;; M-expressions                                S-expressions
;; x                                            X
;; car                                          CAR
;; car[x]                                       (CAR X)
;; T                                            (QUOTE T)
;; ff[car [x]]                                  (FF (CAR X))
;; [atom[x]->x; T->ff[car[x]]]                  (COND ((ATOM X) X)
;;                                                ((QUOTE T)(FF (CAR X))))
;; label[ff;λ[[x];[atom[x]->x; T->ff[car[x]]]]] (LABEL FF (LAMBDA (X) (COND
;;                                                ((ATOM X) X)
;;                                                ((QUOTE T)(FF (CAR X))))))

(deftest variable-tests
  (testing "Variable translation"
    (let [expected "X"
          actual (print-str (gsp "x"))]
      (is (= actual expected)))
    (let [expected "CAR"
          actual (print-str (gsp "car"))]
      (is (= actual expected)))
    ))

(deftest literal-tests
  (testing "Literal translation"
    ;; in the context of an M-expression, an upper case letter
    ;; or string represents a Lisp literal, and should be quoted.
    ;; Wrapping in a function call puts us into mexpr contest;
    ;; "T" would be interpreted as a sexpr, which would not be
    ;; quoted.
    (let [expected "(ATOM (QUOTE A))"
          actual (print-str (gsp "atom[A]"))]
      (is (= actual expected)
          "Atoms should normally be quoted"))
    ;; I'm not clear how `car[(A B C)]` should be translated, but
    ;; I suspect as (CAR (LIST 'A 'B 'C)).

    ))

(deftest fncall-tests
  (testing "Function calls"
    (let [expected "(CAR X)"
          actual (print-str (gsp "car[x]"))]
      (is (= actual expected)))
    (let [expected "(FF (CAR X))"
          actual (print-str (gsp "ff[car[x]]"))]
      (is (= actual expected)))))

(deftest conditional-tests
  (testing "Conditional expressions"
    (let [expected "(COND ((ATOM X) X) (T (FF (CAR X))))"
          actual (print-str (gsp "[atom[x]->x; T->ff[car[x]]]"))]
      (is (= actual expected)))
    (let [expected "(LABEL FF (LAMBDA (X) (COND ((ATOM X) X) (T (FF (CAR X))))))"
          actual (print-str
                   (generate
                     (simplify
                       (parse "label[ff;λ[[x];[atom[x]->x; T->ff[car[x]]]]]"))))]
      (is (= actual expected)))))

(deftest strict-tests
  (testing "Strict feature"
    (binding [*options* {:strict true}]
      (is (thrown-with-msg?
            Exception
            #"Cannot parse meta expressions in strict mode"
            (gsp "label[ff;λ[[x];[atom[x]->x; T->ff[car[x]]]]]"))))))

(deftest assignment-tests
  (testing "Function assignment"
    (let [expected "(SET (QUOTE FF) (LAMBDA (X) (COND ((ATOM X) X) (T (FF (CAR X))))))"
          actual (gsp "ff[x]=[atom[x] -> x; T -> ff[car[x]]]")]
      (is (= actual expected)))))