pyper 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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