glaemscribe 1.1.14 → 1.3.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 (62) hide show
  1. checksums.yaml +5 -5
  2. data/bin/glaemscribe +21 -17
  3. data/glaemresources/charsets/cirth_ds.cst +540 -0
  4. data/glaemresources/charsets/eldamar.cst +210 -0
  5. data/glaemresources/charsets/sarati_eldamar.cst +256 -0
  6. data/glaemresources/charsets/tengwar_ds_annatar.cst +2868 -0
  7. data/glaemresources/charsets/tengwar_ds_eldamar.cst +2729 -0
  8. data/glaemresources/charsets/tengwar_ds_elfica.cst +2742 -0
  9. data/glaemresources/charsets/tengwar_ds_parmaite.cst +2726 -0
  10. data/glaemresources/charsets/tengwar_ds_sindarin.cst +2722 -0
  11. data/glaemresources/charsets/tengwar_freemono.cst +217 -0
  12. data/glaemresources/charsets/tengwar_guni_annatar.cst +2948 -0
  13. data/glaemresources/charsets/tengwar_guni_eldamar.cst +2809 -0
  14. data/glaemresources/charsets/tengwar_guni_elfica.cst +2809 -0
  15. data/glaemresources/charsets/tengwar_guni_parmaite.cst +2813 -0
  16. data/glaemresources/charsets/tengwar_guni_sindarin.cst +2808 -0
  17. data/glaemresources/charsets/tengwar_telcontar.cst +225 -0
  18. data/glaemresources/charsets/unicode_gothic.cst +64 -0
  19. data/glaemresources/charsets/unicode_runes.cst +121 -0
  20. data/glaemresources/modes/{adunaic.glaem → adunaic-tengwar-glaemscrafu.glaem} +14 -2
  21. data/glaemresources/modes/{blackspeech.glaem → blackspeech-tengwar-general_use.glaem} +13 -3
  22. data/glaemresources/modes/english-cirth-espeak.glaem +687 -0
  23. data/glaemresources/modes/english-tengwar-espeak.glaem +814 -0
  24. data/glaemresources/modes/japanese-tengwar.glaem +776 -0
  25. data/glaemresources/modes/{khuzdul.glaem → khuzdul-cirth-moria.glaem} +4 -1
  26. data/glaemresources/modes/lang_belta-tengwar-dadef.glaem +248 -0
  27. data/glaemresources/modes/{futhorc.glaem → old_english-futhorc.glaem} +0 -0
  28. data/glaemresources/modes/{mercian.glaem → old_english-tengwar-mercian.glaem} +22 -12
  29. data/glaemresources/modes/{westsaxon.glaem → old_english-tengwar-westsaxon.glaem} +20 -11
  30. data/glaemresources/modes/{futhark-runicus.glaem → old_norse-futhark-runicus.glaem} +0 -0
  31. data/glaemresources/modes/{futhark-younger.glaem → old_norse-futhark-younger.glaem} +0 -0
  32. data/glaemresources/modes/{quenya.glaem → quenya-tengwar-classical.glaem} +32 -50
  33. data/glaemresources/modes/raw-cirth.glaem +154 -0
  34. data/glaemresources/modes/raw-tengwar.glaem +46 -23
  35. data/glaemresources/modes/{rlyehian.glaem → rlyehian-tengwar.glaem} +14 -3
  36. data/glaemresources/modes/{sindarin-daeron.glaem → sindarin-cirth-daeron.glaem} +55 -14
  37. data/glaemresources/modes/{sindarin-beleriand.glaem → sindarin-tengwar-beleriand.glaem} +154 -28
  38. data/glaemresources/modes/{sindarin.glaem → sindarin-tengwar-general_use.glaem} +86 -25
  39. data/glaemresources/modes/{telerin.glaem → telerin-tengwar-glaemscrafu.glaem} +16 -6
  40. data/glaemresources/modes/{westron.glaem → westron-tengwar-glaemscrafu.glaem} +18 -8
  41. data/lib/api/charset.rb +67 -7
  42. data/lib/api/charset_parser.rb +14 -1
  43. data/lib/api/constants.rb +3 -4
  44. data/lib/api/fragment.rb +26 -5
  45. data/lib/api/if_tree.rb +70 -8
  46. data/lib/api/macro.rb +40 -0
  47. data/lib/api/mode.rb +66 -19
  48. data/lib/api/mode_parser.rb +117 -14
  49. data/lib/api/object_additions.rb +23 -1
  50. data/lib/api/option.rb +17 -2
  51. data/lib/api/post_processor/outspace.rb +44 -0
  52. data/lib/api/post_processor/resolve_virtuals.rb +25 -9
  53. data/lib/api/resource_manager.rb +1 -0
  54. data/lib/api/rule_group.rb +170 -26
  55. data/lib/api/sheaf_chain_iterator.rb +1 -1
  56. data/lib/api/transcription_pre_post_processor.rb +8 -5
  57. data/lib/api/transcription_processor.rb +15 -12
  58. data/lib/api/tts.rb +51 -0
  59. data/lib/glaemscribe.rb +36 -31
  60. data/lib_espeak/espeakng.for.glaemscribe.nowasm.sync.js +35 -0
  61. data/lib_espeak/glaemscribe_tts.js +505 -0
  62. metadata +76 -24
