pyper 1.0.1 → 2.0.0

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