kotodama 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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
+