crisp 0.0.5 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +10 -2
- data/README.md +2 -1
- data/Rakefile +2 -2
- data/autotest/discover.rb +1 -0
- data/crisp.gemspec +32 -8
- data/examples/fibonacci.crisp +6 -0
- data/lib/crisp.rb +3 -1
- data/lib/crisp/chained_env.rb +13 -0
- data/lib/crisp/crisp.treetop +23 -15
- data/lib/crisp/env.rb +7 -0
- data/lib/crisp/function.rb +6 -42
- data/lib/crisp/function_runner.rb +37 -0
- data/lib/crisp/functions.rb +5 -3
- data/lib/crisp/functions/arithmetic.rb +64 -9
- data/lib/crisp/functions/core.rb +80 -18
- data/lib/crisp/native_call_invoker.rb +16 -0
- data/lib/crisp/nodes.rb +30 -59
- data/lib/crisp/nodes/array_literal.rb +21 -0
- data/lib/crisp/nodes/base.rb +22 -0
- data/lib/crisp/nodes/block.rb +22 -0
- data/lib/crisp/nodes/false_literal.rb +11 -0
- data/lib/crisp/nodes/float_literal.rb +11 -0
- data/lib/crisp/nodes/integer_literal.rb +11 -0
- data/lib/crisp/nodes/nil_literal.rb +11 -0
- data/lib/crisp/nodes/operation.rb +20 -0
- data/lib/crisp/nodes/primitive.rb +16 -0
- data/lib/crisp/nodes/string_literal.rb +11 -0
- data/lib/crisp/nodes/symbol_literal.rb +15 -0
- data/lib/crisp/nodes/true_literal.rb +11 -0
- data/lib/crisp/parser.rb +7 -0
- data/lib/crisp/runtime.rb +7 -0
- data/lib/crisp/shell.rb +4 -0
- data/spec/crisp/arithmetics_spec.rb +39 -11
- data/spec/crisp/basic_spec.rb +7 -72
- data/spec/crisp/core_spec.rb +83 -0
- data/spec/crisp/function_spec.rb +49 -0
- data/spec/crisp/internal_spec.rb +60 -0
- data/spec/crisp/native_call_invoker_spec.rb +23 -0
- data/spec/crisp/string_spec.rb +1 -1
- data/spec/spec_helper.rb +2 -0
- metadata +34 -10
data/lib/crisp/parser.rb
CHANGED
@@ -1,11 +1,18 @@
|
|
1
1
|
module Crisp
|
2
|
+
# The Crisp parser
|
3
|
+
#
|
4
|
+
# The parser uses treetop for defining the syntax (see crisp.treetop)
|
5
|
+
# For parsing crisp code (as string) call parse on a instance.
|
6
|
+
# The output of the parser can be evaluated by passing it to a Crisp runtime.
|
2
7
|
class Parser
|
3
8
|
Treetop.load(File.expand_path(File.join(File.dirname(__FILE__), 'crisp.treetop')))
|
4
9
|
|
10
|
+
# create a new treetop parser
|
5
11
|
def initialize
|
6
12
|
@parser = CrispParser.new
|
7
13
|
end
|
8
14
|
|
15
|
+
# parse the code and return an abstract syntax tree (ast)
|
9
16
|
def parse(code)
|
10
17
|
ast = @parser.parse(code)
|
11
18
|
|
data/lib/crisp/runtime.rb
CHANGED
@@ -1,10 +1,17 @@
|
|
1
1
|
module Crisp
|
2
|
+
# The Crisp Runtime
|
3
|
+
#
|
4
|
+
# Each runtime holds its own environment.
|
5
|
+
# All predefined functions are loaded when creating a new runtime.
|
6
|
+
# Run code by calling run with the output of the parser as argurment.
|
2
7
|
class Runtime
|
8
|
+
# create a new env and load all functions when creating a new runtime
|
3
9
|
def initialize
|
4
10
|
@env = Env.new
|
5
11
|
Functions.load(@env)
|
6
12
|
end
|
7
13
|
|
14
|
+
# run the parsed code (abstract syntax tree)
|
8
15
|
def run(ast)
|
9
16
|
ast.eval(@env)
|
10
17
|
end
|
data/lib/crisp/shell.rb
CHANGED
@@ -1,67 +1,95 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe "
|
3
|
+
describe "arithemtic functions" do
|
4
4
|
include Crisp::SpecHelper
|
5
5
|
|
6
|
-
it "
|
6
|
+
it "adds integer values" do
|
7
7
|
evaluate("(+ 1 2)").should == 3
|
8
8
|
evaluate("(+ 12 13 1 5)").should == 31
|
9
9
|
evaluate("(+ 12 13 -1 -5)").should == 19
|
10
10
|
end
|
11
11
|
|
12
|
-
it "
|
12
|
+
it "substracts integer values" do
|
13
13
|
evaluate("(- 3 1)").should == 2
|
14
14
|
evaluate("(- 14 1 4 8)").should == 1
|
15
15
|
evaluate("(- 2 5)").should == -3
|
16
16
|
evaluate("(- 2 -5)").should == 7
|
17
17
|
end
|
18
18
|
|
19
|
-
it "
|
19
|
+
it "multiplies integer values" do
|
20
20
|
evaluate("(* 2 3)").should == 6
|
21
21
|
evaluate("(* 0 3)").should == 0
|
22
22
|
evaluate("(* 2 -3 1 10)").should == -60
|
23
23
|
end
|
24
24
|
|
25
|
-
it "
|
25
|
+
it "divides integer values" do
|
26
26
|
evaluate("(/ 12 3)").should == 4
|
27
27
|
evaluate("(/ 48 2 12)").should == 2
|
28
28
|
evaluate("(/ 30 -3 2)").should == -5
|
29
29
|
end
|
30
30
|
|
31
|
-
it "
|
31
|
+
it "calculates different nested operations" do
|
32
32
|
evaluate("(+ 1 2 (+ 2 3) (- 5 1)) ").should == 12
|
33
33
|
evaluate("(+ 1 2 (* 2 3) (/ 10 2)) ").should == 14
|
34
34
|
evaluate("(+ 1 2 (- 10 2 3) (* 1 2 3) (/ 12 4)) ").should == 17
|
35
35
|
evaluate("(/ 20 2 (+ 2 3) (- 5 3)) ").should == 1
|
36
36
|
end
|
37
37
|
|
38
|
-
it "
|
38
|
+
it "adds float values" do
|
39
39
|
evaluate("(+ 1.0 2.)").should == 3.0
|
40
40
|
evaluate("(+ 12.5 13 1.4 5)").should == 31.9
|
41
41
|
evaluate("(+ 12. 13.5 -1.9 -5.1)").should == 18.5
|
42
42
|
end
|
43
43
|
|
44
|
-
it "
|
44
|
+
it "substracts float values" do
|
45
45
|
evaluate("(- 3. 1.4)").should == 1.6
|
46
46
|
evaluate("(- 2 5.5)").should == -3.5
|
47
47
|
evaluate("(- 2.40 -5)").should == 7.4
|
48
48
|
end
|
49
49
|
|
50
|
-
it "
|
50
|
+
it "multiplies float values" do
|
51
51
|
evaluate("(* 2.1 3.4)").should == 7.14
|
52
52
|
evaluate("(* 0 3.5)").should == 0
|
53
53
|
evaluate("(* 2 -3.1 1.9 10.1)").should == -118.978
|
54
54
|
end
|
55
55
|
|
56
|
-
it "
|
56
|
+
it "divides float values" do
|
57
57
|
evaluate("(/ 12.5 3.1)").should be_within(0.0000001).of(4.03225806451613)
|
58
58
|
evaluate("(/ 48 2.000 12.5)").should == 1.92
|
59
59
|
evaluate("(/ 30.0 -3 2.5)").should == -4.0
|
60
60
|
end
|
61
61
|
|
62
|
-
it "
|
62
|
+
it "calculates different nested operations" do
|
63
63
|
evaluate("(+ 1 2.3 (* 2 3.5) (/ 10 4)) ").should == 12.3
|
64
64
|
evaluate("(+ 1.5 2 (- 10 2.4 3) (* 1.0 2 3) (/ 12 3.0)) ").should == 18.1
|
65
65
|
evaluate("(/ 20.4 2 (+ 2.5 3) (- 5.5 3)) ").should be_within(0.000000001).of(0.741818181818182)
|
66
66
|
end
|
67
|
+
|
68
|
+
it "compares values for equality" do
|
69
|
+
evaluate("(= 1 1)").should == true
|
70
|
+
evaluate("(= 1 2)").should == false
|
71
|
+
end
|
72
|
+
|
73
|
+
it "compares values that are greater than others" do
|
74
|
+
evaluate("(> 2 1)").should == true
|
75
|
+
evaluate("(> 1 1)").should == false
|
76
|
+
evaluate("(> 1 2)").should == false
|
77
|
+
end
|
78
|
+
|
79
|
+
it "compares values that are less than others" do
|
80
|
+
evaluate("(< 2 1)").should == false
|
81
|
+
evaluate("(< 1 1)").should == false
|
82
|
+
evaluate("(< 1 2)").should == true
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should calc fibonacci numbers" do
|
86
|
+
evaluate("
|
87
|
+
(def fib (
|
88
|
+
fn [n]
|
89
|
+
(if (< n 2)
|
90
|
+
n
|
91
|
+
(+ (fib (- n 1)) (fib (- n 2))))))
|
92
|
+
(fib 10)
|
93
|
+
").should == 55
|
94
|
+
end
|
67
95
|
end
|
data/spec/crisp/basic_spec.rb
CHANGED
@@ -3,90 +3,25 @@ require 'spec_helper'
|
|
3
3
|
describe "the language" do
|
4
4
|
include Crisp::SpecHelper
|
5
5
|
|
6
|
-
it "
|
6
|
+
it "does not bother whitspaces characters in expressions" do
|
7
7
|
evaluate(" \r\t\n (\r+\t 1\n2 \t(\n - 9\r\t \n 2)\r)\r\t ").should == 10
|
8
8
|
end
|
9
9
|
|
10
|
-
it "
|
11
|
-
evaluate("(println (+ 1 1))")
|
12
|
-
end
|
13
|
-
|
14
|
-
it "should bind value to symbol" do
|
15
|
-
evaluate("(def bla 3)")
|
16
|
-
end
|
17
|
-
|
18
|
-
it "should not bind value to already binded symbol" do
|
19
|
-
lambda do
|
20
|
-
evaluate("(def name 123)(def name 123)")
|
21
|
-
end.should raise_error(Crisp::EnvironmentError, "name already binded")
|
22
|
-
end
|
23
|
-
|
24
|
-
it "should raise error if use def with wrong args" do
|
25
|
-
lambda do
|
26
|
-
evaluate("(def bla 1 2)")
|
27
|
-
end.should raise_error(Crisp::ArgumentError, "wrong number of arguments for 'def' (3 for 2)")
|
28
|
-
end
|
29
|
-
|
30
|
-
it "should evaluate a list of operations" do
|
31
|
-
lambda do
|
32
|
-
evaluate("(+ 1 2) (def foo 1) (def foo 2)")
|
33
|
-
end.should raise_error(Crisp::EnvironmentError, "foo already binded")
|
34
|
-
end
|
35
|
-
|
36
|
-
it "should produce syntax error on invalid syntax" do
|
10
|
+
it "creates syntax error on invalid expressions" do
|
37
11
|
lambda do
|
38
12
|
evaluate("(()")
|
39
13
|
end.should raise_error(Crisp::SyntaxError, "syntax error at : 0")
|
40
14
|
end
|
41
15
|
|
42
|
-
it "
|
43
|
-
evaluate("(def bla 2) (* 4 bla)").should == 8
|
44
|
-
evaluate("(def bla (* 2 3)) (* 4 bla 2)").should == 48
|
45
|
-
end
|
46
|
-
|
47
|
-
it "should parse an array" do
|
16
|
+
it "bind arrays to symbols" do
|
48
17
|
evaluate("(def bla [1 2 3])").size.should == 3
|
49
18
|
evaluate("(def bla [1 2 3])")[1].should == 2
|
50
|
-
evaluate("(def bla [1 2 foo])")[2].should ==
|
19
|
+
evaluate("(def foo 5)(def bla [1 2 foo])")[2].should == 5
|
51
20
|
end
|
52
21
|
|
53
|
-
it "
|
22
|
+
it "does not evaluate numbers" do
|
54
23
|
lambda do
|
55
|
-
evaluate("(
|
56
|
-
end.should raise_error(Crisp::
|
57
|
-
end
|
58
|
-
|
59
|
-
it "should raise error if not providing proper argument list for function generation" do
|
60
|
-
lambda do
|
61
|
-
evaluate("(fn (+ 2 1) (+ 1 2))")
|
62
|
-
end.should raise_error(Crisp::ArgumentError, "no parameter list defined")
|
63
|
-
end
|
64
|
-
|
65
|
-
it "should raise error if not providing proper function body" do
|
66
|
-
lambda do
|
67
|
-
evaluate("(fn [] [])")
|
68
|
-
end.should raise_error(Crisp::ArgumentError, "no function body defined")
|
69
|
-
end
|
70
|
-
|
71
|
-
it "should create functions" do
|
72
|
-
evaluate("(fn [arg] (+ 2 arg))")
|
73
|
-
end
|
74
|
-
|
75
|
-
it "should bind functions to symbols" do
|
76
|
-
evaluate("(def myfn (fn [arg] (+ 1 arg)))").class.name.should == "Crisp::Function"
|
77
|
-
end
|
78
|
-
|
79
|
-
it "should call functions" do
|
80
|
-
evaluate("(def myfn (fn [a b] (+ 1 1)))(myfn 1 2)").should == 2
|
81
|
-
end
|
82
|
-
|
83
|
-
it "should use parameters when calling functions" do
|
84
|
-
evaluate("(def myfn (fn [a b] (+ a b)))(myfn 5 2)").should == 7
|
85
|
-
end
|
86
|
-
|
87
|
-
it "should raise error on wrong amount of parameters" do
|
88
|
-
lambda do
|
89
|
-
evaluate("(def myfn (fn [a1 a2 a3] (+ 1 1)))(myfn 1)")
|
90
|
-
end.should raise_error(Crisp::ArgumentError, "wrong number of arguments for 'myfn' (1 for 3)")
|
24
|
+
evaluate("(3)")
|
25
|
+
end.should raise_error(Crisp::SyntaxError, "syntax error at : 0")
|
91
26
|
end
|
92
27
|
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "the core language features" do
|
4
|
+
include Crisp::SpecHelper
|
5
|
+
|
6
|
+
it "prints results" do
|
7
|
+
evaluate("(println (+ 1 1))")
|
8
|
+
end
|
9
|
+
|
10
|
+
it "binds values to symbols" do
|
11
|
+
evaluate("(def bla 3)")
|
12
|
+
end
|
13
|
+
|
14
|
+
it "does not allow to bind symbols twice" do
|
15
|
+
lambda do
|
16
|
+
evaluate("(def name 123)(def name 123)")
|
17
|
+
end.should raise_error(Crisp::EnvironmentError, "name already binded")
|
18
|
+
end
|
19
|
+
|
20
|
+
it "produces an error when calling bind function with arguments" do
|
21
|
+
lambda do
|
22
|
+
evaluate("(def bla 1 2)")
|
23
|
+
end.should raise_error(Crisp::ArgumentError, "wrong number of arguments for 'def' (3 for 2)")
|
24
|
+
end
|
25
|
+
|
26
|
+
it "uses binded values in later expressions" do
|
27
|
+
evaluate("(def bla 2) (* 4 bla)").should == 8
|
28
|
+
evaluate("(def bla (* 2 3)) (* 4 bla 2)").should == 48
|
29
|
+
end
|
30
|
+
|
31
|
+
it "can not resolve unbound symbols" do
|
32
|
+
lambda do
|
33
|
+
evaluate("n")
|
34
|
+
end.should raise_error(Crisp::EnvironmentError, "n is unbound")
|
35
|
+
end
|
36
|
+
|
37
|
+
it "can resolve primitve types" do
|
38
|
+
evaluate("2").should == 2
|
39
|
+
evaluate("2.1").should == 2.1
|
40
|
+
evaluate("nil").should == nil
|
41
|
+
evaluate("true").should == true
|
42
|
+
evaluate("false").should == false
|
43
|
+
evaluate('"abc"').should == 'abc'
|
44
|
+
evaluate("[1 2 3]").should == [1, 2, 3]
|
45
|
+
end
|
46
|
+
|
47
|
+
it "can resolve bounded symbols" do
|
48
|
+
evaluate("(def foo 23) foo").should == 23
|
49
|
+
evaluate('(def foo "foo") foo').should == 'foo'
|
50
|
+
end
|
51
|
+
|
52
|
+
it "binds symbols to value of already bound symbol" do
|
53
|
+
evaluate("(def a 1)
|
54
|
+
(def b a)
|
55
|
+
b").should == 1
|
56
|
+
end
|
57
|
+
|
58
|
+
it "has if statements" do
|
59
|
+
evaluate("(if true 1)").should == 1
|
60
|
+
evaluate("(if false 1)").should == nil
|
61
|
+
evaluate("(if nil 1)").should == nil
|
62
|
+
end
|
63
|
+
|
64
|
+
it "has if else statements" do
|
65
|
+
evaluate("(if true 1 2)").should == 1
|
66
|
+
evaluate("(if false 1 2)").should == 2
|
67
|
+
evaluate("(if nil 1 2)").should == 2
|
68
|
+
end
|
69
|
+
|
70
|
+
it "does not run if statements with wrong number of arguments" do
|
71
|
+
lambda do
|
72
|
+
evaluate("(if true true false 2)")
|
73
|
+
end.should raise_error(Crisp::ArgumentError, "wrong number of arguments for 'if' (4 for 2..3)")
|
74
|
+
|
75
|
+
lambda do
|
76
|
+
evaluate("(if true)")
|
77
|
+
end.should raise_error(Crisp::ArgumentError, "wrong number of arguments for 'if' (1 for 2..3)")
|
78
|
+
end
|
79
|
+
|
80
|
+
it "resolves symbols in if statements" do
|
81
|
+
evaluate("(def foo 2)(if true foo)").should == 2
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "the languages functions" do
|
4
|
+
include Crisp::SpecHelper
|
5
|
+
|
6
|
+
it "does not create a function when providing wrong number of arguments" do
|
7
|
+
lambda do
|
8
|
+
evaluate("(fn [] (+ 1 2) [])")
|
9
|
+
end.should raise_error(Crisp::ArgumentError, "wrong number of arguments for 'fn' (3 for 2)")
|
10
|
+
end
|
11
|
+
|
12
|
+
it "does not create a function when not providing a proper argument list" do
|
13
|
+
lambda do
|
14
|
+
evaluate("(fn (+ 2 1) (+ 1 2))")
|
15
|
+
end.should raise_error(Crisp::ArgumentError, "no argument list defined")
|
16
|
+
end
|
17
|
+
|
18
|
+
it "does not create a function when not providing a proper function body" do
|
19
|
+
lambda do
|
20
|
+
evaluate("(fn [] [])")
|
21
|
+
end.should raise_error(Crisp::ArgumentError, "no function body defined")
|
22
|
+
end
|
23
|
+
|
24
|
+
it "creates functions" do
|
25
|
+
evaluate("(fn [arg] (+ 2 arg))")
|
26
|
+
end
|
27
|
+
|
28
|
+
it "binds functions to symbols" do
|
29
|
+
evaluate("(def myfn (fn [arg] (+ 1 arg)))").class.name.should == "Crisp::Function"
|
30
|
+
end
|
31
|
+
|
32
|
+
it "calls functions" do
|
33
|
+
evaluate("(def myfn (fn [a b] (+ 1 1)))(myfn 1 2)").should == 2
|
34
|
+
end
|
35
|
+
|
36
|
+
it "calls functions with arguments" do
|
37
|
+
evaluate("(def myfn (fn [a b] (+ a b)))(myfn 5 2)").should == 7
|
38
|
+
end
|
39
|
+
|
40
|
+
it "does not calls functions with wrong number of arguments" do
|
41
|
+
lambda do
|
42
|
+
evaluate("(def myfn (fn [a1 a2 a3] (+ 1 1)))(myfn 1)")
|
43
|
+
end.should raise_error(Crisp::ArgumentError, "wrong number of arguments for 'myfn' (1 for 3)")
|
44
|
+
end
|
45
|
+
|
46
|
+
it "instantly evaluates a function" do
|
47
|
+
evaluate("((fn [] (+ 1 2)))").should == 3
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "internals" do
|
4
|
+
include Crisp::SpecHelper
|
5
|
+
|
6
|
+
it "sets and gets values to and from the env" do
|
7
|
+
env = Crisp::Env.new
|
8
|
+
env['key'] = 'value'
|
9
|
+
|
10
|
+
env['key'].should == 'value'
|
11
|
+
env[:key].should == 'value'
|
12
|
+
env.has_key?('key').should == true
|
13
|
+
env.has_key?(:key).should == true
|
14
|
+
end
|
15
|
+
|
16
|
+
it "handles mismatched env reads" do
|
17
|
+
env = Crisp::Env.new
|
18
|
+
|
19
|
+
env.has_key?('not_exists').should == false
|
20
|
+
env['not_exists'].should == nil
|
21
|
+
end
|
22
|
+
|
23
|
+
it "is not allowed to set env keys twice" do
|
24
|
+
env = Crisp::Env.new
|
25
|
+
env['key'] = 'value'
|
26
|
+
|
27
|
+
lambda do
|
28
|
+
env['key'] = 'another value'
|
29
|
+
end.should raise_error(Crisp::EnvironmentError, "key already binded")
|
30
|
+
end
|
31
|
+
|
32
|
+
it "gets values from a chained env" do
|
33
|
+
env1 = Crisp::Env.new
|
34
|
+
env1[:key1] = 'val1'
|
35
|
+
env1[:key2] = 'val2'
|
36
|
+
|
37
|
+
env2 = Crisp::Env.new
|
38
|
+
env2[:key1] = 'val3'
|
39
|
+
env2[:key3] = 'val4'
|
40
|
+
|
41
|
+
chained = Crisp::ChainedEnv.new(env1, env2)
|
42
|
+
|
43
|
+
chained[:key1].should == 'val1'
|
44
|
+
chained[:key2].should == 'val2'
|
45
|
+
chained[:key3].should == 'val4'
|
46
|
+
end
|
47
|
+
|
48
|
+
it "sets values to a chained env" do
|
49
|
+
env1 = Crisp::Env.new
|
50
|
+
env1[:key] = 'val'
|
51
|
+
|
52
|
+
env2 = Crisp::Env.new
|
53
|
+
|
54
|
+
chained = Crisp::ChainedEnv.new(env1, env2)
|
55
|
+
|
56
|
+
chained[:key] = 'other'
|
57
|
+
env2[:key].should == 'other'
|
58
|
+
chained[:key].should == 'val'
|
59
|
+
end
|
60
|
+
end
|