crisp 0.0.5 → 0.0.7
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/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
|