Class: Andor::NameGenerator

Inherits:
Object
  • Object
show all
Defined in:
lib/andor.rb

Overview

A Demiurge NameGenerator takes a set of rules for names and creates one or more randomly from that ruleset.

Since:

  • 0.4.0

Constant Summary collapse

NAME_REGEXP =

Regular expression for legal names

Since:

  • 0.4.0

/\A[-_$a-zA-Z0-9]+\Z/

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeNameGenerator

Create a new generator with an empty ruleset

Since:

  • 0.4.0



75
76
77
78
# File 'lib/andor.rb', line 75

def initialize
  @rules = {}
  @randomizer = Random.new(Time.now.to_i)
end

Instance Attribute Details

#randomizerObject

Since:

  • 0.4.0



70
71
72
# File 'lib/andor.rb', line 70

def randomizer
  @randomizer
end

#rulesObject (readonly)

Since:

  • 0.4.0



69
70
71
# File 'lib/andor.rb', line 69

def rules
  @rules
end

Instance Method Details

#evaluate_ast(ast, name: "some name") ⇒ Object

private

Since:

  • 0.4.0



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/andor.rb', line 133

def evaluate_ast(ast, name: "some name")
  # Let's grow out a Parslet-based evaluator to remove the outdated evaluation code below.
  if ast.is_a?(Hash)
    if ast.has_key?(:str_const)
      return ast[:str_const]
    elsif ast.has_key?(:str_val)
      return ast[:str_val].map { |h| h[:char] }.join
    elsif ast.has_key?(:name)
      return generate_from_name(ast[:name].to_s)
    else
      raise ::Andor::Errors::BadlyFormedGeneratorRule.new("Malformed rule internal structure: (Hash) #{ast.inspect}!")
    end
  elsif ast.is_a?(Array)
    if ast[0].has_key?(:left)
      if ast[1].has_key?(:plus)
        left_side = evaluate_ast(ast[0][:left])
        return ast[1..-1].map { |term| evaluate_ast(term[:right]) }.inject(left_side, &:+)
      elsif ast[1].has_key?(:bar)
        left_prob = ast[0][:left_prob] ? ast[0][:left_prob][:prob].to_f : 1.0
        choice_prob = [left_prob] + ast[1..-1].map { |term| term[:right_prob] ? term[:right_prob][:prob].to_f : 1.0 }

        unless choice_prob.all? { |p| p.is_a?(Float) }
          raise ::Andor::Errors::BadlyFormedGeneratorRule.new("Probability isn't a float: #{choice_prob.select { |p| !p.is_a?(Float) }.inspect}!")
        end
        total_prob = choice_prob.inject(0.0, &:+)
        if total_prob < 0.000001
          raise ::Andor::Errors::BadlyFormedGeneratorRule.new("Total probability less than epsilon: #{total_prob.inspect}!")
        end
        r = @randomizer.rand(total_prob)

        # Subtract probability from our random sample until we get that far into the CDF
        cur_index = 0
        while cur_index < choice_prob.size && r >= choice_prob[cur_index]
          r -= choice_prob[cur_index]
          cur_index += 1
        end
        # Shouldn't hit this, but just in case...
        cur_index = (choice_prob.size - 1) if cur_index >= choice_prob.size
        if cur_index == 0
          bar_choice = evaluate_ast(ast[0][:left]).to_s
        else
          bar_choice = evaluate_ast(ast[cur_index][:right]).to_s
        end

        return bar_choice
      else
        raise ::Andor::Errors::BadlyFormedGeneratorRule.new("Malformed rule internal structure: (Array/op) #{ast.inspect}!")
      end
    else
      raise ::Andor::Errors::BadlyFormedGeneratorRule.new("Malformed rule internal structure: (Array) #{ast.inspect}!")
    end
  else
    raise ::Andor::Errors::BadlyFormedGeneratorRule.new("Malformed rule internal structure: (#{ast.class}) #{ast.inspect}!")
  end

  if ast[0] == "|"
    choices = ast[1..-1]
    probabilities = choices.map { |choice| choice[0] == :prob ? choice[1] : 1.0 }
    total = probabilities.inject(0.0, &:+)
    chosen = rand() * total

    index = 0
    while chosen > probabilities[index] && index < choices.size
      chosen -= probabilities[index]
      index += 1
    end
    #STDERR.puts "Chose #{index} / #{choices[index].inspect} from #{choices.inspect}"
    return evaluate_ast choices[index]
  elsif ast[0] == "+"
    return ast[1..-1].map { |elt| evaluate_ast(elt) }.join("")
  elsif ast[0] == :prob
    raise ::Andor::Errors::BadlyFormedGeneratorRule.new("Not supposed to directly evaluate probability rule!")
  else
    raise ::Andor::Errors::BadlyFormedGeneratorRule.new("Malformed rule internal structure: #{ast.inspect}!")
  end
end

#generate_from_name(name) ⇒ Object

Since:

  • 0.4.0



122
123
124
125
126
127
128
129
# File 'lib/andor.rb', line 122

def generate_from_name(name)
  unless @rules.has_key?(name)
    STDERR.puts "Known rules: #{@rules.keys.inspect}"
    raise ::Andor::Errors::NoSuchNameInGenerator.new("Unknown name #{name.inspect} in generator!")
  end

  evaluate_ast @rules[name], name: name
end

#load_rules_from_andor_string(rules) ⇒ void

This method returns an undefined value.

Add rules to this generator from the given string

Since:

  • 0.4.0



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/andor.rb', line 93

def load_rules_from_andor_string(rules)
  defn_parser = AndorDefnParser.new

  rules.split("\n").each_with_index do |line, line_no|
    content, _ = line.split("#", 2)
    next if content == nil
    next if content.strip == ""
    name, defn = content.split(":", 2)
    unless name && defn
      raise ::Andor::Errors::DemiRuleFormatError.new("Badly-formed name definition line in DemiRule format on line #{line_no.inspect}")
    end
    unless name =~ NAME_REGEXP
      raise ::Andor::Errors::DemiRuleFormatError.new("Illegal name #{name.inspect} in DemiRule format on line #{line_no.inspect}")
    end
    if @rules[name]
      raise ::Andor::Errors::DemiRuleFormatError.new("Duplicate name #{name.inspect} in DemiRule format on line #{line_no.inspect}")
    end

    begin
      symbols = defn_parser.parse(defn)
    rescue Parslet::ParseFailed => error
      raise ::Andor::Errors::DemiRuleFormatError.new("Can't parse Andor name definition for #{name.inspect}", cause: error)
    end

    @rules[name] = symbols  # Need to transform to proper ast
  end
  nil
end

#namesArray<String>

Return all names currently defined by rules.

Returns:

  • (Array<String>)

    Array of all names.

Since:

  • 0.4.0



84
85
86
# File 'lib/andor.rb', line 84

def names
  @rules.keys
end