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
@@ -1,51 +1,113 @@
1
1
  module Crisp
2
2
  module Functions
3
+ # Defining core crisp functions
3
4
  class Core
4
- def self.load(env)
5
+ # load the functions and bind them into the given environment
6
+ def self.load(current_env)
5
7
 
8
+ # reserve nil/true/false in environment
9
+ current_env['nil'] = nil
10
+ current_env['true'] = true
11
+ current_env['false'] = false
12
+
13
+ # println
14
+ # print arguments (seperated by whitspace) including a newline to the standard output
15
+ #
16
+ # (println 123)
17
+ # 123
6
18
  Function.new do
7
- print params_evaled.collect(&:to_s).join(' ') + "\n"
8
- end.bind('println', env)
19
+ print args_evaled.collect(&:to_s).join(' ') + "\n"
20
+ end.bind('println', current_env)
9
21
 
22
+ # def
23
+ # bind the second argument to the symbol name of the first argument
24
+ # actually a key/value pair will be stored in the environment
25
+ #
26
+ # (def foo 1)
27
+ # => foo
28
+ # (println foo)
29
+ # 1
10
30
  Function.new do
11
- validate_params_count(2, params.size)
31
+ validate_args_count(2, args.size)
12
32
 
13
- value = params_evaled[1]
33
+ key = args[0].text_value
34
+ value = args[1].resolve_and_eval(env)
14
35
 
15
36
  if value.class.name == "Crisp::Function"
16
- value.bind(params_values[0], env)
37
+ value.bind(key, env)
17
38
  else
18
- env[params_values[0]] = value
39
+ env[key] = value
19
40
  end
20
- end.bind('def', env)
41
+ end.bind('def', current_env)
21
42
 
43
+ # fn
44
+ # creates a function
45
+ # the first argument has to be an array containing the argumentlist
46
+ # the second argument is the function body
47
+ #
48
+ # (fn [a b] (+ a b)
49
+ # => ...)
22
50
  Function.new do
23
- validate_params_count(2, params.size)
51
+ validate_args_count(2, args.size)
24
52
 
25
- if params[0].class.name != "Crisp::ArrayLiteral"
26
- raise ArgumentError, "no parameter list defined"
53
+ if args[0].class.name != "Crisp::Nodes::ArrayLiteral"
54
+ raise ArgumentError, "no argument list defined"
27
55
  end
28
56
 
29
- if params[1].class.name != "Crisp::Operation"
57
+ if args[1].class.name != "Crisp::Nodes::Operation"
30
58
  raise ArgumentError, "no function body defined"
31
59
  end
32
60
 
33
- fn_param_list = params[0].raw_elements
34
- fn_operation = params[1]
61
+ fn_arg_list = args[0].raw_elements
62
+ fn_operation = args[1]
35
63
 
64
+ # create and return the new function
36
65
  Function.new do
37
- validate_params_count(fn_param_list.size, params.size)
66
+ validate_args_count(fn_arg_list.size, args.size)
38
67
 
39
68
  local_env = Env.new
40
- fn_param_list.each_with_index do |key, idx|
41
- local_env[key.eval(env)] = params[idx]
69
+ fn_arg_list.each_with_index do |key, idx|
70
+ local_env[key.text_value] = args[idx].resolve_and_eval(env)
42
71
  end
43
72
 
44
73
  chained_env = ChainedEnv.new(local_env, env)
45
74
 
46
75
  fn_operation.eval(chained_env)
47
76
  end
