glaemscribe 1.1.14 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +5 -5
  2. data/bin/glaemscribe +19 -15
  3. data/glaemresources/charsets/cirth_ds.cst +205 -0
  4. data/glaemresources/charsets/sarati_eldamar.cst +256 -0
  5. data/glaemresources/charsets/tengwar_ds_annatar.cst +546 -0
  6. data/glaemresources/charsets/tengwar_ds_eldamar.cst +535 -0
  7. data/glaemresources/charsets/tengwar_ds_elfica.cst +551 -0
  8. data/glaemresources/charsets/tengwar_ds_parmaite.cst +534 -0
  9. data/glaemresources/charsets/tengwar_ds_sindarin.cst +531 -0
  10. data/glaemresources/charsets/tengwar_freemono.cst +217 -0
  11. data/glaemresources/charsets/tengwar_guni_annatar.cst +628 -0
  12. data/glaemresources/charsets/tengwar_guni_eldamar.cst +618 -0
  13. data/glaemresources/charsets/tengwar_guni_elfica.cst +620 -0
  14. data/glaemresources/charsets/tengwar_guni_parmaite.cst +621 -0
  15. data/glaemresources/charsets/tengwar_guni_sindarin.cst +617 -0
  16. data/glaemresources/charsets/tengwar_telcontar.cst +218 -0
  17. data/glaemresources/charsets/unicode_gothic.cst +64 -0
  18. data/glaemresources/charsets/unicode_runes.cst +121 -0
  19. data/glaemresources/modes/{adunaic.glaem → adunaic-tengwar-glaemscrafu.glaem} +14 -2
  20. data/glaemresources/modes/{blackspeech.glaem → blackspeech-tengwar-general_use.glaem} +12 -2
  21. data/glaemresources/modes/japanese-tengwar.glaem +771 -0
  22. data/glaemresources/modes/{khuzdul.glaem → khuzdul-cirth-moria.glaem} +4 -1
  23. data/glaemresources/modes/{futhorc.glaem → old_english-futhorc.glaem} +0 -0
  24. data/glaemresources/modes/{mercian.glaem → old_english-tengwar-mercian.glaem} +22 -12
  25. data/glaemresources/modes/{westsaxon.glaem → old_english-tengwar-westsaxon.glaem} +20 -11
  26. data/glaemresources/modes/{futhark-runicus.glaem → old_norse-futhark-runicus.glaem} +0 -0
  27. data/glaemresources/modes/{futhark-younger.glaem → old_norse-futhark-younger.glaem} +0 -0
  28. data/glaemresources/modes/{quenya.glaem → quenya-tengwar-classical.glaem} +32 -50
  29. data/glaemresources/modes/raw-tengwar.glaem +46 -23
  30. data/glaemresources/modes/{rlyehian.glaem → rlyehian-tengwar.glaem} +14 -3
  31. data/glaemresources/modes/{sindarin-daeron.glaem → sindarin-cirth-daeron.glaem} +55 -14
  32. data/glaemresources/modes/{sindarin-beleriand.glaem → sindarin-tengwar-beleriand.glaem} +154 -28
  33. data/glaemresources/modes/{sindarin.glaem → sindarin-tengwar-general_use.glaem} +86 -25
  34. data/glaemresources/modes/{telerin.glaem → telerin-tengwar-glaemscrafu.glaem} +16 -6
  35. data/glaemresources/modes/{westron.glaem → westron-tengwar-glaemscrafu.glaem} +18 -8
  36. data/lib/api/charset.rb +67 -7
  37. data/lib/api/charset_parser.rb +7 -0
  38. data/lib/api/constants.rb +3 -4
  39. data/lib/api/fragment.rb +26 -5
  40. data/lib/api/if_tree.rb +70 -8
  41. data/lib/api/macro.rb +40 -0
  42. data/lib/api/mode.rb +35 -13
  43. data/lib/api/mode_parser.rb +106 -12
  44. data/lib/api/object_additions.rb +23 -1
  45. data/lib/api/option.rb +17 -2
  46. data/lib/api/post_processor/resolve_virtuals.rb +25 -9
  47. data/lib/api/resource_manager.rb +1 -0
  48. data/lib/api/rule_group.rb +170 -26
  49. data/lib/api/sheaf_chain_iterator.rb +1 -1
  50. data/lib/api/transcription_processor.rb +3 -3
  51. data/lib/api/tts.rb +51 -0
  52. data/lib/glaemscribe.rb +34 -31
  53. data/lib_espeak/espeakng.for.glaemscribe.nowasm.sync.js +21 -0
  54. data/lib_espeak/glaemscribe_tts.js +365 -0
  55. metadata +67 -21
