concatenative 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +16 -0
- data/README.rdoc +5 -5
- data/examples/benchmarks.rb +5 -5
- data/examples/concatenative_cli.rb +2 -2
- data/lib/concatenative.rb +84 -20
- data/lib/concatenative/kernel.rb +419 -0
- data/lib/concatenative/system_extensions.rb +56 -18
- data/spec/concatenative_spec.rb +22 -4
- data/spec/kernel_spec.rb +149 -0
- data/spec/system_extensions_spec.rb +24 -14
- metadata +6 -7
- data/lib/concatenative/definitions.rb +0 -12
- data/lib/concatenative/system.rb +0 -334
- data/spec/definitions_spec.rb +0 -44
- data/spec/system_spec.rb +0 -107
@@ -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
|
-
#
|
20
|
+
# Execute self as a concatenative program (clears the STACK first).
|
7
21
|
def execute
|
8
|
-
Concatenative.
|
22
|
+
Concatenative.execute self
|
9
23
|
end
|
10
|
-
|
24
|
+
|
11
25
|
# Processes each element of the array as a concatenative expression.
|
12
|
-
def
|
13
|
-
each { |e| Concatenative
|
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
|
-
#
|
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
|
25
|
-
|
26
|
-
raise
|
27
|
-
@
|
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
|
-
#
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
-
#
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
data/spec/concatenative_spec.rb
CHANGED
@@ -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+"
|
6
|
-
|
7
|
-
|
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
|
data/spec/kernel_spec.rb
ADDED
@@ -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
|
14
|
-
[2, 3, :*]
|
15
|
-
Concatenative::
|
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
|
-
[:
|
32
|
-
:
|
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 {:
|
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
|
-
:
|
46
|
-
[3, :
|
55
|
+
:square <= [:dup, :*] unless :square.defined?
|
56
|
+
[3, :square, 2, :+].execute.should == 11
|
47
57
|
end
|
48
58
|
|
49
|
-
it "should
|
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.
|
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-
|
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/
|
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.
|
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
|
data/lib/concatenative/system.rb
DELETED
@@ -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
|
-
|