pyper 1.0.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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