@@ -1,7 +1,9 @@
1
1
  # From http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/43424
2
2
  class Object
3
+ =begin
3
4
  def deep_clone
4
5
  return @deep_cloning_obj if @deep_cloning
6
+
5
7
  @deep_cloning_obj = clone
6
8
  @deep_cloning_obj.instance_variables.each do |var|
7
9
  val = @deep_cloning_obj.instance_variable_get(var)
@@ -19,4 +21,24 @@ class Object
19
21
  @deep_cloning_obj = nil
20
22
  deep_cloning_obj
21
23
  end
22
- end
24
+ =end
25
+
26
+ def deep_clone(cache={})
27
+ return cache[self] if cache.key?(self)
28
+
29
+ copy = clone()
30
+ cache[self] = copy
31
+
32
+ copy.instance_variables.each do |var|
33
+ val = instance_variable_get(var)
34
+ begin
35
+ val = val.deep_clone(cache)
36
+ rescue TypeError
37
+ next
38
+ end
39
+ copy.instance_variable_set(var, val)
40
+ end
41
+
42
+ return copy
43
+ end
44
+ end
@@ -25,24 +25,29 @@ module Glaemscribe
25
25
 
26
26
  class Option
27
27
  attr_reader :mode
28
+ attr_reader :line
28
29
  attr_reader :name
29
30
  attr_reader :type
30
31
  attr_reader :default_value_name
31
32
  attr_reader :values
33
+
34
+ attr_accessor :is_radio
32
35
 
33
-
34
36
  class Type
35
37
  ENUM = "ENUM"
36
38
  BOOL = "BOOL"
37
39
  end
38
40
 
39
- def initialize(mode, name, default_value_name, values, visibility = nil)
41
+ def initialize(mode, name, default_value_name, values, line, visibility = nil)
40
42
  @mode = mode
41
43
  @name = name
42
44
  @default_value_name = default_value_name
43
45
  @type = (values.keys.count == 0)?(Type::BOOL):(Type::ENUM)
44
46
  @values = values
45
47
  @visibility = visibility
48
+ @line = line
49
+ @value_to_names = {}
50
+ @values.each { |vname, val| @value_to_names[val] = vname }
46
51
  end
47
52
 
48
53
  def default_value
@@ -63,6 +68,16 @@ module Glaemscribe
63
68
  end
64
69
  end
65
70
 
71
+ def value_name_for_value(value)
72
+ if @type == Type::BOOL
73
+ return "true" if value == true || value == "true"
74
+ return "false" if value == false || value == "false"
75
+ return nil
76
+ else
77
+ return @value_to_names[value]
78
+ end
79
+ end
80
+
66
81
  def visible?
67
82
  if_eval = Eval::Parser.new()
68
83
 
@@ -50,19 +50,35 @@ module Glaemscribe
50
50
  # Try to replace
51
51
  last_trigger = @last_triggers[c]
52
52
  if last_trigger != nil
53
- new_tokens[idx] = last_trigger.names.first # Take the first name of the non-virtual replacement.
53
+ new_tokens[idx] = last_trigger.names.first # Take the first name of the non-virtual replacement.
54
+ token = new_tokens[idx] # Consider the token replaced, being itself a potential trigger for further virtuals (cascading virtuals)
54
55
  end