@@ -83,7 +83,11 @@ module Glaemscribe
83
83
  doc.root_node.gpath("preprocessor.if").each{ |e| validate_presence_of_args(e, 1) }
84
84
  doc.root_node.gpath("preprocessor.elsif").each{ |e| validate_presence_of_args(e, 1) }
85
85
  doc.root_node.gpath("postprocessor.if").each{ |e| validate_presence_of_args(e, 1) }
86
- doc.root_node.gpath("postprocessor.elsif").each{ |e| validate_presence_of_args(e, 1) }
86
+ doc.root_node.gpath("postprocessor.elsif").each{ |e| validate_presence_of_args(e, 1) }
87
+
88
+ doc.root_node.children.each { |c|
89
+ @mode.errors << Glaeml::Error.new(c.line, "'if' conditions are not allowed in that scope.") if c.name == 'if'
90
+ }
87
91
  end
88
92
 
89
93
  def create_if_cond_for_if_term(line, if_term, cond)
@@ -94,7 +98,13 @@ module Glaemscribe
94
98
  ifcond
95
99
  end
96
100
 
97
- def traverse_if_tree(root_code_block, root_element, text_procedure, element_procedure)
101
+ def traverse_if_tree(context, text_procedure, element_procedure)
102
+
103
+ owner = context[:owner] # The root object of the if tree
104
+ root_element = context[:root_element] # The glaeml root_element of that if tree
105
+ rule_group = context[:rule_group] # The rule group in which this traversal happens (may be null for pre/post processors)
106
+
107
+ root_code_block = owner.root_code_block
98
108
  current_parent_code_block = root_code_block
99
109
 
100
110
  root_element.children.each{ |child|
@@ -145,7 +155,65 @@ module Glaemscribe
145
155
  end
146
156
 
147
157
  current_parent_code_block = if_term.parent_code_block
158
+ when 'macro'
159
+
160
+ # Macro definition, cannot be defined in conditional blocks
161
+ if current_parent_code_block.parent_if_cond || root_element.name != "rules"
162
+ @mode.errors << Glaeml::Error.new(child.line, "Macros can only defined in the 'rules' scope, not in a conditional block (because they are replaced and used at parsing time) or a macro block (local macros are not handled).")
163
+ return
164
+ end
165
+
166
+ if !child.args || child.args.count == 0
167
+ @mode.errors << Glaeml::Error.new(child.line, "Macro misses a name.")
168
+ return
169
+ end
170
+
171
+ macro_args = child.args.clone
172
+ macro_name = macro_args.shift
173
+ macro_args.each{ |arg|
174
+ if(!arg =~ /[0-9A-Z_]+/)
175
+ @mode.errors << Glaeml::Error.new(child.line, "Macro argument name #{arg} has wrong format.")
176
+ return
177
+ end
178
+ }
179
+
180
+ if rule_group.macros[macro_name]
181
+ @mode.errors << Glaeml::Error.new(child.line, "Redefining macro #{macro_name}.")
182
+ return
183
+ end
148
184
 
185
+ macro = Macro.new(rule_group,macro_name,macro_args)
186
+ macro_context = {:owner => macro, :root_element => child, :rule_group => rule_group}
187
+ traverse_if_tree(macro_context, text_procedure, element_procedure)
188
+
189
+ rule_group.macros[macro_name] = macro
190
+
191
+ when 'deploy'
192
+
193
+ if !rule_group
194
+ @mode.errors << Glaeml::Error.new(child.line, "Macros can only be deployed in a rule group.")
195
+ return
196
+ end
197
+
198
+ macro_args = child.args.clone
199
+ macro_name = macro_args.shift
200
+ macro = rule_group.macros[macro_name]
201
+
202
+ if !macro
203
+ @mode.errors << Glaeml::Error.new(child.line, "Macro '#{macro_name}' not found in rule group '#{rule_group.name}'.")
204
+ return
205
+ end
206
+
207
+ wanted_argcount = macro.arg_names.count
208
+ given_argcount = macro_args.count
209
+ if wanted_argcount != given_argcount
210
+ @mode.errors << Glaeml::Error.new(child.line, "Macro '#{macro_name}' takes #{wanted_argcount} arguments, not #{given_argcount}.")
211
+ return
212
+ end
213
+
214
+ macro_node = IfTree::MacroDeployTerm.new(macro, child.line, current_parent_code_block, macro_args)
215
+ current_parent_code_block.terms << macro_node
216
+
149
217
  else
150
218
  # Do something with this child element
151
219
  element_procedure.call(current_parent_code_block, child)
@@ -181,13 +249,16 @@ module Glaemscribe
181
249
  if !operator_class
182
250
  @mode.errors << Glaeml::Error.new(element.line,"Operator #{operator_name} is unknown.")
183
251
  else
184
- term.operators << operator_class.new(element.clone)
252
+ term.operators << operator_class.new(@mode, element.clone)
185
253
  end
186
254
  }
