maroon 0.6.1 → 0.6.5
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 +7 -0
- data/Examples/Dijkstra/CalculateShortestDistance.rb +16 -12
- data/Examples/Dijkstra/calculate_shortest_path.rb +47 -27
- data/Examples/Dijkstra/data.rb +41 -14
- data/Examples/Dijkstra/dijkstra.rb +4 -3
- data/Examples/MoneyTransfer.rb +61 -60
- data/Examples/greeter.rb +8 -7
- data/Examples/meter.rb +35 -29
- data/Gemfile +9 -4
- data/LICENSE.txt +21 -21
- data/README.md +13 -0
- data/Rakefile +41 -1
- data/Test/Generate/method_info_test.rb +12 -0
- data/Test/{Greeter_test.rb → Greeter_test_disabled.rb} +24 -20
- data/Test/ImmutableQueue_test.rb +18 -0
- data/Test/MethodInfo_test.rb +65 -0
- data/Test/alltests.rb +1 -0
- data/Test/{source_assertions.rb → assertions.rb} +15 -7
- data/Test/bind_test.rb +13 -0
- data/Test/expression_test.rb +105 -0
- data/Test/method_call_test.rb +83 -0
- data/Test/self_test.rb +46 -0
- data/Test/stack_test.rb +17 -0
- data/Test/test_helper.rb +14 -0
- data/base/ImmutableStack.rb +32 -0
- data/base/MethodDefinition.rb +124 -0
- data/base/bind_rewriter.rb +58 -0
- data/base/immutable_queue.rb +50 -0
- data/base/maroon_base.rb +267 -0
- data/base/method_call.rb +78 -0
- data/base/method_info.rb +86 -0
- data/base/self.rb +60 -0
- data/generated/build.rb +13 -0
- data/generated/interpretation_context.rb +29 -0
- data/lib/Bind.rb +65 -0
- data/lib/Context.rb +187 -0
- data/lib/ImmutableQueue.rb +50 -0
- data/lib/ImmutableStack.rb +38 -0
- data/lib/MethodCall.rb +91 -0
- data/lib/MethodDefinition.rb +114 -0
- data/lib/MethodInfo.rb +78 -0
- data/lib/Self.rb +71 -0
- data/lib/build.rb +14 -0
- data/lib/interpretation_context.rb +30 -0
- data/lib/maroon/contracts.rb +43 -0
- data/lib/maroon/kernel.rb +2 -0
- data/lib/maroon/version.rb +3 -3
- data/maroon.gemspec +26 -26
- metadata +49 -31
- data/lib/Source_cleaner.rb +0 -34
- data/lib/maroon.rb +0 -165
- data/lib/rewriter.rb +0 -185
data/base/method_info.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
context :MethodInfo do
|
2
|
+
initialize do |on_self, block_source, is_private|
|
3
|
+
raise 'Must be S-Expressions' unless block_source.instance_of? Sexp
|
4
|
+
|
5
|
+
if on_self.instance_of? Hash
|
6
|
+
@block = on_self[:block]
|
7
|
+
@on_self = on_self[:self]
|
8
|
+
else
|
9
|
+
@on_self = on_self
|
10
|
+
end
|
11
|
+
@block_source = block_source
|
12
|
+
@private = is_private
|
13
|
+
self.freeze
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
role :on_self do
|
18
|
+
end
|
19
|
+
|
20
|
+
role :block do
|
21
|
+
end
|
22
|
+
|
23
|
+
role :block_source do
|
24
|
+
get_arguments {
|
25
|
+
sexp = block_source[2]
|
26
|
+
return nil unless sexp
|
27
|
+
return sexp[1] if sexp[0] == :lasgn
|
28
|
+
return [] if sexp[1] == nil
|
29
|
+
sexp = sexp[1..-1]
|
30
|
+
args = []
|
31
|
+
sexp.each do |e|
|
32
|
+
args << if e.instance_of? Symbol
|
33
|
+
e
|
34
|
+
else
|
35
|
+
if e[0] == :splat
|
36
|
+
'*' + e[1][1].to_s
|
37
|
+
else
|
38
|
+
e[1]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
if block
|
44
|
+
b = '&' + block.to_s
|
45
|
+
if args
|
46
|
+
unless args.instance_of? Array
|
47
|
+
args = [args]
|
48
|
+
end
|
49
|
+
args << b
|
50
|
+
else
|
51
|
+
args = [b]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
args
|
55
|
+
}
|
56
|
+
|
57
|
+
arguments {
|
58
|
+
args = block_source.get_arguments
|
59
|
+
args && args.length ? args.join(',') : nil
|
60
|
+
}
|
61
|
+
|
62
|
+
body {
|
63
|
+
block_source[3]
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
is_private do
|
68
|
+
@private
|
69
|
+
end
|
70
|
+
|
71
|
+
build_as_context_method do |context_method_name, interpretation_context|
|
72
|
+
MethodDefinition.new(block_source.body, interpretation_context).transform
|
73
|
+
body = Ruby2Ruby.new.process(block_source.body)
|
74
|
+
args = block_source.arguments ? '(' + block_source.arguments + ')' : ""
|
75
|
+
on = if on_self then
|
76
|
+
'self.'
|
77
|
+
else
|
78
|
+
''
|
79
|
+
end
|
80
|
+
'
|
81
|
+
def ' + on.to_s + context_method_name.to_s + args +'
|
82
|
+
' + body +'
|
83
|
+
end
|
84
|
+
'
|
85
|
+
end
|
86
|
+
end
|
data/base/self.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
context :Self, :execute do
|
2
|
+
initialize do |abstract_syntax_tree, interpretationcontext|
|
3
|
+
raise 'Interpretation context missing' unless interpretationcontext
|
4
|
+
raise 'Must have a defining role' unless interpretationcontext.defining_role
|
5
|
+
|
6
|
+
@abstract_syntax_tree = abstract_syntax_tree
|
7
|
+
@interpretation_context = interpretationcontext
|
8
|
+
end
|
9
|
+
|
10
|
+
role :abstract_syntax_tree do
|
11
|
+
is_indexer_call_on_self do
|
12
|
+
abstract_syntax_tree.length == 4 &&
|
13
|
+
abstract_syntax_tree[0] == :call &&
|
14
|
+
abstract_syntax_tree[1] == nil &&
|
15
|
+
abstract_syntax_tree[2] == :[] &&
|
16
|
+
abstract_syntax_tree[3][0] == :argslist
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
role :interpretation_context do
|
21
|
+
defining_role do
|
22
|
+
interpretation_context.defining_role
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# rewrites a call to self in a role method to a call to the role player accessor
|
27
|
+
# which is subsequently rewritten to a call to the instance variable itself
|
28
|
+
# in the case where no role method is called on the role player
|
29
|
+
# It's rewritten to an instance call on the context object if a role method is called
|
30
|
+
execute do
|
31
|
+
if abstract_syntax_tree
|
32
|
+
if abstract_syntax_tree[0] == :self #if self is used in a role method, then rewrite to role getter
|
33
|
+
abstract_syntax_tree[0] = :call
|
34
|
+
abstract_syntax_tree[1] = nil
|
35
|
+
abstract_syntax_tree[2] = interpretation_context.defining_role
|
36
|
+
elsif abstract_syntax_tree[0] == :call and abstract_syntax_tree[1] == nil
|
37
|
+
method_name = abstract_syntax_tree[2]
|
38
|
+
#self is removed from S-expressions
|
39
|
+
if method_name == :[] or method_name == :[]=
|
40
|
+
get_role = Sexp.new
|
41
|
+
get_role[0] = :call
|
42
|
+
get_role[1] = nil
|
43
|
+
get_role[2] = interpretation_context.defining_role
|
44
|
+
abstract_syntax_tree[1] = get_role
|
45
|
+
end
|
46
|
+
elsif abstract_syntax_tree.instance_of? Sexp
|
47
|
+
if abstract_syntax_tree.is_indexer_call_on_self
|
48
|
+
getter = new Sexp.new
|
49
|
+
getter[0] = :call
|
50
|
+
getter[1] = nil
|
51
|
+
getter[2] = interpretation_context.defining_role
|
52
|
+
arglist = Sexp.new
|
53
|
+
getter[3] = arglist
|
54
|
+
arglist[0] = :arglist
|
55
|
+
abstract_syntax_tree[1] = getter
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/generated/build.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'sorcerer'
|
2
|
+
require 'sourcify'
|
3
|
+
|
4
|
+
require_relative './Self'
|
5
|
+
require_relative './Bind'
|
6
|
+
require_relative './ImmutableStack'
|
7
|
+
require_relative './ImmutableQueue'
|
8
|
+
require_relative './interpretation_context'
|
9
|
+
require_relative './MethodInfo'
|
10
|
+
require_relative './MethodCall'
|
11
|
+
require_relative './MethodDefinition'
|
12
|
+
require_relative './Context'
|
13
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class InterpretationContext
|
2
|
+
def contracts
|
3
|
+
(@contracts ||= {})
|
4
|
+
end
|
5
|
+
|
6
|
+
def roles
|
7
|
+
(@roles ||= {})
|
8
|
+
end
|
9
|
+
|
10
|
+
def role_aliases
|
11
|
+
(@role_aliases ||= {})
|
12
|
+
end
|
13
|
+
|
14
|
+
def defining_role
|
15
|
+
@defining_role
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(roles, contracts, role_aliases, defining_role)
|
19
|
+
raise "Aliases must be a hash" unless role_aliases.instance_of? Hash or role_aliases == nil
|
20
|
+
raise "Roles must be a hash" unless roles.instance_of? Hash or roles == nil
|
21
|
+
raise "Contracts must be a hash" unless contracts.instance_of? Hash or contracts == nil
|
22
|
+
|
23
|
+
@roles = roles
|
24
|
+
raise "Defining role is undefined" if defining_role && (!self.roles.has_key? defining_role)
|
25
|
+
@contracts = contracts
|
26
|
+
@role_aliases = role_aliases
|
27
|
+
@defining_role = defining_role
|
28
|
+
end
|
29
|
+
end
|
data/lib/Bind.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
class Bind
|
2
|
+
|
3
|
+
def initialize(local,aliased_role,block)
|
4
|
+
@local = local
|
5
|
+
@aliased_role = aliased_role
|
6
|
+
@block = block
|
7
|
+
|
8
|
+
end
|
9
|
+
|
10
|
+
def execute()
|
11
|
+
must_b_sym = "aliased_role must be a Symbol".to_sym
|
12
|
+
local_must_b_sym = "local must be a Symbol".to_sym
|
13
|
+
raise(must_b_sym) unless aliased_role.instance_of?(Symbol)
|
14
|
+
raise(local_must_b_sym) unless local.instance_of?(Symbol)
|
15
|
+
aliased_field = ("@" + aliased_role.to_s).to_sym
|
16
|
+
temp_symbol = ("temp____" + aliased_role.to_s).to_sym
|
17
|
+
assignment = Sexp.new
|
18
|
+
assignment[0] = :iasgn
|
19
|
+
assignment[1] = aliased_field
|
20
|
+
load_arg = Sexp.new
|
21
|
+
load_arg[0] = :lvar
|
22
|
+
load_arg[1] = local
|
23
|
+
assignment[2] = load_arg
|
24
|
+
block.insert(1, assignment)
|
25
|
+
assignment = Sexp.new
|
26
|
+
assignment[0] = :lasgn
|
27
|
+
assignment[1] = temp_symbol
|
28
|
+
load_field = Sexp.new
|
29
|
+
load_field[0] = :ivar
|
30
|
+
load_field[1] = aliased_field
|
31
|
+
assignment[2] = load_field
|
32
|
+
block.insert(1, assignment)
|
33
|
+
assignment = Sexp.new
|
34
|
+
assignment[0] = :iasgn
|
35
|
+
assignment[1] = aliased_field
|
36
|
+
load_temp = Sexp.new
|
37
|
+
load_temp[0] = :lvar
|
38
|
+
load_temp[1] = temp_symbol
|
39
|
+
assignment[2] = load_temp
|
40
|
+
block[block.length] = assignment
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.call(*args)
|
45
|
+
arity = Bind.method(:new).arity
|
46
|
+
newArgs = args[0..arity-1]
|
47
|
+
obj = Bind.new *newArgs
|
48
|
+
if arity < args.length
|
49
|
+
methodArgs = args[arity..-1]
|
50
|
+
obj.execute *methodArgs
|
51
|
+
else
|
52
|
+
obj.execute
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def call(*args);execute *args; end
|
57
|
+
|
58
|
+
private
|
59
|
+
attr_reader :block
|
60
|
+
attr_reader :local
|
61
|
+
attr_reader :aliased_role
|
62
|
+
|
63
|
+
|
64
|
+
|
65
|
+
end
|
data/lib/Context.rb
ADDED
@@ -0,0 +1,187 @@
|
|
1
|
+
class Context
|
2
|
+
|
3
|
+
def self.define(*args,&block)
|
4
|
+
@@with_contracts ||= nil
|
5
|
+
@@generate_file_path ||= nil
|
6
|
+
(alias :method_missing :role_or_interaction_method)
|
7
|
+
base_class, ctx, default_interaction, name = self.send(:create_context_factory, args, block)
|
8
|
+
if (args.last.instance_of?(FalseClass) or args.last.instance_of?(TrueClass)) then
|
9
|
+
ctx.generate_files_in(args.last)
|
10
|
+
end
|
11
|
+
return ctx.send(:finalize, name, base_class, default_interaction)
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.generate_files_in(*args,&b)
|
16
|
+
if block_given? then
|
17
|
+
return role_or_interaction_method(:generate_files_in, *args, &b)
|
18
|
+
end
|
19
|
+
@@generate_file_path = args[0]
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def self.with_contracts(*args)
|
26
|
+
return @@with_contracts if (args.length == 0)
|
27
|
+
value = args[0]
|
28
|
+
if @@with_contracts and (not value) then
|
29
|
+
raise("make up your mind! disabling contracts during execution will result in undefined behavior")
|
30
|
+
end
|
31
|
+
@@with_contracts = value
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
def role(*args,&b)
|
36
|
+
role_name = args[0]
|
37
|
+
if (args.length.!=(1) or (not role_name.instance_of?(Symbol))) then
|
38
|
+
return role_or_interaction_method(:role, *args, &b)
|
39
|
+
end
|
40
|
+
@defining_role = role_name
|
41
|
+
@roles[role_name] = Hash.new
|
42
|
+
yield if block_given?
|
43
|
+
@defining_role = nil
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
def initialize(*args,&b)
|
48
|
+
if block_given? then
|
49
|
+
role_or_interaction_method(:initialize, *args, &b)
|
50
|
+
else
|
51
|
+
@roles = Hash.new
|
52
|
+
@interactions = Hash.new
|
53
|
+
@role_alias = Hash.new
|
54
|
+
@contracts = Hash.new
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def private()
|
59
|
+
@private = true
|
60
|
+
end
|
61
|
+
|
62
|
+
def current_interpretation_context(*args,&b)
|
63
|
+
if block_given? then
|
64
|
+
return role_or_interaction_method(:current_interpretation_context, *args, &b)
|
65
|
+
end
|
66
|
+
InterpretationContext.new(roles, contracts, role_alias, nil)
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
def get_methods(*args,&b)
|
71
|
+
return role_or_interaction_method(:get_methods, *args, &b) if block_given?
|
72
|
+
name = args[0]
|
73
|
+
sources = (@defining_role ? (@roles[@defining_role]) : (@interactions))[name]
|
74
|
+
if @defining_role and (not sources) then
|
75
|
+
@roles[@defining_role][name] = []
|
76
|
+
else
|
77
|
+
@interactions[name] = []
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
def add_method(*args,&b)
|
83
|
+
return role_or_interaction_method(:add_method, *args, &b) if block_given?
|
84
|
+
name, method = *args
|
85
|
+
sources = get_methods(name)
|
86
|
+
(sources << method)
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
def finalize(*args,&b)
|
91
|
+
return role_or_interaction_method(:finalize, *args, &b) if block_given?
|
92
|
+
name, base_class, default = *args
|
93
|
+
code = generate_context_code(default, name)
|
94
|
+
if @@generate_file_path then
|
95
|
+
name = name.to_s
|
96
|
+
complete = ((((("class " + name) + (base_class ? (("<< " + base_class.name)) : (""))) + "\n ") + code.to_s) + "\n end")
|
97
|
+
File.open((((("./" + @@generate_file_path.to_s) + "/") + name) + ".rb"), "w") do |f|
|
98
|
+
f.write(complete)
|
99
|
+
end
|
100
|
+
complete
|
101
|
+
else
|
102
|
+
c = base_class ? (Class.new(base_class)) : (Class.new)
|
103
|
+
if @@with_contracts then
|
104
|
+
c.class_eval("def self.assert_that(obj)\n ContextAsserter.new(self.contracts,obj)\n end\n def self.refute_that(obj)\n ContextAsserter.new(self.contracts,obj,false)\n end\n def self.contracts\n @@contracts\n end\n def self.contracts=(value)\n raise 'Contracts must be supplied' unless value\n @@contracts = value\n end")
|
105
|
+
c.contracts = contracts
|
106
|
+
end
|
107
|
+
Kernel.const_set(name, c)
|
108
|
+
temp = c.class_eval(code)
|
109
|
+
(temp or c)
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.create_context_factory(args,block)
|
115
|
+
name, base_class, default_interaction = *args
|
116
|
+
if default_interaction and (not base_class.instance_of?(Class)) then
|
117
|
+
base_class = eval(base_class.to_s)
|
118
|
+
end
|
119
|
+
if base_class and ((not default_interaction) and (not base_class.instance_of?(Class))) then
|
120
|
+
base_class, default_interaction = default_interaction, base_class
|
121
|
+
end
|
122
|
+
ctx = Context.new
|
123
|
+
ctx.instance_eval(&block)
|
124
|
+
return [base_class, ctx, default_interaction, name]
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
def generate_context_code(*args,&b)
|
129
|
+
if block_given? then
|
130
|
+
return role_or_interaction_method(:generate_context_code, *args, &b)
|
131
|
+
end
|
132
|
+
default, name = args
|
133
|
+
getters = ""
|
134
|
+
impl = ""
|
135
|
+
interactions = ""
|
136
|
+
@interactions.each do |method_name, methods|
|
137
|
+
methods.each do |method|
|
138
|
+
@defining_role = nil
|
139
|
+
code = (" " + method.build_as_context_method(method_name, current_interpretation_context))
|
140
|
+
method.is_private ? ((getters << code)) : ((interactions << code))
|
141
|
+
end
|
142
|
+
end
|
143
|
+
if default then
|
144
|
+
(interactions << (((((((("\n def self.call(*args)\n arity = " + name.to_s) + ".method(:new).arity\n newArgs = args[0..arity-1]\n obj = ") + name.to_s) + ".new *newArgs\n if arity < args.length\n methodArgs = args[arity..-1]\n obj.") + default.to_s) + " *methodArgs\n else\n obj.") + default.to_s) + "\n end\n end\n "))
|
145
|
+
(interactions << (("\n def call(*args);" + default.to_s) + " *args; end\n"))
|
146
|
+
end
|
147
|
+
@roles.each do |role, methods|
|
148
|
+
(getters << (("attr_reader :" + role.to_s) + "\n "))
|
149
|
+
methods.each do |method_name, method_sources|
|
150
|
+
unless (method_sources.length < 2) then
|
151
|
+
raise(("Duplicate definition of " + method_name.to_s))
|
152
|
+
end
|
153
|
+
unless (method_sources.length > 0) then
|
154
|
+
raise(("No source for " + method_name.to_s))
|
155
|
+
end
|
156
|
+
method_source = method_sources[0]
|
157
|
+
@defining_role = role
|
158
|
+
rewritten_method_name = ((("self_" + role.to_s) + "_") + method_name.to_s)
|
159
|
+
definition = method_source.build_as_context_method(rewritten_method_name, current_interpretation_context)
|
160
|
+
(impl << (" " + definition.to_s)) if definition
|
161
|
+
end
|
162
|
+
end
|
163
|
+
(((((interactions + "\n private\n") + getters) + "\n ") + impl) + "\n ")
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
def role_or_interaction_method(*arguments,&b)
|
168
|
+
method_name, on_self = *arguments
|
169
|
+
unless method_name.instance_of?(Symbol) then
|
170
|
+
on_self = method_name
|
171
|
+
method_name = :role_or_interaction_method
|
172
|
+
end
|
173
|
+
raise(("Method with out block " + method_name.to_s)) unless block_given?
|
174
|
+
add_method(method_name, MethodInfo.new(on_self, b.to_sexp, @private))
|
175
|
+
|
176
|
+
end
|
177
|
+
attr_reader :roles
|
178
|
+
attr_reader :interactions
|
179
|
+
attr_reader :defining_role
|
180
|
+
attr_reader :role_alias
|
181
|
+
attr_reader :alias_list
|
182
|
+
attr_reader :cached_roles_and_alias_list
|
183
|
+
attr_reader :contracts
|
184
|
+
|
185
|
+
|
186
|
+
|
187
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
class ImmutableQueue
|
2
|
+
|
3
|
+
def push(element)
|
4
|
+
b = (back or ImmutableStack.empty)
|
5
|
+
ImmutableQueue.new(front, b.push(element))
|
6
|
+
|
7
|
+
end
|
8
|
+
|
9
|
+
def pop()
|
10
|
+
f, b = front, back
|
11
|
+
if (f == ImmutableStack.empty) then
|
12
|
+
until (b == ImmutableStack.empty) do
|
13
|
+
(e, b = b.pop
|
14
|
+
f = f.push(e))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
head, f = f.pop
|
18
|
+
if (f == b) then
|
19
|
+
[head, ImmutableQueue.empty]
|
20
|
+
else
|
21
|
+
[head, ImmutableQueue.new(f, b)]
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.empty()
|
27
|
+
@@empty ||= ImmutableQueue.new(ImmutableStack.empty, ImmutableStack.empty)
|
28
|
+
end
|
29
|
+
|
30
|
+
def push_array(arr)
|
31
|
+
q = self
|
32
|
+
arr.each { |i| q = q.push(i) } if arr
|
33
|
+
q
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def initialize(front,back)
|
40
|
+
@front = (front or ImmutableStack.empty)
|
41
|
+
@back = (back or ImmutableStack.empty)
|
42
|
+
self.freeze
|
43
|
+
|
44
|
+
end
|
45
|
+
attr_reader :front
|
46
|
+
attr_reader :back
|
47
|
+
|
48
|
+
|
49
|
+
|
50
|
+
end
|