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 +4 -4
- data/lib/pyper.rb +83 -1022
- data/lib/pyper/control_characters.rb +22 -0
- data/lib/pyper/control_characters/cadr_like.rb +241 -0
- data/lib/pyper/control_characters/greek_letters.rb +142 -0
- data/lib/pyper/control_characters/other.rb +379 -0
- data/lib/pyper/control_characters/other_latin_letters.rb +228 -0
- data/lib/pyper/default_includes.rb +46 -0
- data/lib/pyper/postfix_machine.rb +442 -0
- data/lib/pyper/postfix_machine/argument_source.rb +205 -0
- data/lib/pyper/version.rb +2 -2
- data/test/pyper_test.rb +45 -47
- metadata +9 -1
@@ -0,0 +1,228 @@
|
|
1
|
+
#coding: utf-8
|
2
|
+
|
3
|
+
module Pyper::ControlCharacters
|
4
|
+
# Current pipe is assigned with the argument from the argument source.
|
5
|
+
#
|
6
|
+
def g
|
7
|
+
set grab_arg
|
8
|
+
end
|
9
|
+
|
10
|
+
# Other pipe is assigned with the argument from the argument source.
|
11
|
+
#
|
12
|
+
def h
|
13
|
+
exe "#@r, #{successor_register(@r)} = #{successor_register(@r)}, #@r"
|
14
|
+
set grab_arg
|
15
|
+
exe "#@r, #{successor_register(@r)} = #{successor_register(@r)}, #@r"
|
16
|
+
end
|
17
|
+
|
18
|
+
# Compiler directive that decrements the @arg_count index. With +:args_counted+
|
19
|
+
# argument source, when used once, causes the same argument to be taken twice.
|
20
|
+
# When used twice or more, causes the same number of argument array elements to
|
21
|
+
# be taken twice.
|
22
|
+
#
|
23
|
+
def j
|
24
|
+
@arg_count -= 1
|
25
|
+
end
|
26
|
+
|
27
|
+
# k:
|
28
|
+
|
29
|
+
# l:
|
30
|
+
|
31
|
+
# def m; nullary_method_with_block "map" end # All-important #map method
|
32
|
+
|
33
|
+
def m
|
34
|
+
pipe_2_variable
|
35
|
+
start "if #@r.is_a? String then #@r = #@r.each_char end\n"
|
36
|
+
start
|
37
|
+
nullary_method_with_block "map"
|
38
|
+
end
|
39
|
+
|
40
|
+
# n:
|
41
|
+
# o: prefix character
|
42
|
+
|
43
|
+
# Adaptive prepend.
|
44
|
+
#
|
45
|
+
def p
|
46
|
+
pipe_2_variable; arg = grab_arg; start "#@r =\n" + # arg 2 self
|
47
|
+
"if #@r.respond_to?( :unshift ) then #@r.unshift(#{arg})\n" +
|
48
|
+
"elsif #@r.respond_to?( :prepend ) then #@r.prepend(#{arg})\n" +
|
49
|
+
"elsif #@r.respond_to?( :merge ) and #@r.is_a?( Array ) " +
|
50
|
+
"&& #@r.size == 2\nHash[*#@r].merge(#{arg})\n" +
|
51
|
+
"elsif #@r.respond_to? :merge then #@r.merge(#{arg})\n" +
|
52
|
+
"else raise 'impossible to unshift/prepend' end"
|
53
|
+
start
|
54
|
+
end
|
55
|
+
|
56
|
+
# Adaptive append.
|
57
|
+
#
|
58
|
+
def q
|
59
|
+
pipe_2_variable
|
60
|
+
arg = grab_arg
|
61
|
+
start "#@r =\n" + # arg 2 self
|
62
|
+
"if #@r.respond_to?( :<< ) then #@r << #{arg}\n" +
|
63
|
+
"elsif #@r.respond_to?( :merge ) and #@r.is_a?(Array) " +
|
64
|
+
"&& #@r.size == 2\n#{arg}.merge(Hash[*#@r])\n" +
|
65
|
+
"elsif #@r.respond_to?( :merge ) then #{arg}.merge(#@r)\n" +
|
66
|
+
"else raise 'impossible to <</append' end"
|
67
|
+
start
|
68
|
+
end
|
69
|
+
|
70
|
+
# Pushes the secondary register (beta) onto the argument source stack.
|
71
|
+
#
|
72
|
+
def r
|
73
|
+
@argsrc.beta
|
74
|
+
end
|
75
|
+
|
76
|
+
# Sends the argument from the argument source to the current pipeline.
|
77
|
+
#
|
78
|
+
def s
|
79
|
+
pipe_2_variable
|
80
|
+
start "#@r.send( *(#{grab_arg}) )"
|
81
|
+
end
|
82
|
+
|
83
|
+
# t: prefix character
|
84
|
+
|
85
|
+
# Latin capital letters
|
86
|
+
# ********************************************************************
|
87
|
+
# Applies +#Array+ method to the register.
|
88
|
+
#
|
89
|
+
def A
|
90
|
+
pipe_2_variable
|
91
|
+
start "Array( #@r )"
|
92
|
+
end
|
93
|
+
alias Α A # Greek Α, looks the same, different char
|
94
|
+
|
95
|
+
# Makes the next block-enabled method receive the block that was supplied to
|
96
|
+
# the Pyper method.
|
97
|
+
#
|
98
|
+
def B
|
99
|
+
@take_block = true unless @take_block == :taken
|
100
|
+
end
|
101
|
+
|
102
|
+
# Explicit parenthesizing of the current pipeline.
|
103
|
+
#
|
104
|
+
def C
|
105
|
+
parenthesize_current_pipeline
|
106
|
+
end
|
107
|
+
|
108
|
+
# Explicit dup-ping of the current pipeline (Giving up the original object
|
109
|
+
# and working with its +#dup+ instead.)
|
110
|
+
#
|
111
|
+
def D
|
112
|
+
exe "#@r = #@r.dup"
|
113
|
+
end
|
114
|
+
|
115
|
+
# Equals operator (+:==+).
|
116
|
+
#
|
117
|
+
def E
|
118
|
+
binary_operator "=="
|
119
|
+
end
|
120
|
+
|
121
|
+
# F:
|
122
|
+
|
123
|
+
# Ungrab the argument (unshift from pipeline).
|
124
|
+
#
|
125
|
+
def G
|
126
|
+
exe "args.unshift #@r"
|
127
|
+
end
|
128
|
+
|
129
|
+
# A combo that switches this and other register, ungrabs the argument,
|
130
|
+
# and sets the other register as the argument source.
|
131
|
+
#
|
132
|
+
def H
|
133
|
+
X()
|
134
|
+
β()
|
135
|
+
end
|
136
|
+
|
137
|
+
# Braces method, +#[]+.
|
138
|
+
#
|
139
|
+
def I
|
140
|
+
@pipe[-1] << "[#{grab_arg}]"
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
# I:
|
145
|
+
|
146
|
+
# +#join+ expecting argument.
|
147
|
+
#
|
148
|
+
def J
|
149
|
+
unary_method "join"
|
150
|
+
end
|
151
|
+
|
152
|
+
# K:
|
153
|
+
|
154
|
+
# L:
|
155
|
+
|
156
|
+
# Zip this and other register and invoke +#map+ with a binary block on it.
|
157
|
+
#
|
158
|
+
def M
|
159
|
+
next_block_will_be_binary
|
160
|
+
pipe_2_variable
|
161
|
+
start "#@r.zip(#{successor_register(@r)})"
|
162
|
+
nullary_method_with_block "map"
|
163
|
+
end
|
164
|
+
|
165
|
+
# N:
|
166
|
+
|
167
|
+
# Map this register with object from the other register as the starting
|
168
|
+
# contents of the delta pipeline, and epsilon as the argument source.
|
169
|
+
#
|
170
|
+
def O
|
171
|
+
r()
|
172
|
+
m()
|
173
|
+
g()
|
174
|
+
ε()
|
175
|
+
end
|
176
|
+
|
177
|
+
# P: recursive piper method, begin
|
178
|
+
|
179
|
+
# Q: recursive piper method, end
|
180
|
+
|
181
|
+
def R # Reverse zip: Zip other and this register
|
182
|
+
pipe_2_variable
|
183
|
+
start "#{successor_register}.zip(#@a)"
|
184
|
+
end
|
185
|
+
|
186
|
+
# S
|
187
|
+
|
188
|
+
# Ternary operator as binary method with the current pipeline as the receiver.
|
189
|
+
#
|
190
|
+
def T
|
191
|
+
parenthesize_current_pipeline
|
192
|
+
@pipe[-1] << " ? ( #{grab_arg} ) : ( #{grab_arg} )"
|
193
|
+
end
|
194
|
+
|
195
|
+
# Unshift / prepend this register to the other register.
|
196
|
+
#
|
197
|
+
def U; end # unsh/prep self 2 reg (other changed)
|
198
|
+
|
199
|
+
def V; end # <</app self 2 reg (other changed)
|
200
|
+
|
201
|
+
# Map zipped other and this register using binary block.
|
202
|
+
#
|
203
|
+
def W
|
204
|
+
next_block_will_be_binary # Mnemonic: W is inverted M
|
205
|
+
pipe_2_variable
|
206
|
+
start "#{successor_register(@r)}.zip(#@r)"
|
207
|
+
nullary_method_with_block "map"
|
208
|
+
end
|
209
|
+
|
210
|
+
# Swaps the contents of the primary (alpha) and the secondary (beta) register.
|
211
|
+
#
|
212
|
+
def X
|
213
|
+
case @w
|
214
|
+
when :block then raise "'χ' (swap pipes) may not be used in blocks!"
|
215
|
+
else
|
216
|
+
exe "#@r, #{successor_register} = #{successor_register}, #@r"
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
# Y:
|
221
|
+
|
222
|
+
# Zip this and other register.
|
223
|
+
#
|
224
|
+
def Z
|
225
|
+
pipe_2_variable
|
226
|
+
start "#@r.zip(#{successor_register})"
|
227
|
+
end
|
228
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class Object
|
4
|
+
# Method π is defined on Object, that enables Pyper method calls.
|
5
|
+
#
|
6
|
+
def π
|
7
|
+
tap do |o|
|
8
|
+
begin
|
9
|
+
o.singleton_class
|
10
|
+
rescue TypeError
|
11
|
+
o.class
|
12
|
+
end.class_exec { include Pyper }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Pyper is by default included in Enumerable.
|
18
|
+
#
|
19
|
+
module Enumerable
|
20
|
+
include Pyper
|
21
|
+
end
|
22
|
+
|
23
|
+
# Pyper is by default included in Array.
|
24
|
+
#
|
25
|
+
class Array
|
26
|
+
include Pyper
|
27
|
+
end
|
28
|
+
|
29
|
+
# Pyper is by default included in Hash.
|
30
|
+
#
|
31
|
+
class Hash
|
32
|
+
include Pyper
|
33
|
+
end
|
34
|
+
|
35
|
+
# Pyper is by default included in Range.
|
36
|
+
#
|
37
|
+
class Range
|
38
|
+
include Pyper
|
39
|
+
end
|
40
|
+
|
41
|
+
# Pyper is by default included in String.
|
42
|
+
#
|
43
|
+
class String
|
44
|
+
include Pyper
|
45
|
+
end
|
46
|
+
|
@@ -0,0 +1,442 @@
|
|
1
|
+
#coding: utf-8
|
2
|
+
|
3
|
+
require_relative 'postfix_machine/argument_source'
|
4
|
+
require_relative 'control_characters'
|
5
|
+
|
6
|
+
# PostfixMachine is a simple compiler of Pyper method symbols. Each written
|
7
|
+
# method has two pipelines: 'alpha' (no. 0) and 'beta' (no. 1). Variables
|
8
|
+
# 'alpha' and 'beta' are local to the main scope of a Pyper method.
|
9
|
+
#
|
10
|
+
# When blocks are used inside a Pyper method, variable 'delta' local to the
|
11
|
+
# block is used to hold the pipeline inside the block. For blocks with arity 1,
|
12
|
+
# variable named 'epsilon' is used to hold the block argument. For blocks with
|
13
|
+
# arity 2, variables named 'epsilon', resp. 'zeta' are used to hold 1st, resp.
|
14
|
+
# 2nd block argument. Blocks with arity higher than 2 are not allowed in Pyper
|
15
|
+
# methods. (However, Pyper methods may receive external block of arbitrary
|
16
|
+
# construction.)
|
17
|
+
#
|
18
|
+
class Pyper::PostfixMachine
|
19
|
+
include Pyper::ControlCharacters
|
20
|
+
|
21
|
+
SUCC = { alpha: :beta, beta: :alpha, α: :β, β: :α } # successor table
|
22
|
+
PRE = { alpha: :beta, beta: :alpha, α: :β, β: :α } # predecessor table
|
23
|
+
|
24
|
+
# Template for the def line of the method being written:
|
25
|
+
DEF_LINE = -> name { "def #{name}( *args, &block )" }
|
26
|
+
|
27
|
+
# PostfixMachine init. Requires the command string as an argument -- yes,
|
28
|
+
# for each command string, a new PostfixMachine is instantiated.
|
29
|
+
#
|
30
|
+
def initialize command_string
|
31
|
+
@cmds = parse_command_string( command_string.to_s )
|
32
|
+
end
|
33
|
+
|
34
|
+
# Algorithmically writes a Ruby method, whose name is given in the first
|
35
|
+
# argument. The options hash expects 2 named arguments -- +:op+ and +:ret+:
|
36
|
+
#
|
37
|
+
# op: when 1 (single pipe), makes no assumption about the receiver
|
38
|
+
# When 2 (twin pipe), assumes the receiver is a size 2 array,
|
39
|
+
# consisting of pipes alpha, beta
|
40
|
+
# When -2 (twin pipe with a swap), assumes the same as above and
|
41
|
+
# swaps the pipes immediately (alpha, beta = beta, alpha)
|
42
|
+
#
|
43
|
+
# ret: when 1 (single return value), returns the current pipe only
|
44
|
+
# when 2 (return both pipes), returns size 2 array, consisting
|
45
|
+
# of pipes a, b
|
46
|
+
# when -2 (return both pipes with a swap), returns size 2 array
|
47
|
+
# containing the pipes' results in reverse order [b, a]
|
48
|
+
#
|
49
|
+
def compile( method_name, op: 1, ret: 1 )
|
50
|
+
@opts = { op: op, ret: ret }.tap do |oo|
|
51
|
+
oo.define_singleton_method :op do self[:op] end
|
52
|
+
oo.define_singleton_method :ret do self[:ret] end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Set up compile-time argument sourcing.
|
56
|
+
@argsrc = ArgumentSource.new
|
57
|
+
|
58
|
+
# Write the method skeleton.
|
59
|
+
initialize_writer_state
|
60
|
+
write_method_head_skeleton( method_name )
|
61
|
+
write_initial_pipeline
|
62
|
+
write_method_tail_skeleton
|
63
|
+
|
64
|
+
# Now that we have the skeleton, let's write the meat.
|
65
|
+
write_method_meat
|
66
|
+
|
67
|
+
puts "head is #@head\npipe is #@pipe\ntail is #@tail" if Pyper::DEBUG > 1
|
68
|
+
|
69
|
+
# Finally, close any blocks and return
|
70
|
+
autoclose_open_blocks_and_return
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
# Converts a command string into a command array.
|
76
|
+
#
|
77
|
+
def parse_command_string( arg )
|
78
|
+
return arg if arg.kind_of? Array # assume no work needed
|
79
|
+
# Otherwise, assume arg is a ς and split it using #each_char
|
80
|
+
arg.to_s.each_char.with_object [] do |char, memo|
|
81
|
+
# Handle prefix characters:
|
82
|
+
( PREFIX_CHARACTERS.include?(memo[-1]) ? memo[-1] : memo ) << char
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Initializes method writing flags / state keepers.
|
87
|
+
#
|
88
|
+
def initialize_writer_state
|
89
|
+
# set current pipeline to :alpha (pipeline 0)
|
90
|
+
@r = :alpha
|
91
|
+
|
92
|
+
# set current pipe stack to [@r]
|
93
|
+
@rr = [@r]
|
94
|
+
# (Pipeline stack is needed due to tha fact, that blocks are allowed
|
95
|
+
# inside a Pyper method. At method write time, every time a block is
|
96
|
+
# open, block pipeline symbol is pushed onto this stack.)
|
97
|
+
|
98
|
+
# where are we? flag (whether in :main or :block) set to :main
|
99
|
+
@w = :main
|
100
|
+
|
101
|
+
# argument counter (for args dispensing to the individual methods)
|
102
|
+
@arg_count = 0
|
103
|
+
|
104
|
+
# signal to pass the supplied block to the next method
|
105
|
+
@take_block = false
|
106
|
+
|
107
|
+
# arity flag for next block to be written, default is 1
|
108
|
+
@block_arity = 1
|
109
|
+
end
|
110
|
+
|
111
|
+
# Writes the skeleton of the method header.
|
112
|
+
#
|
113
|
+
def write_method_head_skeleton( ɴ )
|
114
|
+
@head = [ [ DEF_LINE.( ɴ ) ] ] # write first line "def ɴ..."
|
115
|
+
write "\n"
|
116
|
+
# write validation line (written only when @opts[:op] == 2)
|
117
|
+
write "raise 'Receiver must be a size 2 array when double-piping!'" +
|
118
|
+
"unless self.kind_of?( Array ) and self.size == 2\n" if
|
119
|
+
@opts.op == 2
|
120
|
+
# 'main_opener' (global)
|
121
|
+
write @main_opener = ""
|
122
|
+
# 'opener' (local to block)
|
123
|
+
write opener = ""
|
124
|
+
@opener = [ opener ]
|
125
|
+
end
|
126
|
+
|
127
|
+
# Initializes the pipeline (@pipe).
|
128
|
+
#
|
129
|
+
def write_initial_pipeline
|
130
|
+
@pipe = case @opts.op
|
131
|
+
when 1 then [ "self" ] # use receiver (default)
|
132
|
+
when 2 then # use alpha, beta = self[0], self[1]
|
133
|
+
@alpha_touched = @beta_touched = true
|
134
|
+
write "\n( alpha, beta = self[0], self[1]; alpha)\n"
|
135
|
+
[ "alpha" ] # pipe 0 aka. primary pipe
|
136
|
+
when -2 then # use alpha, beta = self[1], self[0]
|
137
|
+
@alpha_touched = @beta_touched = true
|
138
|
+
write "\n( alpha, beta = self[1], self[0]; alpha)\n"
|
139
|
+
[ "alpha" ] # pipe 0 aka. primary pipe
|
140
|
+
end # self compliance tested in the written method itself
|
141
|
+
write "\n"; write @pipe[-1] # make @pipe part of @head
|
142
|
+
end
|
143
|
+
|
144
|
+
# Write the skeleton of the tail part of the method, consisting
|
145
|
+
# of the finisher line, returner line, and end statement itself.
|
146
|
+
#
|
147
|
+
def write_method_tail_skeleton
|
148
|
+
finisher = String.new # 'finisher' (local to block)
|
149
|
+
@finisher = [ finisher ]
|
150
|
+
@returner = case @opts.ret # 'returner' (global finisher)
|
151
|
+
when 1 then ""
|
152
|
+
when 2 then alpha_touch; beta_touch; "return alpha, beta"
|
153
|
+
when -2 then alpha_touch; beta_touch; "return beta, alpha"
|
154
|
+
else raise "wrong @opts[:fin] value: #{@opts.fin}" end
|
155
|
+
@tail = [ [ finisher, "\n", @returner, "\n", "end" ] ] # end line
|
156
|
+
end
|
157
|
+
|
158
|
+
# This consists of taking the atomic commands from @cmds array one by
|
159
|
+
# one and calling the command method to write a small piece of the
|
160
|
+
# program implied by the command.
|
161
|
+
#
|
162
|
+
def write_method_meat
|
163
|
+
while not @cmds.empty?
|
164
|
+
# First, slice off the next command from @cmds array
|
165
|
+
cmd = @cmds.shift
|
166
|
+
|
167
|
+
# puts "doing command #{cmd}, @r is #@r, @head is #@head" # DEBUG
|
168
|
+
# puts "doing command #{cmd}, @argsrc is #@argsrc" # DEBUG
|
169
|
+
|
170
|
+
# Take the block (if not taken) if this is the last command
|
171
|
+
|
172
|
+
@take_block = true unless @take_block == :taken if @cmds.size <= 0
|
173
|
+
# Now send the command to self. Commands are implemented as
|
174
|
+
# methods of Pyper::PostfixMachine with one or two-character
|
175
|
+
# names. These methods then take care of writing the program
|
176
|
+
# pieces implied by these commands. Side effects of this is, that
|
177
|
+
# one- and two-character local variables should be avoided inside
|
178
|
+
# whole PostfixMachine class.
|
179
|
+
# puts "about to self.send( #@w, #{cmd} )" # DEBUG
|
180
|
+
self.send @w, cmd
|
181
|
+
pipe_2_variable if @cmds.size <= 0
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# After we run out of atomic commands, it's time to finalize the
|
186
|
+
# program by closing any blocks still left open. Metod #close_block
|
187
|
+
# called by this method actually produces the program string out of
|
188
|
+
# each block it closes, so this method actually returns the program
|
189
|
+
# string of whole newly written Pyper method.
|
190
|
+
#
|
191
|
+
def autoclose_open_blocks_and_return
|
192
|
+
( rslt = close_block; chain rslt; pipe_2_variable ) while @head.size > 1
|
193
|
+
return close_block
|
194
|
+
end
|
195
|
+
|
196
|
+
# Called to close a block, including the main def.
|
197
|
+
#
|
198
|
+
def close_block
|
199
|
+
unless @rr.empty? then @r = @rr.pop end # back with the register
|
200
|
+
@pipe.pop; @opener.pop; @finisher.pop # pop the writing stack
|
201
|
+
( @head.pop + @tail.pop ).join # join head and tail
|
202
|
+
end
|
203
|
+
|
204
|
+
# Writer of argument grab strings.
|
205
|
+
#
|
206
|
+
def grab_arg
|
207
|
+
msg = "Invalid argument source!"
|
208
|
+
@argsrc.source.size == @argsrc.grab_method.size or fail ArgumentError, msg
|
209
|
+
grab = case @argsrc.grab_method.last
|
210
|
+
when :shift then ".shift"
|
211
|
+
when :ref then ""
|
212
|
+
when :dup then ".dup"
|
213
|
+
else
|
214
|
+
msg = "Unknown arg. grab method #{@argsrc.grab_method.last}!"
|
215
|
+
fail ArgumentError, msg
|
216
|
+
end
|
217
|
+
case @argsrc.source.last
|
218
|
+
when :args_counted
|
219
|
+
x = ( @arg_count += 1 ) - 1
|
220
|
+
"args[#{x}]" + grab
|
221
|
+
when :args then # now this is a bit difficult, cause
|
222
|
+
case @argsrc.grab_method.last # it's necessary to discard the used
|
223
|
+
when :shift then # args (shift #@arg_count):
|
224
|
+
if @arg_count == 0 then
|
225
|
+
"args.shift"
|
226
|
+
else
|
227
|
+
"(args.shift(#@arg_count); args.shift)"
|
228
|
+
end
|
229
|
+
when :ref then "args"
|
230
|
+
else fail TypeError, "Unknown grab method #{@argsrc.grab_method.last}!"
|
231
|
+
end
|
232
|
+
when :alpha then alpha_touch; 'alpha' + grab
|
233
|
+
when :beta then beta_touch; 'beta' + grab
|
234
|
+
when :delta, :epsilon, :zeta then @argsrc.source.last.to_s + grab
|
235
|
+
when :psi then "args[-2]" + grab
|
236
|
+
when :omega then "args[-1]" + grab
|
237
|
+
else fail TypeError, "Unknown argument source #{@argsrc.src.last}!"
|
238
|
+
end.tap do
|
239
|
+
if @argsrc.source.size > 1 then
|
240
|
+
@argsrc.source.pop
|
241
|
+
@argsrc.grab_method.pop
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
# Execution method when in the main def (@w == :main).
|
247
|
+
#
|
248
|
+
def main( cmd )
|
249
|
+
self.send( cmd )
|
250
|
+
end
|
251
|
+
|
252
|
+
# Execution method when in a block (@w == :block).
|
253
|
+
#
|
254
|
+
def block( cmd )
|
255
|
+
self.send( cmd )
|
256
|
+
end
|
257
|
+
|
258
|
+
# *** Method writing subroutines.
|
259
|
+
|
260
|
+
# Active register reader.
|
261
|
+
#
|
262
|
+
def _r_
|
263
|
+
@r
|
264
|
+
end
|
265
|
+
|
266
|
+
# Append string to head.
|
267
|
+
#
|
268
|
+
def write( x )
|
269
|
+
Array( x ).each {|e| @head[-1] << e }
|
270
|
+
end
|
271
|
+
|
272
|
+
# Chain (nullary) method string to the end of the pipe.
|
273
|
+
#
|
274
|
+
def chain( s )
|
275
|
+
@pipe[-1] << ".#{s}"
|
276
|
+
end
|
277
|
+
|
278
|
+
# Suck the pipe into the "memory" ⌿- assign its contents to the designated
|
279
|
+
# register of the current pipelene.
|
280
|
+
#
|
281
|
+
def pipe_2_variable
|
282
|
+
@pipe[-1].prepend "#@r = "
|
283
|
+
eval "#{@r}_touched = true"
|
284
|
+
end
|
285
|
+
|
286
|
+
# Start a new pipe, on a new line. Without arguments, @r is used.
|
287
|
+
#
|
288
|
+
def start( s = "#@r" )
|
289
|
+
write "\n"; @pipe[-1] = s
|
290
|
+
write @pipe.last
|
291
|
+
end
|
292
|
+
|
293
|
+
# Set the pipe to a value, discarding current contents.
|
294
|
+
#
|
295
|
+
def set( s )
|
296
|
+
@pipe[-1].clear << s
|
297
|
+
end
|
298
|
+
|
299
|
+
# Store in active register, and continue in a new pipeline.
|
300
|
+
#
|
301
|
+
def belay
|
302
|
+
pipe_2_variable
|
303
|
+
start
|
304
|
+
end
|
305
|
+
|
306
|
+
# Perform pipe_2_variable, execute something else, and go back to @r.
|
307
|
+
#
|
308
|
+
def exe( s )
|
309
|
+
pipe_2_variable; start s; start
|
310
|
+
end
|
311
|
+
|
312
|
+
# Parethesizes the current pipeline.
|
313
|
+
#
|
314
|
+
def parenthesize_current_pipeline
|
315
|
+
@pipe[-1].prepend("( ") << " )"
|
316
|
+
end
|
317
|
+
|
318
|
+
# Write binary operator.
|
319
|
+
#
|
320
|
+
def binary_operator( s, x = grab_arg )
|
321
|
+
@pipe[-1] << " #{s} " << x
|
322
|
+
end
|
323
|
+
|
324
|
+
# Write unary operator.
|
325
|
+
#
|
326
|
+
def unary_operator( s )
|
327
|
+
parenthesize_current_pipeline
|
328
|
+
@pipe[-1].prepend s
|
329
|
+
end
|
330
|
+
|
331
|
+
# Returns nothing, or optional block, if flagged to do so.
|
332
|
+
#
|
333
|
+
def maybe_block
|
334
|
+
case @take_block
|
335
|
+
when true then @take_block = :taken; '&block'
|
336
|
+
when nil, false, :taken then nil
|
337
|
+
else
|
338
|
+
fail TypeError, "unexpected @take_block value"
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
# Chain unary method.
|
343
|
+
#
|
344
|
+
def nullary_method( s )
|
345
|
+
chain "#{s}(#{maybe_block})"
|
346
|
+
end
|
347
|
+
|
348
|
+
# Chain unary mathod.
|
349
|
+
#
|
350
|
+
def unary_method( s, x = grab_arg )
|
351
|
+
chain "#{s}( #{[x, maybe_block].compact.join(", ")} )"
|
352
|
+
end
|
353
|
+
|
354
|
+
# Chain binary method.
|
355
|
+
#
|
356
|
+
def binary_method( s, x = grab_arg, y = grab_arg )
|
357
|
+
chain "#{s}( #{[x, y, maybe_block].compact.join(", ")} )"
|
358
|
+
end
|
359
|
+
|
360
|
+
# Initiates writing a block method.
|
361
|
+
#
|
362
|
+
def nullary_method_with_block( str )
|
363
|
+
# puts "in nullary_m_with_block, str = #{str}" # DEBUG
|
364
|
+
if @take_block == true then
|
365
|
+
nullary_method( str )
|
366
|
+
else # code a block
|
367
|
+
@w = :block # change writing method
|
368
|
+
belay # a must before block opening
|
369
|
+
# push a new pipe, head and tail to the writing stack:
|
370
|
+
@rr.empty? ? ( @rr = [@r] ) : ( @rr.push @r ) # store the register
|
371
|
+
@r = :delta # a block runs in its own unswitchable register delta
|
372
|
+
@pipe << String.new # push pipe
|
373
|
+
# puts "@pipe is << #@pipe >>" # DEBUG
|
374
|
+
@head << case @block_arity # push head
|
375
|
+
when 0 then [ "#{str} { " ]
|
376
|
+
when 1 then set "delta"; [ "#{str} { |epsilon|" ]
|
377
|
+
when 2 then @argsrc.zeta; @argsrc.ref!
|
378
|
+
set "delta"; [ "#{str} { |epsilon, zeta|" ]
|
379
|
+
when -2 then @argsrc.epsilon; @argsrc.ref!
|
380
|
+
set "delta"; [ "#{str} { |epsilon, zeta|" ]
|
381
|
+
else raise "Unknown @block_arity: #@block_arity"
|
382
|
+
end
|
383
|
+
write "\n"
|
384
|
+
opener = case @block_arity; when 0 then "";
|
385
|
+
when 1, 2 then "delta = epsilon"
|
386
|
+
when -2 then "delta = zeta" end
|
387
|
+
@opener << opener # push opener
|
388
|
+
@block_arity = 1 # after use, set block arity flag back to default
|
389
|
+
# puts "@pipe is << #@pipe >>" # DEBUG
|
390
|
+
write opener; write "\n"; write @pipe.last
|
391
|
+
finisher = String.new
|
392
|
+
@finisher << finisher # push finisher
|
393
|
+
@tail << [ "\n" ] # push tail
|
394
|
+
@tail.last << finisher << "\n" << "}" # done
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
# Next block will be written as binary.
|
399
|
+
#
|
400
|
+
def next_block_will_be_binary
|
401
|
+
@block_arity = 2
|
402
|
+
end
|
403
|
+
|
404
|
+
# Next block will be writen as binary with swapped block arguments
|
405
|
+
# (delta = zeta; @argsrc.epsilon).
|
406
|
+
#
|
407
|
+
def next_block_will_be_binary_with_swapped_arguments
|
408
|
+
@block_arity = -2
|
409
|
+
end
|
410
|
+
|
411
|
+
# Register 0 (alpha) was ever required for computation.
|
412
|
+
#
|
413
|
+
def alpha_touch
|
414
|
+
belay unless @alpha_touched or @beta_touched
|
415
|
+
end
|
416
|
+
|
417
|
+
# Register 1 (beta) was ever required for the computation.
|
418
|
+
#
|
419
|
+
def beta_autoinit
|
420
|
+
case @opts.op
|
421
|
+
when 1 then s = "beta = self.dup rescue self"
|
422
|
+
( @main_opener.clear << s; @beta_touched = true ) unless @beta_touched
|
423
|
+
when 2 then @main_opener.clear << "beta = self[1]" unless @beta_touched
|
424
|
+
when -2 then @main_opener.clear << "beta = self[0]" unless @beta_touched
|
425
|
+
else raise "wrong @opts[:op] value: #{@opts.op}" end
|
426
|
+
end
|
427
|
+
alias :beta_touch :beta_autoinit
|
428
|
+
|
429
|
+
# Touch and return the successor of a register, or @r by default.
|
430
|
+
#
|
431
|
+
def successor_register reg=@r
|
432
|
+
send "#{SUCC[reg]}_touch"
|
433
|
+
SUCC[reg]
|
434
|
+
end
|
435
|
+
|
436
|
+
# Touch and return the predecessor of a register, or @r by default.
|
437
|
+
#
|
438
|
+
def predecessor_register reg=@r
|
439
|
+
send "#{PRE[reg]}_touch"
|
440
|
+
PRE[reg]
|
441
|
+
end
|
442
|
+
end # class Pyper::PostfixMachine
|