kotodama 0.0.3

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/bin/wordgen ADDED
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'kotodama'
4
+
5
+ flags = []
6
+ files = []
7
+ starting_rule = nil
8
+ starting_change = nil
9
+ number = 1
10
+ ARGV.each do |n|
11
+ if n[0] == '-'
12
+ case n[1]
13
+ when 'r'
14
+ starting_rule = n[2..-1]
15
+ when 'c'
16
+ starting_change = n[2..-1]
17
+ else
18
+ flags |= n[1..-1].split
19
+ end
20
+ elsif n.match /\A\d+\Z/
21
+ number = n.to_i
22
+ else
23
+ files << n
24
+ end
25
+ end
26
+
27
+ if files.length == 1
28
+ File.open files[0] do |file|
29
+ result = Kotodama::Parser.new.parse file
30
+ if result.parsed?
31
+ lang = result.result
32
+ if flags.member? 'u'
33
+ trials = []
34
+ until trials.length == number
35
+ trials |= lang.generate
36
+ end
37
+ puts trials
38
+ else
39
+ number.times { puts lang.generate }
40
+ end
41
+ else
42
+ flags.member?('v') ? result.backtrace : STDERR.puts(result.error_message)
43
+ end
44
+ end
45
+ elsif files.length == 2
46
+ File.open files[0] do |file|
47
+ result = Kotodama::Parser.new.parse file
48
+ if result.parsed?
49
+ lang = result.result
50
+ File.open files[1] do |file2|
51
+ puts lang.apply_to file2.to_a.collect {|n| n.chomp }
52
+ end
53
+ else
54
+ flags.member?('v') ? result.backtrace : STDERR.puts(result.error_message)
55
+ end
56
+ end
57
+ else
58
+ STDERR.puts "wrong number of arguments (#{files.length} for 2)"
59
+ end
data/lib/kotodama.rb ADDED
@@ -0,0 +1,23 @@
1
+ require 'pathname'
2
+ require 'kaiseki'
3
+
4
+ module Kotodama
5
+ VERSION = '0.0.3'
6
+ end
7
+
8
+ dir_path = Pathname.new(__FILE__).realpath.dirname
9
+
10
+ [
11
+ 'kotodama/word',
12
+ 'kotodama/language',
13
+ 'kotodama/type',
14
+ 'kotodama/rule',
15
+ 'kotodama/change',
16
+ 'kotodama/change_list',
17
+
18
+ 'kotodama/array',
19
+ 'kotodama/string',
20
+ 'kotodama/parser',
21
+ ].each do |path|
22
+ require dir_path + path
23
+ end
@@ -0,0 +1,22 @@
1
+ class Array
2
+ def random weights = nil
3
+ if weights
4
+ if weights.length == self.length
5
+ max_weight = 0
6
+ weights.each {|n| max_weight += n }
7
+ target_weight = rand max_weight
8
+ index = 0
9
+ index_weight = weights[index]
10
+ while index_weight <= target_weight
11
+ index += 1
12
+ index_weight += weights[index]
13
+ end
14
+ self[index]
15
+ else
16
+ raise "Array length and weights length not equal (#{self.length} and #{weights.length})"
17
+ end
18
+ else
19
+ self[rand(self.length)]
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,103 @@
1
+ module Kotodama
2
+ class Change
3
+ def initialize action, target, env
4
+ @action = action
5
+ @target = target
6
+ @env = env
7
+ end
8
+
9
+ def apply_to word, options = {}
10
+ unless word.is_a? Word
11
+ word = word.to_word
12
+ end
13
+ i = 0
14
+ while i < word.length
15
+ if check_environment word, i, options
16
+ case @action
17
+ when :insert
18
+ @target.length.times do |i2|
19
+ word.insert i + i2, @target[i2]
20
+ end
21
+ i += @target.length - 1
22
+ when :delete
23
+ catch :fail do
24
+ @target.length.times do |i2|
25
+ if options[:lang].types.key? @target[i2]
26
+ throw :fail unless options[:lang].types[@target[i2]].has? word[i + i2]
27
+ else
28
+ throw :fail unless word[i + i2] == @target[i2]
29
+ end
30
+ end
31
+ @target.each { word.delete_at i }
32
+ end
33
+ when :transform
34
+ catch :fail do
35
+ insertion_string = []
36
+ @target[0].length.times do |i2|
37
+ if options[:lang].types.key? @target[0][i2]
38
+ if options[:lang].types[@target[0][i2]].has?(word[i + i2])
39
+ insertion_string << options[:lang].types[@target[0][i2]].symbols.find_index(word[i + i2])
40
+ else
41
+ throw :fail
42
+ end
43
+ else
44
+ throw :fail unless word[i + i2] == @target[0][i2]
45
+ end
46
+ end
47
+ @target[0].each { word.delete_at i }
48
+ insertion_string.reverse!
49
+ @target[1].length.times do |i2|
50
+ if options[:lang].types.key? @target[1][i2]
51
+ raise "Wrong number of type transformations" unless insertion_string.last
52
+ word.insert i + i2, options[:lang].types[@target[1][i2]].symbols[insertion_string.pop]
53
+ else
54
+ word.insert i + i2, @target[1][i2]
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ i += 1
61
+ end
62
+ end
63
+
64
+ def check_environment word, index, options = {}
65
+ if @env
66
+ catch :fail do
67
+ @env[1].length.times do |i|
68
+ if @action == :insert
69
+ next_n = index + i
70
+ else
71
+ next_n = index + @target[0].length + i
72
+ end
73
+ if @env[1][i] == '#'
74
+ throw :fail unless index + 1 + i >= word.length
75
+ elsif options[:lang].types.key? @env[1][i]
76
+ throw :fail if next_n >= word.length
77
+ throw :fail unless options[:lang].types[@env[1][i]].has? word[next_n]
78
+ else
79
+ throw :fail if next_n >= word.length
80
+ throw :fail unless @env[1][i] == word[next_n]
81
+ end
82
+ end
83
+ @env[0].length.times do |i|
84
+ last_n = index - @env[0].length + i
85
+ if @env[0][i] == '#'
86
+ throw :fail unless last_n < 0
87
+ elsif options[:lang].types.key? @env[0][i]
88
+ throw :fail if last_n < 0
89
+ throw :fail unless options[:lang].types[@env[0][i]].has? word[last_n]
90
+ else
91
+ throw :fail if last_n < 0
92
+ throw :fail unless @env[0][i] == word[last_n]
93
+ end
94
+ end
95
+ return true
96
+ end
97
+ false
98
+ else
99
+ true
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,31 @@
1
+ module Kotodama
2
+ class ChangeList
3
+ attr_reader :name, :list
4
+
5
+ def initialize name
6
+ @name = name
7
+ @list = []
8
+ end
9
+
10
+ def push n
11
+ @list << n
12
+ end
13
+
14
+ alias :<< :push
15
+
16
+ def apply_to word, options = {}
17
+ @list.each do |n|
18
+ if n.is_a? Change
19
+ n.apply_to word, options
20
+ else
21
+ if options[:lang].changes.key? n
22
+ options[:lang].changes[n].apply_to word, options
23
+ else
24
+ raise "#{n} is not a sound change"
25
+ end
26
+ end
27
+ end
28
+ word
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,62 @@
1
+ module Kotodama
2
+ class Language
3
+ attr_reader :types, :rules, :changes, :options
4
+
5
+ def initialize
6
+ @types = {}
7
+ @rules = {}
8
+ @changes = {}
9
+ @options = {:symbol_weight => 1, :phrase_weight => 1}
10
+ end
11
+
12
+ def add_type type
13
+ @types[type.name] = type
14
+ end
15
+
16
+ def add_rule rule
17
+ @rules[rule.name] = rule
18
+ end
19
+
20
+ def add_change change
21
+ @changes[change.name] = change
22
+ end
23
+
24
+ def find_type symbol
25
+ @types.values.find {|n| n.has? symbol }
26
+ end
27
+
28
+ def generate starting_rule = nil, starting_change = nil
29
+ starting_rule ||= @options['rule']
30
+ starting_change ||= @options['change']
31
+ if @rules.key? starting_rule
32
+ word = @rules[starting_rule].generate @options.merge(:lang => self)
33
+ if starting_change
34
+ if @changes.key? starting_change
35
+ word.apply @changes[starting_change], @options.merge(:lang => self)
36
+ else
37
+ raise "#{starting_change || 'nil'} is not a sound change"
38
+ end
39
+ end
40
+ word.to_s
41
+ else
42
+ raise "#{starting_rule || 'nil'} is not a rule"
43
+ end
44
+ end
45
+
46
+ def apply_to array, starting_change = nil
47
+ starting_change ||= @options['change']
48
+ if @changes.key? starting_change
49
+ array.collect do |n|
50
+ word = n.to_word
51
+ if word
52
+ word.apply @changes[starting_change], @options.merge(:lang => self)
53
+ else
54
+ "input `#{n}' cannot be correctly converted into a word"
55
+ end
56
+ end
57
+ else
58
+ raise "#{starting_change || 'nil'} is not a sound change"
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,151 @@
1
+ module Kotodama
2
+ Parser = Kaiseki::Grammar.subclass do
3
+ starting :document
4
+ skipping /\s+/
5
+ simplify
6
+
7
+ rule :document do
8
+ parse :options_block.optional & :content_block.zero_or_more & :EOF
9
+ node :params => [:options, :content]
10
+ action do
11
+ lang = Language.new
12
+ lang.options.merge! @options
13
+ @content.each do |n|
14
+ if n.is_a? Type
15
+ lang.add_type n
16
+ elsif n.is_a? Rule
17
+ lang.add_rule n
18
+ elsif n.is_a? ChangeList
19
+ lang.add_change n
20
+ else
21
+ raise "#{n.class} is not a valid Language class"
22
+ end
23
+ end
24
+ lang
25
+ end
26
+ end
27
+
28
+ rule :options_block do
29
+ parse 'options'.skip & '{'.skip & :option.zero_or_more & '}'.skip
30
+ action do
31
+ options_hash = {}
32
+ @results.each {|n| options_hash.merge! n }
33
+ options_hash
34
+ end
35
+ end
36
+
37
+ rule :option do
38
+ parse :ID & '=>'.skip & (:ID | :INT) & ';'.skip
39
+ node :params => [:key, :value]
40
+ action { {@key => @value} }
41
+ end
42
+
43
+ rule :content_block do
44
+ parse :type_block | :rule_block | :change_block
45
+ end
46
+
47
+ rule :type_block do
48
+ parse 'type'.skip & :ID & '{'.skip & :symbol.zero_or_more & '}'.skip
49
+ node :params => [:name, :symbols]
50
+ action do
51
+ type = Type.new @name
52
+ @symbols.each do |symbol|
53
+ if symbol[0].respond_to? :each
54
+ symbol[0].each {|n| type.add_symbol n, symbol[1] }
55
+ else
56
+ type.add_symbol symbol[0], symbol[1]
57
+ end
58
+ end
59
+ type
60
+ end
61
+ end
62
+
63
+ rule :symbol do
64
+ parse (:ID | :LIT) >> (','.skip & (:ID | :LIT)).zero_or_more & :weight.optional & ';'.skip
65
+ end
66
+
67
+ rule :rule_block do
68
+ parse 'rule'.skip & :ID & '{'.skip & :rule.zero_or_more & '}'.skip
69
+ node :params => [:name, :phrases]
70
+ action do
71
+ rule = Rule.new @name
72
+ @phrases.each {|phrase| rule.add_phrase phrase[0], phrase[1] }
73
+ rule
74
+ end
75
+ end
76
+
77
+ rule :rule do
78
+ parse (:ID | :LIT).one_or_more & :weight.optional & ';'.skip
79
+ end
80
+
81
+ rule :change_block do
82
+ parse 'change'.skip & :ID & '{'.skip & :change.zero_or_more & '}'.skip
83
+ node :params => [:name, :changes]
84
+ action do
85
+ change_list = ChangeList.new @name
86
+ @changes.each {|change| change_list.push change }
87
+ change_list
88
+ end
89
+ end
90
+
91
+ rule :change do
92
+ parse :insertion_change | :deletion_change | :transformation_change | :change_name
93
+ end
94
+
95
+ rule :insertion_change do
96
+ parse 'insert'.skip & (:ID | :LIT).one_or_more & :change_env.optional & ';'.skip
97
+ override :simplify => false
98
+ node :params => [:target, :env]
99
+ action do
100
+ Change.new :insert, @target, @env[0]
101
+ end
102
+ end
103
+
104
+ rule :deletion_change do
105
+ parse 'delete'.skip & (:ID | :LIT).one_or_more & :change_env.optional & ';'.skip
106
+ override :simplify => false
107
+ node :params => [:target, :env]
108
+ action do
109
+ Change.new :delete, @target, @env[0]
110
+ end
111
+ end
112
+
113
+ rule :transformation_change do
114
+ parse (:ID | :LIT).one_or_more & '>'.skip & (:ID | :LIT).one_or_more & :change_env.optional & ';'.skip
115
+ override :simplify => false
116
+ node :params => [:target1, :target2, :env]
117
+ action do
118
+ Change.new :transform, [@target1, @target2], @env[0]
119
+ end
120
+ end
121
+
122
+ rule :change_name do
123
+ parse :ID & ';'.skip
124
+ end
125
+
126
+ rule :change_env do
127
+ parse '/'.skip & ('#'.optional >> :ID.zero_or_more) & '_'.skip & (:ID.zero_or_more >> '#'.optional)
128
+ action { @results.to_a }
129
+ end
130
+
131
+ rule :weight do
132
+ parse ':'.skip & :INT
133
+ override :simplify => true
134
+ end
135
+
136
+ rule :ID do
137
+ parse /_*[a-zA-Z][a-zA-Z0-9_]*/
138
+ action { @results.to_s }
139
+ end
140
+
141
+ rule :INT do
142
+ parse /\d+/
143
+ action { @results.to_i }
144
+ end
145
+
146
+ rule :LIT do
147
+ parse /\'([^']*)\'/ | /\"([^"]*)\"/
148
+ action { @results.info[:captures][0] }
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,31 @@
1
+ module Kotodama
2
+ class Rule
3
+ attr_reader :name, :phrases, :weights
4
+
5
+ def initialize name
6
+ @name = name
7
+ @phrases = []
8
+ @weights = []
9
+ end
10
+
11
+ def add_phrase rule, weight = 1
12
+ @phrases << rule
13
+ @weights << weight
14
+ end
15
+
16
+ def generate options = {}
17
+ word = Word.new
18
+ phrase = @phrases.random @weights.collect {|n| n || options[:phrase_weight] || 1 }
19
+ phrase.each do |n|
20
+ if options[:lang].rules.key? n
21
+ word << options[:lang].rules[n].generate(options)
22
+ elsif options[:lang].types.key? n
23
+ word << options[:lang].types[n].generate(options)
24
+ else
25
+ word << n
26
+ end
27
+ end
28
+ word
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,29 @@
1
+ class String
2
+ def to_word language
3
+ max_symbol_length = 0
4
+ language.types.each_pair do |key, type|
5
+ type.each do |symbol|
6
+ max_symbol_length = symbol.length if symbol.length > max_symbol_length
7
+ end
8
+ end
9
+ word = Kotodama::Word.new
10
+ index = 0
11
+ catch :fail do
12
+ while index < self.length
13
+ catch :succeed do
14
+ max_symbol_length.downto 1 do |length|
15
+ type = language.find_type self[index, length]
16
+ if type
17
+ word << self[index, length]
18
+ index += length
19
+ throw :succeed
20
+ end
21
+ end
22
+ throw :fail
23
+ end
24
+ end
25
+ return word
26
+ end
27
+ nil
28
+ end
29
+ end
@@ -0,0 +1,33 @@
1
+ module Kotodama
2
+ class Type
3
+ include Enumerable
4
+ attr_reader :name, :symbols, :weights
5
+
6
+ def initialize name
7
+ @name = name
8
+ @symbols = []
9
+ @weights = []
10
+ end
11
+
12
+ def add_symbol symbol, weight = 1
13
+ @symbols << symbol
14
+ @weights << weight
15
+ end
16
+
17
+ def [] n
18
+ @symbols[n]
19
+ end
20
+
21
+ def each &block
22
+ @symbols.each &block
23
+ end
24
+
25
+ def has? symbol
26
+ @symbols.member? symbol
27
+ end
28
+
29
+ def generate options = {}
30
+ @symbols.random(@weights.collect {|n| n || options[:symbol_weight] || 1 })
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,66 @@
1
+ module Kotodama
2
+ class Word
3
+ include Enumerable
4
+ attr_reader :letters
5
+
6
+ def initialize
7
+ @letters = []
8
+ end
9
+
10
+ def [] n
11
+ @letters[n]
12
+ end
13
+
14
+ def []= n, value
15
+ @letters[n] = value
16
+ end
17
+
18
+ def empty?
19
+ @letters.empty?
20
+ end
21
+
22
+ def length
23
+ @letters.length
24
+ end
25
+
26
+ def push letter
27
+ if letter.is_a? Word
28
+ letter.each {|n| @letters.push n }
29
+ else
30
+ @letters.push letter
31
+ end
32
+ end
33
+
34
+ alias :<< :push
35
+
36
+ def insert index, letter
37
+ if letter.is_a? Word
38
+ letter.length.times do |i|
39
+ @letters.insert index + i, letter[i]
40
+ end
41
+ else
42
+ @letters.insert index, letter
43
+ end
44
+ end
45
+
46
+ def delete_at index
47
+ @letters.delete_at index
48
+ end
49
+
50
+ def each &block
51
+ @letters.each &block
52
+ end
53
+
54
+ def apply change, options = {}
55
+ change.apply_to self, options
56
+ end
57
+
58
+ def to_s
59
+ @letters.join
60
+ end
61
+
62
+ def to_word *args
63
+ self
64
+ end
65
+ end
66
+ end
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kotodama
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 3
9
+ version: 0.0.3
10
+ platform: ruby
11
+ authors:
12
+ - William Hamilton-Levi
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-11-23 00:00:00 -05:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: kaiseki
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :runtime
32
+ version_requirements: *id001
33
+ description: A word generator written in Ruby.
34
+ email: whamilt1@swarthmore.edu
35
+ executables:
36
+ - wordgen
37
+ extensions: []
38
+
39
+ extra_rdoc_files: []
40
+
41
+ files:
42
+ - lib/kotodama.rb
43
+ - lib/kotodama/type.rb
44
+ - lib/kotodama/word.rb
45
+ - lib/kotodama/change.rb
46
+ - lib/kotodama/array.rb
47
+ - lib/kotodama/rule.rb
48
+ - lib/kotodama/change_list.rb
49
+ - lib/kotodama/string.rb
50
+ - lib/kotodama/language.rb
51
+ - lib/kotodama/parser.rb
52
+ - bin/wordgen
53
+ has_rdoc: true
54
+ homepage: http://github.com/phi2dao/Kotodama
55
+ licenses: []
56
+
57
+ post_install_message:
58
+ rdoc_options: []
59
+
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ segments:
68
+ - 0
69
+ version: "0"
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ segments:
76
+ - 0
77
+ version: "0"
78
+ requirements: []
79
+
80
+ rubyforge_project:
81
+ rubygems_version: 1.3.7
82
+ signing_key:
83
+ specification_version: 3
84
+ summary: A word generator written in Ruby.
85
+ test_files: []
86
+