crisp 0.0.5 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/CHANGELOG.md +10 -2
  2. data/README.md +2 -1
  3. data/Rakefile +2 -2
  4. data/autotest/discover.rb +1 -0
  5. data/crisp.gemspec +32 -8
  6. data/examples/fibonacci.crisp +6 -0
  7. data/lib/crisp.rb +3 -1
  8. data/lib/crisp/chained_env.rb +13 -0
  9. data/lib/crisp/crisp.treetop +23 -15
  10. data/lib/crisp/env.rb +7 -0
  11. data/lib/crisp/function.rb +6 -42
  12. data/lib/crisp/function_runner.rb +37 -0
  13. data/lib/crisp/functions.rb +5 -3
  14. data/lib/crisp/functions/arithmetic.rb +64 -9
  15. data/lib/crisp/functions/core.rb +80 -18
  16. data/lib/crisp/native_call_invoker.rb +16 -0
  17. data/lib/crisp/nodes.rb +30 -59
  18. data/lib/crisp/nodes/array_literal.rb +21 -0
  19. data/lib/crisp/nodes/base.rb +22 -0
  20. data/lib/crisp/nodes/block.rb +22 -0
  21. data/lib/crisp/nodes/false_literal.rb +11 -0
  22. data/lib/crisp/nodes/float_literal.rb +11 -0
  23. data/lib/crisp/nodes/integer_literal.rb +11 -0
  24. data/lib/crisp/nodes/nil_literal.rb +11 -0
  25. data/lib/crisp/nodes/operation.rb +20 -0
  26. data/lib/crisp/nodes/primitive.rb +16 -0
  27. data/lib/crisp/nodes/string_literal.rb +11 -0
  28. data/lib/crisp/nodes/symbol_literal.rb +15 -0
  29. data/lib/crisp/nodes/true_literal.rb +11 -0
  30. data/lib/crisp/parser.rb +7 -0
  31. data/lib/crisp/runtime.rb +7 -0
  32. data/lib/crisp/shell.rb +4 -0
  33. data/spec/crisp/arithmetics_spec.rb +39 -11
  34. data/spec/crisp/basic_spec.rb +7 -72
  35. data/spec/crisp/core_spec.rb +83 -0
  36. data/spec/crisp/function_spec.rb +49 -0
  37. data/spec/crisp/internal_spec.rb +60 -0
  38. data/spec/crisp/native_call_invoker_spec.rb +23 -0
  39. data/spec/crisp/string_spec.rb +1 -1
  40. data/spec/spec_helper.rb +2 -0
  41. 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,7 +1,11 @@
1
1
  module Crisp
2
+ # The Crisp interactive shell
3
+ #
4
+ # Create a new instance and call run for having a interactive Crisp shell.
2
5
  class Shell
3
6
  require 'readline'
4
7
 
8
+ # start the shell
5
9
  def run
6
10
  runtime = Runtime.new
7
11
  buffer = ''
@@ -1,67 +1,95 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe "when evaluating arithmetic expressions, the language" do
3
+ describe "arithemtic functions" do
4
4
  include Crisp::SpecHelper
5
5
 
6
- it "should calculate integer addition" do
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 "should calculate integer substraction" do
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 "should calculate integer multiplications" do
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 "should calculate integer divisions" do
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 "should calculate nested arithmetic integer expressions" do
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 "should calculate float addition" do
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 "should calculate float substraction" do
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 "should calculate float multiplications" do
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 "should calculate float divisions" do
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 "should calculate nested arithmetic float expressions" do
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
@@ -3,90 +3,25 @@ require 'spec_helper'
3
3
  describe "the language" do
4
4
  include Crisp::SpecHelper
5
5
 
6
- it "should not bother whitespaces, tabs and linebreaks in statements" do
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 "should print results" do
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 "should use values of binded symbols in later statements" do
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 == :foo
19
+ evaluate("(def foo 5)(def bla [1 2 foo])")[2].should == 5
51
20
  end
52
21
 
53
- it "should raise error if not providing right amount of parameters for function creation" do
22
+ it "does not evaluate numbers" do
54
23
  lambda do
55
- evaluate("(fn [] (+ 1 2) [])")
56
- end.should raise_error(Crisp::ArgumentError, "wrong number of arguments for 'fn' (3 for 2)")
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