theta 0.1.3 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|