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,42 +1,80 @@
1
1
  #!usr/bin/env ruby
2
2
 
3
+ module Kernel
4
+
5
+ # Execute an array as a concatenative program (clears the STACK first).
6
+ def concatenate(*program)
7
+ Concatenative.execute program
8
+ end
9
+
10
+ # Specify the arity of a ruby method (regardless of the receiver).
11
+ def set_arity(meth, arity)
12
+ Concatenative::ARITIES[meth] = arity
13
+ end
14
+
15
+ end
16
+
3
17
  # The Array class is extended to allow execution of concatenative programs.
4
18
  class Array
5
19
 
6
- # Executes a concatenative program (clears the STACK first).
20
+ # Execute self as a concatenative program (clears the STACK first).
7
21
  def execute
8
- Concatenative.concatenate *self
22
+ Concatenative.execute self
9
23
  end
10
-
24
+
11
25
  # Processes each element of the array as a concatenative expression.
12
- def unquote
13
- each { |e| Concatenative::System.process e }
26
+ def ~
27
+ each { |e| Concatenative.process e }
14
28
  end
29
+
15
30
  end
16
31
 
17
32
  # The Symbol class is extended allowing explicit arities to be specified using the | operator,
18
- # concatenative function definition and execution.
33
+ # word definition and execution.
19
34
  class Symbol
20
35
 
36
+ attr_accessor :namespace
21
37
  attr_reader :definition
38
+ attr_writer :name
22
39
 
23
40
  # 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
41
+ def <=(item)
42
+ definition = item.respond_to?(:definition) ? item.definition : item
43
+ raise(RuntimeError, "'#{self}' is already defined.") if self.defined?
44
+ raise(RuntimeError, "Cannot redefine a Ruby word") if @namespace == :ruby
45
+ raise(RuntimeError, "Cannot redefine a Kernel word") if @namespace == :kernel
46
+ case
47
+ when item.is_a?(Symbol) then
48
+ @definition = [item]
49
+ when item.is_a?(Array) then
50
+ @definition = item
51
+ else
52
+ raise ArgumentError, "Invalid definition for '#@namespace/#@name'"
53
+ end
28
54
  end
29
55
 
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
56
+ # Specifies the arity of a ruby method. Example: :gsub|2 will return
57
+ # a RubyMessage with name = :gsub and arity = 2.
58
+ def |(arity)
59
+ Concatenative::RubyMessage.new(self.name, arity)
34
60
  end
35
61
 
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
62
+ # Returns the symbol's name (without namespace).
63
+ def name
64
+ @name||self
65
+ end
66
+
67
+ # Returns whether the symbol is defined or not.
68
+ def defined?
69
+ @definition != nil
70
+ end
71
+
72
+ # Concatenates two symbols (used for namespaces).
73
+ def /(sym)
74
+ s = "#{self}/#{sym}".to_sym
75
+ s.namespace = self
76
+ s.name = sym
77
+ s
40
78
  end
41
79
 
42
80
  end
@@ -1,7 +1,25 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- dir = File.dirname(File.expand_path(__FILE__))
3
+ dir = File.dirname(File.expand_path(__FILE__))+'/../lib/'
4
4
 
