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.
- checksums.yaml +5 -5
- data/bin/glaemscribe +21 -17
- data/glaemresources/charsets/cirth_ds.cst +540 -0
- data/glaemresources/charsets/eldamar.cst +210 -0
- data/glaemresources/charsets/sarati_eldamar.cst +256 -0
- data/glaemresources/charsets/tengwar_ds_annatar.cst +2868 -0
- data/glaemresources/charsets/tengwar_ds_eldamar.cst +2729 -0
- data/glaemresources/charsets/tengwar_ds_elfica.cst +2742 -0
- data/glaemresources/charsets/tengwar_ds_parmaite.cst +2726 -0
- data/glaemresources/charsets/tengwar_ds_sindarin.cst +2722 -0
- data/glaemresources/charsets/tengwar_freemono.cst +217 -0
- data/glaemresources/charsets/tengwar_guni_annatar.cst +2948 -0
- data/glaemresources/charsets/tengwar_guni_eldamar.cst +2809 -0
- data/glaemresources/charsets/tengwar_guni_elfica.cst +2809 -0
- data/glaemresources/charsets/tengwar_guni_parmaite.cst +2813 -0
- data/glaemresources/charsets/tengwar_guni_sindarin.cst +2808 -0
- data/glaemresources/charsets/tengwar_telcontar.cst +225 -0
- data/glaemresources/charsets/unicode_gothic.cst +64 -0
- data/glaemresources/charsets/unicode_runes.cst +121 -0
- data/glaemresources/modes/{adunaic.glaem → adunaic-tengwar-glaemscrafu.glaem} +14 -2
- data/glaemresources/modes/{blackspeech.glaem → blackspeech-tengwar-general_use.glaem} +13 -3
- data/glaemresources/modes/english-cirth-espeak.glaem +687 -0
- data/glaemresources/modes/english-tengwar-espeak.glaem +814 -0
- data/glaemresources/modes/japanese-tengwar.glaem +776 -0
- data/glaemresources/modes/{khuzdul.glaem → khuzdul-cirth-moria.glaem} +4 -1
- data/glaemresources/modes/lang_belta-tengwar-dadef.glaem +248 -0
- data/glaemresources/modes/{futhorc.glaem → old_english-futhorc.glaem} +0 -0
- data/glaemresources/modes/{mercian.glaem → old_english-tengwar-mercian.glaem} +22 -12
- data/glaemresources/modes/{westsaxon.glaem → old_english-tengwar-westsaxon.glaem} +20 -11
- data/glaemresources/modes/{futhark-runicus.glaem → old_norse-futhark-runicus.glaem} +0 -0
- data/glaemresources/modes/{futhark-younger.glaem → old_norse-futhark-younger.glaem} +0 -0
- data/glaemresources/modes/{quenya.glaem → quenya-tengwar-classical.glaem} +32 -50
- data/glaemresources/modes/raw-cirth.glaem +154 -0
- data/glaemresources/modes/raw-tengwar.glaem +46 -23
- data/glaemresources/modes/{rlyehian.glaem → rlyehian-tengwar.glaem} +14 -3
- data/glaemresources/modes/{sindarin-daeron.glaem → sindarin-cirth-daeron.glaem} +55 -14
- data/glaemresources/modes/{sindarin-beleriand.glaem → sindarin-tengwar-beleriand.glaem} +154 -28
- data/glaemresources/modes/{sindarin.glaem → sindarin-tengwar-general_use.glaem} +86 -25
- data/glaemresources/modes/{telerin.glaem → telerin-tengwar-glaemscrafu.glaem} +16 -6
- data/glaemresources/modes/{westron.glaem → westron-tengwar-glaemscrafu.glaem} +18 -8
- data/lib/api/charset.rb +67 -7
- data/lib/api/charset_parser.rb +14 -1
- data/lib/api/constants.rb +3 -4
- data/lib/api/fragment.rb +26 -5
- data/lib/api/if_tree.rb +70 -8
- data/lib/api/macro.rb +40 -0
- data/lib/api/mode.rb +66 -19
- data/lib/api/mode_parser.rb +117 -14
- data/lib/api/object_additions.rb +23 -1
- data/lib/api/option.rb +17 -2
- data/lib/api/post_processor/outspace.rb +44 -0
- data/lib/api/post_processor/resolve_virtuals.rb +25 -9
- data/lib/api/resource_manager.rb +1 -0
- data/lib/api/rule_group.rb +170 -26
- data/lib/api/sheaf_chain_iterator.rb +1 -1
- data/lib/api/transcription_pre_post_processor.rb +8 -5
- data/lib/api/transcription_processor.rb +15 -12
- data/lib/api/tts.rb +51 -0
- data/lib/glaemscribe.rb +36 -31
- data/lib_espeak/espeakng.for.glaemscribe.nowasm.sync.js +35 -0
- data/lib_espeak/glaemscribe_tts.js +505 -0
- metadata +76 -24
data/lib/api/mode_parser.rb
CHANGED
@@ -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(
|
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
|
-
|
189
|
-
|
190
|
-
|
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
|
240
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/api/object_additions.rb
CHANGED
@@ -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,
|
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
|
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
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
|
data/lib/api/resource_manager.rb
CHANGED
data/lib/api/rule_group.rb
CHANGED
@@ -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
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
145
|
-
add_var("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 '
|
155
|
-
@in_charset[inchar] = self if inchar != WORD_BREAKER && inchar !=
|
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|
|