concatenative 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,4 @@
1
+ == Version 0.1.0
2
+
3
+ First preview release of Concatenative, implementing all the most basic operators and
4
+ combinators.
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,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ dir = File.dirname(File.expand_path(__FILE__))
4
+
5
+ require dir+"/system_extensions_spec"
6
+ require dir+"/system_spec"
7
+ require dir+"/definitions_spec"
@@ -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
@@ -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