5
- require dir+"/system_extensions_spec"
6
- require dir+"/system_spec"
7
- require dir+"/definitions_spec"
5
+ require dir+"concatenative"
6
+
7
+ describe Concatenative do
8
+
9
+ it "should process Ruby methods and handle method arities" do
10
+ # Fixnum#>: arity = 1
11
+ [2, 20, :>].execute.should == false
12
+ ["Test", /T/, 'F', :sub|2].execute.should == "Fest"
13
+ [[1,2,3],:join].execute.should == "123"
14
+ [[1,2,3],'|',:join|1].execute.should == "1|2|3"
15
+ end
16
+
17
+ it "should process operators" do
18
+ [2, 2, :dup].execute.should == [2, 2, 2]
19
+ end
20
+
21
+ it "should process combinators" do
22
+ [2, 3, [:swap, :dup], :i].execute.should == [3, 2, 2]
23
+ end
24
+
25
+ end
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ dir = File.dirname(File.expand_path(__FILE__))+'/../lib/'
4
+
5
+ require dir+"concatenative"
6
+
7
+ describe Concatenative::Kernel 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 UNCONS and UNSWONS" do
37
+ [[1,2,3,4], :uncons].execute.should == [1, [2,3,4]]
38
+ [[1,2,3,4], :unswons].execute.should == [[2,3,4], 1]
39
+ end
40
+
41
+ it "should expose CAT" do
42
+ [[1,2],[3,4], :cat].execute.should == [1,2,3,4]
43
+ end
44
+
45
+ it "should expose I" do
46
+ [2, 5, [:*, 6,:+], :i].execute.should == 16
47
+ # Check other definitions of :I according to http://tunes.org/~iepos/joy.html
48
+ [2, 5, [:*, 6,:+], :dup, :dip, :zap].execute.should == 16
49
+ [2, 5, [:*, 6,:+], [[]], :dip, :dip, :zap].execute.should == 16
50
+ [2, 5, [:*, 6,:+], [[]], :dip, :dip, :dip].execute.should == 16
51
+ end
52
+
53
+ it "should expose DIP" do
54
+ [2, 3, 4, [:+], :dip].execute.should == [5, 4]
55
+ end
56
+
57
+ it "should expose TWODIP" do
58
+ [2, 3, 9, 4, [:+], :twodip].execute.should == [5, 9, 4]
59
+ end
60
+
61
+ it "should expose THREEDIP" do
62
+ [2, 3, 10, 8, 4, [:+], :threedip].execute.should == [5, 10, 8, 4]
63
+ end
64
+
65
+ it "should expose SWONS" do
66
+ [[2], 1, :swap, :cons].execute.should == [1,2]
67
+ [[2],1, :swons].execute.should == [[2],1, :swap, :cons].execute
68
+ [[2],1, :swons].execute.should == [1,2]
69
+ end
70
+
71
+ it "should expose POPD" do
72
+ [1,2,3, :popd].execute.should == [1,3]
73
+ end
74
+
75
+ it "should expose DUPD" do
76
+ [1,2,3, :dupd].execute.should == [1,2,2,3]
77
+ end
78
+
79
+ it "should expose SWAPD" do
80
+ [1,2,3, :swapd].execute.should == [2,1,3]
81
+ end
82
+
83
+ it "should expose ROLLUP, ROLLDOWN and ROTATE" do
84
+ a = [3,2,1]
85
+ (a.dup << :rollup).execute.should == [1,3,2]
86
+ (a.dup << :rolldown).execute.should == [2,1,3]
87
+ (a.dup << :rotate).execute.should == [1,2,3]
88
+ end
89
+
90
+
91
+ it "should expose UNIT" do
92
+ [2, 3, :unit].execute.should == [2, [3]]
93
+ end
94
+
95
+ it "should expose IFTE" do
96
+ t = [1000, :>], [2, :/], [3, :*], :ifte
97
+ [1200, *t].execute.should == 600
98
+ [800, *t].execute.should == 2400
99
+ # Test factorial with explicit recursion
100
+ :factorial <= [[0, :==], [:pop, 1], [:dup, 1, :- , :factorial, :*], :ifte]
101
+ [5, :factorial].execute.should == 120
102
+ end
103
+
104
+ it "should expose MAP" do
105
+ [[1,2,3,4], [:dup, :*], :map, 1].execute.should == [[1,4,9,16], 1]
106
+ end
107
+
108
+ it "should expose STEP" do
109
+ [[1,2,3,4], [:dup, :*], :step, 1].execute.should == [1,4,9,16, 1]
110
+ end
111
+
112
+ it "should expose LINREC" do
113
+ # factorial
114
+ [5, [0, :==], [1, :+], [:dup, 1, :-], [:*], :linrec].execute.should == 120
115
+ end
116
+
117
+ it "should expose PRIMREC" do
118
+ # factorial
119
+ [5, [1], [:*], :primrec].execute.should == 120
120
+ end
121
+
122
+ it "should expose TIMES" do
123
+ [4, [5, 2, :*], :times].execute.should == [10, 10, 10, 10]
124
+ # factorial
125
+ [5, 1, 1, :rolldown, [:dup, [:*], :dip, :succ], :times, :pop].execute.should == 120
126
+ x1,x2 = 0, 1
127
+ res = []
128
+ 0.upto(50){ res << x1; x1+=x2; x1,x2= x2,x1}
129
+ # Fibonacci number
130
+ [50, 0, 1, :rolldown, [:dup, [:+], :dip, :swap], :times, :pop].execute.should == res[res.length-1]
131
+ end
132
+
133
+ it "should expose WHILE" do
134
+ # gcd
135
+ [40, 25, [0, :>], [:dup, :rollup, :remainder|1], :while, :pop].execute.should == 5
136
+ end
137
+
138
+ it "should expose SPLIT" do
139
+ [4, [1,2,3,4,5,6], [:>], :split].execute.should == [4, [1,2,3], [4,5,6]]
140
+ end
141
+
142
+ it "should expose BINREC" do
143
+ # quicksort
144
+ [[6,4,2,8,1,7,9],
145
+ [:length, 2, :<], [], [:uncons, [:>], :split], [[:swap], :dip, :cons, :concat],
146
+ :binrec].execute.should == [1,2,4,6,7,8,9]
147
+ end
148
+
149
+ end
@@ -5,14 +5,10 @@ dir = File.dirname(File.expand_path(__FILE__))+'/../lib/'
5
5
  require dir+"concatenative"
