diff --git a/README.md b/README.md index d234942..f88a839 100644 --- a/README.md +++ b/README.md @@ -45,8 +45,10 @@ Features - [x] __conditional__ - (if test conseq alt) Evaluate test; if true, evaluate and return conseq; otherwise evaluate and return alt. _Example: (if (< 10 20) (+ 1 1) (+ 3 3)) ⇒ 2_ -- [ ] __quotation__ - (quote exp) Return the exp literally; do not evaluate it. _Example: (quote (a b c)) ⇒ (a b c)_ +- [x] __quotation__ - (quote exp) Return the exp literally; do not evaluate it. _Example: (quote (a b c)) ⇒ (a b c)_ -- [ ] __assignment__ - (set! var exp) Evaluate exp and assign that value to var, which must have been previously defined (with a define or as a parameter to an enclosing procedure). _Example: (set! x2 (* x x))_ +- [x] __assignment__ - (set! var exp) Evaluate exp and assign that value to var, which must have been previously defined (with a define or as a parameter to an enclosing procedure). _Example: (set! x2 (* x x))_ -- [ ] __sequencing__ - (begin exp...) Evaluate each of the expressions in left-to-right order, and return the final value. _Example: (begin (set! x 1) (set! x (+ x 1)) (* x 2)) ⇒ 4_ +- [x] __sequencing__ - (begin exp...) Evaluate each of the expressions in left-to-right order, and return the final value. _Example: (begin (define x 1) (set! x (+ x 1)) (* x 2)) ⇒ 4_ + +- [x] __display__ - (display exp...) Evaluate each of the expressions in left-to-right order, and write each result to STDOUT. _Example: (display Hello World! 42) ⇒ Hello World! 42_ diff --git a/lib/lisp.rb b/lib/lisp.rb index b943d9e..fb61fed 100644 --- a/lib/lisp.rb +++ b/lib/lisp.rb @@ -30,8 +30,10 @@ def self.parse(tokens, tree = []) def self.evaluator(token) case token - when /\d/ + when /\d*\.\d*/ token.to_f + when /\d/ + token.to_i else token.to_sym end @@ -52,6 +54,21 @@ def self.execute(exp, scope = global) _, test, conseq, alt = exp exp = execute(test, scope) ? conseq : alt execute(exp, scope) + when :quote + _, exp = exp + exp + when :set! + _, var, exp = exp + unless scope.has_key?(var) + raise "#{var} must be defined before you can set! it" + end + scope[var] = execute(exp, scope) + when :begin + _ = exp.shift + exp.map { |exp| execute(exp) }.last + when :display + _ = exp.shift + exp.map { |exp| execute(exp) || exp }.join(' ').tap { |str| puts str } else func, *args = exp.map { |exp| execute(exp, scope) } func.call(*args) diff --git a/test/test_lisp.rb b/test/test_lisp.rb index c78141d..ff82add 100755 --- a/test/test_lisp.rb +++ b/test/test_lisp.rb @@ -54,4 +54,29 @@ def test_lambda Lisp.eval("(define fact (lambda (n) (if (<= n 1) 1 (* n (fact (- n 1))))))") assert_equal 3628800, Lisp.eval("(fact 10)") end + + def test_quote + assert_equal [:a, :b, :c], Lisp.eval('(quote (a b c))') + end + + def test_assignment + ex = assert_raises(RuntimeError) { Lisp.eval('(set! foo 42)') } + assert_equal 'foo must be defined before you can set! it', ex.message + + Lisp.eval('(define foo 3.14)') + assert_equal 42, Lisp.eval('(set! foo 42)') + assert_equal 42, Lisp.eval('(* 1 foo)') + + assert_equal -42, Lisp.eval('(set! foo (* -1 foo))') + end + + def test_sequencing + assert_equal 4, Lisp.eval('(begin (define x 1) (set! x (+ x 1)) (* x 2))') + end + + def test_display + assert_output("Hello World! 42\n") { Lisp.eval('(display Hello World! 42)') } + assert_output("Evaluated: 3.14\n") { Lisp.eval('(display Evaluated: (* 1 3.14))') } + end + end