55
- else
56
- # Update states of virtual classes
57
- charset.virtual_chars.each{|vc|
58
- rc = vc[token]
59
- @last_triggers[vc] = rc if rc != nil
60
- }
61
- end
56
+ end
57
+
58
+ # Update states of virtual classes
59
+ charset.virtual_chars.each{|vc|
60
+ rc = vc[token]
61
+ @last_triggers[vc] = rc if rc != nil
62
+ }
63
+ end
64
+
65
+ def apply_sequences(charset,tokens)
66
+ ret = []
67
+ tokens.each { |token|
68
+ c = charset[token]
69
+ if c && c.sequence?
70
+ ret += c.sequence
71
+ else
72
+ ret << token
73
+ end
74
+ }
75
+ ret
62
76
  end
63
77
 
64
78
  def apply(tokens,charset)
65
-
79
+ # Apply sequence chars
80
+ tokens = apply_sequences(charset,tokens)
81
+
66
82
  # Clone the tokens so that we can perform ligatures AND diacritics without interferences
67
83
  new_tokens = tokens.clone
68
84
 
@@ -23,6 +23,7 @@ module Glaemscribe
23
23
  module API
24
24
  module ResourceManager
25
25
 
26
+ # The same structure is kept within the gem
26
27
  MODE_PATH = File.dirname(__FILE__) + "/../../glaemresources/modes/"
27
28
  MODE_EXT = "glaem"
28
29
 
@@ -22,35 +22,92 @@
22
22
 
23
23
  module Glaemscribe
24
24
  module API
25
+
26
+ class RuleGroupVar
27
+ attr_reader :name, :value
28
+
29
+ def initialize(name, value, is_pointer)
30
+ @name = name
31
+ @value = value
32
+ @is_pointer = is_pointer
33
+ end
34
+
35
+ def pointer?
36
+ is_pointer
37
+ end
38
+ end
39
+
25
40
  class RuleGroup
26
41
 