6
6
 
7
7
  describe Array do
8
-
9
- it "should be executable" do
10
- [2, 3, :+].execute.should == 5
11
- end
12
8
 
13
- it "should be dequotable" do
14
- [2, 3, :*].unquote
15
- Concatenative::System::STACK.last.should == 6
9
+ it "should be unquotable" do
10
+ ~[2, 3, :*]
11
+ Concatenative::STACK.last.should == 6
16
12
  end
17
13
 
18
14
  end
@@ -28,27 +24,41 @@ describe Kernel do
28
24
  ).should == "Hello, World!"
29
25
  concatenate(
30
26
  [1,2,3],
31
- [:DUP, :*],
32
- :STEP
27
+ [:dup, :*],
28
+ :step
33
29
  ).should == [1,4,9]
34
30
  end
35
31
 
36
32
  end
37
33
 
38
34
  describe Symbol do
35
+
36
+ it "should support namespaces" do
37
+ lambda { :local/:a }.should_not raise_error
38
+ [[1,2,3],[4,5,6],:concat].execute.should == [1,2,3,4,5,6]
39
+ [[1,2,3],[4,5,6],:kernel/:concat].execute.should == [1,2,3,4,5,6]
40
+ [[1,2,3],[4,5,6],:ruby/:concat|1].execute.should == [1,2,3,4,5,6]
41
+
42
+ end
39
43
 
40
44
  it "should allow definitions" do
41
- lambda {:SQUARE.define [:DUP, :*]}.should_not raise_error
45
+ lambda {:square <= [:dup, :*]}.should_not raise_error
46
+ lambda {:square <= [:dup, :+]}.should raise_error
47
+ lambda {:kernel/:dup <= [:dup]}.should raise_error
48
+ lambda {:ruby/:gsub <= []}.should raise_error
49
+ a = :test <= :square
50
+ a.class.to_s.should == "Array"
51
+ [4, :test].execute.should == 16
42
52
  end
43
53
 
44
54
  it "should be executable" do
45
- :SQUARE.define [:DUP, :*]
46
- [3, :SQUARE, 2, :+].execute.should == 11
55
+ :square <= [:dup, :*] unless :square.defined?
56
+ [3, :square, 2, :+].execute.should == 11
47
57
  end
48
58
 
49
- it "should allo arity to be specified" do
59
+ it "should allow arity to be specified" do
50
60
  msg = :gsub|2
51
- msg.is_a?(RubyMessage).should == true
61
+ msg.is_a?(Concatenative::RubyMessage).should == true
52
62
  msg.arity.should == 2
53
63
  msg.name.should == :gsub
54
64
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: concatenative
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fabio Cevasco
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-03-29 00:00:00 +01:00
12
+ date: 2009-04-19 00:00:00 +02:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -26,15 +26,13 @@ extra_rdoc_files:
26
26
  files:
27
27
  - lib/concatenative
28
28
  - lib/concatenative/system_extensions.rb
29
- - lib/concatenative/definitions.rb
30
- - lib/concatenative/system.rb
29
+ - lib/concatenative/kernel.rb
31
30
  - lib/concatenative.rb
32
31
  - examples/concatenative_cli.rb
33
32
  - examples/benchmarks.rb
34
33
  - spec/concatenative_spec.rb
35
- - spec/definitions_spec.rb
36
- - spec/system_spec.rb
37
34
  - spec/system_extensions_spec.rb
35
+ - spec/kernel_spec.rb
38
36
  - README.rdoc
39
37
  - LICENSE
40
38
  - CHANGELOG.rdoc
@@ -46,6 +44,7 @@ rdoc_options:
46
44
  - README.rdoc
47
45
  - --exclude
48
46
  - spec
47
+ - --line-numbers
49
48
  require_paths:
50
49
  - lib
51
50
  required_ruby_version: !ruby/object:Gem::Requirement
@@ -63,7 +62,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
63
62
  requirements: []
64
63
 
65
64
  rubyforge_project: concatenative
66
- rubygems_version: 1.2.0
65
+ rubygems_version: 1.3.1
67
66
  signing_key:
68
67
  specification_version: 2
69
68
  summary: A Ruby DSL for concatenative programming.
@@ -1,12 +0,0 @@
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
@@ -1,334 +0,0 @@
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
-