I made many commits but most of them were improvements for internal structure. Actual new features are if and (strict) equal/notequal.
if
I implemented if
as an expression like a ? b : c
as below.
(defn evaluate [expr env]
"assumption: env won't change"
(if (list? expr)
(let [[car & cdr] expr]
(case car
if (let [[cond- then- else-] cdr]
(if (js-boolean (evaluate cond- env))
(evaluate then- env)
(evaluate else- env)))
...snip...
Too straightforward to explain. Since cond
looked ambiguous to Clojure's one, so I added dash as suffix, and just for naming consistency, I added it to all the names..
equal/not equal
Strict equal (===)
Returns true if the operands are strictly equal (see above) with no type conversion.
https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/Comparison_Operators
Luckily I could simply use Clojure's =
function.
(defn evaluate [expr env]
"assumption: env won't change"
(if (list? expr)
(let [[car & cdr] expr]
(case car
if (let [[cond- then- else-] cdr]
(if (js-boolean (evaluate cond- env))
(evaluate then- env)
(evaluate else- env)))
function (let [params (first cdr)
body (second cdr)]
{:type :function :params params :body body})
fcall (let [func (evaluate (first cdr) env)
args (map #(evaluate % env) (second cdr))]
(case func
console.log (println (js-string (first args)))
+ (if (every? number? args)
(+ (first args) (second args))
(str (js-string (first args)) (js-string (second args))))
=== (= (first args) (second args))
!== (not= (first args) (second args))
(if (= (:type func) :function)
(let [applied-params (into {} (map (fn [x y] [x y])
(:params func)
args))]
(run- (:body func) (merge env applied-params)))
(prn 'must-not-happen 'missing-function func))))
quote (get env (first cdr) 'missing-local-var)
expr))
expr))
Now you have if
and a predicate function, you can make a safe loop with recursive function calls.
var f = function(n) {
(n === 10) ? console.log('end') : f(n + 1);
}
f();
It'll be represented as the below.
(run '[(var f (function [n]
[(fcall 'console.log ['n])
(if (fcall '=== ['n 10])
(fcall 'console.log ["end"])
(fcall 'f [(fcall '+ ['n 1])]))]))
(fcall 'f [0])])
It outputs 0 to 10 and "end"
Refactoring to make it more declarative
You may have noticed that the evaluate
function is already long and have guessed that it would get longer and longer as I add more builtin functions. First I made change to separate each built-in functions with updating a global variable *builtins*
. That's still not well readable because of the boilerplate code. I made another change to provide defbuiltin
macro. Now you can add a new builtin function with like the following code.
(defbuiltin + [x y]
(if (and (number? x) (number? y))
(+ x y)
(str (js-string x) (js-string y))))
It looks very like Clojure's defn
.
No comments:
Post a Comment