concatenative 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -22,16 +22,16 @@ Execute a Concatenative program:
22
22
  10,
23
23
  [0, :==],
24
24
  [1, :+],
25
- [:DUP, 1, :-],
25
+ [:dup, 1, :-],
26
26
  [:*],
27
- :LINREC
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 (tey must be all uppercase).
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
- :FACTORIAL.define [0, :==], [:POP, 1], [:DUP, 1, :- , :FACTORIAL, :*], :IFTE
34
- [5, :FACTORIAL].execute
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
 
@@ -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 = 5_000
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, :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) }
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, :ROLLDOWN, [:DUP, [:+], :DIP, :SWAP], :TIMES, :POP) }
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::System.process(instance_eval(gets))
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::System::STACK
24
+ pp Concatenative::STACK
25
25
  end
@@ -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+'system'
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
- # 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#|).
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
- # Execute an array as a concatenative program (clears the STACK first).
34
- def concatenate(*program)
35
- System.execute program
30
+ class << self
31
+ attr_accessor :frozen, :popped, :pushed
36
32
  end
37
33
 
38
- # Execute an array as a concatenative program (clears the STACK first).
39
- def self.concatenate(*program)
40
- System.execute program
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
- include Concatenative
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