madderlib 0.1.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.
- 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
|