madderlib 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +3 -0
- data/LICENSE +22 -0
- data/README.rdoc +173 -0
- data/Rakefile +134 -0
- data/lib/madderlib.rb +27 -0
- data/lib/madderlib/builder.rb +659 -0
- data/lib/madderlib/conditional/allowed.rb +144 -0
- data/lib/madderlib/conditional/helper.rb +135 -0
- data/lib/madderlib/conditional/likely.rb +162 -0
- data/lib/madderlib/conditional/recur.rb +103 -0
- data/lib/madderlib/conditional/registry.rb +56 -0
- data/lib/madderlib/conditional/repeat.rb +130 -0
- data/lib/madderlib/context.rb +140 -0
- data/lib/madderlib/core.rb +217 -0
- data/lib/madderlib/extensions.rb +40 -0
- data/lib/madderlib/instruction.rb +171 -0
- data/lib/madderlib/phrase.rb +284 -0
- data/lib/madderlib/sequencer.rb +337 -0
- data/madderlib.gemspec +72 -0
- data/spec/benchmark_spec.rb +69 -0
- data/spec/builder_spec.rb +321 -0
- data/spec/builder_to_other_spec.rb +47 -0
- data/spec/builder_to_sequencer_spec.rb +388 -0
- data/spec/conditional_allowed_spec.rb +130 -0
- data/spec/conditional_helper_spec.rb +131 -0
- data/spec/conditional_likely_spec.rb +138 -0
- data/spec/conditional_recur_spec.rb +102 -0
- data/spec/conditional_registry_spec.rb +94 -0
- data/spec/conditional_repeat_spec.rb +88 -0
- data/spec/doc_spec.rb +550 -0
- data/spec/error_spec.rb +33 -0
- data/spec/examples_spec.rb +151 -0
- data/spec/extensions_spec.rb +47 -0
- data/spec/grammar_spec.rb +101 -0
- data/spec/instruction_spec.rb +133 -0
- data/spec/kernel_spec.rb +58 -0
- data/spec/phrase_spec.rb +7 -0
- data/spec/sequencer_spec.rb +317 -0
- data/spec/spec_helper.rb +54 -0
- metadata +98 -0
@@ -0,0 +1,56 @@
|
|
1
|
+
module MadderLib
|
2
|
+
module Conditional
|
3
|
+
module Registry #:nodoc: all
|
4
|
+
|
5
|
+
module Static
|
6
|
+
# registers a preparation closure for the container
|
7
|
+
def add_prepare(&block)
|
8
|
+
conditional_prepares << block
|
9
|
+
end
|
10
|
+
|
11
|
+
def conditional_prepares
|
12
|
+
@conditional_prepares ||= []
|
13
|
+
end
|
14
|
+
|
15
|
+
# registers a test closure for the container
|
16
|
+
def add_test(&block)
|
17
|
+
raise Error, 'block required' unless block_given?
|
18
|
+
conditional_tests << block
|
19
|
+
end
|
20
|
+
|
21
|
+
def conditional_tests
|
22
|
+
@conditional_tests ||= []
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
|
28
|
+
module Instance
|
29
|
+
# called once per execution
|
30
|
+
def prepare(context)
|
31
|
+
# execute all of our registered preparation blocks
|
32
|
+
self.class.conditional_prepares.each do |block|
|
33
|
+
(block.arity == 1 ? block.call(self) : block.call(self, context))
|
34
|
+
end
|
35
|
+
|
36
|
+
if self.methods.include?('instructions')
|
37
|
+
# prepare each instruction
|
38
|
+
self.instructions.each {|instruction| instruction.prepare(context) }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# returns true if the owner should be used
|
43
|
+
def test(context)
|
44
|
+
# find the first failing test closure
|
45
|
+
# it'd be nil if they all pass
|
46
|
+
failed = self.class.conditional_tests.find do |block|
|
47
|
+
# first failure stops us
|
48
|
+
(! (block.arity == 1 ? block.call(self) : block.call(self, context)))
|
49
|
+
end
|
50
|
+
|
51
|
+
failed.nil?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
module MadderLib
|
2
|
+
module Conditional
|
3
|
+
#= Recur
|
4
|
+
#
|
5
|
+
#Introduces support for repeating usage of a Phrase
|
6
|
+
module Repeat
|
7
|
+
|
8
|
+
#= Recur::Phrase
|
9
|
+
#
|
10
|
+
#Introduces support for repeating usage of a Phrase.
|
11
|
+
#The Phrase itself is largely uninvolved; it simply uses the repeat logic of its Instruction
|
12
|
+
#
|
13
|
+
#See: Recur::Instruction
|
14
|
+
module Phrase
|
15
|
+
#Adds repetition logic to the current Instruction
|
16
|
+
#
|
17
|
+
#See: Instruction#repeat
|
18
|
+
def repeat(*args, &block)
|
19
|
+
self.instruction.repeat *args, &block
|
20
|
+
end
|
21
|
+
alias :repeats :repeat
|
22
|
+
alias :repeating :repeat
|
23
|
+
alias :times :repeat
|
24
|
+
alias :while :repeat
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
|
29
|
+
#= Recur::Instruction
|
30
|
+
#
|
31
|
+
#Introduces support for repeating usage of a Phrase.
|
32
|
+
#The Instruction will simply call its own Instruction#speak method until the repetition is over.
|
33
|
+
#The sum and total of all those calls becomes the Instruction's resulting words.
|
34
|
+
#Note that this is not simply a blind duplication of the results of the first call to speak
|
35
|
+
#
|
36
|
+
#See: Recur::Phrase
|
37
|
+
module Instruction
|
38
|
+
def self.included(target) #:nodoc:
|
39
|
+
# this method won't exist until inclusion
|
40
|
+
# can't mess with it until that point
|
41
|
+
# moreover, can't call the method 'speak'
|
42
|
+
# won't overwrite an existing method
|
43
|
+
# so, we do aliasing to swap
|
44
|
+
target.class_eval %q{
|
45
|
+
alias :pre_repeat_speak :speak
|
46
|
+
alias :speak :repeat_speak
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
|
52
|
+
def repeat_speak(context) #:nodoc:
|
53
|
+
# no repetition may be requested
|
54
|
+
return pre_repeat_speak(context) unless @repeat_tester
|
55
|
+
|
56
|
+
# keep speaking until we're told to stop
|
57
|
+
composite, count = [], 0
|
58
|
+
|
59
|
+
loop do
|
60
|
+
break unless @repeat_tester.invoke(count, context)
|
61
|
+
|
62
|
+
words = pre_repeat_speak(context)
|
63
|
+
break if words.empty?
|
64
|
+
|
65
|
+
composite << words
|
66
|
+
count += 1
|
67
|
+
end
|
68
|
+
|
69
|
+
# as if we said it all at once
|
70
|
+
composite.flatten
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
|
75
|
+
#Specifies the repetition of this Phrase
|
76
|
+
#
|
77
|
+
#If provided, the arguments should contain:
|
78
|
+
#* a numeric value, which becomes the count
|
79
|
+
#* a Range, or two numerics (which define a Range), from which the count is chosen randomly
|
80
|
+
#* a Proc / lambda / block / closure, which returns false when the repetition should stop. \
|
81
|
+
#The block can either take (a) no arguments, (b) the repetition count, or; (c) the count <i>and</i> a Context.
|
82
|
+
#
|
83
|
+
#A repetition count of 0 will exclude the Phrase from the Builder result
|
84
|
+
#
|
85
|
+
#A repetition always ends when any Instruction returns an empty set of words.
|
86
|
+
#Processing will skip to the next Phrase, even if it could repeat again.
|
87
|
+
#This is due to the fact that Instruction#speak is called each time, which could provide different results
|
88
|
+
#
|
89
|
+
#Examples:
|
90
|
+
# builder = madderlib do
|
91
|
+
# say(:twice).times(2)
|
92
|
+
# say(:couple).repeats(1, 2)
|
93
|
+
# say(:thrice).while {|count| count < 3 }
|
94
|
+
# end
|
95
|
+
#
|
96
|
+
# words = builder.words
|
97
|
+
# words.find_all {|word| word == 'twice' }.should have(2).items
|
98
|
+
# words.find_all {|word| word == 'thrice' }.should have(3).items
|
99
|
+
# count = words.find_all {|word| word == 'couple' }.size
|
100
|
+
# (count >= 1 && count <= 2).should be_true
|
101
|
+
def repeat(*args, &block)
|
102
|
+
# build a tester, set it aside
|
103
|
+
@repeat_tester = Helper::TestBlock.new *args, &block
|
104
|
+
self
|
105
|
+
end
|
106
|
+
alias :repeats :repeat
|
107
|
+
alias :repeating :repeat
|
108
|
+
|
109
|
+
#Specifies the repetition of this Phrase using arguments
|
110
|
+
#
|
111
|
+
#This is syntactic sugar, but also does not accept a block
|
112
|
+
#
|
113
|
+
#See: repeat
|
114
|
+
def times(*args)
|
115
|
+
repeat *args
|
116
|
+
end
|
117
|
+
|
118
|
+
#Specifies the repetition of this Phrase using a block
|
119
|
+
#
|
120
|
+
#This is syntactic sugar, but also does not accept numeric arguments
|
121
|
+
#
|
122
|
+
#See: repeat
|
123
|
+
def while(&block)
|
124
|
+
repeat &block
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
module MadderLib
|
2
|
+
#= Context
|
3
|
+
#
|
4
|
+
#A context-holder object for MadderLib sentences.
|
5
|
+
#
|
6
|
+
#During the execution of a builder, the context is used to retain state for all parties that care about such things.
|
7
|
+
#Each execution produces a new context
|
8
|
+
#
|
9
|
+
#It is a useful tool for providing dynamic logic and data to a completed (eg. 'static') Builder
|
10
|
+
class Context
|
11
|
+
attr_reader :sequencer #:nodoc:
|
12
|
+
#An Array of all Phrases which contributed words.
|
13
|
+
#The Phrases are listed in the order that they were executed
|
14
|
+
attr_reader :spoken
|
15
|
+
#An Array of all Phrases which <i>did not</i> contribute words.
|
16
|
+
#A conditional may have failed, or the resulting content was either empty or nil.
|
17
|
+
#The Phrases are listed in the order that they were executed
|
18
|
+
attr_reader :silent
|
19
|
+
#An Array of the ids of all Phrases which contributed words.
|
20
|
+
#The ids are listed in the order that their Phrases were executed
|
21
|
+
attr_reader :spoken_ids
|
22
|
+
#An Array of all Instructions which contributed words, as chosen from their Phrase.
|
23
|
+
#The Phrases are listed in the order that they were executed
|
24
|
+
attr_reader :instructions
|
25
|
+
#A Hash of arbitrary data for the Context.
|
26
|
+
#It is reserved for custom developer logic; the Context doesn't consider its data
|
27
|
+
attr_reader :data
|
28
|
+
|
29
|
+
#Constructs a new Context.
|
30
|
+
#
|
31
|
+
#An optional Sequencer can be provided.
|
32
|
+
#The Sequencer is intentionally clouded in mystery, since it fulfils no external purpose.
|
33
|
+
#It is optional only for mock testing; it is <i>required</i> for Builder execution.
|
34
|
+
def initialize(sequencer=nil)
|
35
|
+
@sequencer = sequencer
|
36
|
+
@spoken, @silent, @spoken_ids = [], [], []
|
37
|
+
@instructions, @contexts = [], []
|
38
|
+
@state, @data = {}, {}
|
39
|
+
end
|
40
|
+
|
41
|
+
#Returns the Builder associated with the Context, via its Sequencer
|
42
|
+
def builder
|
43
|
+
@sequencer.builder
|
44
|
+
end
|
45
|
+
|
46
|
+
#Returns a Hash associated with the key provided.
|
47
|
+
#The value returned will not be nil
|
48
|
+
#
|
49
|
+
#This Hash can be used to store state data through the lifecycle of the Context.
|
50
|
+
#
|
51
|
+
#Examples:
|
52
|
+
# context = MadderLib::Context.new
|
53
|
+
# state = context.state(:state)
|
54
|
+
# state.should_not be_nil
|
55
|
+
#
|
56
|
+
# state[:key] = :value
|
57
|
+
# context.state(:state)[:key].should equal(:value)
|
58
|
+
def state(key)
|
59
|
+
hash = @state[key]
|
60
|
+
@state[key] = hash = {} unless hash
|
61
|
+
hash
|
62
|
+
end
|
63
|
+
|
64
|
+
#Provides convenient access to the data Hash.
|
65
|
+
#
|
66
|
+
#Examples:
|
67
|
+
# context = MadderLib::Context.new
|
68
|
+
# context.data[:key] = :value
|
69
|
+
#
|
70
|
+
# context[:key].should equal(:value)
|
71
|
+
def [](k)
|
72
|
+
@data[k]
|
73
|
+
end
|
74
|
+
def []=(k, v)
|
75
|
+
@data[k] = v
|
76
|
+
end
|
77
|
+
|
78
|
+
#Returns a list of all sub-contexts which were generated during Builder execution.
|
79
|
+
#These would come from any Builders that were executed as children of their parent Builder
|
80
|
+
#The list will <i>not</i> include self, only its children (etc.)
|
81
|
+
#
|
82
|
+
#The sub-contexts will be returned as an Array, and so on down the Context hierarchy.
|
83
|
+
#If <code>:flat</code> is passed as an argument, the Array returned will contain a flattened hierarchy
|
84
|
+
def contexts(mode=nil)
|
85
|
+
mode ||= :flat
|
86
|
+
|
87
|
+
if mode == :flat
|
88
|
+
queue, ctxs = @contexts.clone, []
|
89
|
+
while (ctx = queue.shift)
|
90
|
+
# myself
|
91
|
+
ctxs << ctx
|
92
|
+
# all my children
|
93
|
+
queue += ctx.contexts
|
94
|
+
end
|
95
|
+
|
96
|
+
ctxs
|
97
|
+
else
|
98
|
+
# only the ones for our immediate children
|
99
|
+
@contexts
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
#Adds a sub-context to the Context hierarchy
|
104
|
+
def add_context(context)
|
105
|
+
@contexts << context
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
|
110
|
+
def freeze #:nodoc:
|
111
|
+
super
|
112
|
+
|
113
|
+
# just like clone, we have to do this deeply!
|
114
|
+
[
|
115
|
+
@sequencer,
|
116
|
+
@spoken, @silent, @spoken_ids,
|
117
|
+
@instructions, @contexts,
|
118
|
+
@state, @data,
|
119
|
+
].each {|o| o.freeze }
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
|
124
|
+
class << self
|
125
|
+
def validate(block) #:nodoc:
|
126
|
+
raise Error, 'block required' unless block
|
127
|
+
raise Error, 'block arity should be 0 or 1 (Context)' unless (block.arity < 2)
|
128
|
+
end
|
129
|
+
|
130
|
+
def invoke(block, context) #:nodoc:
|
131
|
+
(block.arity == 0 ? block.call : block.call(context))
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
#An immutable empty Context singleton.
|
137
|
+
#Beats returning a null
|
138
|
+
Context::EMPTY = Context.new
|
139
|
+
Context::EMPTY.freeze
|
140
|
+
end
|
@@ -0,0 +1,217 @@
|
|
1
|
+
module MadderLib
|
2
|
+
#= Error
|
3
|
+
#
|
4
|
+
#A Module-specific Exception class
|
5
|
+
#
|
6
|
+
#--
|
7
|
+
# i would have called it MadderLib::Exception
|
8
|
+
# except that i don't know how to access Kernel::Exception within the initialize logic
|
9
|
+
#++
|
10
|
+
class Error < Exception
|
11
|
+
#The propagated cause of this Exception, if appropriate
|
12
|
+
attr_reader :cause
|
13
|
+
|
14
|
+
#Constructed with a message and an optional 'causing' Exception.
|
15
|
+
#
|
16
|
+
#If no message is passed -- eg. only an Exception -- then this Error inherits its message.
|
17
|
+
def initialize(message, cause=nil)
|
18
|
+
if (Exception === message)
|
19
|
+
super message.to_s
|
20
|
+
@cause = message
|
21
|
+
else
|
22
|
+
super message
|
23
|
+
@cause = cause
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
|
30
|
+
#= KernelMethods
|
31
|
+
#
|
32
|
+
#A Module containing MadderLib methods which are injected into the Kernel scope / namespace.
|
33
|
+
#Requiring the gem has the side-effect of injecting these methods.
|
34
|
+
module KernelMethods
|
35
|
+
#A proxy for MadderLib::Builder.new .
|
36
|
+
#It returns a constructed Builder.
|
37
|
+
#
|
38
|
+
#The resulting Builder is automatically added to the active Grammar.
|
39
|
+
#The active grammar can be accessed via madderlib_grammar .
|
40
|
+
#
|
41
|
+
#Please see MadderLib::Builder for extensive examples of how a Builder itself is put to use
|
42
|
+
#
|
43
|
+
#Examples:
|
44
|
+
# builder = madderlib do
|
45
|
+
# say 'no id'
|
46
|
+
# end
|
47
|
+
# madderlib_grammar.builders.include?(builder).should be_true
|
48
|
+
# madderlib_grammar.builder_map.values.include?(builder).should_not be_true
|
49
|
+
#
|
50
|
+
# builder = madderlib :id do
|
51
|
+
# say 'has id'
|
52
|
+
# end
|
53
|
+
# madderlib_grammar.builders.include?(builder).should be_true
|
54
|
+
# madderlib_grammar.builder_map.values.include?(builder).should be_true
|
55
|
+
def madderlib(*args, &block)
|
56
|
+
builder = Builder.new *args
|
57
|
+
madderlib_grammar.add builder
|
58
|
+
|
59
|
+
builder.extend &block
|
60
|
+
end
|
61
|
+
|
62
|
+
#A proxy for MadderLib::Grammar.get_instance .
|
63
|
+
#It returns the active Grammar
|
64
|
+
#
|
65
|
+
#See: madderlib
|
66
|
+
def madderlib_grammar
|
67
|
+
# the current instance we're working with
|
68
|
+
Grammar.get_instance
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
|
74
|
+
#= Grammar
|
75
|
+
#
|
76
|
+
#A class for registering MadderLib Builders.
|
77
|
+
#
|
78
|
+
#It is intended to help de-couple Ruby scripts which generate Builders from those which use them.
|
79
|
+
class Grammar
|
80
|
+
class << self
|
81
|
+
#Constructs a new Grammar instance
|
82
|
+
#
|
83
|
+
#The new instance becomes the active Grammar, as accessible from get_instance
|
84
|
+
#
|
85
|
+
#Examples:
|
86
|
+
# current = MadderLib::Grammar.new_instance
|
87
|
+
# current.should have(0).builders
|
88
|
+
# current.should equal(MadderLib::Grammar.get_instance)
|
89
|
+
#
|
90
|
+
# one = madderlib { say 'one' }
|
91
|
+
# current.should have(1).builders
|
92
|
+
# current.builders.include?(one).should be_true
|
93
|
+
#
|
94
|
+
# fresh = MadderLib::Grammar.new_instance
|
95
|
+
# fresh.should equal(MadderLib::Grammar.get_instance)
|
96
|
+
#
|
97
|
+
# two = madderlib { say 'two' }
|
98
|
+
# fresh.should have(1).builders
|
99
|
+
# fresh.builders.include?(two).should be_true
|
100
|
+
#
|
101
|
+
# current.should_not equal(MadderLib::Grammar.get_instance)
|
102
|
+
# current.builders.include?(two).should_not be_true
|
103
|
+
def new_instance
|
104
|
+
@instance = self.new
|
105
|
+
end
|
106
|
+
|
107
|
+
#Returns the active Grammar instance
|
108
|
+
#
|
109
|
+
#If no such Grammar exists, a new one is created
|
110
|
+
#
|
111
|
+
#See: new_instance
|
112
|
+
def get_instance
|
113
|
+
@instance ||= new_instance
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
#An Array of all Builders in the Grammar
|
118
|
+
attr_reader :builders
|
119
|
+
#A Hash of all the Builders in the Grammar which have an id
|
120
|
+
attr_reader :builder_map
|
121
|
+
|
122
|
+
#Constructs a new Grammar
|
123
|
+
def initialize
|
124
|
+
@builders = []
|
125
|
+
@builder_map = {}
|
126
|
+
|
127
|
+
# randomness
|
128
|
+
srand(Time.now.to_i)
|
129
|
+
end
|
130
|
+
|
131
|
+
#Adds a Builder to the Grammar.
|
132
|
+
#
|
133
|
+
#How this is done depends on the arguments passed
|
134
|
+
#* if an existing Builder is provided, it is added to the Grammar (as-is)
|
135
|
+
#* if nothing is provided, then a new Builder is constructed (without any id)
|
136
|
+
#* otherwise, any argument passed is treated as the id for a newly-constructed Builder
|
137
|
+
#
|
138
|
+
#If a block is provided, and a Builder is constructed, that block is leveraged <i>a la</i> Builder#extend
|
139
|
+
#
|
140
|
+
#Examples:
|
141
|
+
# grammar = MadderLib::Grammar.new_instance
|
142
|
+
#
|
143
|
+
# builder = madderlib { say 'exists' }
|
144
|
+
# x = grammar.add(builder)
|
145
|
+
# x.should equal(builder)
|
146
|
+
# grammar.should have(1).builders
|
147
|
+
# grammar.builder_map.should have(0).keys
|
148
|
+
#
|
149
|
+
# builder = grammar.add { say 'no id' }
|
150
|
+
# grammar.should have(2).builders
|
151
|
+
# grammar.builder_map.should have(0).keys
|
152
|
+
# builder.sentence.should eql('no id')
|
153
|
+
#
|
154
|
+
# builder = grammar << :id
|
155
|
+
# grammar.should have(3).builders
|
156
|
+
# grammar.builder_map.values.include?(builder).should be_true
|
157
|
+
# builder.sentence.should eql('')
|
158
|
+
def add(*args, &block)
|
159
|
+
builder = args.first
|
160
|
+
|
161
|
+
case builder
|
162
|
+
when Builder
|
163
|
+
# leave it alone
|
164
|
+
when nil
|
165
|
+
# new, with block dispatched
|
166
|
+
builder = Builder.new &block
|
167
|
+
else
|
168
|
+
# new, assume the arg is an ID
|
169
|
+
builder = Builder.new args.first, &block
|
170
|
+
end
|
171
|
+
|
172
|
+
unless @builders.include?(builder)
|
173
|
+
@builders << builder
|
174
|
+
|
175
|
+
# an id is not required
|
176
|
+
id = builder.id
|
177
|
+
(@builder_map[id] = builder) if id
|
178
|
+
end
|
179
|
+
|
180
|
+
builder
|
181
|
+
end
|
182
|
+
alias :<< :add
|
183
|
+
|
184
|
+
#Provides convenient access to the Builder map (builder_map).
|
185
|
+
#
|
186
|
+
#Examples:
|
187
|
+
# grammar = MadderLib::Grammar.new_instance
|
188
|
+
#
|
189
|
+
# builder = grammar.add(:id) { say 'has id' }
|
190
|
+
# grammar[:id].should equal(builder)
|
191
|
+
def [](key)
|
192
|
+
@builder_map[key]
|
193
|
+
end
|
194
|
+
|
195
|
+
#Freezes, and closes / completes, the current Grammar
|
196
|
+
#
|
197
|
+
#The Grammar becomes immutable.
|
198
|
+
#As a side-effect, if it is the current Grammar, a new_instance is created and used from this point forwards
|
199
|
+
def freeze
|
200
|
+
super
|
201
|
+
|
202
|
+
# deep freeze
|
203
|
+
[@builders, @builder_map].each {|o| o.freeze }
|
204
|
+
|
205
|
+
if self.class.get_instance == self
|
206
|
+
# we can no longer be the current Grammar
|
207
|
+
self.class.new_instance
|
208
|
+
end
|
209
|
+
end
|
210
|
+
alias :close :freeze
|
211
|
+
alias :complete :freeze
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
|
216
|
+
# inject into the Kernel
|
217
|
+
include MadderLib::KernelMethods
|