glaemscribe 1.1.14 → 1.3.0

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