48
- end.bind('fn', env)
77
+ end.bind('fn', current_env)
78
+
79
+ # if
80
+ # the if function evaluates the second argument if the condition (first argument) returns not nil or false.
81
+ # Otherwise the third argument will be evaluated (optional)
82
+ #
83
+ # (if (= 1 2) 1 2)
84
+ # => 2
85
+ Function.new do
86
+ validate_args_count((2..3), args.size)
87
+
88
+ result = args[0].resolve_and_eval(env)
89
+
90
+ res = if ![nil, false].include?(result)
91
+ args[1]
92
+ elsif args[2]
93
+ args[2]
94
+ end
95
+
96
+ res ? res.resolve_and_eval(env) : res
97
+ end.bind('if', current_env)
98
+
99
+ # .
100
+ # perform a native call on an object with optional arguments
101
+ #
102
+ # (. upcase "abc")
103
+ # => "ABC"
104
+ Function.new do
105
+ meth = args[0].text_value.to_sym
106
+ target = args[1].resolve_and_eval(env)
107
+ values = args[2..-1].map { |arg| arg.resolve_and_eval(env) }
108
+
109
+ NativeCallInvoker.new(target, meth, values).invoke!
110
+ end.bind('.', current_env)
49
111
 
50
112
  end
51
113
  end
@@ -0,0 +1,16 @@
1
+ module Crisp
2
+ # class for handling native ruby method invokations
3
+ class NativeCallInvoker
4
+ # create instance with target object, method to be called and arguments
5
+ def initialize(target, method, *args)
6
+ @target = target
7
+ @method = method
8
+ @args = args.flatten
9
+ end
10
+
11
+ # invoke the native ruby call
12
+ def invoke!
13
+ @target.send(@method, *@args)
14
+ end
15
+ end
16
+ end
data/lib/crisp/nodes.rb CHANGED
@@ -1,62 +1,33 @@
1
1
  module Crisp
2
- class Base < Treetop::Runtime::SyntaxNode
3
- def eval(env)
4
- nil
5
- end
6
- end
7
-
8
- class Operation < Base
9
- def eval(env)
10
- env[self.func_identifier.text_value].eval(env, self.element_list.elements.collect(&:element))
11
- end
12
- end
13
-
14
- class Block < Base
15
- def eval(env)
16
- last_result = nil
17
-
18
- elements.each do |op|
19
- last_result = op.eval(env)
20
- end
21
-
22
- last_result
23
- end
24
- end
25
-
26
- class ArrayLiteral < Base
27
- def eval(env)
28
- raw_elements.map { |e| e.eval(env) }
29
- end
30
-
31
- def raw_elements
32
- self.element_list.elements.collect(&:element)
33
- end
34
- end
35
-
36
- class Primitive < Base
37
- end
38
-
39
- class Number < Primitive
40
- def eval(env)
41
- text_value.to_i
42
- end
43
- end
44
-
45
- class Float < Primitive
46
- def eval(env)
47
- text_value.to_f
48
- end
49
- end
50
-
51
- class StringLiteral < Primitive
52
- def eval(env)
53
- text_value[1..-2]
54
- end
55
- end
56
-
57
- class Symbol < Primitive
58
- def eval(env)
59
- text_value.to_sym
60
- end
2
+ module Nodes
3
+ # require all used node types
4
+ #
5
+ # hierarchy:
6
+ #
7
+ # Base
8
+ # + Operation
9
+ # + Block
10
+ # + Array
11
+ # + Primitive
12
+ # + Integer
13
+ # + Float
14
+ # + String
15
+ # + Symbol
16
+ # + True
17
+ # + False
18
+ # + Nil
19
+
20
+ require 'crisp/nodes/base'
21
+ require 'crisp/nodes/operation'
22
+ require 'crisp/nodes/block'
23
+ require 'crisp/nodes/array_literal'
24
+ require 'crisp/nodes/primitive'
25
+ require 'crisp/nodes/integer_literal'
26
+ require 'crisp/nodes/float_literal'
27
+ require 'crisp/nodes/string_literal'
28
+ require 'crisp/nodes/symbol_literal'
29
+ require 'crisp/nodes/true_literal'
30
+ require 'crisp/nodes/false_literal'
31
+ require 'crisp/nodes/nil_literal'
61
32
  end
62
33
  end