27
- VAR_NAME_REGEXP = /{([0-9A-Z_]+)}/
28
- VAR_DECL_REGEXP = /^\s*{([0-9A-Z_]+)}\s+===\s+(.+?)\s*$/
29
- RULE_REGEXP = /^\s*(.*?)\s+-->\s+(.+?)\s*$/
30
- CROSS_RULE_REGEXP = /^\s*(.*?)\s+-->\s+([\s0-9,]+)\s+-->\s+(.+?)\s*$/
42
+ VAR_NAME_REGEXP = /{([0-9A-Z_]+)}/
43
+ UNICODE_VAR_NAME_REGEXP_IN = /^UNI_([0-9A-F]+)$/
44
+ UNICODE_VAR_NAME_REGEXP_OUT = /{UNI_([0-9A-F]+)}/
45
+
46
+ VAR_DECL_REGEXP = /^\s*{([0-9A-Z_]+)}\s+===\s+(.+?)\s*$/
47
+ POINTER_VAR_DECL_REGEXP = /^\s*{([0-9A-Z_]+)}\s+<=>\s+(.+?)\s*$/
48
+ RULE_REGEXP = /^\s*(.*?)\s+-->\s+(.+?)\s*$/
49
+
50
+ CROSS_SCHEMA_REGEXP = /[0-9]+(\s*,\s*[0-9]+)*/
51
+
52
+ CROSS_RULE_REGEXP = /^\s*(.*?)\s+-->\s+(#{CROSS_SCHEMA_REGEXP}|#{VAR_NAME_REGEXP}|identity)\s+-->\s+(.+?)\s*$/
31
53
 
32
- attr_reader :root_code_block, :name, :mode, :in_charset, :rules
54
+ attr_reader :root_code_block, :name, :mode, :in_charset, :rules, :macros
33
55
 
34
56
  def initialize(mode,name)
35
57
  @name = name
36
58
  @mode = mode
59
+ @macros = {}
37
60
  @root_code_block = IfTree::CodeBlock.new
38
61
  end
39
62
 
40
- def add_var(var_name, value)
41
- @vars[var_name] = value
63
+ def add_var(var_name, value, is_pointer)
64
+ @vars[var_name] = RuleGroupVar.new(var_name, value, is_pointer)
42
65
  end
43
-
66
+
44
67
  # Replace all vars in expression
45
- def apply_vars(line, string)
46
- ret = string.gsub(VAR_NAME_REGEXP) { |cap_var|
47
- rep = @vars[$1]
48
- if !rep
49
- @mode.errors << Glaeml::Error.new(line, "In expression: #{string}: failed to evaluate variable: #{cap_var}.")
68
+ def apply_vars(line, string, allow_unicode_vars=false)
69
+
70
+ ret = string
71
+ stack_depth = 0
72
+ had_replacements = true
73
+
74
+ while had_replacements
75
+
76
+ had_replacements = false
77
+ ret = ret.gsub(VAR_NAME_REGEXP) { |cap_var|
78
+ vname = $1
79
+ v = @vars[vname]
80
+ if !v
81
+ if vname =~ UNICODE_VAR_NAME_REGEXP_IN
82
+ # A unicode variable.
83
+ if allow_unicode_vars
84
+ # Just keep this variable intact, it will be replaced at the last moment of the parsing
85
+ rep = cap_var
86
+ else
87
+ @mode.errors << Glaeml::Error.new(line, "In expression: #{string}: making wrong use of unicode variable: #{cap_var}. Unicode vars can only be used in source members of a rule or in the definition of another variable.")
88
+ return nil
89
+ end
90
+ else
91
+ @mode.errors << Glaeml::Error.new(line, "In expression: #{string}: failed to evaluate variable: #{cap_var}.")
92
+ return nil
93
+ end
94
+ else
95
+ rep = v.value
96
+ # Only count replacements on non unicode vars
97
+ had_replacements = true
98
+ end
99
+ rep
100
+ }
101
+ stack_depth += 1
102
+
103
+ break if !had_replacements
104
+
105
+ if stack_depth > 16
106
+ @mode.errors << Glaeml::Error.new(line, "In expression: #{string}: evaluation stack overflow.")
50
107
  return nil
51
108
  end
52
- rep
53
- }
109
+ end
110
+
54
111
  ret
55
112
  end
56
113
 
@@ -60,6 +117,58 @@ module Glaemscribe
60
117
  term.code_lines.each{ |cl|
61
118
  finalize_code_line(cl)
62
119
  }
120
+ elsif(term.is_macro_deploy?)
121
+
122
+ # Ok this is a bit dirty but I don't want to rewrite the error managamenet
123
+ # So add an error and if it's still the last (meaning there were no error) one remove it
124
+ possible_error = Glaeml::Error.new(term.line, ">> Macro backtrace : #{term.macro.name}")
125
+ @mode.errors << possible_error
126
+
127
+ # First, test if variable is pushable
128
+ arg_values = []
129
+ term.macro.arg_names.each_with_index { |arg_name, i|
130
+
131
+ var_value = nil
132
+
133
+ if @vars[arg_name]
134
+ @mode.errors << Glaeml::Error.new(term.line, "Local variable #{arg_name} hinders a variable with the same name in this context. Use only local variable names in macros!")
135
+ else
136
+ # Evaluate local var
137
+ var_value_ex = term.arg_value_expressions[i]
138
+ var_value = apply_vars(term.line, var_value_ex, true)
139
+
140
+ if !var_value
141
+ @mode.errors << Glaeml::Error.new(term.line, "Thus, variable {#{var_name}} could not be declared.")
142
+ end
143
+ end
144
+
145
+ arg_values << {name: arg_name, val: var_value}
146
+ }
147
+
148
+ # We push local vars after the whole loop to avoid interferences between them when evaluating them
149
+ arg_values.each { |v|
150
+ if v[:val]
151
+ add_var(v[:name],v[:val],false)
152
+ end
153
+ }
154
+
155
+ descend_if_tree(term.macro.root_code_block, trans_options)
156
+
157
+ # Remove the local vars from the scope (only if they were leggit)
158
+ arg_values.each { |v|
159
+ if v[:val]
160
+ @vars[v[:name]] = nil
161
+ end
162
+ }
163
+
164
+ if mode.errors.last == possible_error
165
+ # Remove the error scope if there were no errors
166
+ mode.errors.pop
167
+ else
168
+ # Add another one to close the context
169
+ @mode.errors << Glaeml::Error.new(term.line, "<< Macro backtrace : #{term.macro.name}")
170
+ end
171
+
63
172
  else
64
173
  term.if_conds.each{ |if_cond|
65
174
 
@@ -81,8 +190,8 @@ module Glaemscribe
81
190
 
82
191
  def finalize_rule(line, match_exp, replacement_exp, cross_schema = nil)
83
192
 
84
- match = apply_vars(line, match_exp)
85
- replacement = apply_vars(line, replacement_exp)
193
+ match = apply_vars(line, match_exp, true)
194
+ replacement = apply_vars(line, replacement_exp, false)
86
195
 
87
196
  return if !match || !replacement # Failed
88
197
 
@@ -102,20 +211,42 @@ module Glaemscribe
102
211
 
103
212
  var_name = $1
104
213
  var_value_ex = $2
105
- var_value = apply_vars(code_line.line, var_value_ex)
214
+ var_value = apply_vars(code_line.line, var_value_ex, true)
106
215
 
107
216
  if !var_value
108
217
  @mode.errors << Glaeml::Error.new(code_line.line, "Thus, variable {#{var_name}} could not be declared.")
109
218
  return
110
219
  end
111
220
 
112
- add_var(var_name,var_value)
221
+ add_var(var_name, var_value, false)
113
222
 
223
+ elsif code_line.expression =~ POINTER_VAR_DECL_REGEXP
224
+
225
+ var_name = $1
226
+ var_value_ex = $2
227
+
228
+ add_var(var_name, var_value_ex, true)
229
+
114
230
  elsif code_line.expression =~ CROSS_RULE_REGEXP
115
231
 
116
232
  match = $1
117
233
  cross = $2
118
- replacement = $3
234
+ var_name = $4
235
+ replacement = $5
236
+
237
+ if var_name
238
+ # This was a variable declaration
239
+ var_value = apply_vars(code_line.line, cross, false)
240
+ if !var_value
241
+ @mode.errors << Glaeml::Error.new(code_line.line, "Thus, variable {#{var_name}} could not be declared.")
242
+ return
243
+ end
244
+ cross = var_value
245
+ end
246
+
247
+ if cross == "identity"
248
+ cross = nil
249
+ end
119
250
 
120
251
  finalize_rule(code_line.line, match, replacement, cross)
121
252
 
@@ -139,11 +270,24 @@ module Glaemscribe
139
270
  @in_charset = {}
140
271
  @rules = []
141
272
 
142
- add_var("NULL","")
273
+ add_var("NULL","",false)
143
274
 
144
- add_var("UNDERSCORE", SPECIAL_CHAR_UNDERSCORE)
145
- add_var("NBSP", SPECIAL_CHAR_NBSP)
146
-
275
+ # Characters that are not easily entered or visible in a text editor
276
+ add_var("NBSP", "{UNI_A0}", false)
277
+ add_var("WJ", "{UNI_2060}", false)
278
+ add_var("ZWSP", "{UNI_200B}", false)
279
+ add_var("ZWNJ", "{UNI_200C}", false)
280
+
281
+ # The following characters are used by the mode syntax.
282
+ # Redefine some convenient tools.
283
+ add_var("UNDERSCORE", "{UNI_5F}", false)
284
+ add_var("ASTERISK", "{UNI_2A}", false)
285
+ add_var("COMMA", "{UNI_2C}", false)
286
+ add_var("LPAREN", "{UNI_28}", false)
287
+ add_var("RPAREN", "{UNI_29}", false)
288
+ add_var("LBRACKET", "{UNI_5B}", false)
289
+ add_var("RBRACKET", "{UNI_5D}", false)
290
+
147
291
  descend_if_tree(@root_code_block, trans_options)
148
292
 
149
293
  # Now that we have selected our rules, create the in_charset of the rule_group
@@ -151,8 +295,8 @@ module Glaemscribe
151
295
  r.sub_rules.each { |sr|
152
296
  sr.src_combination.join("").split(//).each{ |inchar|
153
297
  # Add the character to the map of input characters
154
- # Ignore '_' (bounds of word) and '|' (word breaker)
155
- @in_charset[inchar] = self if inchar != WORD_BREAKER && inchar != WORD_BOUNDARY
298
+ # Ignore '\u0000' (bounds of word) and '|' (word breaker)
299
+ @in_charset[inchar] = self if inchar != WORD_BREAKER && inchar != WORD_BOUNDARY_TREE
156
300
  }
157
301
  }
158
302
  }
@@ -108,7 +108,7 @@ module Glaemscribe
108
108
  return false
109
109
  end
110
110
 
111
- # Calculate all cominations for the chain
111
+ # Calculate all cominations for the chain, for the current iterator value
112
112
  def combinations
113
113
  resolved = []
114
114
  @iterators.each_with_index{ |counter, index|
@@ -43,8 +43,8 @@ module Glaemscribe
43
43
  @transcription_tree = TranscriptionTreeNode.new(nil,nil)
44
44
 
45
45
  # Add WORD_BOUNDARY and WORD_BREAKER in the tree
46
- @transcription_tree.add_subpath(WORD_BOUNDARY, [""])
47
- @transcription_tree.add_subpath(WORD_BREAKER, [""])
46
+ @transcription_tree.add_subpath(WORD_BOUNDARY_TREE, [""])
47
+ @transcription_tree.add_subpath(WORD_BREAKER, [""])
48
48
 
49
49
  rule_groups.each{ |rgname, rg|
50
50
  rg.finalize(trans_options)
@@ -110,7 +110,7 @@ module Glaemscribe
110
110
 
111
111
  def transcribe_word(word)
112
112
  res = []
113
- word = WORD_BOUNDARY + word + WORD_BOUNDARY
113
+ word = WORD_BOUNDARY_TREE + word + WORD_BOUNDARY_TREE
114
114
  while word.length != 0
115
115
  r, len = @transcription_tree.transcribe(word)
116
116
  word = word[len..-1]
@@ -0,0 +1,51 @@
1
+ module Glaemscribe
2
+ module API
3
+
4
+ class TTS
5
+
6
+ TTS_ENGINE_PATH = File.dirname(__FILE__) + "/../../lib_espeak/espeakng.for.glaemscribe.nowasm.sync.js"
7
+ TTS_MODULE_PATH = File.dirname(__FILE__) + "/../../lib_espeak/glaemscribe_tts.js"
8
+
9
+ def self.loaded?
10
+ !@context.nil?
11
+ end
12
+
13
+ def self.load_engine
14
+ return if @context
15
+
16
+ @context = ::MiniRacer::Context.new
17
+
18
+ @context.attach 'console.log', proc{|o| puts o}
19
+ @context.attach 'print', proc{|o| puts o}
20
+
21
+ espeak_lib = File.open(TTS_ENGINE_PATH,"rb").read
22
+ @context.eval(espeak_lib)
23
+
24
+ tts_module = File.open(TTS_MODULE_PATH,"rb").read
25
+ @context.eval(tts_module)
26
+
27
+ @voice_list = @context.eval("Glaemscribe.TTS.voice_list()")
28
+ # puts "TTS Engine loaded."
29
+ end
30
+
31
+ def self.voice_list
32
+ load_engine if !@context
33
+ @voice_list
34
+ end
35
+
36
+ def self.ipa(text, voice, has_raw_mode)
37
+ load_engine if !@context
38
+ @context.eval("var esp = new Glaemscribe.TTS(); esp.synthesize_ipa(#{text.inspect},{voice:'#{voice}', has_raw_mode: #{has_raw_mode}})")
39
+ end
40
+
41
+ def self.wav(text, voice, has_raw_mode)
42
+ load_engine if !@context
43
+ @context.eval("var esp = new Glaemscribe.TTS(); esp.synthesize_wav(#{text.inspect},{voice:'#{voice}', has_raw_mode: #{has_raw_mode}})")
44
+ end
45
+
46
+ def self.option_name_to_voice(option_name)
47
+ option_name.downcase.gsub(/^espeak_voice_/,'').gsub('_','-')
48
+ end
49
+ end
50
+ end
51
+ end