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.
@@ -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
@@ -0,0 +1,20 @@
1
+ *~
2
+ .#*
3
+ \#*#
4
+ *.gem
5
+ *.rbc
6
+ .bundle
7
+ .config
8
+ .yardoc
9
+ Gemfile.lock
10
+ InstalledFiles
11
+ _yardoc
12
+ coverage
13
+ doc/
14
+ lib/bundler/man
15
+ pkg
16
+ rdoc
17
+ spec/reports
18
+ test/tmp
19
+ test/version_tmp
20
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in pyper.gemspec
4
+ gemspec
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.
@@ -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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -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