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/functions/core.rb
CHANGED
@@ -1,51 +1,113 @@
|
|
1
1
|
module Crisp
|
2
2
|
module Functions
|
3
|
+
# Defining core crisp functions
|
3
4
|
class Core
|
4
|
-
|
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
|
8
|
-
end.bind('println',
|
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
|
-
|
31
|
+
validate_args_count(2, args.size)
|
12
32
|
|
13
|
-
|
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(
|
37
|
+
value.bind(key, env)
|
17
38
|
else
|
18
|
-
env[
|
39
|
+
env[key] = value
|
19
40
|
end
|
20
|
-
end.bind('def',
|
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
|
-
|
51
|
+
validate_args_count(2, args.size)
|
24
52
|
|
25
|
-
if
|
26
|
-
raise ArgumentError, "no
|
53
|
+
if args[0].class.name != "Crisp::Nodes::ArrayLiteral"
|
54
|
+
raise ArgumentError, "no argument list defined"
|
27
55
|
end
|
28
56
|
|
29
|
-
if
|
57
|
+
if args[1].class.name != "Crisp::Nodes::Operation"
|
30
58
|
raise ArgumentError, "no function body defined"
|
31
59
|
end
|
32
60
|
|
33
|
-
|
34
|
-
fn_operation =
|
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
|
-
|
66
|
+
validate_args_count(fn_arg_list.size, args.size)
|
38
67
|
|
39
68
|
local_env = Env.new
|
40
|
-
|
41
|
-
local_env[key.
|
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',
|
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
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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,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,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
|