@@ -0,0 +1,21 @@
1
+ module Crisp
2
+ module Nodes
3
+ # The array node
4
+ class ArrayLiteral < Base
5
+ # eval each array element and return it as array
6
+ def eval(env)
7
+ raw_elements.map { |e| e.eval(env) }
8
+ end
9
+
10
+ # an array resolves to its raw elements
11
+ def resolve(env)
12
+ raw_elements.map { |e| e.resolve(env) }
13
+ end
14
+
15
+ # return an array with the raw parsed array elements
16
+ def raw_elements
17
+ self.element_list.elements.collect(&:element)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,22 @@
1
+ module Crisp
2
+ module Nodes
3
+ # The base node
4
+ class Base < Treetop::Runtime::SyntaxNode
5
+ # this is only an abstract method
6
+ def eval(env)
7
+ raise "abstract method!"
8
+ end
9
+
10
+ # this is only an abstract method
11
+ def resolve(env)
12
+ raise "abstract method!"
13
+ end
14
+
15
+ # resolves the node and evals it it is a crisp operation node
16
+ def resolve_and_eval(env)
17
+ res = self.resolve(env)
18
+ res.class.name == "Crisp::Nodes::Operation" ? res.eval(env) : res
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ module Crisp
2
+ module Nodes
3
+ # The block node
4
+ class Block < Base
5
+ # eval each element of the block and return the last result
6
+ def eval(env)
7
+ last_result = nil
8
+
9
+ elements.each do |op|
10
+ last_result = op.resolve_and_eval(env)
11
+ end
12
+
13
+ last_result
14
+ end
15
+
16
+ # a block resolves to itself
17
+ def resolve(env)
18
+ self
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,11 @@
1
+ module Crisp
2
+ module Nodes
3
+ # The false node
4
+ class FalseLiteral < Primitive
5
+ # yes it resolves to false
6
+ def resolve(env)
7
+ false
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Crisp
2
+ module Nodes
3
+ # The float node
4
+ class FloatLiteral < Primitive
5
+ # returns float value
6
+ def resolve(env)
7
+ text_value.to_f
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Crisp
2
+ module Nodes
3
+ # The integer node
4
+ class IntegerLiteral < Primitive
5
+ # returns integer value
6
+ def resolve(env)
7
+ text_value.to_i
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Crisp
2
+ module Nodes
3
+ # The nil node
4
+ class NilLiteral < Primitive
5
+ # yes it resolves to nil
6
+ def resolve(env)
7
+ nil
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,20 @@
1
+ module Crisp
2
+ module Nodes
3
+ # The operation node
4
+ class Operation < Base
5
+ # get the function binded to the given function name and eval it (including arguments)
6
+ def eval(env)
7
+ if self.func_identifier.class.name == "Crisp::Nodes::Operation"
8
+ self.func_identifier.eval(env).eval(env, self.element_list.elements.collect(&:element))
9
+ else
10
+ env[self.func_identifier.text_value].eval(env, self.element_list.elements.collect(&:element))
11
+ end
12
+ end
13
+
14
+ # an operation resolves to itself
15
+ def resolve(env)
16
+ self
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,16 @@
1
+ module Crisp
2
+ module Nodes
3
+ # The primitive node
4
+ class Primitive < Base
5
+ # raise error if trying to eval a primitve
6
+ def eval(env)
7
+ raise "cannot eval primitive"
8
+ end
9
+
10
+ # abstract method
11
+ def resolve(env)
12
+ raise "abstract method!"
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ module Crisp
2
+ module Nodes
3
+ # The string node
4
+ class StringLiteral < Primitive
5
+ # return a string
6
+ def resolve(env)
7
+ text_value[1..-2]
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ module Crisp
2
+ module Nodes
3
+ # The symbol node
4
+ class SymbolLiteral < Primitive
5
+ # return the value for the key in the env the symbol stays for
6
+ def resolve(env)
7
+ if !env.has_key?(text_value)
8
+ raise Crisp::EnvironmentError, "#{text_value} is unbound"
9
+ end
10
+
11
+ env[text_value]
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ module Crisp
2
+ module Nodes
3
+ # The true node
4
+ class TrueLiteral < Primitive
5
+ # yes it resolves to true
6
+ def resolve(env)
7
+ true
8
+ end
9
+ end
10
+ end
11
+ end