theta 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +75 -2
- data/VERSION +1 -1
- data/lib/theta.rb +7 -4
- data/lib/theta/environment.rb +2 -2
- data/lib/theta/interpreter.rb +5 -10
- data/lib/theta/library/display.scm +8 -0
- data/lib/theta/library/equality.scm +1 -0
- data/lib/theta/library/input.scm +7 -0
- data/lib/theta/library/string-to-num.scm +7 -0
- data/lib/theta/parser.rb +38 -14
- data/test/library/test_add.rb +27 -0
- data/test/library/test_divide.rb +37 -0
- data/test/library/test_equality.rb +41 -0
- data/test/library/test_greater_than.rb +33 -0
- data/test/library/test_greater_than_or_equal.rb +40 -0
- data/test/library/test_if.rb +49 -0
- data/test/library/test_lambda.rb +40 -0
- data/test/library/test_less_than.rb +33 -0
- data/test/library/test_less_than_or_equal.rb +40 -0
- data/test/library/test_multiply.rb +27 -0
- data/test/library/test_set.rb +21 -0
- data/test/library/test_subtract.rb +27 -0
- data/test/test_environment.rb +49 -0
- data/test/test_interpreter.rb +29 -0
- data/test/test_parser.rb +59 -0
- data/theta.gemspec +36 -3
- metadata +44 -11
data/README.rdoc
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
= Theta
|
2
2
|
|
3
|
-
Theta is a Lisp interpreter for Ruby. It was heavily influenced by {lis.py}[http://norvig.com/lispy.html] and {flea}[https://github.com/aarongough/flea]. It was primarily made as a learning exercise.
|
3
|
+
Theta is a Lisp interpreter for Ruby (though it's really just a small subset of Lisp it interprets). It was heavily influenced by {lis.py}[http://norvig.com/lispy.html] and {flea}[https://github.com/aarongough/flea]. It was primarily made as a learning exercise.
|
4
4
|
|
5
5
|
== How to use
|
6
6
|
|
@@ -12,7 +12,6 @@ After installation, simply type theta to get to the interactive interpreter prom
|
|
12
12
|
|
13
13
|
% theta
|
14
14
|
theta> (define a 2)
|
15
|
-
2
|
16
15
|
theta> (+ a 3)
|
17
16
|
5
|
18
17
|
theta> (define square (lambda (x) (* x x)))
|
@@ -37,3 +36,77 @@ And finally, you can use Theta inside your own Ruby programs, if that sounds lik
|
|
37
36
|
t = Theta::Interpreter.new
|
38
37
|
t.run "(define n 15)"
|
39
38
|
puts t.run "(+ 10 n)"
|
39
|
+
|
40
|
+
== Syntax
|
41
|
+
|
42
|
+
Theta uses a very simplified Lisp-like syntax.
|
43
|
+
|
44
|
+
=== Math
|
45
|
+
|
46
|
+
Theta has your basic mathematical functions.
|
47
|
+
|
48
|
+
theta> (+ 2 2)
|
49
|
+
4
|
50
|
+
theta> (- 3 2)
|
51
|
+
1
|
52
|
+
theta> (* 4 2)
|
53
|
+
8
|
54
|
+
theta> (/ 4 2)
|
55
|
+
2
|
56
|
+
|
57
|
+
=== Defining Functions and Variables
|
58
|
+
|
59
|
+
You also have the basic variable definition stuff.
|
60
|
+
|
61
|
+
theta> (define a 2)
|
62
|
+
theta> (+ a 3)
|
63
|
+
5
|
64
|
+
|
65
|
+
And functions.
|
66
|
+
|
67
|
+
theta> (define square (lambda (x) (* x x)))
|
68
|
+
theta> (square 3)
|
69
|
+
9
|
70
|
+
|
71
|
+
=== Conditionals and Truthiness
|
72
|
+
|
73
|
+
We have your basic if statement that will evaluate the first piece of code
|
74
|
+
if true, otherwise the second (if you give it one).
|
75
|
+
|
76
|
+
theta> (if (= 2 2) (display "true") (display "not true"))
|
77
|
+
true
|
78
|
+
theta> (if (= 1 2) (display "true") (display "not true"))
|
79
|
+
not true
|
80
|
+
|
81
|
+
And some basic Boolean statements.
|
82
|
+
|
83
|
+
theta> (= 2 2)
|
84
|
+
true
|
85
|
+
theta> (> 1 2)
|
86
|
+
false
|
87
|
+
theta> (< 2 3)
|
88
|
+
true
|
89
|
+
theta> (>= 3 3 2)
|
90
|
+
true
|
91
|
+
theta> (<= 4 5 2)
|
92
|
+
false
|
93
|
+
|
94
|
+
=== Input and Output
|
95
|
+
|
96
|
+
Theta also has basic input and output.
|
97
|
+
|
98
|
+
theta> (display "poop")
|
99
|
+
poop
|
100
|
+
theta> (define a (input))
|
101
|
+
hi <this is user input>
|
102
|
+
theta> (display a)
|
103
|
+
hi
|
104
|
+
|
105
|
+
And a function to turn user input into an integer.
|
106
|
+
|
107
|
+
theta> (define a (string-to-num (input)))
|
108
|
+
123 <this is user input>
|
109
|
+
theta>(+ a 3)
|
110
|
+
126
|
111
|
+
|
112
|
+
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.4
|
data/lib/theta.rb
CHANGED
@@ -16,7 +16,10 @@ module Theta
|
|
16
16
|
|
17
17
|
# run given code
|
18
18
|
def run(program)
|
19
|
-
|
19
|
+
output = @interpreter.run(program)
|
20
|
+
if not output.nil? and not output.empty?
|
21
|
+
output.each { |value| puts @interpreter.make_readable(value) }
|
22
|
+
end
|
20
23
|
end
|
21
24
|
|
22
25
|
# start an interactive interpreter
|
@@ -36,11 +39,11 @@ module Theta
|
|
36
39
|
repl_help
|
37
40
|
else
|
38
41
|
begin
|
39
|
-
|
42
|
+
output = @interpreter.run(input)
|
40
43
|
rescue SyntaxError
|
41
44
|
end
|
42
|
-
|
43
|
-
puts @interpreter.make_readable(value)
|
45
|
+
if not output.nil? and not output.empty?
|
46
|
+
output.each { |value| puts @interpreter.make_readable(value) }
|
44
47
|
end
|
45
48
|
end
|
46
49
|
end
|
data/lib/theta/environment.rb
CHANGED
data/lib/theta/interpreter.rb
CHANGED
@@ -28,7 +28,9 @@ module Theta
|
|
28
28
|
# run some code
|
29
29
|
def run(program)
|
30
30
|
expression = parse(program)
|
31
|
-
|
31
|
+
output = []
|
32
|
+
expression.each { |e| output << evaluate(e) }
|
33
|
+
return output.delete_if { |x| x.nil? }
|
32
34
|
end
|
33
35
|
|
34
36
|
# call the parser to make a string interpretable
|
@@ -49,17 +51,9 @@ module Theta
|
|
49
51
|
return expression
|
50
52
|
end
|
51
53
|
|
52
|
-
if expression.count == 1
|
53
|
-
if expression.is_a? Symbol
|
54
|
-
return @current_environment.find(expression[0])
|
55
|
-
else
|
56
|
-
return expression[0]
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
54
|
case expression[0]
|
61
55
|
when :define
|
62
|
-
|
56
|
+
@current_environment.define(expression [1], evaluate(expression[2]))
|
63
57
|
when :ruby_func
|
64
58
|
return eval expression[1]
|
65
59
|
else
|
@@ -71,6 +65,7 @@ module Theta
|
|
71
65
|
raise RuntimeError, "#{expression[0]} is not a function"
|
72
66
|
end
|
73
67
|
end
|
68
|
+
return nil
|
74
69
|
end
|
75
70
|
end
|
76
71
|
end
|
data/lib/theta/parser.rb
CHANGED
@@ -73,26 +73,49 @@ module Theta
|
|
73
73
|
raise SyntaxError, "unexpected EOF while reading"
|
74
74
|
end
|
75
75
|
tokens.delete_if { |item| item.strip == "" || item == []}
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
76
|
+
tmp, expression = restructure tokens
|
77
|
+
return expression
|
78
|
+
end
|
79
|
+
|
80
|
+
def restructure(token_array, offset = 0)
|
81
|
+
statement = []
|
82
|
+
while offset < token_array.length
|
83
|
+
if token_array[offset] == "("
|
84
|
+
offset, tmp_array = restructure(token_array, offset + 1)
|
85
|
+
statement << tmp_array
|
86
|
+
elsif token_array[offset] == ")"
|
87
|
+
break
|
88
|
+
else
|
89
|
+
statement << atom(token_array[offset])
|
81
90
|
end
|
82
|
-
|
83
|
-
return l
|
84
|
-
elsif ")" == token
|
85
|
-
raise SyntaxError, "unexpected )"
|
86
|
-
elsif token.start_with?("\"")
|
87
|
-
return token.gsub("\"", "")
|
88
|
-
else
|
89
|
-
return atom(token)
|
91
|
+
offset += 1
|
90
92
|
end
|
93
|
+
return offset, statement
|
91
94
|
end
|
95
|
+
|
96
|
+
# token = tokens.shift
|
97
|
+
# if "(" == token
|
98
|
+
# l = []
|
99
|
+
# until tokens[0] == ")"
|
100
|
+
# l << read_from(tokens)
|
101
|
+
# end
|
102
|
+
# tokens.shift
|
103
|
+
# return l
|
104
|
+
# elsif ")" == token
|
105
|
+
# raise SyntaxError, "unexpected )"
|
106
|
+
# elsif token.start_with?("\"")
|
107
|
+
# return token.gsub("\"", "")
|
108
|
+
# else
|
109
|
+
# return atom(token)
|
110
|
+
# end
|
111
|
+
# end
|
92
112
|
|
93
113
|
# returns appropriate numeric object if a number,
|
94
114
|
# otherwise returns a symbol
|
95
115
|
def atom(token)
|
116
|
+
if token.start_with?("\"")
|
117
|
+
return token.gsub("\"", "")
|
118
|
+
end
|
96
119
|
token.gsub!(/\n\t/, "")
|
97
120
|
begin
|
98
121
|
return Integer(token)
|
@@ -108,7 +131,8 @@ module Theta
|
|
108
131
|
# convert an expression back to a readable string
|
109
132
|
def to_string(expression)
|
110
133
|
if expression.is_a? Array
|
111
|
-
|
134
|
+
expression.map { |exp| to_string(exp) }
|
135
|
+
return "(" + expression.join(" ") + ")"
|
112
136
|
else
|
113
137
|
return expression.to_s
|
114
138
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "helper.rb"))
|
2
|
+
|
3
|
+
module Theta
|
4
|
+
class TestAdd < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@interpreter = Interpreter.new
|
7
|
+
end
|
8
|
+
|
9
|
+
should "add numbers correctly" do
|
10
|
+
result = @interpreter.run("(+ 2 2)")
|
11
|
+
assert_equal(4, result[0])
|
12
|
+
end
|
13
|
+
|
14
|
+
should "add a series of numbers" do
|
15
|
+
result = @interpreter.run("(+ 5 4 3 2 1)")
|
16
|
+
assert_equal(15, result[0])
|
17
|
+
end
|
18
|
+
|
19
|
+
should "add defined variables" do
|
20
|
+
result = @interpreter.run(
|
21
|
+
"(define a 1)
|
22
|
+
(define b 2)
|
23
|
+
(+ a b)")
|
24
|
+
assert_equal(3, result[0])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "helper.rb"))
|
2
|
+
|
3
|
+
module Theta
|
4
|
+
class TestDivide < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@interpreter = Interpreter.new
|
7
|
+
end
|
8
|
+
|
9
|
+
should "divide numbers properly" do
|
10
|
+
result = @interpreter.run("(/ 10 2)")
|
11
|
+
assert_equal(5, result[0])
|
12
|
+
end
|
13
|
+
|
14
|
+
should "divide multiple numbers" do
|
15
|
+
result = @interpreter.run("(/ 4 2 2)")
|
16
|
+
assert_equal(1, result[0])
|
17
|
+
end
|
18
|
+
|
19
|
+
should "divide defined variables" do
|
20
|
+
result = @interpreter.run(
|
21
|
+
"(define a 4)
|
22
|
+
(define b 2)
|
23
|
+
(/ a b)")
|
24
|
+
assert_equal(2, result[0])
|
25
|
+
end
|
26
|
+
|
27
|
+
should "properly round for integer division" do
|
28
|
+
result = @interpreter.run("(/ 5 2)")
|
29
|
+
assert_equal(2, result[0])
|
30
|
+
end
|
31
|
+
|
32
|
+
should "not round for float division" do
|
33
|
+
result = @interpreter.run("(/ 5.0 2)")
|
34
|
+
assert_equal(2.5, result[0])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "helper.rb"))
|
2
|
+
|
3
|
+
module Theta
|
4
|
+
class TestEquality < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@interpreter = Interpreter.new
|
7
|
+
end
|
8
|
+
|
9
|
+
should "return true for correct expressions" do
|
10
|
+
result = @interpreter.run("(= 2 2)")
|
11
|
+
assert_equal(true, result[0])
|
12
|
+
end
|
13
|
+
|
14
|
+
should "return false for incorrect expressions" do
|
15
|
+
result = @interpreter.run("(= 2 3)")
|
16
|
+
assert_equal(false, result[0])
|
17
|
+
end
|
18
|
+
|
19
|
+
should "work with multiple arguments" do
|
20
|
+
result = @interpreter.run("
|
21
|
+
(= 2 2 2)
|
22
|
+
(= 2 2 3 2)
|
23
|
+
")
|
24
|
+
assert_equal(true, result[0])
|
25
|
+
assert_equal(false, result[1])
|
26
|
+
end
|
27
|
+
|
28
|
+
should "work with variables" do
|
29
|
+
@interpreter.run("
|
30
|
+
(define a 1)
|
31
|
+
(define b 2)
|
32
|
+
")
|
33
|
+
result = @interpreter.run("
|
34
|
+
(= a a)
|
35
|
+
(= a b)
|
36
|
+
")
|
37
|
+
assert_equal(true, result[0])
|
38
|
+
assert_equal(false, result[1])
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "helper.rb"))
|
2
|
+
|
3
|
+
module Theta
|
4
|
+
class TestGreaterThan < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@interpreter = Interpreter.new
|
7
|
+
end
|
8
|
+
|
9
|
+
should "return true if first arg is largest" do
|
10
|
+
result = @interpreter.run("(> 5 1 2 3)")
|
11
|
+
assert_equal(true, result[0])
|
12
|
+
end
|
13
|
+
|
14
|
+
should "return false if first arg isn't largest" do
|
15
|
+
result = @interpreter.run("(> 5 1 2 10)")
|
16
|
+
assert_equal(false, result[0])
|
17
|
+
end
|
18
|
+
|
19
|
+
should "work with variables" do
|
20
|
+
@interpreter.run("
|
21
|
+
(define a 1)
|
22
|
+
(define b 2)
|
23
|
+
(define c 3)
|
24
|
+
")
|
25
|
+
result = @interpreter.run("
|
26
|
+
(> c b a)
|
27
|
+
(> b a c)
|
28
|
+
")
|
29
|
+
assert_equal(true, result[0])
|
30
|
+
assert_equal(false, result[1])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "helper.rb"))
|
2
|
+
|
3
|
+
module Theta
|
4
|
+
class TestGreaterThanOrEqual < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@interpreter = Interpreter.new
|
7
|
+
end
|
8
|
+
|
9
|
+
should "return true if first arg is largest" do
|
10
|
+
result = @interpreter.run("(>= 4 3 2)")
|
11
|
+
assert_equal(true, result[0])
|
12
|
+
end
|
13
|
+
|
14
|
+
should "return true if first arg is equal to the largest" do
|
15
|
+
result = @interpreter.run("(>= 4 3 2 4)")
|
16
|
+
assert_equal(true, result[0])
|
17
|
+
end
|
18
|
+
|
19
|
+
should "return false if first arg is not greater than or equal to the largest" do
|
20
|
+
result = @interpreter.run("(>= 4 3 2 10)")
|
21
|
+
assert_equal(false, result[0])
|
22
|
+
end
|
23
|
+
|
24
|
+
should "work with variables" do
|
25
|
+
@interpreter.run("
|
26
|
+
(define a 1)
|
27
|
+
(define b 2)
|
28
|
+
(define c 3)
|
29
|
+
")
|
30
|
+
result = @interpreter.run("
|
31
|
+
(>= c b a)
|
32
|
+
(>= c b a c)
|
33
|
+
(>= b c a)
|
34
|
+
")
|
35
|
+
assert_equal(true, result[0])
|
36
|
+
assert_equal(true, result[1])
|
37
|
+
assert_equal(false, result[2])
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "helper.rb"))
|
2
|
+
|
3
|
+
module Theta
|
4
|
+
class TestIf < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@interpreter = Interpreter.new
|
7
|
+
end
|
8
|
+
|
9
|
+
context "without else" do
|
10
|
+
should "execute statement if true" do
|
11
|
+
@interpreter.run("
|
12
|
+
(if #t
|
13
|
+
(define a 1))
|
14
|
+
")
|
15
|
+
assert_equal(1, @interpreter.current_environment.find(:a))
|
16
|
+
end
|
17
|
+
|
18
|
+
should "not execute statement if false" do
|
19
|
+
@interpreter.run("
|
20
|
+
(if #f
|
21
|
+
(define a 1))
|
22
|
+
")
|
23
|
+
assert_equal(nil, @interpreter.current_environment.find(:a))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context "with else" do
|
28
|
+
should "execute else statement if false" do
|
29
|
+
@interpreter.run("
|
30
|
+
(if #f
|
31
|
+
(define a 1)
|
32
|
+
(define b 1))
|
33
|
+
")
|
34
|
+
assert_equal(nil, @interpreter.current_environment.find(:a))
|
35
|
+
assert_equal(1, @interpreter.current_environment.find(:b))
|
36
|
+
end
|
37
|
+
|
38
|
+
should "not execute else statement if true" do
|
39
|
+
@interpreter.run("
|
40
|
+
(if #t
|
41
|
+
(define a 1)
|
42
|
+
(define b 1))
|
43
|
+
")
|
44
|
+
assert_equal(1, @interpreter.current_environment.find(:a))
|
45
|
+
assert_equal(nil, @interpreter.current_environment.find(:b))
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "helper.rb"))
|
2
|
+
|
3
|
+
module Theta
|
4
|
+
class TestLambda < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@interpreter = Interpreter.new
|
7
|
+
end
|
8
|
+
|
9
|
+
should "create a function" do
|
10
|
+
result = @interpreter.run("
|
11
|
+
(lambda () ())
|
12
|
+
")
|
13
|
+
assert_kind_of(Proc, result[0])
|
14
|
+
end
|
15
|
+
|
16
|
+
should "accept an argument" do
|
17
|
+
result = @interpreter.run("
|
18
|
+
(define test (lambda (a) (+ a a)))
|
19
|
+
(test 1)
|
20
|
+
")
|
21
|
+
assert_equal(2, result[0])
|
22
|
+
end
|
23
|
+
|
24
|
+
should "accept multiple arguments" do
|
25
|
+
result = @interpreter.run("
|
26
|
+
(define test (lambda (a b c) (+ a b c)))
|
27
|
+
(test 1 2 3)
|
28
|
+
")
|
29
|
+
assert_equal(6, result[0])
|
30
|
+
end
|
31
|
+
|
32
|
+
should "raise an error if a param is used more than once" do
|
33
|
+
assert_raise(RuntimeError) {
|
34
|
+
@interpreter.run("
|
35
|
+
(lambda (a a) (+ a 2))
|
36
|
+
")
|
37
|
+
}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "helper.rb"))
|
2
|
+
|
3
|
+
module Theta
|
4
|
+
class TestLessThan < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@interpreter = Interpreter.new
|
7
|
+
end
|
8
|
+
|
9
|
+
should "return true if first arg is smallest" do
|
10
|
+
result = @interpreter.run("(< 1 2 3)")
|
11
|
+
assert_equal(true, result[0])
|
12
|
+
end
|
13
|
+
|
14
|
+
should "return false if first arg isn't smallest" do
|
15
|
+
result = @interpreter.run("(< 5 2 10)")
|
16
|
+
assert_equal(false, result[0])
|
17
|
+
end
|
18
|
+
|
19
|
+
should "work with variables" do
|
20
|
+
@interpreter.run("
|
21
|
+
(define a 1)
|
22
|
+
(define b 2)
|
23
|
+
(define c 3)
|
24
|
+
")
|
25
|
+
result = @interpreter.run("
|
26
|
+
(< a b c)
|
27
|
+
(< b a c)
|
28
|
+
")
|
29
|
+
assert_equal(true, result[0])
|
30
|
+
assert_equal(false, result[1])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "helper.rb"))
|
2
|
+
|
3
|
+
module Theta
|
4
|
+
class TestGreaterThanOrEqual < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@interpreter = Interpreter.new
|
7
|
+
end
|
8
|
+
|
9
|
+
should "return true if first arg is smallest" do
|
10
|
+
result = @interpreter.run("(<= 1 3 2)")
|
11
|
+
assert_equal(true, result[0])
|
12
|
+
end
|
13
|
+
|
14
|
+
should "return true if first arg is equal to the smallest" do
|
15
|
+
result = @interpreter.run("(<= 2 3 2 4)")
|
16
|
+
assert_equal(true, result[0])
|
17
|
+
end
|
18
|
+
|
19
|
+
should "return false if first arg is not less than or equal to the smallest" do
|
20
|
+
result = @interpreter.run("(<= 4 3 2 10)")
|
21
|
+
assert_equal(false, result[0])
|
22
|
+
end
|
23
|
+
|
24
|
+
should "work with variables" do
|
25
|
+
@interpreter.run("
|
26
|
+
(define a 1)
|
27
|
+
(define b 2)
|
28
|
+
(define c 3)
|
29
|
+
")
|
30
|
+
result = @interpreter.run("
|
31
|
+
(<= a b c)
|
32
|
+
(<= a b a c)
|
33
|
+
(<= b c a)
|
34
|
+
")
|
35
|
+
assert_equal(true, result[0])
|
36
|
+
assert_equal(true, result[1])
|
37
|
+
assert_equal(false, result[2])
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "helper.rb"))
|
2
|
+
|
3
|
+
module Theta
|
4
|
+
class TestMultiply < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@interpreter = Interpreter.new
|
7
|
+
end
|
8
|
+
|
9
|
+
should "multiply numbers properly" do
|
10
|
+
result = @interpreter.run("(* 2 2)")
|
11
|
+
assert_equal(4, result[0])
|
12
|
+
end
|
13
|
+
|
14
|
+
should "multiply multiple numbers" do
|
15
|
+
result = @interpreter.run("(* 2 3 4)")
|
16
|
+
assert_equal(24, result[0])
|
17
|
+
end
|
18
|
+
|
19
|
+
should "multiply defined variables" do
|
20
|
+
result = @interpreter.run(
|
21
|
+
"(define a 4)
|
22
|
+
(define b 2)
|
23
|
+
(* a b)")
|
24
|
+
assert_equal(8, result[0])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "helper.rb"))
|
2
|
+
|
3
|
+
module Theta
|
4
|
+
class TestSet < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@interpreter = Interpreter.new
|
7
|
+
end
|
8
|
+
|
9
|
+
should "set value of existing variable" do
|
10
|
+
@interpreter.run("
|
11
|
+
(define a 1)
|
12
|
+
(set! a 2)
|
13
|
+
")
|
14
|
+
assert_equal(2, @interpreter.current_environment.find(:a))
|
15
|
+
end
|
16
|
+
|
17
|
+
should "raise an error if variable not defined" do
|
18
|
+
assert_raise(TypeError) { @interpreter.run("(set! a 2)") }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "helper.rb"))
|
2
|
+
|
3
|
+
module Theta
|
4
|
+
class TestSubtract < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@interpreter = Interpreter.new
|
7
|
+
end
|
8
|
+
|
9
|
+
should "subtract numbers properly" do
|
10
|
+
result = @interpreter.run("(- 10 5)")
|
11
|
+
assert_equal(5, result[0])
|
12
|
+
end
|
13
|
+
|
14
|
+
should "subtract multiple numbers" do
|
15
|
+
result = @interpreter.run("(- 20 1 2 3 4 5)")
|
16
|
+
assert_equal(5, result[0])
|
17
|
+
end
|
18
|
+
|
19
|
+
should "subtract defined variables" do
|
20
|
+
result = @interpreter.run(
|
21
|
+
"(define a 4)
|
22
|
+
(define b 2)
|
23
|
+
(- a b)")
|
24
|
+
assert_equal(2, result[0])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module Theta
|
4
|
+
class TestEnvironment < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@environment = Environment.new
|
7
|
+
end
|
8
|
+
|
9
|
+
context "new" do
|
10
|
+
should "add definition for true" do
|
11
|
+
result = @environment.find("#t".to_sym)
|
12
|
+
assert_equal(true, result)
|
13
|
+
end
|
14
|
+
|
15
|
+
should "add definition for false" do
|
16
|
+
result = @environment.find("#f".to_sym)
|
17
|
+
assert_equal(false, result)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context "find" do
|
22
|
+
should "return nil for undefined item" do
|
23
|
+
result = @environment.find(:a)
|
24
|
+
assert_equal(nil, result)
|
25
|
+
end
|
26
|
+
|
27
|
+
should "return defined item" do
|
28
|
+
@environment.define(:a, 1)
|
29
|
+
result = @environment.find(:a)
|
30
|
+
assert_equal(1, result)
|
31
|
+
end
|
32
|
+
|
33
|
+
should "return item defined in parent" do
|
34
|
+
@environment.define(:a, 1)
|
35
|
+
child = Environment.new(@environment)
|
36
|
+
result = child.find(:a)
|
37
|
+
assert_equal(1, result)
|
38
|
+
end
|
39
|
+
|
40
|
+
should "override a variable in parent if defined in current" do
|
41
|
+
@environment.define(:a, 1)
|
42
|
+
child = Environment.new(@environment)
|
43
|
+
child.define(:a, 3)
|
44
|
+
result = child.find(:a)
|
45
|
+
assert_equal(3, result)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module Theta
|
4
|
+
class TestInterpreter < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@interpreter = Interpreter.new
|
7
|
+
end
|
8
|
+
|
9
|
+
context "run" do
|
10
|
+
should "run code and return a result" do
|
11
|
+
result = @interpreter.run("(+ 0 1)")
|
12
|
+
assert_equal(1, result[0])
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context "evaluate" do
|
17
|
+
should "allow definition of items" do
|
18
|
+
@interpreter.evaluate([:define, :a, 1])
|
19
|
+
assert_equal(1, @interpreter.current_environment.find(:a))
|
20
|
+
end
|
21
|
+
|
22
|
+
should "allow ruby functions" do
|
23
|
+
result = @interpreter.evaluate([:ruby_func,
|
24
|
+
"lambda { 1 }"])
|
25
|
+
assert_equal(1, result.call)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/test/test_parser.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module Theta
|
4
|
+
class TestParser < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@parser = Parser.new
|
7
|
+
end
|
8
|
+
|
9
|
+
context "atom" do
|
10
|
+
should "convert string to symbol" do
|
11
|
+
result = @parser.atom("test")
|
12
|
+
assert_equal(:test, result)
|
13
|
+
end
|
14
|
+
|
15
|
+
should "leave numbers alone" do
|
16
|
+
result = @parser.atom("1")
|
17
|
+
assert_equal(1, result)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context "tokenize" do
|
22
|
+
should "split a statement apart into its component pieces" do
|
23
|
+
result = @parser.tokenize("(define a 1)")
|
24
|
+
assert_equal(["(", "define", "a", "1", ")"], result)
|
25
|
+
end
|
26
|
+
|
27
|
+
should "be able to handle multiple statements at a time" do
|
28
|
+
result = @parser.tokenize("(define a 1) (define b 2)")
|
29
|
+
assert_equal(["(", "define", "a", "1", ")","(", "define", "b", "2", ")"], result)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "read_from" do
|
34
|
+
should "turn a tokenized statement into an interpreter understandable array" do
|
35
|
+
result = @parser.read_from(["(", "define", "a", "1", ")"])
|
36
|
+
assert_equal([[:define, :a, 1]], result)
|
37
|
+
end
|
38
|
+
|
39
|
+
should "be able to handle multiple statements at a time" do
|
40
|
+
tokens = @parser.tokenize("(define a 1) (define b 2)")
|
41
|
+
result = @parser.read_from(tokens)
|
42
|
+
assert_equal([[:define, :a, 1], [:define, :b, 2]], result)
|
43
|
+
end
|
44
|
+
|
45
|
+
should "be able to handle nested expressions" do
|
46
|
+
tokens = @parser.tokenize("(+ 2 (+ 3 5))")
|
47
|
+
result = @parser.read_from(tokens)
|
48
|
+
assert_equal([[:+, 2, [:+, 3, 5]]], result)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context "to_string" do
|
53
|
+
should "turn a parsed statement back into a human readable format" do
|
54
|
+
result = @parser.to_string([:define, :a, 1])
|
55
|
+
assert_equal("(define a 1)", result)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/theta.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{theta}
|
8
|
-
s.version = "0.1.
|
8
|
+
s.version = "0.1.4"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Chris O'Neal"]
|
12
|
-
s.date = %q{2011-06-
|
12
|
+
s.date = %q{2011-06-20}
|
13
13
|
s.description = %q{Theta was created as a learning project based off of lis.py and flea}
|
14
14
|
s.email = %q{ctoneal@gmail.com}
|
15
15
|
s.executables = ["theta", "theta"]
|
@@ -30,19 +30,37 @@ Gem::Specification.new do |s|
|
|
30
30
|
"lib/theta/environment.rb",
|
31
31
|
"lib/theta/interpreter.rb",
|
32
32
|
"lib/theta/library/add.scm",
|
33
|
+
"lib/theta/library/display.scm",
|
33
34
|
"lib/theta/library/divide.scm",
|
34
35
|
"lib/theta/library/equality.scm",
|
35
36
|
"lib/theta/library/greater_than.scm",
|
36
37
|
"lib/theta/library/greater_than_or_equal.scm",
|
37
38
|
"lib/theta/library/if.scm",
|
39
|
+
"lib/theta/library/input.scm",
|
38
40
|
"lib/theta/library/lambda.scm",
|
39
41
|
"lib/theta/library/less_than.scm",
|
40
42
|
"lib/theta/library/less_than_or_equal.scm",
|
41
43
|
"lib/theta/library/multiply.scm",
|
42
44
|
"lib/theta/library/set.scm",
|
45
|
+
"lib/theta/library/string-to-num.scm",
|
43
46
|
"lib/theta/library/subtract.scm",
|
44
47
|
"lib/theta/parser.rb",
|
45
48
|
"test/helper.rb",
|
49
|
+
"test/library/test_add.rb",
|
50
|
+
"test/library/test_divide.rb",
|
51
|
+
"test/library/test_equality.rb",
|
52
|
+
"test/library/test_greater_than.rb",
|
53
|
+
"test/library/test_greater_than_or_equal.rb",
|
54
|
+
"test/library/test_if.rb",
|
55
|
+
"test/library/test_lambda.rb",
|
56
|
+
"test/library/test_less_than.rb",
|
57
|
+
"test/library/test_less_than_or_equal.rb",
|
58
|
+
"test/library/test_multiply.rb",
|
59
|
+
"test/library/test_set.rb",
|
60
|
+
"test/library/test_subtract.rb",
|
61
|
+
"test/test_environment.rb",
|
62
|
+
"test/test_interpreter.rb",
|
63
|
+
"test/test_parser.rb",
|
46
64
|
"theta.gemspec"
|
47
65
|
]
|
48
66
|
s.homepage = %q{http://github.com/ctoneal/theta}
|
@@ -51,7 +69,22 @@ Gem::Specification.new do |s|
|
|
51
69
|
s.rubygems_version = %q{1.6.2}
|
52
70
|
s.summary = %q{Theta is a Lisp interpreter in Ruby}
|
53
71
|
s.test_files = [
|
54
|
-
"test/helper.rb"
|
72
|
+
"test/helper.rb",
|
73
|
+
"test/library/test_add.rb",
|
74
|
+
"test/library/test_divide.rb",
|
75
|
+
"test/library/test_equality.rb",
|
76
|
+
"test/library/test_greater_than.rb",
|
77
|
+
"test/library/test_greater_than_or_equal.rb",
|
78
|
+
"test/library/test_if.rb",
|
79
|
+
"test/library/test_lambda.rb",
|
80
|
+
"test/library/test_less_than.rb",
|
81
|
+
"test/library/test_less_than_or_equal.rb",
|
82
|
+
"test/library/test_multiply.rb",
|
83
|
+
"test/library/test_set.rb",
|
84
|
+
"test/library/test_subtract.rb",
|
85
|
+
"test/test_environment.rb",
|
86
|
+
"test/test_interpreter.rb",
|
87
|
+
"test/test_parser.rb"
|
55
88
|
]
|
56
89
|
|
57
90
|
if s.respond_to? :specification_version then
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: theta
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,12 +9,12 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-06-
|
12
|
+
date: 2011-06-20 00:00:00.000000000 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: shoulda
|
17
|
-
requirement: &
|
17
|
+
requirement: &2053176 !ruby/object:Gem::Requirement
|
18
18
|
none: false
|
19
19
|
requirements:
|
20
20
|
- - ! '>='
|
@@ -22,10 +22,10 @@ dependencies:
|
|
22
22
|
version: '0'
|
23
23
|
type: :development
|
24
24
|
prerelease: false
|
25
|
-
version_requirements: *
|
25
|
+
version_requirements: *2053176
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
27
|
name: bundler
|
28
|
-
requirement: &
|
28
|
+
requirement: &2051736 !ruby/object:Gem::Requirement
|
29
29
|
none: false
|
30
30
|
requirements:
|
31
31
|
- - ~>
|
@@ -33,10 +33,10 @@ dependencies:
|
|
33
33
|
version: 1.0.0
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
|
-
version_requirements: *
|
36
|
+
version_requirements: *2051736
|
37
37
|
- !ruby/object:Gem::Dependency
|
38
38
|
name: jeweler
|
39
|
-
requirement: &
|
39
|
+
requirement: &2050476 !ruby/object:Gem::Requirement
|
40
40
|
none: false
|
41
41
|
requirements:
|
42
42
|
- - ~>
|
@@ -44,10 +44,10 @@ dependencies:
|
|
44
44
|
version: 1.5.2
|
45
45
|
type: :development
|
46
46
|
prerelease: false
|
47
|
-
version_requirements: *
|
47
|
+
version_requirements: *2050476
|
48
48
|
- !ruby/object:Gem::Dependency
|
49
49
|
name: rcov
|
50
|
-
requirement: &
|
50
|
+
requirement: &2048832 !ruby/object:Gem::Requirement
|
51
51
|
none: false
|
52
52
|
requirements:
|
53
53
|
- - ! '>='
|
@@ -55,7 +55,7 @@ dependencies:
|
|
55
55
|
version: '0'
|
56
56
|
type: :development
|
57
57
|
prerelease: false
|
58
|
-
version_requirements: *
|
58
|
+
version_requirements: *2048832
|
59
59
|
description: Theta was created as a learning project based off of lis.py and flea
|
60
60
|
email: ctoneal@gmail.com
|
61
61
|
executables:
|
@@ -78,19 +78,37 @@ files:
|
|
78
78
|
- lib/theta/environment.rb
|
79
79
|
- lib/theta/interpreter.rb
|
80
80
|
- lib/theta/library/add.scm
|
81
|
+
- lib/theta/library/display.scm
|
81
82
|
- lib/theta/library/divide.scm
|
82
83
|
- lib/theta/library/equality.scm
|
83
84
|
- lib/theta/library/greater_than.scm
|
84
85
|
- lib/theta/library/greater_than_or_equal.scm
|
85
86
|
- lib/theta/library/if.scm
|
87
|
+
- lib/theta/library/input.scm
|
86
88
|
- lib/theta/library/lambda.scm
|
87
89
|
- lib/theta/library/less_than.scm
|
88
90
|
- lib/theta/library/less_than_or_equal.scm
|
89
91
|
- lib/theta/library/multiply.scm
|
90
92
|
- lib/theta/library/set.scm
|
93
|
+
- lib/theta/library/string-to-num.scm
|
91
94
|
- lib/theta/library/subtract.scm
|
92
95
|
- lib/theta/parser.rb
|
93
96
|
- test/helper.rb
|
97
|
+
- test/library/test_add.rb
|
98
|
+
- test/library/test_divide.rb
|
99
|
+
- test/library/test_equality.rb
|
100
|
+
- test/library/test_greater_than.rb
|
101
|
+
- test/library/test_greater_than_or_equal.rb
|
102
|
+
- test/library/test_if.rb
|
103
|
+
- test/library/test_lambda.rb
|
104
|
+
- test/library/test_less_than.rb
|
105
|
+
- test/library/test_less_than_or_equal.rb
|
106
|
+
- test/library/test_multiply.rb
|
107
|
+
- test/library/test_set.rb
|
108
|
+
- test/library/test_subtract.rb
|
109
|
+
- test/test_environment.rb
|
110
|
+
- test/test_interpreter.rb
|
111
|
+
- test/test_parser.rb
|
94
112
|
- theta.gemspec
|
95
113
|
has_rdoc: true
|
96
114
|
homepage: http://github.com/ctoneal/theta
|
@@ -108,7 +126,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
108
126
|
version: '0'
|
109
127
|
segments:
|
110
128
|
- 0
|
111
|
-
hash:
|
129
|
+
hash: -440596785
|
112
130
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
113
131
|
none: false
|
114
132
|
requirements:
|
@@ -123,3 +141,18 @@ specification_version: 3
|
|
123
141
|
summary: Theta is a Lisp interpreter in Ruby
|
124
142
|
test_files:
|
125
143
|
- test/helper.rb
|
144
|
+
- test/library/test_add.rb
|
145
|
+
- test/library/test_divide.rb
|
146
|
+
- test/library/test_equality.rb
|
147
|
+
- test/library/test_greater_than.rb
|
148
|
+
- test/library/test_greater_than_or_equal.rb
|
149
|
+
- test/library/test_if.rb
|
150
|
+
- test/library/test_lambda.rb
|
151
|
+
- test/library/test_less_than.rb
|
152
|
+
- test/library/test_less_than_or_equal.rb
|
153
|
+
- test/library/test_multiply.rb
|
154
|
+
- test/library/test_set.rb
|
155
|
+
- test/library/test_subtract.rb
|
156
|
+
- test/test_environment.rb
|
157
|
+
- test/test_interpreter.rb
|
158
|
+
- test/test_parser.rb
|