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