187
-
188
- root_code_block = ((pre_not_post)?(@mode.pre_processor.root_code_block):(@mode.post_processor.root_code_block))
189
-
190
- self.traverse_if_tree(root_code_block, processor_element, text_procedure, element_procedure )
255
+
256
+ processor_context = {
257
+ owner: ((pre_not_post)?(@mode.pre_processor):(@mode.post_processor)),
258
+ root_element: processor_element,
259
+ rule_group: nil
260
+ }
261
+ traverse_if_tree(processor_context, text_procedure, element_procedure )
191
262
  end
192
263
 
193
264
  def parse(file_path, mode_options = {})
@@ -219,6 +290,7 @@ module Glaemscribe
219
290
  doc.root_node.gpath("options.option").each{ |option_element|
220
291
  values = {}
221
292
  visibility = nil
293
+ is_radio = false
222
294
 
223
295
  option_element.gpath("value").each{ |value_element|
224
296
  value_name = value_element.args.first
@@ -227,6 +299,8 @@ module Glaemscribe
227
299
  option_element.gpath("visible_when").each{ |visible_element|
228
300
  visibility = visible_element.args.first
229
301
  }
302
+
303
+ option_element.gpath('radio').each{|e| is_radio = true}
230
304
 
231
305
  option_name_at = option_element.args[0]
232
306
  option_default_val_at = option_element.args[1]
@@ -236,8 +310,9 @@ module Glaemscribe
236
310
  @mode.errors << Glaeml::Error.new(option_element.line, "Missing option default value.")
237
311
  end
238
312
 
239
- option = Option.new(@mode, option_name_at, option_default_val_at, values, visibility)
240
- @mode.options[option.name] = option
313
+ option = Option.new(@mode, option_name_at, option_default_val_at, values, option_element.line, visibility)
314
+ option.is_radio = is_radio
315
+ @mode.options[option.name] = option
241
316
  }
242
317
 
243
318
  # Read the supported font list
@@ -301,6 +376,7 @@ module Glaemscribe
301
376
 
302
377
  lcount = element.line
303
378
  element.args[0].lines.to_a.each{ |l|
379
+ # Split into lines of code and count the lines
304
380
  l = l.strip
305
381
  term.code_lines << IfTree::CodeLine.new(l, lcount)
306
382
  lcount += 1
@@ -310,13 +386,40 @@ module Glaemscribe
310
386
  element_procedure = Proc.new { |current_parent_code_block, element|
311
387
  # This is fatal.
312
388
  @mode.errors << Glaeml::Error.new(element.line, "Unknown directive #{element.name}.")
313
- }
314
-
315
- self.traverse_if_tree( rule_group.root_code_block, rules_element, text_procedure, element_procedure )
389
+ }
390
+
391
+ processor_context = {
392
+ owner: rule_group,
393
+ root_element: rules_element,
394
+ rule_group: rule_group
395
+ }
396
+ traverse_if_tree(processor_context, text_procedure, element_procedure )
316
397
  }
