concatenative 0.1.0 → 0.2.0
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.rdoc +16 -0
- data/README.rdoc +5 -5
- data/examples/benchmarks.rb +5 -5
- data/examples/concatenative_cli.rb +2 -2
- data/lib/concatenative.rb +84 -20
- data/lib/concatenative/kernel.rb +419 -0
- data/lib/concatenative/system_extensions.rb +56 -18
- data/spec/concatenative_spec.rb +22 -4
- data/spec/kernel_spec.rb +149 -0
- data/spec/system_extensions_spec.rb +24 -14
- metadata +6 -7
- data/lib/concatenative/definitions.rb +0 -12
- data/lib/concatenative/system.rb +0 -334
- data/spec/definitions_spec.rb +0 -44
- data/spec/system_spec.rb +0 -107
data/CHANGELOG.rdoc
CHANGED
@@ -1,3 +1,19 @@
|
|
1
|
+
== Version 0.2.0
|
2
|
+
|
3
|
+
* Implemented new combinators:
|
4
|
+
* :binrec
|
5
|
+
* :split
|
6
|
+
* :twodip
|
7
|
+
* :threedip
|
8
|
+
* Moved combinators to kernel.rb.
|
9
|
+
* Removed definitions.rb (all words are now defined in kernel.rb).
|
10
|
+
* Performance improvement: stack is never copied.
|
11
|
+
* Performance improvement: no symbol/string conversion when processing words.
|
12
|
+
* Added ~ operator (call, unquote) for Symbols and Arrays. Removed Array#unquote and Symbol#unquote
|
13
|
+
* Added Symbol#<= to define words and Symbol#/ to concatenate them.
|
14
|
+
* Added Symbol#/ to concatenate symbols and simulate namespaces.
|
15
|
+
* Removed Symbol#define (use Symbol#<= instead)
|
16
|
+
|
1
17
|
== Version 0.1.0
|
2
18
|
|
3
19
|
First preview release of Concatenative, implementing all the most basic operators and
|
data/README.rdoc
CHANGED
@@ -22,16 +22,16 @@ Execute a Concatenative program:
|
|
22
22
|
10,
|
23
23
|
[0, :==],
|
24
24
|
[1, :+],
|
25
|
-
[:
|
25
|
+
[:dup, 1, :-],
|
26
26
|
[:*],
|
27
|
-
:
|
27
|
+
:linrec
|
28
28
|
)
|
29
29
|
|
30
|
-
The program above returns the factorial of 10, computed using the linrec combinator. It is also possible to execute arrays directly and define concatenative programs as symbols
|
30
|
+
The program above returns the factorial of 10, computed using the linrec combinator. It is also possible to execute arrays directly and define concatenative programs as symbols, as folows:
|
31
31
|
|
32
32
|
|
33
|
-
:
|
34
|
-
[5, :
|
33
|
+
:factorial <= [0, :==], [:pop, 1], [:dup, 1, :- , :factorial, :*], :if
|
34
|
+
[5, :factorial].execute
|
35
35
|
|
36
36
|
The program above calculates the factorial of 5, using explicit recursion.
|
37
37
|
|
data/examples/benchmarks.rb
CHANGED
@@ -4,7 +4,7 @@ require 'benchmark'
|
|
4
4
|
dir = File.dirname(File.expand_path(__FILE__))+'/../lib/'
|
5
5
|
require dir+"concatenative"
|
6
6
|
|
7
|
-
n =
|
7
|
+
n = 3_000
|
8
8
|
|
9
9
|
def factorial(n)
|
10
10
|
(n == 0) ? 1 : factorial(n-1)
|
@@ -22,9 +22,9 @@ puts "=====> Factorial of #{n}"
|
|
22
22
|
puts "======================================================================"
|
23
23
|
Benchmark.bmbm(20) do |x|
|
24
24
|
x.report("Standard Ruby Code:") { factorial n }
|
25
|
-
x.report("Concatenative (times):") { concatenate(n, 1, 1, :
|
26
|
-
x.report("Concatenative (linrec):") { concatenate(n, [0, :==], [1, :+], [:
|
27
|
-
x.report("Concatenative (primrec):") { concatenate(n, [1], [:*], :
|
25
|
+
x.report("Concatenative (times):") { concatenate(n, 1, 1, :rolldown, [:dup, [:*], :dip, :succ], :times, :pop) }
|
26
|
+
x.report("Concatenative (linrec):") { concatenate(n, [0, :==], [1, :+], [:dup, 1, :-], [:*], :linrec) }
|
27
|
+
x.report("Concatenative (primrec):") { concatenate(n, [1], [:*], :primrec) }
|
28
28
|
end
|
29
29
|
puts
|
30
30
|
puts
|
@@ -33,6 +33,6 @@ puts "=====> Fibonacci number for #{n}"
|
|
33
33
|
puts "======================================================================"
|
34
34
|
Benchmark.bmbm(20) do |x|
|
35
35
|
x.report("Standard Ruby Code:") { fibonacci n }
|
36
|
-
x.report("Concatenative (times):") { concatenate(n, 0, 1, :
|
36
|
+
x.report("Concatenative (times):") { concatenate(n, 0, 1, :rolldown, [:dup, [:+], :dip, :swap], :times, :pop) }
|
37
37
|
end
|
38
38
|
|
@@ -11,7 +11,7 @@ puts " ========================================="
|
|
11
11
|
loop do
|
12
12
|
print " => "
|
13
13
|
begin
|
14
|
-
Concatenative
|
14
|
+
Concatenative.process(instance_eval(gets))
|
15
15
|
rescue Exception => e
|
16
16
|
if e.is_a? SystemExit then
|
17
17
|
puts " Exiting."
|
@@ -21,5 +21,5 @@ loop do
|
|
21
21
|
puts e.message
|
22
22
|
end
|
23
23
|
print " STACK: "
|
24
|
-
pp Concatenative::
|
24
|
+
pp Concatenative::STACK
|
25
25
|
end
|
data/lib/concatenative.rb
CHANGED
@@ -1,27 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
1
3
|
require 'pp'
|
2
4
|
|
3
5
|
libdir = File.dirname(File.expand_path(__FILE__))+'/concatenative/'
|
4
6
|
|
5
7
|
class EmptyStackError < RuntimeError; end
|
6
8
|
|
7
|
-
require libdir+'
|
9
|
+
require libdir+'kernel'
|
8
10
|
require libdir+'system_extensions'
|
9
|
-
require libdir+'definitions'
|
10
11
|
|
11
|
-
# The Concatenative module (included automatically when required) defines
|
12
|
-
# some constants, the <tt>concatenate</tt> method and the RubyMessage class.
|
13
12
|
module Concatenative
|
14
13
|
|
14
|
+
extend Concatenative::Kernel
|
15
|
+
|
16
|
+
STACK = []
|
15
17
|
ARITIES = {}
|
16
18
|
DEBUG = false
|
17
19
|
|
18
|
-
#
|
19
|
-
|
20
|
-
ARITIES[meth] = arity
|
21
|
-
end
|
22
|
-
|
23
|
-
# RubyMessage objects wrap a symbol and its arity
|
24
|
-
# (returned by Symbol#|).
|
20
|
+
# RubyMessage objects wrap a symbol and its arity
|
21
|
+
# (returned by Symbol#|).
|
25
22
|
class RubyMessage
|
26
23
|
attr_reader :name, :arity
|
27
24
|
def initialize(name, arity)
|
@@ -30,19 +27,86 @@ module Concatenative
|
|
30
27
|
end
|
31
28
|
end
|
32
29
|
|
33
|
-
|
34
|
-
|
35
|
-
System.execute program
|
30
|
+
class << self
|
31
|
+
attr_accessor :frozen, :popped, :pushed
|
36
32
|
end
|
37
33
|
|
38
|
-
|
39
|
-
|
40
|
-
|
34
|
+
@frozen = nil
|
35
|
+
@popped = nil
|
36
|
+
@pushed = nil
|
37
|
+
|
38
|
+
# Pushes an item on the stack.
|
39
|
+
def self.push(element)
|
40
|
+
STACK.push element
|
41
|
+
@pushed += 1 if @frozen
|
42
|
+
element
|
41
43
|
end
|
42
|
-
|
43
|
-
end
|
44
44
|
|
45
|
-
|
45
|
+
# Saves the stack state
|
46
|
+
def self.save_stack
|
47
|
+
@frozen = STACK.length
|
48
|
+
@popped = 0
|
49
|
+
@pushed = 0
|
50
|
+
end
|
51
|
+
|
52
|
+
# Restored the previously saved stack state
|
53
|
+
def self.restore_stack
|
54
|
+
diff = STACK.length - @frozen
|
55
|
+
@frozen = nil
|
56
|
+
diff.times { _pop }
|
57
|
+
end
|
58
|
+
|
59
|
+
# Executes an array as a concatenative program (clears the stack first).
|
60
|
+
def self.execute(array)
|
61
|
+
STACK.clear
|
62
|
+
array.each { |e| process e }
|
63
|
+
(STACK.length == 1) ? STACK[0] : STACK
|
64
|
+
end
|
65
|
+
|
66
|
+
# Processes an item (without clearning the stack).
|
67
|
+
def self.process(item)
|
68
|
+
case
|
69
|
+
when item.is_a?(Symbol) then
|
70
|
+
if item.defined?
|
71
|
+
~item.definition
|
72
|
+
else
|
73
|
+
case item.namespace
|
74
|
+
when :kernel then
|
75
|
+
Concatenative.send(item.name) rescue raise(RuntimeError, "Kernel word '#{item.name}' is not defined.")
|
76
|
+
when :ruby then
|
77
|
+
push ruby_method(item.name)
|
78
|
+
else
|
79
|
+
return Concatenative.send(item.name) if Concatenative::Kernel.method_defined? item.name
|
80
|
+
push ruby_method(item.name)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
when item.is_a?(RubyMessage) then
|
84
|
+
push ruby_method(item.name, item.arity)
|
85
|
+
else
|
86
|
+
push item
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Calls a Ruby method, consuming elements from the stack according to its
|
91
|
+
# explicit or implicit arity.
|
92
|
+
def self.ruby_method(message, arity=nil)
|
93
|
+
raise EmptyStackError, "Empty stack" if STACK.empty?
|
94
|
+
n = arity || ARITIES[message] || 0
|
95
|
+
method = message
|
96
|
+
elements = []
|
97
|
+
(n+1).times { elements << pop }
|
98
|
+
receiver = elements.pop
|
99
|
+
args = []
|
100
|
+
(elements.length).times { args << elements.pop }
|
101
|
+
begin
|
102
|
+
(args.length == 0) ? receiver.send(method) : receiver.send(method, *args)
|
103
|
+
rescue Exception => e
|
104
|
+
raise RuntimeError,
|
105
|
+
"Error when calling: #{receiver}##{method}(#{args.join(', ')}) [#{receiver.class}##{method}]"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
46
110
|
|
47
111
|
# Setting some default arities
|
48
112
|
set_arity :+, 1
|
@@ -0,0 +1,419 @@
|
|
1
|
+
#!usr/bin/env ruby
|
2
|
+
|
3
|
+
module Concatenative
|
4
|
+
|
5
|
+
module Kernel
|
6
|
+
|
7
|
+
# Clears the stack.
|
8
|
+
def clear
|
9
|
+
STACK.clear
|
10
|
+
end
|
11
|
+
|
12
|
+
# Pops an item out of the stack.
|
13
|
+
#
|
14
|
+
# <tt>A =></tt>
|
15
|
+
def pop
|
16
|
+
raise EmptyStackError, "Empty stack" if STACK.empty?
|
17
|
+
return STACK.pop unless @frozen
|
18
|
+
if @pushed > 0 then
|
19
|
+
@pushed -= 1
|
20
|
+
STACK.pop
|
21
|
+
else
|
22
|
+
raise EmptyStackError, "Empty stack" if @frozen <= 0
|
23
|
+
@popped += 1
|
24
|
+
STACK[@frozen-@popped]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Prints the top stack item.
|
29
|
+
def put
|
30
|
+
puts STACK.last
|
31
|
+
end
|
32
|
+
|
33
|
+
# Pushes a user-entered string on the stack.
|
34
|
+
def get
|
35
|
+
push gets
|
36
|
+
end
|
37
|
+
|
38
|
+
# Duplicates the top stack item.
|
39
|
+
#
|
40
|
+
# <tt>A => A A</tt>
|
41
|
+
def dup
|
42
|
+
raise EmptyStackError, "Empty stack" if STACK.empty?
|
43
|
+
push STACK.last
|
44
|
+
end
|
45
|
+
|
46
|
+
# Swaps the first two elements on the stack.
|
47
|
+
#
|
48
|
+
# <tt>A B => B A</tt>
|
49
|
+
def swap
|
50
|
+
a = pop
|
51
|
+
b = pop
|
52
|
+
push a
|
53
|
+
push b
|
54
|
+
end
|
55
|
+
|
56
|
+
# Prepends an element to an Array.
|
57
|
+
#
|
58
|
+
# <tt>[A] B => [A B]</tt>
|
59
|
+
def cons
|
60
|
+
array = pop
|
61
|
+
element = pop
|
62
|
+
raise ArgumentError, "CONS: first element is not an Array." unless array.is_a? Array
|
63
|
+
push array.insert(0, element)
|
64
|
+
end
|
65
|
+
|
66
|
+
def swons
|
67
|
+
swap
|
68
|
+
cons
|
69
|
+
end
|
70
|
+
|
71
|
+
# Removes the first element of an array and puts it on the stack, along with the
|
72
|
+
# new array
|
73
|
+
#
|
74
|
+
# <tt>[A] => B [C]</tt>
|
75
|
+
def uncons
|
76
|
+
array = pop
|
77
|
+
raise ArgumentError, "UNCONS: first element is not an Array." unless array.is_a? Array
|
78
|
+
push array.first
|
79
|
+
push array.drop 1
|
80
|
+
end
|
81
|
+
|
82
|
+
def unswons
|
83
|
+
uncons
|
84
|
+
swap
|
85
|
+
end
|
86
|
+
|
87
|
+
# Concatenates two arrays.
|
88
|
+
#
|
89
|
+
# <tt>[A] [B] => [A B]</tt>
|
90
|
+
def cat
|
91
|
+
array1 = pop
|
92
|
+
array2 = pop
|
93
|
+
raise ArgumentError, "CAT: first element is not an Array." unless array1.is_a? Array
|
94
|
+
raise ArgumentError, "CAT: first element is not an Array." unless array2.is_a? Array
|
95
|
+
push array2.concat(array1)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Returns the first element of an array.
|
99
|
+
#
|
100
|
+
# <tt>[A B] => A</tt>
|
101
|
+
def first
|
102
|
+
array = pop
|
103
|
+
raise ArgumentError, "FIRST: first element is not an Array." unless array.is_a? Array
|
104
|
+
raise ArgumentError, "FIRST: empty array." if array.length == 0
|
105
|
+
push array.first
|
106
|
+
end
|
107
|
+
|
108
|
+
# Returns everything but the first element of an array.
|
109
|
+
#
|
110
|
+
# <tt>[A B C] => [B C]</tt>
|
111
|
+
def rest
|
112
|
+
array = pop
|
113
|
+
raise ArgumentError, "REST: first element is not an Array." unless array.is_a? Array
|
114
|
+
raise ArgumentError, "REST: empty array." if array.length == 0
|
115
|
+
array.delete_at 0
|
116
|
+
push array
|
117
|
+
end
|
118
|
+
|
119
|
+
alias drop pop
|
120
|
+
alias zap pop
|
121
|
+
alias concat cat
|
122
|
+
|
123
|
+
# Saves A, executes P, restores A.
|
124
|
+
#
|
125
|
+
# <tt>A [P] => A</tt>
|
126
|
+
def dip
|
127
|
+
program = pop
|
128
|
+
raise ArgumentError, "DIP: first element is not an Array." unless program.is_a? Array
|
129
|
+
item = pop
|
130
|
+
~program
|
131
|
+
push item
|
132
|
+
end
|
133
|
+
|
134
|
+
# Saves A and B, executes P, restores A and B.
|
135
|
+
#
|
136
|
+
# <tt>A B [P] => A B</tt>
|
137
|
+
def twodip
|
138
|
+
program = pop
|
139
|
+
raise ArgumentError, "2DIP: first element is not an Array." unless program.is_a? Array
|
140
|
+
items = []
|
141
|
+
2.times { items << pop }
|
142
|
+
~program
|
143
|
+
items.reverse.each {|i| push i }
|
144
|
+
end
|
145
|
+
|
146
|
+
# Saves A, B and C, executes P, restores A, B and C.
|
147
|
+
#
|
148
|
+
# <tt>A B C [P] => A B C</tt>
|
149
|
+
def threedip
|
150
|
+
program = pop
|
151
|
+
raise ArgumentError, "2DIP: first element is not an Array." unless program.is_a? Array
|
152
|
+
items = []
|
153
|
+
3.times { items << pop }
|
154
|
+
~program
|
155
|
+
items.reverse.each {|i| push i }
|
156
|
+
end
|
157
|
+
|
158
|
+
# Removes the second item on the stack.
|
159
|
+
#
|
160
|
+
# <tt>A B => B</tt>
|
161
|
+
def popd
|
162
|
+
push [:pop]
|
163
|
+
dip
|
164
|
+
end
|
165
|
+
|
166
|
+
# Duplicates the second item on the stack
|
167
|
+
#
|
168
|
+
# <tt>A B => A A B</tt>
|
169
|
+
def dupd
|
170
|
+
push [:dup]
|
171
|
+
dip
|
172
|
+
end
|
173
|
+
|
174
|
+
# Swaps the second and third items on the stack
|
175
|
+
#
|
176
|
+
# <tt>A B C => B A C</tt>
|
177
|
+
def swapd
|
178
|
+
push [:swap]
|
179
|
+
dip
|
180
|
+
end
|
181
|
+
|
182
|
+
#
|
183
|
+
# <tt>A B C => B C A</tt>
|
184
|
+
def rollup
|
185
|
+
swap
|
186
|
+
push [:swap]
|
187
|
+
dip
|
188
|
+
end
|
189
|
+
|
190
|
+
#
|
191
|
+
# <tt>A B C => C A B</tt>
|
192
|
+
def rolldown
|
193
|
+
push [:swap]
|
194
|
+
dip
|
195
|
+
swap
|
196
|
+
end
|
197
|
+
|
198
|
+
#
|
199
|
+
# <tt>A B C => C B A</tt>
|
200
|
+
def rotate
|
201
|
+
swap
|
202
|
+
push [:swap]
|
203
|
+
dip
|
204
|
+
swap
|
205
|
+
end
|
206
|
+
|
207
|
+
# Executes a quoted program.
|
208
|
+
#
|
209
|
+
# <tt>[P] => </tt>
|
210
|
+
def i
|
211
|
+
program = pop
|
212
|
+
raise ArgumentError, "I: first element is not an Array." unless program.is_a? Array
|
213
|
+
~program
|
214
|
+
end
|
215
|
+
|
216
|
+
# Executes THEN if IF is true, otherwise executes ELSE.
|
217
|
+
#
|
218
|
+
# <tt>[IF] [THEN] [ELSE] =></tt>
|
219
|
+
def ifte
|
220
|
+
_else = pop
|
221
|
+
_then = pop
|
222
|
+
_if = pop
|
223
|
+
raise ArgumentError, "IFTE: first element is not an Array." unless _if.is_a? Array
|
224
|
+
raise ArgumentError, "IFTE: second element is not an Array." unless _then.is_a? Array
|
225
|
+
raise ArgumentError, "IFTE: third element is not an Array." unless _else.is_a? Array
|
226
|
+
save_stack
|
227
|
+
~_if
|
228
|
+
condition = pop
|
229
|
+
restore_stack
|
230
|
+
if condition then
|
231
|
+
~_then
|
232
|
+
else
|
233
|
+
~_else
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
alias if ifte
|
238
|
+
|
239
|
+
# Quotes the top stack element.
|
240
|
+
#
|
241
|
+
# <tt>A => [A]</tt>
|
242
|
+
def unit
|
243
|
+
push [pop]
|
244
|
+
end
|
245
|
+
|
246
|
+
# Executes P for each element of A, pushes an array containing the results on the stack.
|
247
|
+
#
|
248
|
+
# <tt>[A] [P] => [B]</tt>
|
249
|
+
def map
|
250
|
+
program = pop
|
251
|
+
list = pop
|
252
|
+
raise ArgumentError, "MAP: first element is not an Array." unless program.is_a? Array
|
253
|
+
raise ArgumentError, "MAP: second element is not an array." unless list.is_a? Array
|
254
|
+
push []
|
255
|
+
list.map do |e|
|
256
|
+
push e
|
257
|
+
~program
|
258
|
+
unit
|
259
|
+
cat
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
# Executes P for each element of A, pushes the results on the stack.
|
264
|
+
#
|
265
|
+
# <tt>[A] [P] => B</tt>
|
266
|
+
def step
|
267
|
+
program = pop
|
268
|
+
list = pop
|
269
|
+
raise ArgumentError, "STEP: first element is not an Array." unless program.is_a? Array
|
270
|
+
raise ArgumentError, "STEP: second element is not an array." unless list.is_a? Array
|
271
|
+
list.map do |e|
|
272
|
+
push e
|
273
|
+
~program
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
# If IF is true, executes THEN. Otherwise, executes REC1, recurses and then executes REC2.
|
278
|
+
#
|
279
|
+
# <tt>[IF] [THEN] [REC1] [REC2] =></tt>
|
280
|
+
def linrec
|
281
|
+
rec2 = pop
|
282
|
+
rec1 = pop
|
283
|
+
_then = pop
|
284
|
+
_if = pop
|
285
|
+
raise ArgumentError, "LINREC: first element is not an Array." unless _if.is_a? Array
|
286
|
+
raise ArgumentError, "LINREC: second element is not an Array." unless _then.is_a? Array
|
287
|
+
raise ArgumentError, "LINREC: third element is not an Array." unless rec1.is_a? Array
|
288
|
+
raise ArgumentError, "LINREC: fourth element is not an Array." unless rec2.is_a? Array
|
289
|
+
save_stack
|
290
|
+
~_if
|
291
|
+
condition = pop
|
292
|
+
restore_stack
|
293
|
+
if condition then
|
294
|
+
~_then
|
295
|
+
else
|
296
|
+
~rec1
|
297
|
+
[_if, _then, rec1, rec2].each {|e| push e }
|
298
|
+
linrec
|
299
|
+
~rec2
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
# Same as linrec, but it is only necessary to specify THEN and REC2.
|
304
|
+
# * REC1 = a program to reduce A to its zero value (0, [], "").
|
305
|
+
# * IF = a condition to verify if A is its zero value (0, [], "") or not.
|
306
|
+
#
|
307
|
+
# <tt>A [THEN] [REC2] =><tt>
|
308
|
+
def primrec
|
309
|
+
rec2 = pop
|
310
|
+
_then = [:pop, pop, :i]
|
311
|
+
arg = pop
|
312
|
+
# Guessing IF
|
313
|
+
case
|
314
|
+
when arg.respond_to?(:blank?) then
|
315
|
+
_if = [:blank?]
|
316
|
+
when arg.respond_to?(:empty?) then
|
317
|
+
_if = [:empty]
|
318
|
+
when arg.is_a?(Numeric) then
|
319
|
+
_if = [0, :==]
|
320
|
+
when arg.is_a?(String) then
|
321
|
+
_if = ["", :==]
|
322
|
+
else
|
323
|
+
raise ArgumentError, "PRIMREC: Unable to create IF element for #{arg} (#{arg.class})"
|
324
|
+
end
|
325
|
+
# Guessing REC1
|
326
|
+
case
|
327
|
+
when arg.respond_to?(:length) && arg.respond_to?(:slice) then
|
328
|
+
rec1 = [0, (arg.length-2), :slice|2]
|
329
|
+
when arg.respond_to?(:-) then
|
330
|
+
rec1 = [:dup, 1, :-]
|
331
|
+
else
|
332
|
+
raise ArgumentError, "PRIMREC: Unable to create REC1 element for #{arg} (#{arg.class})"
|
333
|
+
end
|
334
|
+
[arg, _if, _then, rec1, rec2].each {|e| push e }
|
335
|
+
linrec
|
336
|
+
end
|
337
|
+
|
338
|
+
# Executes P N times.
|
339
|
+
#
|
340
|
+
# <tt>N [P] => </tt>
|
341
|
+
def times
|
342
|
+
program = pop
|
343
|
+
n = pop
|
344
|
+
raise ArgumentError, "TIMEs: second element is not an Array." unless program.is_a? Array
|
345
|
+
n.times { ~program.dup }
|
346
|
+
end
|
347
|
+
|
348
|
+
# While COND is true, executes P
|
349
|
+
#
|
350
|
+
# <tt>[P] [COND] => </tt>
|
351
|
+
def while
|
352
|
+
program = pop
|
353
|
+
cond = pop
|
354
|
+
raise ArgumentError, "WHILE: first element is not an Array." unless cond.is_a? Array
|
355
|
+
raise ArgumentError, "WHILE: second element is not an Array." unless program.is_a? Array
|
356
|
+
save_stack
|
357
|
+
~cond
|
358
|
+
res = pop
|
359
|
+
restore_stack
|
360
|
+
if res then
|
361
|
+
~program
|
362
|
+
[cond, program].each {|e| push e }
|
363
|
+
self.while
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
# Splits an Array into two parts, depending on which element satisfy a condition
|
368
|
+
#
|
369
|
+
# <tt>[A] [P] => [B] [C]</tt>
|
370
|
+
def split
|
371
|
+
cond = pop
|
372
|
+
array = pop
|
373
|
+
raise ArgumentError, "SPLIT: first element is not an Array." unless cond.is_a? Array
|
374
|
+
raise ArgumentError, "SPLIT: second element is not an Array." unless array.is_a? Array
|
375
|
+
yes, no = [], []
|
376
|
+
array.each do |e|
|
377
|
+
save_stack
|
378
|
+
push e
|
379
|
+
~cond.dup
|
380
|
+
pop ? yes << e : no << e
|
381
|
+
restore_stack
|
382
|
+
end
|
383
|
+
push yes
|
384
|
+
push no
|
385
|
+
end
|
386
|
+
|
387
|
+
# If IF is true, executes THEN. Otherwise, executes REC1 (which must return two elements),
|
388
|
+
# recurses twice and then executes REC2.
|
389
|
+
#
|
390
|
+
# <tt>[IF] [THEN] [REC1] [REC2] =></tt>
|
391
|
+
def binrec
|
392
|
+
rec2 = pop
|
393
|
+
rec1 = pop
|
394
|
+
_then = pop
|
395
|
+
_if = pop
|
396
|
+
raise ArgumentError, "BINREC: first element is not an Array." unless _if.is_a? Array
|
397
|
+
raise ArgumentError, "BINREC: second element is not an Array." unless _then.is_a? Array
|
398
|
+
raise ArgumentError, "BINREC: third element is not an Array." unless rec1.is_a? Array
|
399
|
+
raise ArgumentError, "BINREC: fourth element is not an Array." unless rec2.is_a? Array
|
400
|
+
save_stack
|
401
|
+
~_if
|
402
|
+
condition = pop
|
403
|
+
restore_stack
|
404
|
+
if condition then
|
405
|
+
~_then
|
406
|
+
else
|
407
|
+
~rec1
|
408
|
+
a = pop
|
409
|
+
b = pop
|
410
|
+
[b, _if, _then, rec1, rec2].each {|e| push e }
|
411
|
+
binrec
|
412
|
+
[a, _if, _then, rec1, rec2].each {|e| push e }
|
413
|
+
binrec
|
414
|
+
~rec2
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
end
|
419
|
+
end
|