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/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
|