glaemscribe 1.1.14 → 1.2.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.
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