317
-
318
- @mode.finalize(mode_options) if !@mode.errors.any?
398
+
399
+ espeak_option = @mode.options['espeak_voice']
400
+ if espeak_option
401
+ # Singleton lazy load the TTS engine
402
+ # If the mode relies on espeak
403
+ @mode.has_tts = true
404
+
405
+ begin
406
+ TTS::load_engine
407
+
408
+ # Check if all voices are supported
409
+ espeak_option.values.keys.each { |vname|
410
+ voice = TTS::option_name_to_voice(vname)
411
+ if !(TTS::voice_list.include? voice)
412
+ @mode.errors << Glaeml::Error.new(espeak_option.line, "Option has unhandled voice #{voice}.")
413
+ end
414
+ }
415
+ rescue
416
+ @mode.errors << Glaeml::Error.new(espeak_option.line, "Failed to load TTS engine.")
417
+ end
418
+
419
+ end
319
420
 
421
+ @mode.finalize(mode_options) if !@mode.errors.any?
422
+
320
423
  @mode
321
424
  end
322
425
  end
@@ -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
data/lib/api/option.rb CHANGED
@@ -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
 
@@ -0,0 +1,44 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Glǽmscribe (also written Glaemscribe) is a software dedicated to
4
+ # the transcription of texts between writing systems, and more
5
+ # specifically dedicated to the transcription of J.R.R. Tolkien's
6
+ # invented languages to some of his devised writing systems.
7
+ #
8
+ # Copyright (C) 2015 Benjamin Babut (Talagan).
9
+ #
10
+ # This program is free software: you can redistribute it and/or modify
11
+ # it under the terms of the GNU Affero General Public License as published by
12
+ # the Free Software Foundation, either version 3 of the License, or
13
+ # any later version.
14
+ #
15
+ # This program is distributed in the hope that it will be useful,
16
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
17
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
+ # GNU Affero General Public License for more details.
19
+ #
20
+ # You should have received a copy of the GNU Affero General Public License
21
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
22
+
23
+ # A post processor operator to replace the out_space on the fly.
24
+ # This has the same effect as the \outspace parameter
25
+ # But can be included in the postprocessor and benefit from the if/then logic
26
+
27
+ module Glaemscribe
28
+ module API
29
+
30
+ class OutspacePostProcessorOperator < PostProcessorOperator
31
+ def initialize(mode, glaeml_element)
32
+ super(mode, glaeml_element)
33
+ @out_space = @mode.post_processor.out_space = glaeml_element.args[0].split.reject{|token| token.empty? }
34
+ end
35
+
36
+ def apply(tokens, charset)
37
+ @mode.post_processor.out_space = @out_space
38
+ tokens
39
+ end
40
+ end
41
+
42
+ ResourceManager::register_post_processor_class("outspace", OutspacePostProcessorOperator)
43
+ end
44
+ end
@@ -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 {#{arg_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|
@@ -27,7 +27,8 @@ module Glaemscribe
27
27
  attr_reader :glaeml_element
28
28
  attr_reader :finalized_glaeml_element
29
29
 
30
- def initialize(glaeml_element)
30
+ def initialize(mode, glaeml_element)
31
+ @mode = mode
31
32
  @glaeml_element = glaeml_element
32
33
  end
33
34
 
@@ -121,15 +122,17 @@ module Glaemscribe
121
122
  attr_accessor :out_space
122
123
 
123
124
  def apply(tokens, out_charset)
124
-
125
- out_space_str = " "
126
- out_space_str = @out_space.map{ |token| out_charset[token].str }.join("") if @out_space
127
125
 
128
126
  # Apply filters
129
127
  @operators.each{ |operator|
130
128
  tokens = operator.apply(tokens,out_charset)
131
129
  }
132
-
130
+
131
+ out_space_str = " "
132
+ out_space_str = @out_space.map{ |token|
133
+ out_charset[token]&.str || UNKNOWN_CHAR_OUTPUT
134
+ }.join("") if @out_space
135
+
133
136
  # Convert output
134
137
  ret = ""
135
138
  tokens.each{ |token|