concatenative 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +4 -0
- data/LICENSE +11 -0
- data/README.rdoc +50 -0
- data/examples/benchmarks.rb +38 -0
- data/examples/concatenative_cli.rb +25 -0
- data/lib/concatenative.rb +78 -0
- data/lib/concatenative/definitions.rb +12 -0
- data/lib/concatenative/system.rb +334 -0
- data/lib/concatenative/system_extensions.rb +42 -0
- data/spec/concatenative_spec.rb +7 -0
- data/spec/definitions_spec.rb +44 -0
- data/spec/system_extensions_spec.rb +56 -0
- data/spec/system_spec.rb +107 -0
- metadata +71 -0
data/CHANGELOG.rdoc
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
Copyright (c) 2009, Fabio Cevasco
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
5
|
+
- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
6
|
+
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
7
|
+
Neither the name of the organization nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
8
|
+
|
9
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
10
|
+
|
11
|
+
|
data/README.rdoc
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
= Concatenative
|
2
|
+
|
3
|
+
Concatenative implements a stack-based DSL to use concatenative programming in Ruby. Because of its high-level implementation, it is not nearly as fast as ordinary Ruby code, but it can be used to learn the basics of concatenative programming without having to learn another programming language.
|
4
|
+
|
5
|
+
Concatenative's syntax is still valid Ruby code, but resambles a concatenative programming language like Joy (http://www.latrobe.edu.au/philosophy/phimvt/joy.html).
|
6
|
+
|
7
|
+
== Installation
|
8
|
+
|
9
|
+
The simplest method to install Concatenative is to install the gem:
|
10
|
+
|
11
|
+
gem install concatenative
|
12
|
+
|
13
|
+
== Usage
|
14
|
+
|
15
|
+
Initialization:
|
16
|
+
|
17
|
+
require 'concatentive'
|
18
|
+
|
19
|
+
Execute a Concatenative program:
|
20
|
+
|
21
|
+
concatenate(
|
22
|
+
10,
|
23
|
+
[0, :==],
|
24
|
+
[1, :+],
|
25
|
+
[:DUP, 1, :-],
|
26
|
+
[:*],
|
27
|
+
:LINREC
|
28
|
+
)
|
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 (tey must be all uppercase).
|
31
|
+
|
32
|
+
|
33
|
+
:FACTORIAL.define [0, :==], [:POP, 1], [:DUP, 1, :- , :FACTORIAL, :*], :IFTE
|
34
|
+
[5, :FACTORIAL].execute
|
35
|
+
|
36
|
+
The program above calculates the factorial of 5, using explicit recursion.
|
37
|
+
|
38
|
+
You can use all Ruby methods in Concatenative programs as well, making sure that the right number of arguments (and the method's receiver) are retrieved from the stack correctly. For this to work, Concatenative must know the arity of the method in advance, so the following rules are applied:
|
39
|
+
|
40
|
+
* All operators have an arity of 1
|
41
|
+
* All other method have an arity of 0
|
42
|
+
* If a method has a different arity, you must specify it explicitly using the pipe (|) operator.
|
43
|
+
|
44
|
+
Example:
|
45
|
+
|
46
|
+
concatenate(
|
47
|
+
"Goodbye, World!", /Goodbye/, "Hello", :sub|2
|
48
|
+
)
|
49
|
+
|
50
|
+
The program above is equivalent to <tt>"Goodbye, World!".sub(/Goodbye/, "Hello")</tt>.
|
@@ -0,0 +1,38 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'benchmark'
|
4
|
+
dir = File.dirname(File.expand_path(__FILE__))+'/../lib/'
|
5
|
+
require dir+"concatenative"
|
6
|
+
|
7
|
+
n = 5_000
|
8
|
+
|
9
|
+
def factorial(n)
|
10
|
+
(n == 0) ? 1 : factorial(n-1)
|
11
|
+
end
|
12
|
+
|
13
|
+
def fibonacci(n)
|
14
|
+
x1,x2 = 0, 1
|
15
|
+
res = []
|
16
|
+
0.upto(n){ res << x1; x1+=x2; x1,x2= x2,x1}
|
17
|
+
res[res.length-1]
|
18
|
+
end
|
19
|
+
|
20
|
+
puts "======================================================================"
|
21
|
+
puts "=====> Factorial of #{n}"
|
22
|
+
puts "======================================================================"
|
23
|
+
Benchmark.bmbm(20) do |x|
|
24
|
+
x.report("Standard Ruby Code:") { factorial n }
|
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
|
+
end
|
29
|
+
puts
|
30
|
+
puts
|
31
|
+
puts "======================================================================"
|
32
|
+
puts "=====> Fibonacci number for #{n}"
|
33
|
+
puts "======================================================================"
|
34
|
+
Benchmark.bmbm(20) do |x|
|
35
|
+
x.report("Standard Ruby Code:") { fibonacci n }
|
36
|
+
x.report("Concatenative (times):") { concatenate(n, 0, 1, :ROLLDOWN, [:DUP, [:+], :DIP, :SWAP], :TIMES, :POP) }
|
37
|
+
end
|
38
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
dir = File.dirname(File.expand_path(__FILE__))+'/../lib/'
|
4
|
+
require dir+"concatenative"
|
5
|
+
|
6
|
+
puts " ========================================="
|
7
|
+
puts " => Concatenative CLI"
|
8
|
+
puts " Enter an item to push it on the stack"
|
9
|
+
puts " or 'exit' to end the program."
|
10
|
+
puts " ========================================="
|
11
|
+
loop do
|
12
|
+
print " => "
|
13
|
+
begin
|
14
|
+
Concatenative::System.process(instance_eval(gets))
|
15
|
+
rescue Exception => e
|
16
|
+
if e.is_a? SystemExit then
|
17
|
+
puts " Exiting."
|
18
|
+
exit
|
19
|
+
end
|
20
|
+
print " ERROR: "
|
21
|
+
puts e.message
|
22
|
+
end
|
23
|
+
print " STACK: "
|
24
|
+
pp Concatenative::System::STACK
|
25
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'pp'
|
2
|
+
|
3
|
+
libdir = File.dirname(File.expand_path(__FILE__))+'/concatenative/'
|
4
|
+
|
5
|
+
class EmptyStackError < RuntimeError; end
|
6
|
+
|
7
|
+
require libdir+'system'
|
8
|
+
require libdir+'system_extensions'
|
9
|
+
require libdir+'definitions'
|
10
|
+
|
11
|
+
# The Concatenative module (included automatically when required) defines
|
12
|
+
# some constants, the <tt>concatenate</tt> method and the RubyMessage class.
|
13
|
+
module Concatenative
|
14
|
+
|
15
|
+
ARITIES = {}
|
16
|
+
DEBUG = false
|
17
|
+
|
18
|
+
# Specify the arity of a ruby method (regardless of the receiver).
|
19
|
+
def set_arity(meth, arity)
|
20
|
+
ARITIES[meth] = arity
|
21
|
+
end
|
22
|
+
|
23
|
+
# RubyMessage objects wrap a symbol and its arity
|
24
|
+
# (returned by Symbol#|).
|
25
|
+
class RubyMessage
|
26
|
+
attr_reader :name, :arity
|
27
|
+
def initialize(name, arity)
|
28
|
+
@name = name
|
29
|
+
@arity = arity
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Execute an array as a concatenative program (clears the STACK first).
|
34
|
+
def concatenate(*program)
|
35
|
+
System.execute program
|
36
|
+
end
|
37
|
+
|
38
|
+
# Execute an array as a concatenative program (clears the STACK first).
|
39
|
+
def self.concatenate(*program)
|
40
|
+
System.execute program
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
include Concatenative
|
46
|
+
|
47
|
+
# Setting some default arities
|
48
|
+
set_arity :+, 1
|
49
|
+
set_arity :*, 1
|
50
|
+
set_arity :-, 1
|
51
|
+
set_arity :/, 1
|
52
|
+
set_arity :|, 1
|
53
|
+
set_arity :&, 1
|
54
|
+
set_arity :^, 1
|
55
|
+
set_arity :%, 1
|
56
|
+
set_arity :>, 1
|
57
|
+
set_arity :<, 1
|
58
|
+
set_arity :~, 1
|
59
|
+
set_arity :**, 1
|
60
|
+
set_arity :[], 1
|
61
|
+
set_arity :[]=, 1
|
62
|
+
set_arity :<<, 1
|
63
|
+
set_arity :>>, 1
|
64
|
+
set_arity :==, 1
|
65
|
+
set_arity :'!=', 1
|
66
|
+
set_arity :>=, 1
|
67
|
+
set_arity :<=, 1
|
68
|
+
set_arity :'%=', 1
|
69
|
+
set_arity :'*=', 1
|
70
|
+
set_arity :'+=', 1
|
71
|
+
set_arity :'-=', 1
|
72
|
+
set_arity :'/=', 1
|
73
|
+
set_arity :'||', 1
|
74
|
+
set_arity :'&&', 1
|
75
|
+
set_arity :'-=', 1
|
76
|
+
set_arity :===, 1
|
77
|
+
set_arity :<=>, 1
|
78
|
+
set_arity :'**=', 1
|
@@ -0,0 +1,12 @@
|
|
1
|
+
#!usr/bin/env ruby
|
2
|
+
|
3
|
+
# Some definitions of common concatenative functions
|
4
|
+
:REP.define :I, :DUP
|
5
|
+
:SWONS.define :SWAP, :CONS
|
6
|
+
:POPD.define [:POP], :DIP
|
7
|
+
:DUPD.define [:DUP], :DIP
|
8
|
+
:SWAPD.define [:SWAP], :DIP
|
9
|
+
:SIP.define :DUPD, :SWAP, [:I], :DIP
|
10
|
+
:ROLLUP.define :SWAP, [:SWAP], :DIP
|
11
|
+
:ROLLDOWN.define [:SWAP], :DIP, :SWAP
|
12
|
+
:ROTATE.define :SWAP, [:SWAP], :DIP, :SWAP
|
@@ -0,0 +1,334 @@
|
|
1
|
+
#!usr/bin/env ruby
|
2
|
+
|
3
|
+
module Concatenative
|
4
|
+
|
5
|
+
# The System module includes the STACK constant, methods to interpret items pushed on
|
6
|
+
# the stack and the implementations of all concatenative combinators and operators.
|
7
|
+
module System
|
8
|
+
|
9
|
+
STACK = []
|
10
|
+
|
11
|
+
# Executes an array as a concatenative program (clears the stack first).
|
12
|
+
def self.execute(array)
|
13
|
+
STACK.clear
|
14
|
+
array.each { |e| process e }
|
15
|
+
(STACK.length == 1) ? STACK[0] : STACK
|
16
|
+
end
|
17
|
+
|
18
|
+
# Processes an item (without clearning the stack).
|
19
|
+
def self.process(item)
|
20
|
+
case
|
21
|
+
when !item.is_a?(Symbol) && !item.is_a?(Concatenative::RubyMessage) then
|
22
|
+
_push item
|
23
|
+
when item.is_a?(Symbol) && item.definition then
|
24
|
+
item.definition.each {|e| process e}
|
25
|
+
else
|
26
|
+
call_function item
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Calls a function (defined using Symbol#define) or a Ruby method identified by item (a Symbol or RubyMessage).
|
31
|
+
def self.call_function(item)
|
32
|
+
name = "_#{item.to_s.downcase}".to_sym
|
33
|
+
if (item.to_s.upcase == item.to_s) && !ARITIES[item] then
|
34
|
+
respond_to?(name) ? send(name) : raise(RuntimeError, "Unknown function: #{item}")
|
35
|
+
else
|
36
|
+
_push send_message(item)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Calls a Ruby method, consuming elements from the stack according to its
|
41
|
+
# explicit or implicit arity.
|
42
|
+
def self.send_message(message)
|
43
|
+
raise EmptyStackError, "Empty stack" if STACK.empty?
|
44
|
+
case
|
45
|
+
when message.is_a?(Concatenative::RubyMessage) then
|
46
|
+
n = message.arity
|
47
|
+
method = message.name
|
48
|
+
when message.is_a?(Symbol) then
|
49
|
+
n = ARITIES[message] || 0
|
50
|
+
method = message
|
51
|
+
end
|
52
|
+
elements = []
|
53
|
+
(n+1).times { elements << _pop }
|
54
|
+
receiver = elements.pop
|
55
|
+
args = []
|
56
|
+
(elements.length).times { args << elements.pop }
|
57
|
+
begin
|
58
|
+
(args.length == 0) ? receiver.send(method) : receiver.send(method, *args)
|
59
|
+
rescue Exception => e
|
60
|
+
raise RuntimeError,
|
61
|
+
"Error when calling: #{receiver}##{method}(#{args.join(', ')}) [#{receiver.class}##{method}]"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Operators & Combinators
|
66
|
+
|
67
|
+
# Clears the stack.
|
68
|
+
def self._clear
|
69
|
+
STACK.clear
|
70
|
+
end
|
71
|
+
|
72
|
+
# Pops an item out of the stack.
|
73
|
+
#
|
74
|
+
# A, B => A
|
75
|
+
def self._pop
|
76
|
+
raise EmptyStackError, "Empty stack" if STACK.empty?
|
77
|
+
STACK.pop
|
78
|
+
end
|
79
|
+
|
80
|
+
# Pushes an item on the stack.
|
81
|
+
#
|
82
|
+
# A => A, B
|
83
|
+
def self._push(element)
|
84
|
+
STACK.push element
|
85
|
+
end
|
86
|
+
|
87
|
+
# Prints the top stack item.
|
88
|
+
def self._put
|
89
|
+
puts STACK.last
|
90
|
+
end
|
91
|
+
|
92
|
+
# Pushes a user-entered string on the stack.
|
93
|
+
def self._get
|
94
|
+
_push gets
|
95
|
+
end
|
96
|
+
|
97
|
+
# Duplicates the top stack item.
|
98
|
+
#
|
99
|
+
# A => A, A
|
100
|
+
def self._dup
|
101
|
+
raise EmptyStackError, "Empty stack" if STACK.empty?
|
102
|
+
_push STACK.last
|
103
|
+
end
|
104
|
+
|
105
|
+
# Swaps the first two elements on the stack.
|
106
|
+
#
|
107
|
+
# A, B => B, A
|
108
|
+
def self._swap
|
109
|
+
a = _pop
|
110
|
+
b = _pop
|
111
|
+
_push a
|
112
|
+
_push b
|
113
|
+
end
|
114
|
+
|
115
|
+
# Prepends an element to an Array.
|
116
|
+
#
|
117
|
+
# [A], B => [A, B]
|
118
|
+
def self._cons
|
119
|
+
array = _pop
|
120
|
+
element = _pop
|
121
|
+
raise ArgumentError, "CONS: first element is not an Array." unless array.is_a? Array
|
122
|
+
_push array.insert(0, element)
|
123
|
+
end
|
124
|
+
|
125
|
+
# Concatenates two arrays.
|
126
|
+
#
|
127
|
+
# [A], [B] => [A, B]
|
128
|
+
def self._cat
|
129
|
+
array1 = _pop
|
130
|
+
array2 = _pop
|
131
|
+
raise ArgumentError, "CAT: first element is not an Array." unless array1.is_a? Array
|
132
|
+
raise ArgumentError, "CAT: first element is not an Array." unless array2.is_a? Array
|
133
|
+
_push array2.concat(array1)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Returns the first element of an array.
|
137
|
+
#
|
138
|
+
# [A, B] => A
|
139
|
+
def self._first
|
140
|
+
array = _pop
|
141
|
+
raise ArgumentError, "FIRST: first element is not an Array." unless array.is_a? Array
|
142
|
+
raise ArgumentError, "FIRST: empty array." if array.length == 0
|
143
|
+
_push array.first
|
144
|
+
end
|
145
|
+
|
146
|
+
# Returns everything but the first element of an array.
|
147
|
+
#
|
148
|
+
# [A, B, C] => [B, C]
|
149
|
+
def self._rest
|
150
|
+
array = _pop
|
151
|
+
raise ArgumentError, "REST: first element is not an Array." unless array.is_a? Array
|
152
|
+
raise ArgumentError, "REST: empty array." if array.length == 0
|
153
|
+
array.delete_at 0
|
154
|
+
_push array
|
155
|
+
end
|
156
|
+
|
157
|
+
instance_eval do
|
158
|
+
alias _zap _pop
|
159
|
+
alias _concat _cat
|
160
|
+
end
|
161
|
+
|
162
|
+
# Saves A, executes P, pushes A back.
|
163
|
+
#
|
164
|
+
# A, [P] => B, A
|
165
|
+
def self._dip
|
166
|
+
program = _pop
|
167
|
+
raise ArgumentError, "DIP: first element is not an Array." unless program.is_a? Array
|
168
|
+
arg = _pop
|
169
|
+
program.unquote
|
170
|
+
_push arg
|
171
|
+
end
|
172
|
+
|
173
|
+
# Executes a quoted program.
|
174
|
+
#
|
175
|
+
# [P] => A
|
176
|
+
def self._i
|
177
|
+
program = _pop
|
178
|
+
raise ArgumentError, "I: first element is not an Array." unless program.is_a? Array
|
179
|
+
program.unquote
|
180
|
+
end
|
181
|
+
|
182
|
+
# Executes THEN if IF is true, otherwise executes ELSE.
|
183
|
+
#
|
184
|
+
# A, [IF], [THEN], [ELSE] => B
|
185
|
+
def self._ifte
|
186
|
+
_else = _pop
|
187
|
+
_then = _pop
|
188
|
+
_if = _pop
|
189
|
+
raise ArgumentError, "IFTE: first element is not an Array." unless _if.is_a? Array
|
190
|
+
raise ArgumentError, "IFTE: second element is not an Array." unless _then.is_a? Array
|
191
|
+
raise ArgumentError, "IFTE: third element is not an Array." unless _else.is_a? Array
|
192
|
+
snapshot = STACK.clone
|
193
|
+
_if.unquote
|
194
|
+
condition = _pop
|
195
|
+
STACK.replace snapshot
|
196
|
+
if condition then
|
197
|
+
_then.unquote
|
198
|
+
else
|
199
|
+
_else.unquote
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
# Quotes the top stack element.
|
204
|
+
#
|
205
|
+
# A => [A]
|
206
|
+
def self._unit
|
207
|
+
_push [_pop]
|
208
|
+
end
|
209
|
+
|
210
|
+
# Executes P for each element of A, pushes an array containing the results on the stack.
|
211
|
+
#
|
212
|
+
# [A], [P] => [B]
|
213
|
+
def self._map
|
214
|
+
program = _pop
|
215
|
+
list = _pop
|
216
|
+
raise ArgumentError, "MAP: first element is not an Array." unless program.is_a? Array
|
217
|
+
raise ArgumentError, "MAP: second element is not an array." unless list.is_a? Array
|
218
|
+
_push []
|
219
|
+
list.map do |e|
|
220
|
+
_push e
|
221
|
+
program.unquote
|
222
|
+
_unit
|
223
|
+
_cat
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
# Executes P for each element of A, pushes the results on the stack.
|
228
|
+
#
|
229
|
+
# [A], [P] => B
|
230
|
+
def self._step
|
231
|
+
program = _pop
|
232
|
+
list = _pop
|
233
|
+
raise ArgumentError, "STEP: first element is not an Array." unless program.is_a? Array
|
234
|
+
raise ArgumentError, "STEP: second element is not an array." unless list.is_a? Array
|
235
|
+
list.map do |e|
|
236
|
+
_push e
|
237
|
+
program.unquote
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
# If IF is true, executes THEN. Otherwise, executes REC1, recurses and then executes REC2.
|
242
|
+
#
|
243
|
+
# A, [IF], [THEN], [REC1], [REC2] => B
|
244
|
+
def self._linrec
|
245
|
+
rec2 = _pop
|
246
|
+
rec1 = _pop
|
247
|
+
_then = _pop
|
248
|
+
_if = _pop
|
249
|
+
raise ArgumentError, "LINREC: first element is not an Array." unless _if.is_a? Array
|
250
|
+
raise ArgumentError, "LINREC: second element is not an Array." unless _then.is_a? Array
|
251
|
+
raise ArgumentError, "LINREC: third element is not an Array." unless rec1.is_a? Array
|
252
|
+
raise ArgumentError, "LINREC: fourth element is not an Array." unless rec2.is_a? Array
|
253
|
+
snapshot = STACK.clone
|
254
|
+
_if.unquote
|
255
|
+
condition = _pop
|
256
|
+
STACK.replace snapshot
|
257
|
+
if condition then
|
258
|
+
_then.unquote
|
259
|
+
else
|
260
|
+
rec1.unquote
|
261
|
+
STACK.concat [_if, _then, rec1, rec2]
|
262
|
+
_linrec
|
263
|
+
rec2.unquote
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
# Same as _linrec, but it is only necessary to specify THEN and REC2.
|
268
|
+
#
|
269
|
+
# * REC1 = a program to reduce A to its zero value (0, [], "").
|
270
|
+
# * IF = a condition to verify if A is its zero value (0, [], "") or not.
|
271
|
+
#
|
272
|
+
# A, [THEN], [REC2] => B
|
273
|
+
def self._primrec
|
274
|
+
rec2 = _pop
|
275
|
+
_then = [:POP, _pop, :I]
|
276
|
+
arg = _pop
|
277
|
+
# Guessing IF
|
278
|
+
case
|
279
|
+
when arg.respond_to?(:blank?) then
|
280
|
+
_if = [:blank?]
|
281
|
+
when arg.respond_to?(:empty?) then
|
282
|
+
_if = [:empty]
|
283
|
+
when arg.is_a?(Numeric) then
|
284
|
+
_if = [0, :==]
|
285
|
+
when arg.is_a?(String) then
|
286
|
+
_if = ["", :==]
|
287
|
+
else
|
288
|
+
raise ArgumentError, "PRIMREC: Unable to create IF element for #{arg} (#{arg.class})"
|
289
|
+
end
|
290
|
+
# Guessing REC1
|
291
|
+
case
|
292
|
+
when arg.respond_to?(:length) && arg.respond_to?(:slice) then
|
293
|
+
rec1 = [0, (arg.length-2), :slice|2]
|
294
|
+
when arg.respond_to?(:-) then
|
295
|
+
rec1 = [:DUP, 1, :-]
|
296
|
+
else
|
297
|
+
raise ArgumentError, "PRIMREC: Unable to create REC1 element for #{arg} (#{arg.class})"
|
298
|
+
end
|
299
|
+
STACK.concat [arg, _if, _then, rec1, rec2]
|
300
|
+
_linrec
|
301
|
+
end
|
302
|
+
|
303
|
+
# Executes P N times.
|
304
|
+
#
|
305
|
+
# N [P] => A
|
306
|
+
def self._times
|
307
|
+
program = _pop
|
308
|
+
n = _pop
|
309
|
+
raise ArgumentError, "TIMEs: second element is not an Array." unless program.is_a? Array
|
310
|
+
n.times { program.clone.unquote }
|
311
|
+
end
|
312
|
+
|
313
|
+
# While COND is true, executes P
|
314
|
+
#
|
315
|
+
# [P] [COND] => A
|
316
|
+
def self._while
|
317
|
+
program = _pop
|
318
|
+
cond = _pop
|
319
|
+
raise ArgumentError, "WHILE: first element is not an Array." unless cond.is_a? Array
|
320
|
+
raise ArgumentError, "WHILE: second element is not an Array." unless program.is_a? Array
|
321
|
+
snapshot = STACK.clone
|
322
|
+
cond.unquote
|
323
|
+
res = _pop
|
324
|
+
STACK.replace snapshot
|
325
|
+
if res then
|
326
|
+
program.unquote
|
327
|
+
STACK.concat [cond, program]
|
328
|
+
_while
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
@@ -0,0 +1,42 @@
|
|
1
|
+
#!usr/bin/env ruby
|
2
|
+
|
3
|
+
# The Array class is extended to allow execution of concatenative programs.
|
4
|
+
class Array
|
5
|
+
|
6
|
+
# Executes a concatenative program (clears the STACK first).
|
7
|
+
def execute
|
8
|
+
Concatenative.concatenate *self
|
9
|
+
end
|
10
|
+
|
11
|
+
# Processes each element of the array as a concatenative expression.
|
12
|
+
def unquote
|
13
|
+
each { |e| Concatenative::System.process e }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# The Symbol class is extended allowing explicit arities to be specified using the | operator,
|
18
|
+
# concatenative function definition and execution.
|
19
|
+
class Symbol
|
20
|
+
|
21
|
+
attr_reader :definition
|
22
|
+
|
23
|
+
# Assigns a quoted program (Array) as the symbol's definition.
|
24
|
+
def define(*array)
|
25
|
+
d = (array.length == 1) ? array.first : array
|
26
|
+
raise ArgumentError, "Argument for :#{self} definition is not a quoted program" unless d.is_a? Array
|
27
|
+
@definition = d
|
28
|
+
end
|
29
|
+
|
30
|
+
# Executes a concatenative function identified by the symbol (if it has been defined).
|
31
|
+
def execute
|
32
|
+
raise RuntimeError, ":#{self} is not defined" unless @definition
|
33
|
+
@definition.execute
|
34
|
+
end
|
35
|
+
|
36
|
+
# Specifies the arity of a ruby method. Example: :gsub|2 will return a RubyMessage with name = :gsub and
|
37
|
+
# arity = 2.
|
38
|
+
def |(arity)
|
39
|
+
Concatenative::RubyMessage.new self, arity
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
#!usr/bin/env ruby
|
2
|
+
|
3
|
+
dir = File.dirname(File.expand_path(__FILE__))+'/../lib/'
|
4
|
+
|
5
|
+
require dir+"concatenative"
|
6
|
+
|
7
|
+
describe Concatenative do
|
8
|
+
|
9
|
+
it "should define SWONS" do
|
10
|
+
[[2], 1, :SWAP, :CONS].execute.should == [1,2]
|
11
|
+
[[2],1, :SWONS].execute.should == [[2],1, :SWAP, :CONS].execute
|
12
|
+
[[2],1, :SWONS].execute.should == [1,2]
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should define POPD" do
|
16
|
+
[1,2,3, :POPD].execute.should == [1,3]
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should define DUPD" do
|
20
|
+
[1,2,3, :DUPD].execute.should == [1,2,2,3]
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should define SWAPD" do
|
24
|
+
[1,2,3, :SWAPD].execute.should == [2,1,3]
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should define SIP" do
|
28
|
+
[[1,2],[3,4],:SIP].execute.should == [[1,2],3,4,[1,2]]
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should define REP" do
|
32
|
+
[[2,3, :*], :REP, 2].execute.should == [6,6,2]
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should define ROLLUP, ROLLDOWN and ROTATE" do
|
36
|
+
a = [3,2,1]
|
37
|
+
(a.dup << :ROLLUP).execute.should == [1,3,2]
|
38
|
+
(a.dup << :ROLLDOWN).execute.should == [2,1,3]
|
39
|
+
(a.dup << :ROTATE).execute.should == [1,2,3]
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
|
@@ -0,0 +1,56 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
dir = File.dirname(File.expand_path(__FILE__))+'/../lib/'
|
4
|
+
|
5
|
+
require dir+"concatenative"
|
6
|
+
|
7
|
+
describe Array do
|
8
|
+
|
9
|
+
it "should be executable" do
|
10
|
+
[2, 3, :+].execute.should == 5
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should be dequotable" do
|
14
|
+
[2, 3, :*].unquote
|
15
|
+
Concatenative::System::STACK.last.should == 6
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
describe Kernel do
|
21
|
+
|
22
|
+
it "should concatenate programs" do
|
23
|
+
concatenate(
|
24
|
+
"Goodbye, World!",
|
25
|
+
/Goodbye/,
|
26
|
+
"Hello",
|
27
|
+
:sub|2
|
28
|
+
).should == "Hello, World!"
|
29
|
+
concatenate(
|
30
|
+
[1,2,3],
|
31
|
+
[:DUP, :*],
|
32
|
+
:STEP
|
33
|
+
).should == [1,4,9]
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
describe Symbol do
|
39
|
+
|
40
|
+
it "should allow definitions" do
|
41
|
+
lambda {:SQUARE.define [:DUP, :*]}.should_not raise_error
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should be executable" do
|
45
|
+
:SQUARE.define [:DUP, :*]
|
46
|
+
[3, :SQUARE, 2, :+].execute.should == 11
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should allo arity to be specified" do
|
50
|
+
msg = :gsub|2
|
51
|
+
msg.is_a?(RubyMessage).should == true
|
52
|
+
msg.arity.should == 2
|
53
|
+
msg.name.should == :gsub
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
data/spec/system_spec.rb
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
dir = File.dirname(File.expand_path(__FILE__))+'/../lib/'
|
4
|
+
|
5
|
+
require dir+"concatenative"
|
6
|
+
|
7
|
+
describe Concatenative::System do
|
8
|
+
|
9
|
+
it "should expose CLEAR" do
|
10
|
+
[1,2,3,4,5, :CLEAR].execute.should == []
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should expose POP " do
|
14
|
+
lambda { concatenate :POP }.should raise_error(EmptyStackError)
|
15
|
+
concatenate(1,2,3,4,:POP, :POP, :POP).should == 1
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should expose DUP" do
|
19
|
+
lambda { concatenate :DUP }.should raise_error(EmptyStackError)
|
20
|
+
concatenate(1,2,:DUP).should == [1,2,2]
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should expose SWAP" do
|
24
|
+
lambda { concatenate :SWAP }.should raise_error(EmptyStackError)
|
25
|
+
[1,3,2, :SWAP].execute.should == [1,2,3]
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should expose CONS, FIRST and REST" do
|
29
|
+
[1, [2], :CONS].execute.should == [1,2]
|
30
|
+
[4, [3], [2, 1], :CONS, :CONS, 5, :SWAP, :CONS].execute.should == [5,4,[3],2,1]
|
31
|
+
[[1,2,3,4], :REST].execute.should == [2,3,4]
|
32
|
+
[[1,2,3,4], :FIRST].execute.should == 1
|
33
|
+
lambda { [1,2,3, :CONS].execute}.should raise_error
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should expose CAT" do
|
37
|
+
[[1,2],[3,4], :CAT].execute.should == [1,2,3,4]
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should handle method arities" do
|
41
|
+
# Fixnum#>: arity = 1
|
42
|
+
[2, 20, :>].execute.should == false
|
43
|
+
["Test", /T/, 'F', :sub|2].execute.should == "Fest"
|
44
|
+
[[1,2,3],:join].execute.should == "123"
|
45
|
+
[[1,2,3],'|',:join|1].execute.should == "1|2|3"
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should expose I" do
|
49
|
+
[2, 5, [:*, 6,:+], :I].execute.should == 16
|
50
|
+
# Check other definitions of :I according to http://tunes.org/~iepos/joy.html
|
51
|
+
[2, 5, [:*, 6,:+], :DUP, :DIP, :ZAP].execute.should == 16
|
52
|
+
[2, 5, [:*, 6,:+], [[]], :DIP, :DIP, :ZAP].execute.should == 16
|
53
|
+
[2, 5, [:*, 6,:+], [[]], :DIP, :DIP, :DIP].execute.should == 16
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should expose DIP" do
|
57
|
+
[2, 3, 4, [:+], :DIP].execute.should == [5, 4]
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should expose UNIT" do
|
61
|
+
[2, 3, :UNIT].execute.should == [2, [3]]
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should expose IFTE" do
|
65
|
+
t = [1000, :>], [2, :/], [3, :*], :IFTE
|
66
|
+
[1200, *t].execute.should == 600
|
67
|
+
[800, *t].execute.should == 2400
|
68
|
+
# Test factorial with explicit recursion
|
69
|
+
:FACTORIAL.define [0, :==], [:POP, 1], [:DUP, 1, :- , :FACTORIAL, :*], :IFTE
|
70
|
+
[5, :FACTORIAL].execute.should == 120
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should expose MAP" do
|
74
|
+
[[1,2,3,4], [:DUP, :*], :MAP, 1].execute.should == [[1,4,9,16], 1]
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should expose STEP" do
|
78
|
+
[[1,2,3,4], [:DUP, :*], :STEP, 1].execute.should == [1,4,9,16, 1]
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should expose LINREC" do
|
82
|
+
# factorial
|
83
|
+
[5, [0, :==], [1, :+], [:DUP, 1, :-], [:*], :LINREC].execute.should == 120
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should expose PRIMREC" do
|
87
|
+
# factorial
|
88
|
+
[5, [1], [:*], :PRIMREC].execute.should == 120
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should expose TIMES" do
|
92
|
+
[4, [5, 2, :*], :TIMES].execute.should == [10, 10, 10, 10]
|
93
|
+
# factorial
|
94
|
+
[5, 1, 1, :ROLLDOWN, [:DUP, [:*], :DIP, :succ], :TIMES, :POP].execute.should == 120
|
95
|
+
x1,x2 = 0, 1
|
96
|
+
res = []
|
97
|
+
0.upto(50){ res << x1; x1+=x2; x1,x2= x2,x1}
|
98
|
+
# Fibonacci number
|
99
|
+
[50, 0, 1, :ROLLDOWN, [:DUP, [:+], :DIP, :SWAP], :TIMES, :POP].execute.should == res[res.length-1]
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should expose WHILE" do
|
103
|
+
# gcd
|
104
|
+
[40, 25, [0, :>], [:DUP, :ROLLUP, :remainder|1], :WHILE, :POP].execute.should == 5
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: concatenative
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Fabio Cevasco
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-03-29 00:00:00 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Concatenative can be used to program in Ruby using a concatenative syntax through ordinary arrays. Because of its high-level implementation, it is not nearly as fast as standard Ruby code.
|
17
|
+
email: h3rald@h3rald.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README.rdoc
|
24
|
+
- LICENSE
|
25
|
+
- CHANGELOG.rdoc
|
26
|
+
files:
|
27
|
+
- lib/concatenative
|
28
|
+
- lib/concatenative/system_extensions.rb
|
29
|
+
- lib/concatenative/definitions.rb
|
30
|
+
- lib/concatenative/system.rb
|
31
|
+
- lib/concatenative.rb
|
32
|
+
- examples/concatenative_cli.rb
|
33
|
+
- examples/benchmarks.rb
|
34
|
+
- spec/concatenative_spec.rb
|
35
|
+
- spec/definitions_spec.rb
|
36
|
+
- spec/system_spec.rb
|
37
|
+
- spec/system_extensions_spec.rb
|
38
|
+
- README.rdoc
|
39
|
+
- LICENSE
|
40
|
+
- CHANGELOG.rdoc
|
41
|
+
has_rdoc: true
|
42
|
+
homepage: http://rubyforge.org/projects/concatenative
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options:
|
45
|
+
- --main
|
46
|
+
- README.rdoc
|
47
|
+
- --exclude
|
48
|
+
- spec
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: "0"
|
56
|
+
version:
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: "0"
|
62
|
+
version:
|
63
|
+
requirements: []
|
64
|
+
|
65
|
+
rubyforge_project: concatenative
|
66
|
+
rubygems_version: 1.2.0
|
67
|
+
signing_key:
|
68
|
+
specification_version: 2
|
69
|
+
summary: A Ruby DSL for concatenative programming.
|
70
|
+
test_files:
|
71
|
+
- spec/concatenative_spec.rb
|