pyper 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +129 -0
- data/Rakefile +2 -0
- data/lib/pyper.rb +1131 -0
- data/lib/pyper/version.rb +3 -0
- data/pyper.gemspec +25 -0
- data/test/pyper_test.rb +109 -0
- metadata +75 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 86bd94959f7d92d97f3ae50b938bc4cc08bf2f3f
|
4
|
+
data.tar.gz: 3a7d993030ddd5084ee0df32892fb5ddac5873cb
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 747e5089a4095f008391f9215e0b467fc87b52561946116176b2b60f3c9fb9f60eb37affe191c5799fdfab659a8c02103e9699d30926fc8b33a48d8ac052ffc6
|
7
|
+
data.tar.gz: 51282f2e4f2eec7c4e91e09c3f7023a3926adf175a5070b7593829aebea8eb2b385b4c69eecc4cfba89dd1fc8214232af18aeaacbf9961d072369435045f5713
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 boris
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
# Pyper
|
2
|
+
|
3
|
+
Pyper is a wide extension of the Lispy car/cdr idea.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'pyper'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install pyper
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
Everybody knows Lispy functions #car, #cdr, #caar, #cdar, #cadr, #cddr...
|
22
|
+
When you `require 'pyper'` and `include Pyper`, you can try them by yourself:
|
23
|
+
|
24
|
+
[1, 2, 3].car # will return the first element, 1
|
25
|
+
[1, 2, 3].cdr # will return the remaining elements, [2, 3]
|
26
|
+
|
27
|
+
Similarly, #caar will return the first element of the first element, #cadr
|
28
|
+
will return the first element of the remaining elements (that is, second
|
29
|
+
element), #cddr will return the list of [3rd, 4th, ... ] elements, etc.
|
30
|
+
|
31
|
+
In Lisp, these compositions can theoretically extend ad infinitum:
|
32
|
+
|
33
|
+
caaar, caadr, cadar,...
|
34
|
+
caaaar, caaadr, ...
|
35
|
+
caaaaar, ..., cadaadr, ...
|
36
|
+
|
37
|
+
In effect, such character sequences form an APL-like language consisting of
|
38
|
+
one-character operators 'a' and 'd', whose combination determines the overall
|
39
|
+
operation. Pyper adds a few modifications and widely extends the idea:
|
40
|
+
|
41
|
+
1. Twin-barrel piping: Instead of just one pipeline, in which the
|
42
|
+
operations are applied in sequence, Pyper has 2 parallel pipelines.
|
43
|
+
|
44
|
+
2. Greek letters τ, π, χ as method delimiters: Instead of 'c' and 'r' of
|
45
|
+
car/cdr family, Pyper methods start and end with any of the characters
|
46
|
+
'τ', 'π', 'χ' (small Greek tau, pi and chi). Choice of the character
|
47
|
+
conveys specific meaning, which is best explained by case enumeration:
|
48
|
+
|
49
|
+
τ...τ means single-pipe input and output,
|
50
|
+
τ...π means single-pipe input, double-pipe output
|
51
|
+
τ...χ means single-pipe input, double-pipe output with a swap
|
52
|
+
π...τ means double-pipe input, single-pipe output
|
53
|
+
. . .
|
54
|
+
χ...χ means double-pipe input with a swap, and same for the output
|
55
|
+
|
56
|
+
(Mnemonic for this is, that τ has one (vertical) pipe, π has two pipes,
|
57
|
+
and χ looks like two pipes crossed)
|
58
|
+
|
59
|
+
As for the meaning, single-pipe input means, that a single object (the
|
60
|
+
message receiver) is fed to the pipeline. Double-pipe input means, that
|
61
|
+
the receiver is assumed to respond to methods #size and #[], its size is
|
62
|
+
2, and this being fulfilled, pipeline 0 and 1 are initialized
|
63
|
+
respectively with the first and second element of the receiver as per
|
64
|
+
method #[]. Double-pipe input with swap is the same, but the two
|
65
|
+
elements of the receiver are swapped: pipeline 1 receives the first,
|
66
|
+
pipeline 0 the second.
|
67
|
+
|
68
|
+
3. Postfix order of commands: While traditional car/cdr family of
|
69
|
+
methods applies the letters in the prefix order (from right to left),
|
70
|
+
Pyper uses postfix order (left to right).
|
71
|
+
|
72
|
+
Example: #cdar becomes τadτ ('da' reversed to 'ad')
|
73
|
+
#cadaar becomes τaadaτ ('adaa' reversed to 'aada')
|
74
|
+
|
75
|
+
4. Extended set of commands: The set of command characters, which in the
|
76
|
+
traditional car/cdr method family consists only of two characters, 'a'
|
77
|
+
and 'd', is greatly extended.
|
78
|
+
|
79
|
+
For example, apart from 'a', mening first, 'b' means second, and 'c'
|
80
|
+
means third:
|
81
|
+
|
82
|
+
["See", "you", "later", "alligator"].τaτ #=> "See"
|
83
|
+
["See", "you", "later", "alligator"].τbτ #=> "you"
|
84
|
+
["See", "you", "later", "alligator"].τcτ #=> "later"
|
85
|
+
|
86
|
+
For another example, apart from 'd', meaning all except first, 'e' means
|
87
|
+
all except first two, and 'f' means all except first three:
|
88
|
+
["See", "you", "later", "alligator"].τdτ = ["you", "later", "alligator"]
|
89
|
+
["See", "you", "later", "alligator"].τeτ = ["later", "alligator"]
|
90
|
+
["See", "you", "later", "alligator"].τfτ = ["alligator"]
|
91
|
+
|
92
|
+
These command characters can be combined just like 'a' and 'd' letters
|
93
|
+
in the traditional car/cdr family - just beware of the Pyper's postfix
|
94
|
+
order:
|
95
|
+
|
96
|
+
["See", "you", "later", "alligator"].τddτ = ["later", "alligator"]
|
97
|
+
["See", "you", "later", "alligator"].τdeτ = ["alligator"]
|
98
|
+
["See", "you", "later", "alligator"].τdeaτ = "alligator"
|
99
|
+
["See", "you", "later", "alligator"].τdeadτ = "lligator"
|
100
|
+
["See", "you", "later", "alligator"].τdeafτ = "igator"
|
101
|
+
["See", "you", "later", "alligator"].τdeafbτ = "g"
|
102
|
+
|
103
|
+
Allready with these few command characters (a-c, d-f, u-w, x-z, plus
|
104
|
+
numbers 0-4 and 5-9), one can compose intelligent car/cdr-like methods.
|
105
|
+
But there are more command characters available, representing various
|
106
|
+
common Ruby methods, operators etc.
|
107
|
+
|
108
|
+
5. Method arguments are possible: Unlike the traditional car/cdr family,
|
109
|
+
Pyper methods accept arguments. Regardless of the combination of the
|
110
|
+
command characters, any Pyper method can accept an arbitrary number of
|
111
|
+
arguments, which are [collected] into the 'args' variable, from which
|
112
|
+
the methods triggered by the command characters may take their arguments
|
113
|
+
as their arity requires.
|
114
|
+
|
115
|
+
******************************************************************
|
116
|
+
|
117
|
+
So much for the main concepts. As for the character meanings, those are
|
118
|
+
defined as PostfixMachine methods of the same name (the name consists of
|
119
|
+
1 or 2 characters). At the moment, it is necessary to read the
|
120
|
+
PostfixMachine code as their documentation.
|
121
|
+
|
122
|
+
|
123
|
+
## Contributing
|
124
|
+
|
125
|
+
1. Fork it
|
126
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
127
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
128
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
129
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/lib/pyper.rb
ADDED
@@ -0,0 +1,1131 @@
|
|
1
|
+
#coding: utf-8
|
2
|
+
|
3
|
+
require "pyper/version"
|
4
|
+
|
5
|
+
# Pyper is an extension of the Lispy car/cdr idea.
|
6
|
+
#
|
7
|
+
module Pyper
|
8
|
+
|
9
|
+
# Everybody knows Lispy functions #car, #cdr. In Ruby, these functions
|
10
|
+
# can be defined for example as:
|
11
|
+
def car; first end # a: first
|
12
|
+
def cdr; drop 1 end # d: all except first
|
13
|
+
|
14
|
+
# In their basic form, they are only marginally useful.
|
15
|
+
# Their popularity stems from their compositions:
|
16
|
+
def caar; first.first end
|
17
|
+
def cdar; first.drop 1 end
|
18
|
+
def cadr; drop(1).first end
|
19
|
+
def cddr; drop(1).drop(1) end
|
20
|
+
|
21
|
+
# These compositions can extend ad infinitum:
|
22
|
+
# caaar, caadr, cadar,...
|
23
|
+
# caaaar, caaadr, ...
|
24
|
+
# caaaaar, ..., cadaadr, ...
|
25
|
+
# ...
|
26
|
+
#
|
27
|
+
# The combination of 'a' and 'd' letter controls, in reverse order,
|
28
|
+
# the combination in which car and cdr is applied in a single pipeline.
|
29
|
+
#
|
30
|
+
# Pyper adds a few modifications and extensions to this idea:
|
31
|
+
#
|
32
|
+
# ******************************************************************
|
33
|
+
#
|
34
|
+
# 1. Twin-barrel piping: Instead of just one pipeline, in which the
|
35
|
+
# operations are applied in sequence, Pyper has 2 parallel pipelines.
|
36
|
+
#
|
37
|
+
#
|
38
|
+
# 2. Greek letters τ, π, χ as method delimiters: Instead of 'c' and 'r' of
|
39
|
+
# car/cdr family, Pyper methods start and end with any of the characters
|
40
|
+
# 'τ', 'π', 'χ' (small Greek tau, pi and chi). Choice of the character
|
41
|
+
# conveys specific meaning, which is best explained by case enumeration:
|
42
|
+
#
|
43
|
+
# τ...τ means single-pipe input and output,
|
44
|
+
# τ...π means single-pipe input, double-pipe output
|
45
|
+
# τ...χ means single-pipe input, double-pipe output with a swap
|
46
|
+
# π...τ means double-pipe input, single-pipe output
|
47
|
+
# . . .
|
48
|
+
# χ...χ means double-pipe input with a swap, and same for the output
|
49
|
+
#
|
50
|
+
# (Mnemonic for this is, that τ has one (vertical) pipe, π has two pipes,
|
51
|
+
# and χ looks like two pipes crossed)
|
52
|
+
#
|
53
|
+
# As for the meaning, single-pipe input means, that a single object (the
|
54
|
+
# message receiver) is fed to the pipeline. Double-pipe input means, that
|
55
|
+
# the receiver is assumed to respond to methods #size and #[], its size is
|
56
|
+
# 2, and this being fulfilled, pipeline 0 and 1 are initialized
|
57
|
+
# respectively with the first and second element of the receiver as per
|
58
|
+
# method #[]. Double-pipe input with swap is the same, but the two
|
59
|
+
# elements of the receiver are swapped: pipeline 1 receives the first,
|
60
|
+
# pipeline 0 the second.
|
61
|
+
#
|
62
|
+
# 3. Postfix order of commands: While traditional car/cdr family of
|
63
|
+
# methods applies the letters in the prefix order (from right to left),
|
64
|
+
# Pyper uses postfix order (left to right).
|
65
|
+
#
|
66
|
+
# Example: #cdar becomes τadτ ('da' reversed to 'ad')
|
67
|
+
# #cadaar becomes τaadaτ ('adaa' reversed to 'aada')
|
68
|
+
#
|
69
|
+
# 4. Extended set of commands: The set of command characters, which in the
|
70
|
+
# traditional car/cdr method family consists only of two characters, 'a'
|
71
|
+
# and 'd', is greatly extended.
|
72
|
+
#
|
73
|
+
# For example, apart from 'a', mening first, 'b' means second, and 'c'
|
74
|
+
# means third:
|
75
|
+
#
|
76
|
+
# ["See", "you", "later", "alligator"].τaτ #=> "See"
|
77
|
+
# ["See", "you", "later", "alligator"].τbτ #=> "you"
|
78
|
+
# ["See", "you", "later", "alligator"].τcτ #=> "later"
|
79
|
+
#
|
80
|
+
# For another example, apart from 'd', meaning all except first, 'e' means
|
81
|
+
# all except first two, and 'f' means all except first three:
|
82
|
+
# ["See", "you", "later", "alligator"].τdτ = ["you", "later", "alligator"]
|
83
|
+
# ["See", "you", "later", "alligator"].τeτ = ["later", "alligator"]
|
84
|
+
# ["See", "you", "later", "alligator"].τfτ = ["alligator"]
|
85
|
+
#
|
86
|
+
# These command characters can be combined just like 'a' and 'd' letters
|
87
|
+
# in the traditional car/cdr family - just beware of the Pyper's postfix
|
88
|
+
# order:
|
89
|
+
#
|
90
|
+
# ["See", "you", "later", "alligator"].τddτ = ["later", "alligator"]
|
91
|
+
# ["See", "you", "later", "alligator"].τdeτ = ["alligator"]
|
92
|
+
# ["See", "you", "later", "alligator"].τdeaτ = "alligator"
|
93
|
+
# ["See", "you", "later", "alligator"].τdeadτ = "lligator"
|
94
|
+
# ["See", "you", "later", "alligator"].τdeafτ = "igator"
|
95
|
+
# ["See", "you", "later", "alligator"].τdeafbτ = "g"
|
96
|
+
#
|
97
|
+
# Many more command characters are available.
|
98
|
+
#
|
99
|
+
# 5. Method arguments are possible: Unlike the traditional car/cdr family,
|
100
|
+
# Pyper methods accept arguments. Regardless of the combination of the
|
101
|
+
# command characters, any Pyper method can accept an arbitrary number of
|
102
|
+
# arguments, which are [collected] into the 'args' variable, from which
|
103
|
+
# the methods triggered by the command characters may take their arguments
|
104
|
+
# as their arity requires.
|
105
|
+
#
|
106
|
+
# ******************************************************************
|
107
|
+
#
|
108
|
+
# So much for the main concepts. As for the character meanings, those are
|
109
|
+
# defined as PostfixMachine methods of the same name (the name consists of
|
110
|
+
# 1 or 2 characters). At the moment, it is necessary to read the
|
111
|
+
# PostfixMachine code as their documentation.
|
112
|
+
|
113
|
+
def method_missing( mτ_sym, *args, &block )
|
114
|
+
pyperλ = lambda { | opts |
|
115
|
+
mτ_string = PostfixMachine.new( $1 ).write_mτ( mτ_sym, opts )
|
116
|
+
mτ_string.gsub! /^alpha = alpha\n/, "alpha\n" # workaround
|
117
|
+
mτ_string.gsub! /^alpha\nalpha\n/, "alpha\n" # workaround
|
118
|
+
mτ_string.gsub! /^alpha\nalpha =/, "alpha =" # workaround
|
119
|
+
mτ_string.gsub! /^alpha = alpha =/, 'alpha =' # workaround
|
120
|
+
# puts mτ_string # DEBUG
|
121
|
+
self.class.module_eval( mτ_string )
|
122
|
+
send( mτ_sym, *args, &block )
|
123
|
+
}
|
124
|
+
# puts "received msg #{mτ_sym}" # DEBUG
|
125
|
+
case mτ_sym.to_s
|
126
|
+
when /^τ(.+)τ$/ then pyperλ.( op: 1, ret: 1 )
|
127
|
+
when /^π(.+)τ$/ then pyperλ.( op: 2, ret: 1 )
|
128
|
+
when /^χ(.+)τ$/ then pyperλ.( op: -2, ret: 1 )
|
129
|
+
when /^τ(.+)π$/ then pyperλ.( op: 1, ret: 2 )
|
130
|
+
when /^π(.+)π$/ then pyperλ.( op: 2, ret: 2 )
|
131
|
+
when /^χ(.+)π$/ then pyperλ.( op: -2, ret: 2 )
|
132
|
+
when /^τ(.+)χ$/ then pyperλ.( op: 1, ret: -2 )
|
133
|
+
when /^π(.+)χ$/ then pyperλ.( op: 2, ret: -2 )
|
134
|
+
when /^χ(.+)χ$/ then pyperλ.( op: -2, ret: -2 )
|
135
|
+
else super end
|
136
|
+
end
|
137
|
+
|
138
|
+
def respond_to_missing?( mτ_sym, include_private = false )
|
139
|
+
case mτ_sym.to_s
|
140
|
+
when /^τ(\w+)τ$/, /^π(\w+)τ$/, /^χ(\w+)τ$/,
|
141
|
+
/^τ(\w+)π$/, /^π(\w+)π$/, /^χ(\w+)π$/,
|
142
|
+
/^τ(\w+)χ$/, /^π(\w+)χ$/, /^χ(\w+)χ$/ then true
|
143
|
+
else super end
|
144
|
+
end
|
145
|
+
|
146
|
+
# PostfixMachine is an algorithmic writer of Pyper methods. Each Pyper
|
147
|
+
# method has two pipelines: 'alpha' (no. 0) and 'beta' (no. 1). Variables
|
148
|
+
# 'alpha' and 'beta' are local to the main scope of a Pyper method.
|
149
|
+
#
|
150
|
+
# When blocks are used inside a Pyper method, variable 'delta' local to
|
151
|
+
# the block is used to hold the pipeline inside the block. For blocks with
|
152
|
+
# arity 1, variable named 'epsilon' is used to hold the block argument.
|
153
|
+
# For blocks with arity 2, variables named 'epsilon', resp. 'zeta' are
|
154
|
+
# used to hold 1st, resp. 2nd block argument. Blocks with arity higher
|
155
|
+
# than 2 are not allowed in Pyper methods. (However, Pyper methods may
|
156
|
+
# receive external block of arbitrary construction.)
|
157
|
+
#
|
158
|
+
# Control characters are still under heavy development - presently, one
|
159
|
+
# must read the code to learn about their exact meaning.
|
160
|
+
#
|
161
|
+
class PostfixMachine
|
162
|
+
PREFIX_CHARACTERS =
|
163
|
+
['ℓ'] << # math script ℓ (as in litre)
|
164
|
+
'¡' << # inverted exclamation mark
|
165
|
+
'¿' << # inverted question mark
|
166
|
+
'‹' << # single left pointing quotation mark
|
167
|
+
'›' << # single right pointing quotation mark
|
168
|
+
'﹦' << # small equals sign
|
169
|
+
'﹕' << # small colon
|
170
|
+
'﹡' # small asterisk
|
171
|
+
|
172
|
+
SUCC = { alpha: :beta, beta: :alpha, α: :β, β: :α } # successor table
|
173
|
+
PRE = { alpha: :beta, beta: :alpha, α: :β, β: :α } # predecessor table
|
174
|
+
|
175
|
+
# Template for the def line of the method being written:
|
176
|
+
DEF_LINE = lambda { |ɴ| "def #{ɴ}( *args, &block )" }
|
177
|
+
|
178
|
+
# The default source of arguments in Pyper methods is 'args' local
|
179
|
+
# variable, where arguments supplied to the Pyper methods are
|
180
|
+
# collected. However, this default argument source can be changed to
|
181
|
+
# something else. For this purpose, at write time of a Pyper method,
|
182
|
+
# stack is maintained, showing where the next argument will come from.
|
183
|
+
# The following closure is basically the constructor of this stack,
|
184
|
+
# which is implemented as a Hash with two keys :src and :grab,
|
185
|
+
# describing respectively the argument source, and what to do with it to
|
186
|
+
# obtain the required argument from it.
|
187
|
+
#
|
188
|
+
# Possible argument source objects:
|
189
|
+
# :args (whole argument array),
|
190
|
+
# :args_counted (args referenced using a write-time counter - default)
|
191
|
+
# :alpha (primary pipeline)
|
192
|
+
# :beta (secondary pipeline)
|
193
|
+
# :delta (in-block pipeline)
|
194
|
+
# :epsilon (block argument 0)
|
195
|
+
# :zeta (block argument 1)
|
196
|
+
# :psi (penultimate element in the args array; penultimate argument)
|
197
|
+
# :omega (last element in the args array; last argument)
|
198
|
+
#
|
199
|
+
# Argument grab methods:
|
200
|
+
# :ref (by simple reference to the object specified as the arg. source)
|
201
|
+
# :dup (by #dup of the object specified as the arg. sourc)
|
202
|
+
# :shift (by calling runtime #shift on the obj. spec. as the arg. src.)
|
203
|
+
#
|
204
|
+
# So here goes the closure:
|
205
|
+
ARG_SOURCES_AND_GRAB_METHODS = lambda {
|
206
|
+
# We start from a ꜧ with 2 keys (:src & :grab) pointing to 2 ᴀs:
|
207
|
+
◉ = { src: [:args_counted], grab: [:ref] }
|
208
|
+
◉.define_singleton_method :src do self[:src] end
|
209
|
+
◉.define_singleton_method :grab do self[:grab] end
|
210
|
+
◉.define_singleton_method :src= do |arg| self[:src] = arg end
|
211
|
+
◉.define_singleton_method :grab= do |arg| self[:grab] = arg end
|
212
|
+
# Now, onto this ◉, mτs are patched for setting argument sources.
|
213
|
+
# In general, mτs ending in ! modify topmost source on the arg.
|
214
|
+
# source stack, while mτs without ! push a new arg. source on the
|
215
|
+
# stack. The exception is the #std! method, which resets the stack:
|
216
|
+
◉.define_singleton_method :std! do src = [:args_counted]; grab = [:ref] end
|
217
|
+
# #define_singleton_method means #define_singleton_method
|
218
|
+
◉.define_singleton_method :args_counted do src.push :args_counted; grab.push :ref end
|
219
|
+
◉.define_singleton_method :args_counted! do src[-1] = :args_counted end
|
220
|
+
◉.define_singleton_method :args do src.push :args; grab.push :shift end
|
221
|
+
◉.define_singleton_method :args! do src[-1] = :args; grab[-1] = :shift end
|
222
|
+
◉.define_singleton_method :alpha do src.push :alpha; grab.push :ref end
|
223
|
+
◉.define_singleton_method :alpha! do src[-1] = :alpha end
|
224
|
+
◉.define_singleton_method :beta do src.push :beta; grab.push :ref end
|
225
|
+
◉.define_singleton_method :beta! do src[-1] = :beta end
|
226
|
+
◉.define_singleton_method :delta do src.push :delta; grab.push :ref end
|
227
|
+
◉.define_singleton_method :delta! do src[-1] = :delta end
|
228
|
+
◉.define_singleton_method :epsilon do src.push :epsilon; grab.push :ref end
|
229
|
+
◉.define_singleton_method :epsilon! do src[-1] = :epsilon end
|
230
|
+
◉.define_singleton_method :zeta do src.push :zeta; grab.push :ref end
|
231
|
+
◉.define_singleton_method :zeta! do src[-1] = :zeta end
|
232
|
+
◉.define_singleton_method :psi do src.push :psi; grab.push :ref end
|
233
|
+
◉.define_singleton_method :psi! do src[-1] = :psi end
|
234
|
+
◉.define_singleton_method :omega do src.push :omega; grab.push :ref end
|
235
|
+
◉.define_singleton_method :omega! do src[-1] = :omega end
|
236
|
+
# methods #var/#var! take a parameter and push/change the stack top
|
237
|
+
◉.define_singleton_method :var do |variable| src.push variable; grab.push :ref end
|
238
|
+
◉.define_singleton_method :var! do |variable| src[-1] = variable end
|
239
|
+
# methods #shift! and #ref! change only the grab method:
|
240
|
+
◉.define_singleton_method :shift! do grab[-1] = :shift end
|
241
|
+
◉.define_singleton_method :ref! do grab[-1] = :ref end
|
242
|
+
◉.define_singleton_method :dup! do grab[-1] = :dup end
|
243
|
+
return ◉
|
244
|
+
}
|
245
|
+
|
246
|
+
# PostfixMachine initialization
|
247
|
+
def initialize command_ς
|
248
|
+
@cmds = parse_command_string( command_ς )
|
249
|
+
end
|
250
|
+
|
251
|
+
# Command ς -> command ᴀ
|
252
|
+
def parse_command_string( arg )
|
253
|
+
# If supplied arg is an ᴀ, assume that it already is a command
|
254
|
+
# sequence, and thus, no work at all is needed:
|
255
|
+
return arg if arg.kind_of? Array
|
256
|
+
# Otherwise, assume arg is a ς and split it using #each_char
|
257
|
+
arg.to_s.each_char.with_object [] do |char, memo|
|
258
|
+
# Handle prefix characters:
|
259
|
+
( PREFIX_CHARACTERS.include?(memo[-1]) ? memo[-1] : memo ) << char
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
# Algorithmically writes a Ruby mτ, whose name is given as 1st arg.,
|
264
|
+
# and the options ꜧ expects 2 keys (:op and :ret) as follows:
|
265
|
+
#
|
266
|
+
# op: when 1 (single pipe), makes no assumption about the receiver
|
267
|
+
# When 2 (twin pipe), assumes the receiver is a size 2 ᴀ,
|
268
|
+
# consisting of pipes a, b
|
269
|
+
# When -2 (twin pipe with a swap), assumes the same as above and
|
270
|
+
# swaps the pipes immediately (a, b = b, a)
|
271
|
+
#
|
272
|
+
# ret: when 1 (single return value), returns current pipe only
|
273
|
+
# when 2 (return both pipes), returns size 2 ᴀ, consisting
|
274
|
+
# of pipes a, b
|
275
|
+
# when -2 (return both pipes with a swap), returns size 2 ᴀ
|
276
|
+
# containing the pipes' results in reverse order [b, a]
|
277
|
+
#
|
278
|
+
def write_mτ( ɴ, opts={} )
|
279
|
+
@opts = { op: 1, ret: 1 }.merge( opts )
|
280
|
+
@opts.define_singleton_method :op do self[:op] end
|
281
|
+
@opts.define_singleton_method :ret do self[:ret] end
|
282
|
+
|
283
|
+
# Initialize argument sourcing
|
284
|
+
@argsrc = ARG_SOURCES_AND_GRAB_METHODS.call
|
285
|
+
|
286
|
+
initialize_writer_state
|
287
|
+
write_mτ_head_skeleton( ɴ )
|
288
|
+
write_initial_pipeline
|
289
|
+
write_mτ_tail_skeleton
|
290
|
+
|
291
|
+
# Now that we have the skeleton, let's write the meat.
|
292
|
+
write_mτ_meat
|
293
|
+
|
294
|
+
# puts "head is #@head\npipe is #@pipe\ntail is #@tail" # DEBUG
|
295
|
+
|
296
|
+
# Finally, close any blocks and return
|
297
|
+
autoclose_open_blocks_and_return
|
298
|
+
end
|
299
|
+
|
300
|
+
# private
|
301
|
+
|
302
|
+
# Initialize method writing flags / state keepers
|
303
|
+
def initialize_writer_state
|
304
|
+
# set current pipeline to :alpha (pipeline 0)
|
305
|
+
@r = :alpha
|
306
|
+
|
307
|
+
# set current pipe stack to [@r]
|
308
|
+
@rr = [@r]
|
309
|
+
# (Pipeline stack is needed due to tha fact, that blocks are allowed
|
310
|
+
# inside a Pyper method. At method write time, every time a block is
|
311
|
+
# open, block pipeline symbol is pushed onto this stack.)
|
312
|
+
|
313
|
+
# where are we? flag (whether in :main or :block) set to :main
|
314
|
+
@w = :main
|
315
|
+
|
316
|
+
# argument counter (for args dispensing to the individual methods)
|
317
|
+
@arg_count = 0
|
318
|
+
|
319
|
+
# signal to pass the supplied block to the next method
|
320
|
+
@take_block = false
|
321
|
+
|
322
|
+
# arity flag for next block to be written, default is 1
|
323
|
+
@block_arity = 1
|
324
|
+
end
|
325
|
+
|
326
|
+
# Write the skeleton of the method header:
|
327
|
+
def write_mτ_head_skeleton( ɴ )
|
328
|
+
@head = [ [ DEF_LINE.( ɴ ) ] ] # write first line "def ɴ..."
|
329
|
+
write "\n"
|
330
|
+
# write validation line (written only when @opts[:op] == 2)
|
331
|
+
write "raise 'Receiver must be a size 2 array when double-piping!'" +
|
332
|
+
"unless self.kind_of?( Array ) and self.size == 2\n" if
|
333
|
+
@opts.op == 2
|
334
|
+
# 'main_opener' (global)
|
335
|
+
write @main_opener = ""
|
336
|
+
# 'opener' (local to block)
|
337
|
+
write opener = ""
|
338
|
+
@opener = [ opener ]
|
339
|
+
end
|
340
|
+
|
341
|
+
# Initialize the pipeline (@pipe)
|
342
|
+
def write_initial_pipeline
|
343
|
+
@pipe = case @opts.op
|
344
|
+
when 1 then [ "self" ] # use receiver (default)
|
345
|
+
when 2 then # use alpha, beta = self[0], self[1]
|
346
|
+
@alpha_touched = @beta_touched = true
|
347
|
+
write "\n( alpha, beta = self[0], self[1]; alpha)\n"
|
348
|
+
[ "alpha" ] # pipe 0 aka. primary pipe
|
349
|
+
when -2 then # use alpha, beta = self[1], self[0]
|
350
|
+
@alpha_touched = @beta_touched = true
|
351
|
+
write "\n( alpha, beta = self[1], self[0]; alpha)\n"
|
352
|
+
[ "alpha" ] # pipe 0 aka. primary pipe
|
353
|
+
end # self compliance tested in the written method itself
|
354
|
+
write "\n"; write @pipe[-1] # make @pipe part of @head
|
355
|
+
end
|
356
|
+
|
357
|
+
# Write the skeleton of the tail part of the method, consisting
|
358
|
+
# of the finisher line, returner line, and end statement itself.
|
359
|
+
def write_mτ_tail_skeleton
|
360
|
+
finisher = String.new # 'finisher' (local to block)
|
361
|
+
@finisher = [ finisher ]
|
362
|
+
@returner = case @opts.ret # 'returner' (global finisher)
|
363
|
+
when 1 then ""
|
364
|
+
when 2 then alpha_touch; beta_touch; "return alpha, beta"
|
365
|
+
when -2 then alpha_touch; beta_touch; "return beta, alpha"
|
366
|
+
else raise "wrong @opts[:fin] value: #{@opts.fin}" end
|
367
|
+
@tail = [ [ finisher, "\n", @returner, "\n", "end" ] ] # end line
|
368
|
+
end
|
369
|
+
|
370
|
+
# This consists of taking the atomic commands from @cmds array one by
|
371
|
+
# one and calling the command method to write a small piece of the
|
372
|
+
# program implied by the command.
|
373
|
+
def write_mτ_meat
|
374
|
+
while not @cmds.empty?
|
375
|
+
# First, slice off the next command from @cmds array
|
376
|
+
cmd = @cmds.shift
|
377
|
+
|
378
|
+
# puts "doing command #{cmd}, @r is #@r, @head is #@head" # DEBUG
|
379
|
+
# puts "doing command #{cmd}, @argsrc is #@argsrc" # DEBUG
|
380
|
+
|
381
|
+
# Take the block (if not taken) if this is the last command
|
382
|
+
|
383
|
+
@take_block = true unless @take_block == :taken if @cmds.size <= 0
|
384
|
+
# Now send the command to self. Commands are implemented as
|
385
|
+
# methods of Pyper::PostfixMachine with one or two-character
|
386
|
+
# names. These methods then take care of writing the program
|
387
|
+
# pieces implied by these commands. Side effects of this is, that
|
388
|
+
# one- and two-character local variables should be avoided inside
|
389
|
+
# whole PostfixMachine class.
|
390
|
+
# puts "about to self.send( #@w, #{cmd} )" # DEBUG
|
391
|
+
self.send @w, cmd
|
392
|
+
pipe_2_variable if @cmds.size <= 0
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
# After we run out of atomic commands, it's time to finalize the
|
397
|
+
# program by closing any blocks still left open. Metod #close_block
|
398
|
+
# called by this method actually produces the program string out of
|
399
|
+
# each block it closes, so this method actually returns the program
|
400
|
+
# string of whole newly written Pyper method.
|
401
|
+
def autoclose_open_blocks_and_return
|
402
|
+
( rslt = close_block; chain rslt; pipe_2_variable ) while @head.size > 1
|
403
|
+
return close_block
|
404
|
+
end
|
405
|
+
|
406
|
+
# Called to close a block, including the main def
|
407
|
+
def close_block
|
408
|
+
unless @rr.empty? then @r = @rr.pop end # back with the register
|
409
|
+
@pipe.pop; @opener.pop; @finisher.pop # pop the writing stack
|
410
|
+
( @head.pop + @tail.pop ).join # join head and tail
|
411
|
+
end
|
412
|
+
|
413
|
+
# Writer of argument grab strings.
|
414
|
+
def grab_arg
|
415
|
+
raise ArgumentError unless @argsrc.src.size == @argsrc.grab.size
|
416
|
+
grab = case @argsrc.grab.last
|
417
|
+
when :shift then ".shift"
|
418
|
+
when :ref then ""
|
419
|
+
when :dup then ".dup"
|
420
|
+
else raise "unknown arg. grab method: #{@argsrc.grab.last}" end
|
421
|
+
str = case @argsrc.src.last
|
422
|
+
when :args_counted
|
423
|
+
x = (@arg_count += 1) - 1; "args[#{x}]" + grab
|
424
|
+
when :args then # now this is a bit difficult, cause
|
425
|
+
case @argsrc.grab.last # it's necessary to discard the used
|
426
|
+
when :shift then # args (shift #@arg_count):
|
427
|
+
if @arg_count == 0 then "args.shift"
|
428
|
+
else "(args.shift(#@arg_count); args.shift)" end
|
429
|
+
when :ref then "args"
|
430
|
+
else raise "unknown arg. grab method: #{@argsrc.grab.last}" end
|
431
|
+
when :alpha then alpha_touch; 'alpha' + grab
|
432
|
+
when :beta then beta_touch; 'beta' + grab
|
433
|
+
when :delta, :epsilon, :zeta then @argsrc.src.last.to_s + grab
|
434
|
+
when :psi then "args[-2]" + grab
|
435
|
+
when :omega then "args[-1]" + grab
|
436
|
+
else raise "unknown argument source: #{@argsrc.src.last}" end
|
437
|
+
unless @argsrc.src.size <= 1 then @argsrc.src.pop; @argsrc.grab.pop end
|
438
|
+
return str
|
439
|
+
end
|
440
|
+
|
441
|
+
# Execution methods (depending on @w at the moment)
|
442
|
+
def main( cmd ); self.send( cmd ) end
|
443
|
+
def block( cmd ); self.send( cmd ) end
|
444
|
+
|
445
|
+
# ********************************************************************
|
446
|
+
# Script writing subroutines
|
447
|
+
# ********************************************************************
|
448
|
+
|
449
|
+
# Active register reader
|
450
|
+
def _r_; @r end
|
451
|
+
# Append string to head
|
452
|
+
def write( x ); Array( x ).each {|e| @head[-1] << e } end
|
453
|
+
# Chain (nullary) method string to the end of the pipe
|
454
|
+
def chain( s ); @pipe[-1] << ".#{s}" end
|
455
|
+
# Suck the pipe into the "memory" (active register)
|
456
|
+
def pipe_2_variable; @pipe[-1].prepend "#@r = "; eval "#{@r}_touched = true" end
|
457
|
+
# Start a new pipe, on a new line. Without arguments, @r is used
|
458
|
+
def start( s = "#@r" ); write "\n"; @pipe[-1] = s; write @pipe.last end
|
459
|
+
# Set the pipe to a value, discarding current contents
|
460
|
+
def set( s ); @pipe[-1].clear << s end
|
461
|
+
# Store in active register, and continue in a new pipeline:
|
462
|
+
def belay; pipe_2_variable; start end
|
463
|
+
# pipe_2_variable, execute something else, and go back to @r
|
464
|
+
def exe( s ); pipe_2_variable; start s; start end
|
465
|
+
# parethesize current pipe
|
466
|
+
def paren; @pipe[-1].prepend("( ") << " )" end
|
467
|
+
# Write binary operator
|
468
|
+
def bin_op( s, x = grab_arg ); @pipe[-1] << " #{s} " << x end
|
469
|
+
# Write unary operator
|
470
|
+
def unary_op( s ); paren; @pipe[-1].prepend s end
|
471
|
+
# Returns nothing or optional block, if flagged to do so
|
472
|
+
def maybe_block; case @take_block
|
473
|
+
when true then @take_block = :taken; '&block'
|
474
|
+
when nil, false, :taken then nil
|
475
|
+
else raise "unexpected @take_block value" end
|
476
|
+
end
|
477
|
+
# Chain unary method
|
478
|
+
def nullary_m( s ); chain "#{s}(#{maybe_block})" end
|
479
|
+
def unary_m( s, x = grab_arg )
|
480
|
+
chain "#{s}( #{[x, maybe_block].compact.join(", ")} )" end
|
481
|
+
# Chain binary method
|
482
|
+
def binary_m( s, x = grab_arg, y = grab_arg )
|
483
|
+
chain "#{s}( #{[x, y, maybe_block].compact.join(", ")} )" end
|
484
|
+
# Initiates writing a block method.
|
485
|
+
def nullary_m_with_block( str )
|
486
|
+
# puts "in nullary_m_with_block, str = #{str}" # DEBUG
|
487
|
+
if @take_block == true then
|
488
|
+
nullary_m( str )
|
489
|
+
else # code a block
|
490
|
+
@w = :block # change writing method
|
491
|
+
belay # a must before block opening
|
492
|
+
# push a new pipe, head and tail to the writing stack:
|
493
|
+
@rr.empty? ? ( @rr = [@r] ) : ( @rr.push @r ) # store the register
|
494
|
+
@r = :delta # a block runs in its own unswitchable register delta
|
495
|
+
@pipe << String.new # push pipe
|
496
|
+
# puts "@pipe is << #@pipe >>" # DEBUG
|
497
|
+
@head << case @block_arity # push head
|
498
|
+
when 0 then [ "#{str} { " ]
|
499
|
+
when 1 then set "delta"; [ "#{str} { |epsilon|" ]
|
500
|
+
when 2 then @argsrc.zeta; @argsrc.ref!
|
501
|
+
set "delta"; [ "#{str} { |epsilon, zeta|" ]
|
502
|
+
when -2 then @argsrc.epsilon; @argsrc.ref!
|
503
|
+
set "delta"; [ "#{str} { |epsilon, zeta|" ]
|
504
|
+
else raise "Unknown @block_arity: #@block_arity"
|
505
|
+
end
|
506
|
+
write "\n"
|
507
|
+
opener = case @block_arity; when 0 then "";
|
508
|
+
when 1, 2 then "delta = epsilon"
|
509
|
+
when -2 then "delta = zeta" end
|
510
|
+
@opener << opener # push opener
|
511
|
+
@block_arity = 1 # after use, set block arity flag back to default
|
512
|
+
# puts "@pipe is << #@pipe >>" # DEBUG
|
513
|
+
write opener; write "\n"; write @pipe.last
|
514
|
+
finisher = String.new
|
515
|
+
@finisher << finisher # push finisher
|
516
|
+
@tail << [ "\n" ] # push tail
|
517
|
+
@tail.last << finisher << "\n" << "}" # done
|
518
|
+
end
|
519
|
+
end
|
520
|
+
|
521
|
+
# Next block will be written as binary:
|
522
|
+
def block_2ary; @block_arity = 2 end
|
523
|
+
|
524
|
+
# Next block will be writen as binary with swapped block arguments
|
525
|
+
# (delta = zeta; @argsrc.epsilon):
|
526
|
+
def block_2ary_swapped; @block_arity = -2 end
|
527
|
+
|
528
|
+
# register 0 (alpha) was required for computation
|
529
|
+
def alpha_touch; belay unless @alpha_touched or @beta_touched end
|
530
|
+
|
531
|
+
# register 1 (beta) was required for the computation
|
532
|
+
def beta_autoinit
|
533
|
+
case @opts.op
|
534
|
+
when 1 then s = "beta = self.dup rescue self"
|
535
|
+
( @main_opener.clear << s; @beta_touched = true ) unless @beta_touched
|
536
|
+
when 2 then @main_opener.clear << "beta = self[1]" unless @beta_touched
|
537
|
+
when -2 then @main_opener.clear << "beta = self[0]" unless @beta_touched
|
538
|
+
else raise "wrong @opts[:op] value: #{@opts.op}" end
|
539
|
+
end
|
540
|
+
alias :beta_touch :beta_autoinit
|
541
|
+
|
542
|
+
# touch and return successor of a register, or @r by default
|
543
|
+
def rSUCC reg=@r; send "#{SUCC[reg]}_touch"; SUCC[reg] end
|
544
|
+
|
545
|
+
# touch and return predecessor of a register, or @r by default
|
546
|
+
def rPRE reg=@r; send "#{PRE[reg]}_touch"; PRE[reg] end
|
547
|
+
|
548
|
+
# Traditional letters with extension to the first 3 elements
|
549
|
+
# ********************************************************************
|
550
|
+
# In the strict sense, there are only 2 traditional letters in these
|
551
|
+
# kinds of functions: 'a' and 'd' of car/cdr Lisp fame.
|
552
|
+
|
553
|
+
# In Pyper, 'car' becomes 'τaτ', and applies to strings, too:
|
554
|
+
def a; pipe_2_variable; start "#@r =\n" +
|
555
|
+
"if #@r.respond_to?( :first ) then #@r.first\n" +
|
556
|
+
"elsif #@r.respond_to?( :[] ) then #@r[0]\n" +
|
557
|
+
"else raise 'impossible to extract first element' end"
|
558
|
+
start
|
559
|
+
end
|
560
|
+
|
561
|
+
# Extension of this idea: 'b' is 2nd, 'c' is 3rd:
|
562
|
+
def b; pipe_2_variable; start "#@r =\n" +
|
563
|
+
"if #@r.respond_to?( :take ) then #@r.take(2)[1]\n" +
|
564
|
+
"elsif #@r.respond_to?( :[] ) then #@r[1]\n" +
|
565
|
+
"else raise 'unable to extract second collection element' end"
|
566
|
+
start
|
567
|
+
end
|
568
|
+
|
569
|
+
def c; pipe_2_variable; start "#@r =\n" +
|
570
|
+
"if #@r.respond_to?( :take ) then #@r.take(3)[2]\n" +
|
571
|
+
"elsif #@r.respond_to?( :[] ) then #@r[2]\n" +
|
572
|
+
"else raise 'unable to extract third collection element' end"
|
573
|
+
start
|
574
|
+
end
|
575
|
+
|
576
|
+
# In Pyper 'cdr' becomes 'τdτ':
|
577
|
+
def d; pipe_2_variable; start "#@r =\n" +
|
578
|
+
"if #@r.is_a?( Hash ) then Hash[ @r.drop(1) ]\n" +
|
579
|
+
"elsif #@r.respond_to?( :drop ) then #@r.drop(1)\n" +
|
580
|
+
"elsif #@r.respond_to?( :[] ) then #@r[1..-1]\n" +
|
581
|
+
"else raise 'unable to #drop(1) or #[1..-1]' end"
|
582
|
+
start
|
583
|
+
end
|
584
|
+
|
585
|
+
# 'e', 'f' mean all but first 2, resp. 3 elements:
|
586
|
+
def e; pipe_2_variable; start "#@r =\n" +
|
587
|
+
"if #@r.is_a?( Hash ) then Hash[ @r.drop(2) ]\n" +
|
588
|
+
"elsif #@r.respond_to?( :drop ) then #@r.drop(2)\n" +
|
589
|
+
"elsif #@r.respond_to?( :[] ) then #@r[2..-1]\n" +
|
590
|
+
"else raise 'unable to #drop(2) or #[2..-1]' end"
|
591
|
+
start
|
592
|
+
end
|
593
|
+
def f; pipe_2_variable; start "#@r =\n" +
|
594
|
+
"if #@r.is_a?( Hash ) then Hash[ @r.drop(3) ]\n" +
|
595
|
+
"elsif #@r.respond_to?( :drop ) then #@r.drop(3)\n" +
|
596
|
+
"elsif #@r.respond_to?( :[] ) then #@r[3..-1]\n" +
|
597
|
+
"else raise 'unable to #drop(3) or #[3..-1]' end"
|
598
|
+
start
|
599
|
+
end
|
600
|
+
|
601
|
+
# Extending these ideas also to the collection last 3 elements
|
602
|
+
# ********************************************************************
|
603
|
+
|
604
|
+
# 'z' - last element
|
605
|
+
def z; pipe_2_variable; start "#@r =\n" +
|
606
|
+
"if #@r.respond_to?( :drop ) then #@r.drop( #@r.size - 1 ).first\n" +
|
607
|
+
"elsif #@r.respond_to?( :[] ) then #@r[-1]\n" +
|
608
|
+
"else raise 'unable to extract last element' end"
|
609
|
+
start
|
610
|
+
end
|
611
|
+
|
612
|
+
# 'y' - penultimate element
|
613
|
+
def y; pipe_2_variable; start "#@r =\n" +
|
614
|
+
"if #@r.respond_to?( :drop ) then #@r.drop( #@r.size - 2 ).first\n" +
|
615
|
+
"elsif #@r.respond_to?( :[] ) then #@r[-2]\n" +
|
616
|
+
"else raise 'unable to extract second-from-the-end element' end"
|
617
|
+
start
|
618
|
+
end
|
619
|
+
|
620
|
+
# 'x' - 3rd from the end
|
621
|
+
def x; pipe_2_variable; start "#@r =\n" +
|
622
|
+
"if #@r.respond_to?( :drop ) then #@r.drop( #@r.size - 3 ).first\n" +
|
623
|
+
"elsif #@r.respond_to?( :[] ) then #@r[-3]\n" +
|
624
|
+
"else raise 'unable to extract third-from-the-end element' end"
|
625
|
+
start
|
626
|
+
end
|
627
|
+
|
628
|
+
# 'w' - all except last
|
629
|
+
def w; pipe_2_variable; start "#@r =\n" +
|
630
|
+
"if #@r.is_a?( Hash ) then Hash[ @r.take( #@r.size - 1 ) ]\n" +
|
631
|
+
"elsif #@r.respond_to?( :take ) then #@r.take( #@r.size - 1 )\n" +
|
632
|
+
"elsif #@r.respond_to?( :[] ) then #@r[0...-1]\n" +
|
633
|
+
"else raise 'unable to #drop(1) or #[1...-1]' end"
|
634
|
+
start
|
635
|
+
end
|
636
|
+
|
637
|
+
# 'v' - all except last 2
|
638
|
+
def v; pipe_2_variable; start "#@r =\n" +
|
639
|
+
"if #@r.is_a?( Hash ) then Hash[ @r.take( #@r.size - 2 ) ]\n" +
|
640
|
+
"elsif #@r.respond_to?( :take ) then #@r.take( #@r.size - 2 )\n" +
|
641
|
+
"elsif #@r.respond_to?( :[] ) then #@r[0...-2]\n" +
|
642
|
+
"else raise 'unable to #drop(1) or #[1...-2]' end"
|
643
|
+
start
|
644
|
+
end
|
645
|
+
|
646
|
+
# 'u' - all except last 3
|
647
|
+
def u; pipe_2_variable; start "#@r =\n" +
|
648
|
+
"if #@r.is_a?( Hash ) then Hash[ @r.take( #@r.size - 3 ) ]\n" +
|
649
|
+
"elsif #@r.respond_to?( :take ) then #@r.take( #@r.size - 3 )\n" +
|
650
|
+
"elsif #@r.respond_to?( :[] ) then #@r[0...-3]\n" +
|
651
|
+
"else raise 'unable to #drop(1) or #[1...-3]' end"
|
652
|
+
start
|
653
|
+
end
|
654
|
+
|
655
|
+
# Extending these ideas to access *lists* of first/last few elements
|
656
|
+
# ********************************************************************
|
657
|
+
# Now we still miss the lists of first n and last n elements. Digits
|
658
|
+
# 0..4 will be used to refer to the lists of first 1, first 2, ...
|
659
|
+
# first 5 elements. Digits 9..5 will be used to refer to the lists of
|
660
|
+
# last 1, last 2, ... last 5 elements of the collection:
|
661
|
+
|
662
|
+
# '0' - [1st]
|
663
|
+
self.send :define_method, :'0' do
|
664
|
+
pipe_2_variable; start "#@r =\n" +
|
665
|
+
"if #@r.is_a?( Hash ) then Hash[@r.take(1)]\n" +
|
666
|
+
"elsif #@r.respond_to?( :take ) then #@r.take(1)\n" +
|
667
|
+
"elsif #@r.respond_to?( :[] ) then #@r[0..0]\n" +
|
668
|
+
"else raise 'unable to #take(1) or #[0..0]' end"
|
669
|
+
start
|
670
|
+
end
|
671
|
+
|
672
|
+
# '1' - [1st, 2nd]
|
673
|
+
self.send :define_method, :'1' do
|
674
|
+
pipe_2_variable; start "#@r =\n" +
|
675
|
+
"if #@r.is_a?( Hash ) then Hash[@r.take(2)]\n" +
|
676
|
+
"elsif #@r.respond_to?( :take ) then #@r.take(2)\n" +
|
677
|
+
"elsif #@r.respond_to?( :[] ) then #@r[0..1]\n" +
|
678
|
+
"else raise 'unable to #take(2) or #[0..1]' end"
|
679
|
+
start
|
680
|
+
end
|
681
|
+
|
682
|
+
# '2' - [1st, 2nd, 3rd]
|
683
|
+
self.send :define_method, :'2' do
|
684
|
+
pipe_2_variable; start "#@r =\n" +
|
685
|
+
"if #@r.is_a?( Hash ) then Hash[@r.take(3)]\n" +
|
686
|
+
"elsif #@r.respond_to?( :take ) then #@r.take(3)\n" +
|
687
|
+
"elsif #@r.respond_to?( :[] ) then #@r[0..2]\n" +
|
688
|
+
"else raise 'unable to #take(3) or #[0..2]' end"
|
689
|
+
start
|
690
|
+
end
|
691
|
+
|
692
|
+
# '3' - [1st, 2nd, 3rd, 4th]
|
693
|
+
self.send :define_method, :'3' do
|
694
|
+
pipe_2_variable; start "#@r =\n" +
|
695
|
+
"if #@r.is_a?( Hash ) then Hash[@r.take(4)]\n" +
|
696
|
+
"elsif #@r.respond_to?( :take ) then #@r.take(4)\n" +
|
697
|
+
"elsif #@r.respond_to?( :[] ) then #@r[0..3]\n" +
|
698
|
+
"else raise 'unable to #take(4) or #[0..3]' end"
|
699
|
+
start
|
700
|
+
end
|
701
|
+
|
702
|
+
# '4' - [1st, 2nd, 3rd, 4th, 5th]
|
703
|
+
self.send :define_method, :'4' do
|
704
|
+
pipe_2_variable; start "#@r =\n" +
|
705
|
+
"if #@r.is_a?( Hash ) then Hash[@r.take(5)]\n" +
|
706
|
+
"elsif #@r.respond_to?( :take ) then #@r.take(5)\n" +
|
707
|
+
"elsif #@r.respond_to?( :[] ) then #@r[0..4]\n" +
|
708
|
+
"else raise 'unable to #take(5) or #[0..4]' end"
|
709
|
+
start
|
710
|
+
end
|
711
|
+
|
712
|
+
# '5' - [-5th, -4th, -3rd, -2nd, -1st] (ie. last 5 elements)
|
713
|
+
self.send :define_method, :'5' do
|
714
|
+
pipe_2_variable; start "#@r =\n" +
|
715
|
+
"if #@r.is_a?( Hash ) then Hash[ @r.drop( #@r.size - 5 ) ]\n" +
|
716
|
+
"elsif #@r.respond_to?( :drop ) then #@r.drop( #@r.size - 5 )\n" +
|
717
|
+
"elsif #@r.respond_to?( :[] ) then #@r[-5..-1]\n" +
|
718
|
+
"else raise 'unable to take last 5 or call #[-5..-1]' end"
|
719
|
+
start
|
720
|
+
end
|
721
|
+
|
722
|
+
# '6' - [-4th, -3rd, -2nd, -1st] (ie. last 4 elements)
|
723
|
+
self.send :define_method, :'6' do
|
724
|
+
pipe_2_variable; start "#@r =\n" +
|
725
|
+
"if #@r.is_a?( Hash ) then Hash[ @r.drop( #@r.size - 4 ) ]\n" +
|
726
|
+
"elsif #@r.respond_to?( :drop ) then #@r.drop( #@r.size - 4 )\n" +
|
727
|
+
"elsif #@r.respond_to?( :[] ) then #@r[-4..-1]\n" +
|
728
|
+
"else raise 'unable to take last 4 or call #[-4..-1]' end"
|
729
|
+
start
|
730
|
+
end
|
731
|
+
|
732
|
+
# '7' - [-3rd, -2nd, -1st] (ie. last 3 elements)
|
733
|
+
self.send :define_method, :'7' do
|
734
|
+
pipe_2_variable; start "#@r =\n" +
|
735
|
+
"if #@r.is_a?( Hash ) then Hash[ @r.drop( #@r.size - 3 ) ]\n" +
|
736
|
+
"elsif #@r.respond_to?( :drop ) then #@r.drop( #@r.size - 3 )\n" +
|
737
|
+
"elsif #@r.respond_to?( :[] ) then #@r[-3..-1]\n" +
|
738
|
+
"else raise 'unable to take last 3 or call #[-3..-1]' end"
|
739
|
+
start
|
740
|
+
end
|
741
|
+
|
742
|
+
# '8' - [-3rd, -2nd] (ie. last 2 elements)
|
743
|
+
self.send :define_method, :'8' do
|
744
|
+
pipe_2_variable; start "#@r =\n" +
|
745
|
+
"if #@r.is_a?( Hash ) then Hash[ @r.drop( #@r.size - 2 ) ]\n" +
|
746
|
+
"elsif #@r.respond_to?( :drop ) then #@r.drop( #@r.size - 2 )\n" +
|
747
|
+
"elsif #@r.respond_to?( :[] ) then #@r[-2..-1]\n" +
|
748
|
+
"else raise 'unable to take last 2 or call #[-2..-1]' end"
|
749
|
+
start
|
750
|
+
end
|
751
|
+
|
752
|
+
# '9' - [-1st] (ie. an array with only the last collection element)
|
753
|
+
self.send :define_method, :'9' do
|
754
|
+
pipe_2_variable; start "#@r =\n" +
|
755
|
+
"if #@r.is_a?( Hash ) then Hash[ @r.drop( #@r.size - 1 ) ]\n" +
|
756
|
+
"elsif #@r.respond_to?( :drop ) then #@r.drop( #@r.size - 1 )\n" +
|
757
|
+
"elsif #@r.respond_to?( :[] ) then #@r[-1..-1]\n" +
|
758
|
+
"else raise 'unable to take last 1 or call #[-1..-1]' end"
|
759
|
+
start
|
760
|
+
end
|
761
|
+
|
762
|
+
# (Remark: In the method definitions above, the message sent to the
|
763
|
+
# PostfixMachine instance consist of a single digit. Due to the
|
764
|
+
# syntactic rules, it is not possible to define these methods with 'def'
|
765
|
+
# statement. Also, these methods cann be invoked only by explicit
|
766
|
+
# message passing. This limitation is fine for this particular usecase.)
|
767
|
+
|
768
|
+
# Controlling block writing
|
769
|
+
# ********************************************************************
|
770
|
+
# Certain command characters cause writing a block opening. This block
|
771
|
+
# has certain arity (1 or 2), and is closed either automatically closed
|
772
|
+
# at the end of the command character sequence, or it can be closed
|
773
|
+
# explicitly earlier.
|
774
|
+
|
775
|
+
# Next block arity 2 selection
|
776
|
+
def ²; block_2ary end
|
777
|
+
|
778
|
+
# Superscript i. Next block will have arity 2 and will be written with
|
779
|
+
# inverse parameter order.
|
780
|
+
def ⁱ; block_2ary_swapped end
|
781
|
+
|
782
|
+
# Explicit block closing.
|
783
|
+
def _
|
784
|
+
case @w # close block when in :block
|
785
|
+
when :block then
|
786
|
+
chain( close_block )
|
787
|
+
@w = :main if @rr.size == 1 unless @rr.empty?
|
788
|
+
else raise "'_' (close block) used when not in block" end
|
789
|
+
end
|
790
|
+
|
791
|
+
# Controlling the pipes
|
792
|
+
# ********************************************************************
|
793
|
+
def χ; case @w # swap registers when in :main
|
794
|
+
when :block then raise "'χ' (swap pipes) used when in block"
|
795
|
+
else exe "#@r, #{rSUCC} = #{rSUCC}, #@r" end
|
796
|
+
end
|
797
|
+
|
798
|
+
# Controlling the argument source
|
799
|
+
# ********************************************************************
|
800
|
+
# Pyper extends the car/cdr idea not just by adding more command
|
801
|
+
# letters, but also by allowing the methods triggered by these command
|
802
|
+
# letters to take arguments. Normally, 0 arity methods act only upon a
|
803
|
+
# single object: the method receiver present in the current
|
804
|
+
# pipeline. Higher arity methods, that require arguments, grab these
|
805
|
+
# arguments by default from the argument field supplied to the Pyper
|
806
|
+
# method (available as args local array variable). The argument source
|
807
|
+
# can also be redefined to something else. This is done by pushing the
|
808
|
+
# argument source prescription onto the write-time argument source stack
|
809
|
+
# (@argsrc instance variable of the PostfixMachine method writer). After
|
810
|
+
# this, the methods written by the command characters pop their argument
|
811
|
+
# sources as needed from the argument source stack.
|
812
|
+
#
|
813
|
+
# As already said, the default argument source is the argument list
|
814
|
+
# supplied to the Pyper method accessible at runtime as 'args' local
|
815
|
+
# variable. In the course of writing a method, PostfixMachine maintains
|
816
|
+
# the index (@arg_count PostfixMachine instance variable), pointing at
|
817
|
+
# position in the 'args' variable, from which the next argument will be
|
818
|
+
# taken. @arg_count is gradually incremented (at method write time) as
|
819
|
+
# the arguments are distributed from args variable to the internal
|
820
|
+
# methods in need of arguments. @arg_count does not apply at all at
|
821
|
+
# runtime, so for methods inside blocks, that are looped over many times
|
822
|
+
# at runtime, their arguments still come from the same position in the
|
823
|
+
# args array. This can be changed by switching on the 'shift' grab
|
824
|
+
# method: In this case, #shift method is called upon the argument source
|
825
|
+
# object, which normally cuts off and returns the current first element
|
826
|
+
# from a collection, which happens at runtime. (Examples needed.)
|
827
|
+
#
|
828
|
+
# Greek characters corresponding to the internal variable names of the
|
829
|
+
# Pyper method are used to manipulate the argument source stack:
|
830
|
+
|
831
|
+
# α pushes the primary pipeline (0) on the @argsrc stack:
|
832
|
+
def α; @argsrc.alpha end
|
833
|
+
# (Remark: Current pipe name is at the bottom of the @rr pipe stack)
|
834
|
+
|
835
|
+
# β pushes the secondary pipeline (1) on the @argsrc stack:
|
836
|
+
def β; @argsrc.beta end
|
837
|
+
# (Remark: SUCC hash tells us what the other pipe is, based on the
|
838
|
+
# current pipe name, seen on the *bottom* of the pipe stack @rr)
|
839
|
+
|
840
|
+
# γ refers to the successor pipe (SUCC[@rr[0]]), but as there are only
|
841
|
+
# two pipes, it is always the other pipe.
|
842
|
+
def γ; @argsrc.var rSUCC( @rr[0] ) end
|
843
|
+
|
844
|
+
# δ pushes the in-block pipeline delta on the @argsrc stack:
|
845
|
+
def δ; @argsrc.delta end
|
846
|
+
|
847
|
+
# ε, ζ push block arguments epsilon, resp. zeta on the @argsrc stack:
|
848
|
+
def ε; @argsrc.epsilon end
|
849
|
+
def ζ; @argsrc.zeta end
|
850
|
+
|
851
|
+
# ψ and ω respectively refer to the penultimate and last args element:
|
852
|
+
def ψ; @argsrc.psi end
|
853
|
+
def ω; @argsrc.omega end
|
854
|
+
|
855
|
+
# Lambda pushes onto the argument stack the default argument source, which
|
856
|
+
# is the argument list indexed with write-time @arg_count index:
|
857
|
+
def λ; @argsrc.args_counted end
|
858
|
+
|
859
|
+
# Capital omega pushes onto the argument stack whole 'args' variable
|
860
|
+
# (whole argument list), with 'shift' mode turned on by default:
|
861
|
+
def Ω; @argsrc.args end
|
862
|
+
|
863
|
+
# When inverted exclamation mark '¡' is used a prefix to the source
|
864
|
+
# selector, then rather then being pushed on the @argsrc stack, the new
|
865
|
+
# argument source replaces the topmost element of the stack. When the
|
866
|
+
# stack size is 1, this has the additional effect of setting the given
|
867
|
+
# argument source as default, until another such change happens, or
|
868
|
+
# stack reset is invoked.
|
869
|
+
def ¡α; @argsrc.alpha! end
|
870
|
+
def ¡β; @argsrc.beta! end
|
871
|
+
# def ¡γ; @argsrc.var! PRE[@rr[0]]
|
872
|
+
def ¡δ; @argsrc.delta! end
|
873
|
+
def ¡ε; @argsrc.epsilon! end
|
874
|
+
def ¡ζ; @argsrc.zeta! end
|
875
|
+
def ¡ψ; @argsrc.psi! end
|
876
|
+
def ¡ω; @argsrc.omega! end
|
877
|
+
def ¡λ; @argsrc.args_counted! end
|
878
|
+
def ¡Ω; @argsrc.args! end
|
879
|
+
|
880
|
+
# Small pi sets the 'dup' grab mode for the top @argsrc element:
|
881
|
+
def π; @argsrc.shift! end
|
882
|
+
|
883
|
+
# Small sigma sets the 'shift' grab mode for the top @argsrc element:
|
884
|
+
def σ; @argsrc.shift! end
|
885
|
+
|
886
|
+
# Small pi prefixed with inverted exclamation mark sets the 'ref'
|
887
|
+
# (default) grab mode for the top@argsrc element (naturally, turning off
|
888
|
+
# 'shift' or 'dup' mode).
|
889
|
+
def ¡π; @argsrc.ref! end
|
890
|
+
# Same for small sigma prefixed with inverted exclamation mark:
|
891
|
+
alias :¡σ :¡π
|
892
|
+
|
893
|
+
# Iota decrements the @arg_count index. If iota is used once, it causes
|
894
|
+
# that same argument is used twice. If iota is used repeatedly, pointer
|
895
|
+
# goes further back in the arg. ᴀ.
|
896
|
+
def ι; @arg_count -= 1 end
|
897
|
+
|
898
|
+
# Rho prefixed with inverted exclamation mark resets the @argsrc stack
|
899
|
+
# (to size 1, source: args_counted):
|
900
|
+
def ¡ρ; @am.std! end
|
901
|
+
|
902
|
+
# Remaining latin letters
|
903
|
+
# ********************************************************************
|
904
|
+
def g; @am.r rSUCC( @rr[0] ) end # arg. source: register (other)
|
905
|
+
def h; set "args" end # set pipe <- whole args array
|
906
|
+
# i:
|
907
|
+
def j; chain "join" end # nullary join
|
908
|
+
# k:
|
909
|
+
# l:
|
910
|
+
def m; nullary_m_with_block "map" end # All-important #map method
|
911
|
+
# n:
|
912
|
+
# o: prefix character
|
913
|
+
# p: ? recursive piper method, begin
|
914
|
+
# q: ? recursive piper method, end
|
915
|
+
# r:
|
916
|
+
# s: prefix character
|
917
|
+
# t: prefix character
|
918
|
+
|
919
|
+
# Latin capital letters
|
920
|
+
# ********************************************************************
|
921
|
+
def A; pipe_2_variable; start "Array(#@r)" end # Array( pipe )
|
922
|
+
def B; @take_block = true unless @take_block == :taken end # eat block
|
923
|
+
def C; paren end # explicit parenthesize
|
924
|
+
def D; exe "#@r = #@r.dup" end # self.dup
|
925
|
+
def E; exe "#{rSUCC} = #@r.dup" end # -> g
|
926
|
+
# L
|
927
|
+
def H; pipe_2_variable; start "Hash[#@r.zip(#{rSUCC})]" end
|
928
|
+
def J; unary_m "join" end # binary join
|
929
|
+
# L:
|
930
|
+
def M # Map zipped this and other register using binary block
|
931
|
+
block_2ary
|
932
|
+
pipe_2_variable
|
933
|
+
start "#@r.zip(#{rSUCC})"
|
934
|
+
nullary_m_with_block "map"
|
935
|
+
end
|
936
|
+
# M: occupied by map with binary block
|
937
|
+
|
938
|
+
# N:
|
939
|
+
# O: prefix character (ready to append literal)
|
940
|
+
# P: recursive piper method, begin
|
941
|
+
# Q: recursive piper method, end
|
942
|
+
def R # Reverse zip: Zip other and this register
|
943
|
+
pipe_2_variable
|
944
|
+
start "#{rSUCC}.zip(#@a)"
|
945
|
+
end
|
946
|
+
# S:
|
947
|
+
# T: prefix character
|
948
|
+
def U; end # unsh/prep self 2 reg (other changed)
|
949
|
+
def V; end # <</app self 2 reg (other changed)
|
950
|
+
def W # Map zipped other and this register using binary block
|
951
|
+
block_2ary # Mnemonic: W is inverted M
|
952
|
+
pipe_2_variable
|
953
|
+
start "#{rSUCC}.zip(#@r)"
|
954
|
+
nullary_m_with_block "map"
|
955
|
+
end
|
956
|
+
|
957
|
+
# W: occupied by map with reverse order binary block
|
958
|
+
# X:
|
959
|
+
# Y:
|
960
|
+
def Z # Zip this and other register
|
961
|
+
pipe_2_variable
|
962
|
+
start "#@r.zip(#{rSUCC})"
|
963
|
+
end
|
964
|
+
|
965
|
+
# Remaining Greek letters
|
966
|
+
# ********************************************************************
|
967
|
+
def ς; nullary_m "to_s" end
|
968
|
+
|
969
|
+
# Small caps
|
970
|
+
# ********************************************************************
|
971
|
+
def ᴇ; bin_op "==" end # equal
|
972
|
+
def ɪ; bin_op "||" end # memo: v is log. or
|
973
|
+
def ᴊ; unary_m "join" end
|
974
|
+
def ᴍ # Map in the other pipe
|
975
|
+
exe "#@r, #{rSUCC} = #{rSUCC}, #@r"
|
976
|
+
nullary_m_with_block "map"
|
977
|
+
exe "#@r, #{rSUCC} = #{rSUCC}, #@r"
|
978
|
+
end
|
979
|
+
def ᴘ # make a pair
|
980
|
+
pipe_2_variable
|
981
|
+
arg = grab_arg
|
982
|
+
start "[#@r, #{arg}]"
|
983
|
+
end
|
984
|
+
|
985
|
+
# Ternary operator
|
986
|
+
# ********************************************************************
|
987
|
+
# Guards in Pyper methods are provided by ( * ? * : * ) operator, using
|
988
|
+
# the following command characters:
|
989
|
+
|
990
|
+
# Question mark literal:
|
991
|
+
def ﹖; @pipe[-1] << " ? " end
|
992
|
+
# Colon literal:
|
993
|
+
def ﹕; @pipe[-1] << " : " end
|
994
|
+
# As binary method:
|
995
|
+
def ⁇; paren; @pipe[-1] << " ? ( #{grab_arg} ) : ( #{grab_arg} )" end
|
996
|
+
# Left part up to colon (included) as unary method:
|
997
|
+
def ⁈; @pipe[-1] << " ? ( #{grab_arg} ) : " end
|
998
|
+
# Right part from colon (included) on as unary method:
|
999
|
+
def ⁉; @pipe[-1] << " : ( #{grab_arg} )" end # ternary op. r. part
|
1000
|
+
|
1001
|
+
# Other special character methods
|
1002
|
+
# ********************************************************************
|
1003
|
+
|
1004
|
+
def ß; nullary_m "to_sym" end
|
1005
|
+
|
1006
|
+
# Adaptive prepend:
|
1007
|
+
def →
|
1008
|
+
pipe_2_variable; arg = grab_arg; start "#@r =\n" + # arg 2 self
|
1009
|
+
"if #@r.respond_to?( :unshift ) then #@r.unshift(#{arg})\n" +
|
1010
|
+
"elsif #@r.respond_to?( :prepend ) then #@r.prepend(#{arg})\n" +
|
1011
|
+
"elsif #@r.respond_to?( :merge ) and #@r.is_a?( Array ) " +
|
1012
|
+
"&& #@r.size == 2\nHash[*#@r].merge(#{arg})\n" +
|
1013
|
+
"elsif #@r.respond_to? :merge then #@r.merge(#{arg})\n" +
|
1014
|
+
"else raise 'impossible to unshift/prepend' end"
|
1015
|
+
start
|
1016
|
+
end
|
1017
|
+
|
1018
|
+
# Adaptive append:
|
1019
|
+
def ←
|
1020
|
+
pipe_2_variable
|
1021
|
+
arg = grab_arg
|
1022
|
+
start "#@r =\n" + # arg 2 self
|
1023
|
+
"if #@r.respond_to?( :<< ) then #@r << #{arg}\n" +
|
1024
|
+
"elsif #@r.respond_to?( :merge ) and #@r.is_a?(Array) " +
|
1025
|
+
"&& #@r.size == 2\n#{arg}.merge(Hash[*#@r])\n" +
|
1026
|
+
"elsif #@r.respond_to?( :merge ) then #{arg}.merge(#@r)\n" +
|
1027
|
+
"else raise 'impossible to <</append' end"
|
1028
|
+
start
|
1029
|
+
end
|
1030
|
+
# unsh. r to self, << r to self
|
1031
|
+
# And eight more with Array construct [a, b]
|
1032
|
+
# def w; @am.args! end # arg. source = whole args array (shift! on)
|
1033
|
+
# def x; pipe_2_variable; start( "#{rSUCC}.zip(#@r)" ) # zip other w. this
|
1034
|
+
|
1035
|
+
def «; set grab_arg end # grab argument into the current pipe
|
1036
|
+
def »; exe "args.unshift #@r" end # args.unshift from current pipe
|
1037
|
+
def ¡« # grab argument into the other pipe
|
1038
|
+
exe "#@r, #{rSUCC} = #{rSUCC}, #@r"
|
1039
|
+
set grab_arg
|
1040
|
+
exe "#@r, #{rSUCC} = #{rSUCC}, #@r"
|
1041
|
+
end
|
1042
|
+
def ¡»; exe "args.unshift #{rSUCC}" end # args.unshift from the other pipe
|
1043
|
+
|
1044
|
+
def ¿i; unary_m "include?" end
|
1045
|
+
def ●; nullary_m "compact" end # ji3 - compact
|
1046
|
+
|
1047
|
+
# Unary operators
|
1048
|
+
# ********************************************************************
|
1049
|
+
def ‹₊; unary_op "+" end # subscript +, +@ method
|
1050
|
+
def ‹₋; unary_op "-" end # subscript -, -@ method
|
1051
|
+
def ‹n; unary_op "not" end # double exclamation mark, not operator
|
1052
|
+
def ‹﹗; unary_op "!" end # small exclamation mark, !@ method
|
1053
|
+
|
1054
|
+
def ₊; bin_op "+" end # binary + as +() unary method
|
1055
|
+
def ₋; bin_op "-" end # binary - as -() unary method
|
1056
|
+
def ★; bin_op "*" end # binary * as *() unary method
|
1057
|
+
def ÷; bin_op "/" end # binary / as /() unary method
|
1058
|
+
def ﹡﹡; bin_op "**" end # binary ** as **() unary method
|
1059
|
+
def ﹪; bin_op "%" end # binary % as %() unary method
|
1060
|
+
|
1061
|
+
def ﹤; bin_op "<" end
|
1062
|
+
def ﹥; bin_op ">" end
|
1063
|
+
def ≤; bin_op "<=" end
|
1064
|
+
def ≥; bin_op ">=" end
|
1065
|
+
|
1066
|
+
def ﹫; @pipe[-1] << "[#{grab_arg}]" end # []
|
1067
|
+
def ﹦﹫; @pipe[-1] << "[#{grab_arg}] = #{grab_arg}" end # []=
|
1068
|
+
def ﹠; bin_op "&&" end # memo: x is log. mult.
|
1069
|
+
def ››; bin_op ">>" end # mnemonic: precedes <<
|
1070
|
+
def ‹‹; bin_op '<<' end # mnemonic: z is last
|
1071
|
+
|
1072
|
+
# Misc
|
1073
|
+
# ********************************************************************
|
1074
|
+
|
1075
|
+
# def ru; end # unsh/prep reg 2 self (this changed)
|
1076
|
+
# def rv; end # <</app reg 2 self (this changed)
|
1077
|
+
# def rU; end # unsh/prep reg 2 self (other changed)
|
1078
|
+
# def rV; end # <</app reg 2 self (other changed)
|
1079
|
+
|
1080
|
+
|
1081
|
+
|
1082
|
+
# def su; end # unsh/prep self 2 arg
|
1083
|
+
# def sv; end # <</app self 2 arg
|
1084
|
+
|
1085
|
+
# def sy; nullary_m "to_sym" end
|
1086
|
+
|
1087
|
+
# # sA: ? prependmap other, this, switch to other
|
1088
|
+
# # sB: ? appendmap other, this, switch to other
|
1089
|
+
|
1090
|
+
# def sU; end #
|
1091
|
+
# def sV; end
|
1092
|
+
|
1093
|
+
def ›i; nullary_m "to_i" end
|
1094
|
+
def ›A; pipe_2_variable; start "[#@r]" end # make a singleton array
|
1095
|
+
|
1096
|
+
|
1097
|
+
# Appending literals
|
1098
|
+
|
1099
|
+
def ﹕n; @pipe[-1] << "nil" end # nil literal
|
1100
|
+
def ﹕ς; @pipe[-1] << '' end # empty string literal
|
1101
|
+
def ﹕ᴀ; @pipe[-1] << '[]' end # empty array literal
|
1102
|
+
def ﹕ʜ; @pipe[-1] << '{}' end # empty hash literal
|
1103
|
+
|
1104
|
+
def ﹕₊; @pipe[-1] << ' + ' end # literal + waiting for another literal
|
1105
|
+
def ﹕₋; @pipe[-1] << ' - ' end # literal - waiting for another literal
|
1106
|
+
def ﹕★; @pipe[-1] << ' * ' end # literal * waiting for another literal
|
1107
|
+
def ﹕÷; @pipe[-1] << ' / ' end # literal / waiting for another literal
|
1108
|
+
def ﹕﹪; @pipe[-1] << ' % ' end # literal % waiting for another literal
|
1109
|
+
def ﹦﹦; @pipe[-1] << ' == ' end # literal == waiting for another literal
|
1110
|
+
def ﹕﹤; @pipe[-1] << ' < ' end # literal < waiting for another literal
|
1111
|
+
def ﹕«; @pipe[-1] << ' << ' end # literal << waiting for another literal
|
1112
|
+
def ﹕»; @pipe[-1] << ' >> ' end # literal >> waiting for another literal
|
1113
|
+
|
1114
|
+
# Digit literals
|
1115
|
+
def ₀; @pipe[-1] << "0" end
|
1116
|
+
def ₁; @pipe[-1] << "1" end
|
1117
|
+
def ₂; @pipe[-1] << "2" end
|
1118
|
+
def ₃; @pipe[-1] << "3" end
|
1119
|
+
def ₄; @pipe[-1] << "4" end
|
1120
|
+
def ₅; @pipe[-1] << "5" end
|
1121
|
+
def ₆; @pipe[-1] << "6" end
|
1122
|
+
def ₇; @pipe[-1] << "7" end
|
1123
|
+
def ₈; @pipe[-1] << "8" end
|
1124
|
+
def ₉; @pipe[-1] << "9" end
|
1125
|
+
|
1126
|
+
# Clear the current pipe (set to empty string):
|
1127
|
+
def ∅; set "" end
|
1128
|
+
alias :⊘ :∅ # similarly looking circled slash
|
1129
|
+
alias :ø :∅ # similarly looking Danish ø
|
1130
|
+
end # class PostfixMachine
|
1131
|
+
end # module Pyper
|