glaemscribe 1.0.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 +7 -0
- data/LICENSE.txt +19 -0
- data/bin/glaemscribe +307 -0
- data/glaemresources/charsets/cirth_ds.cst +205 -0
- data/glaemresources/charsets/sarati_eldamar.cst +256 -0
- data/glaemresources/charsets/tengwar_ds.cst +318 -0
- data/glaemresources/charsets/unicode_gothic.cst +64 -0
- data/glaemresources/charsets/unicode_runes.cst +120 -0
- data/glaemresources/modes/adunaic.glaem +251 -0
- data/glaemresources/modes/blackspeech-annatar.glaem +318 -0
- data/glaemresources/modes/blackspeech.glaem +260 -0
- data/glaemresources/modes/gothic.glaem +78 -0
- data/glaemresources/modes/khuzdul.glaem +141 -0
- data/glaemresources/modes/mercian.glaem +419 -0
- data/glaemresources/modes/oldnorse-medieval.glaem +127 -0
- data/glaemresources/modes/quenya-sarati.glaem +320 -0
- data/glaemresources/modes/quenya.glaem +307 -0
- data/glaemresources/modes/sindarin-beleriand.glaem +285 -0
- data/glaemresources/modes/sindarin-classical.glaem +276 -0
- data/glaemresources/modes/sindarin-daeron.glaem +182 -0
- data/glaemresources/modes/telerin.glaem +302 -0
- data/glaemresources/modes/valarin-sarati.glaem +210 -0
- data/glaemresources/modes/westron.glaem +340 -0
- data/glaemresources/modes/westsaxon.glaem +342 -0
- data/lib/api/charset.rb +84 -0
- data/lib/api/charset_parser.rb +55 -0
- data/lib/api/constants.rb +29 -0
- data/lib/api/debug.rb +36 -0
- data/lib/api/eval.rb +268 -0
- data/lib/api/fragment.rb +113 -0
- data/lib/api/glaeml.rb +200 -0
- data/lib/api/if_tree.rb +96 -0
- data/lib/api/mode.rb +112 -0
- data/lib/api/mode_parser.rb +314 -0
- data/lib/api/option.rb +64 -0
- data/lib/api/post_processor/reverse.rb +36 -0
- data/lib/api/pre_processor/downcase.rb +35 -0
- data/lib/api/pre_processor/elvish_numbers.rb +47 -0
- data/lib/api/pre_processor/rxsubstitute.rb +40 -0
- data/lib/api/pre_processor/substitute.rb +38 -0
- data/lib/api/pre_processor/up_down_tehta_split.rb +138 -0
- data/lib/api/resource_manager.rb +130 -0
- data/lib/api/rule.rb +99 -0
- data/lib/api/rule_group.rb +159 -0
- data/lib/api/sheaf.rb +70 -0
- data/lib/api/sheaf_chain.rb +86 -0
- data/lib/api/sheaf_chain_iterator.rb +108 -0
- data/lib/api/sub_rule.rb +40 -0
- data/lib/api/transcription_pre_post_processor.rb +118 -0
- data/lib/api/transcription_processor.rb +137 -0
- data/lib/api/transcription_tree_node.rb +91 -0
- data/lib/glaemscribe.rb +70 -0
- metadata +112 -0
data/lib/api/glaeml.rb
ADDED
@@ -0,0 +1,200 @@
|
|
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
|
+
module Glaemscribe
|
24
|
+
module API
|
25
|
+
module Glaeml
|
26
|
+
|
27
|
+
class Error
|
28
|
+
attr_accessor :text
|
29
|
+
attr_accessor :line
|
30
|
+
|
31
|
+
def initialize(line,text)
|
32
|
+
@line = line
|
33
|
+
@text = text
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class Document
|
38
|
+
attr_accessor :root_node
|
39
|
+
attr_accessor :errors
|
40
|
+
|
41
|
+
def initialize
|
42
|
+
@errors = []
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class Node
|
47
|
+
|
48
|
+
class Type
|
49
|
+
Text = 0
|
50
|
+
ElementInline = 1
|
51
|
+
ElementBlock = 2
|
52
|
+
end
|
53
|
+
|
54
|
+
def text?
|
55
|
+
@type == Type::Text
|
56
|
+
end
|
57
|
+
|
58
|
+
def element?
|
59
|
+
@type == Type::ElementInline || @type == Type::ElementBlock
|
60
|
+
end
|
61
|
+
|
62
|
+
def initialize(line, type, name)
|
63
|
+
@line = line
|
64
|
+
@type = type
|
65
|
+
@name = name
|
66
|
+
@args = []
|
67
|
+
@children = []
|
68
|
+
end
|
69
|
+
|
70
|
+
def pathfind_crawl(apath, found)
|
71
|
+
|
72
|
+
children.each{ |c|
|
73
|
+
if(c.name == apath[0])
|
74
|
+
if apath.count == 1
|
75
|
+
found << c
|
76
|
+
else
|
77
|
+
bpath = apath.dup
|
78
|
+
bpath.shift
|
79
|
+
c.pathfind_crawl(bpath, found)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
def gpath(path)
|
86
|
+
apath = path.split(".")
|
87
|
+
found = []
|
88
|
+
pathfind_crawl(apath, found)
|
89
|
+
found
|
90
|
+
end
|
91
|
+
|
92
|
+
attr_accessor :line
|
93
|
+
|
94
|
+
attr_reader :type
|
95
|
+
attr_reader :name
|
96
|
+
|
97
|
+
attr_accessor :args
|
98
|
+
attr_reader :children
|
99
|
+
|
100
|
+
attr_accessor :parent_node
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
|
105
|
+
class Parser
|
106
|
+
|
107
|
+
def add_text_node(lnum, text)
|
108
|
+
n = Node.new(lnum, Node::Type::Text, "root")
|
109
|
+
n.args << text
|
110
|
+
n.parent_node = @current_parent_node
|
111
|
+
@current_parent_node.children << n
|
112
|
+
end
|
113
|
+
|
114
|
+
def parse(raw_data)
|
115
|
+
raw_data.gsub!(/\\\*\*(.*?)\*\*\\/m) { |found|
|
116
|
+
"\n" * found.count("\n")
|
117
|
+
}
|
118
|
+
|
119
|
+
lnum = 0
|
120
|
+
|
121
|
+
@document = Document.new
|
122
|
+
@document.root_node = Node.new(lnum, Node::Type::ElementBlock, nil)
|
123
|
+
|
124
|
+
@current_parent_node = @document.root_node
|
125
|
+
|
126
|
+
raw_data.lines.each{ |l|
|
127
|
+
lnum += 1
|
128
|
+
|
129
|
+
l = l.strip
|
130
|
+
add_text_node(lnum, l) and next if l.empty?
|
131
|
+
|
132
|
+
if(l[0..0] == "\\")
|
133
|
+
if(l.length == 1)
|
134
|
+
@document.errors << Error.new(lnum, "Incomplete node.")
|
135
|
+
else
|
136
|
+
if(l[1..1] == "\\") # First backslash is escaped
|
137
|
+
|
138
|
+
add_text_node(lnum, l[1..-1])
|
139
|
+
|
140
|
+
elsif l =~ /^(\\beg\s+)/
|
141
|
+
|
142
|
+
rest = $'.strip
|
143
|
+
args = []
|
144
|
+
name = "???"
|
145
|
+
if(! (rest =~ /^([a-z_]+)/) )
|
146
|
+
@document.errors << Error.new(lnum, "Bad element name #{rest}.")
|
147
|
+
else
|
148
|
+
name = $1
|
149
|
+
args = $'.shellsplit
|
150
|
+
end
|
151
|
+
|
152
|
+
n = Node.new(lnum, Node::Type::ElementBlock, name)
|
153
|
+
n.args += args
|
154
|
+
n.parent_node = @current_parent_node
|
155
|
+
|
156
|
+
@current_parent_node.children << n
|
157
|
+
@current_parent_node = n
|
158
|
+
|
159
|
+
elsif l =~ /^(\\end(\s|$))/
|
160
|
+
|
161
|
+
if !@current_parent_node.parent_node
|
162
|
+
@document.errors << Error.new(lnum, "Element 'end' unexpected.")
|
163
|
+
elsif $'.strip != ""
|
164
|
+
@document.errors << Error.new(lnum, "Element 'end' should not have arguments.")
|
165
|
+
else
|
166
|
+
@current_parent_node = @current_parent_node.parent_node
|
167
|
+
end
|
168
|
+
|
169
|
+
else
|
170
|
+
|
171
|
+
# Read the name of the node
|
172
|
+
l = l[1..-1]
|
173
|
+
l =~ /^([a-z_]+)/
|
174
|
+
name = $1
|
175
|
+
args = $'.shellsplit
|
176
|
+
|
177
|
+
n = Node.new(lnum, Node::Type::ElementInline, name)
|
178
|
+
n.args += args
|
179
|
+
n.parent_node = @current_parent_node
|
180
|
+
|
181
|
+
@current_parent_node.children << n
|
182
|
+
|
183
|
+
end
|
184
|
+
end
|
185
|
+
else
|
186
|
+
add_text_node(lnum, l)
|
187
|
+
end
|
188
|
+
}
|
189
|
+
|
190
|
+
if @current_parent_node != @document.root_node
|
191
|
+
@document.errors << Error.new(lnum, "Missing 'end' element.")
|
192
|
+
end
|
193
|
+
|
194
|
+
return @document
|
195
|
+
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
data/lib/api/if_tree.rb
ADDED
@@ -0,0 +1,96 @@
|
|
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
|
+
module Glaemscribe
|
24
|
+
module API
|
25
|
+
module IfTree
|
26
|
+
|
27
|
+
class IfCond
|
28
|
+
attr_accessor :line, :expression, :parent_if_term, :child_code_block
|
29
|
+
def initialize(line, parent_if_term, expression)
|
30
|
+
@parent_if_term = parent_if_term
|
31
|
+
@expression = expression
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Term
|
36
|
+
attr_accessor :parent_code_block
|
37
|
+
def initialize(parent_code_block)
|
38
|
+
@parent_code_block = parent_code_block
|
39
|
+
end
|
40
|
+
def is_code_lines?
|
41
|
+
false
|
42
|
+
end
|
43
|
+
def is_pre_post_processor_operators?
|
44
|
+
false
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class IfTerm < Term
|
49
|
+
attr_accessor :if_conds
|
50
|
+
def initialize(parent_code_block)
|
51
|
+
super(parent_code_block)
|
52
|
+
@if_conds = []
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class CodeLine
|
57
|
+
attr_accessor :expression, :line
|
58
|
+
def initialize(expression, line)
|
59
|
+
@expression = expression
|
60
|
+
@line = line
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class PrePostProcessorOperatorsTerm < Term
|
65
|
+
attr_accessor :operators
|
66
|
+
def initialize(parent_code_block)
|
67
|
+
super(parent_code_block)
|
68
|
+
@operators = []
|
69
|
+
end
|
70
|
+
def is_pre_post_processor_operators?
|
71
|
+
true
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class CodeLinesTerm < Term
|
76
|
+
attr_accessor :code_lines
|
77
|
+
def initialize(parent_code_block)
|
78
|
+
super(parent_code_block)
|
79
|
+
@code_lines = []
|
80
|
+
end
|
81
|
+
def is_code_lines?
|
82
|
+
true
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
class CodeBlock
|
87
|
+
attr_accessor :terms, :parent_if_cond
|
88
|
+
def initialize(parent_if_cond = nil)
|
89
|
+
@parent_if_cond = parent_if_cond
|
90
|
+
@terms = []
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
data/lib/api/mode.rb
ADDED
@@ -0,0 +1,112 @@
|
|
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
|
+
module Glaemscribe
|
24
|
+
module API
|
25
|
+
class Mode
|
26
|
+
|
27
|
+
attr_accessor :errors
|
28
|
+
attr_accessor :warnings
|
29
|
+
|
30
|
+
attr_accessor :name
|
31
|
+
|
32
|
+
attr_accessor :language, :writing, :human_name, :authors, :version
|
33
|
+
attr_accessor :pre_processor, :processor, :post_processor
|
34
|
+
|
35
|
+
attr_accessor :options
|
36
|
+
|
37
|
+
attr_accessor :supported_charsets
|
38
|
+
attr_accessor :default_charset
|
39
|
+
|
40
|
+
def initialize(name)
|
41
|
+
@name = name
|
42
|
+
@errors = []
|
43
|
+
@warnings = []
|
44
|
+
@supported_charsets = {}
|
45
|
+
@options = {}
|
46
|
+
|
47
|
+
@pre_processor = TranscriptionPreProcessor.new(self)
|
48
|
+
@processor = TranscriptionProcessor.new(self)
|
49
|
+
@post_processor = TranscriptionPostProcessor.new(self)
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_s
|
53
|
+
" <Mode #{name}: Language '#{language}', Writing '#{writing}', Human Name '#{human_name}', Authors '#{authors}', Version '#{version}'> "
|
54
|
+
end
|
55
|
+
|
56
|
+
def inspect
|
57
|
+
to_s
|
58
|
+
end
|
59
|
+
|
60
|
+
def finalize(options={})
|
61
|
+
|
62
|
+
# Hash: option_name => value_name
|
63
|
+
trans_options = {}
|
64
|
+
|
65
|
+
# Build default options
|
66
|
+
@options.each { |oname, o|
|
67
|
+
trans_options[oname] = o.default_value_name
|
68
|
+
}
|
69
|
+
|
70
|
+
# Push user options
|
71
|
+
options.each { |oname, valname|
|
72
|
+
# Check if option exists
|
73
|
+
opt = @options[oname]
|
74
|
+
next if(!opt)
|
75
|
+
|
76
|
+
val = opt.value_for_value_name(valname)
|
77
|
+
next if val == nil
|
78
|
+
|
79
|
+
trans_options[oname] = valname
|
80
|
+
}
|
81
|
+
|
82
|
+
trans_options_converted = {}
|
83
|
+
|
84
|
+
# Do a conversion to values space
|
85
|
+
trans_options.each{ |oname,valname|
|
86
|
+
trans_options_converted[oname] = @options[oname].value_for_value_name(valname)
|
87
|
+
}
|
88
|
+
|
89
|
+
@pre_processor.finalize(trans_options_converted)
|
90
|
+
@post_processor.finalize(trans_options_converted)
|
91
|
+
@processor.finalize(trans_options_converted)
|
92
|
+
|
93
|
+
self
|
94
|
+
end
|
95
|
+
|
96
|
+
def transcribe(content, charset = nil)
|
97
|
+
charset = default_charset if !charset
|
98
|
+
return false, "*** No charset usable for transcription. Failed!" if !charset
|
99
|
+
|
100
|
+
ret = content.lines.map{ |l|
|
101
|
+
l = l.strip # Clean the lines
|
102
|
+
l = @pre_processor.apply(l)
|
103
|
+
l = @processor.apply(l, charset)
|
104
|
+
l = @post_processor.apply(l)
|
105
|
+
}.join("\n")
|
106
|
+
|
107
|
+
return true, ret
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,314 @@
|
|
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
|
+
module Glaemscribe
|
24
|
+
module API
|
25
|
+
|
26
|
+
class ModeParser
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
@mode = nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def validate_presence_of_args(node, arg_count = nil)
|
33
|
+
if(arg_count)
|
34
|
+
if(node.args.count != arg_count)
|
35
|
+
@mode.errors << Glaeml::Error.new(node.line,"Element '#{node.name}' should have #{arg_count} arguments.")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def validate_presence_of_children(parent_node, elt_name, elt_count = nil, arg_count = nil)
|
41
|
+
res = parent_node.gpath(elt_name)
|
42
|
+
if(elt_count)
|
43
|
+
if(res.count != elt_count)
|
44
|
+
@mode.errors << Glaeml::Error.new(parent_node.line,"Element '#{parent_node.name}' should have exactly #{elt_count} children of type '#{elt_name}'.")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
if(arg_count)
|
48
|
+
res.each{ |child_node|
|
49
|
+
validate_presence_of_args(child_node, arg_count)
|
50
|
+
}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Very simplified 'dtd' like verification
|
55
|
+
def verify_mode_glaeml(doc)
|
56
|
+
validate_presence_of_children(doc.root_node, "language", 1, 1)
|
57
|
+
validate_presence_of_children(doc.root_node, "writing", 1, 1)
|
58
|
+
validate_presence_of_children(doc.root_node, "mode", 1, 1)
|
59
|
+
validate_presence_of_children(doc.root_node, "authors", 1, 1)
|
60
|
+
validate_presence_of_children(doc.root_node, "version", 1, 1)
|
61
|
+
|
62
|
+
doc.root_node.gpath("charset").each{ |charset_element|
|
63
|
+
validate_presence_of_args(charset_element, 2)
|
64
|
+
}
|
65
|
+
|
66
|
+
doc.root_node.gpath("options.option").each{ |option_element|
|
67
|
+
validate_presence_of_args(option_element, 2)
|
68
|
+
option_element.gpath("value").each{ |value_element|
|
69
|
+
validate_presence_of_args(value_element, 2)
|
70
|
+
}
|
71
|
+
}
|
72
|
+
|
73
|
+
doc.root_node.gpath("processor.outspace").each{ |outspace_element|
|
74
|
+
validate_presence_of_args(outspace_element, 1)
|
75
|
+
}
|
76
|
+
|
77
|
+
doc.root_node.gpath("processor.rules").each{ |rules_element|
|
78
|
+
validate_presence_of_args(rules_element, 1)
|
79
|
+
}
|
80
|
+
end
|
81
|
+
|
82
|
+
def create_if_cond_for_if_term(line, if_term, cond)
|
83
|
+
ifcond = IfTree::IfCond.new(line, if_term, cond)
|
84
|
+
child_code_block = IfTree::CodeBlock.new(ifcond)
|
85
|
+
ifcond.child_code_block = child_code_block
|
86
|
+
if_term.if_conds << ifcond
|
87
|
+
ifcond
|
88
|
+
end
|
89
|
+
|
90
|
+
def traverse_if_tree(root_code_block, root_element, text_procedure, element_procedure)
|
91
|
+
current_parent_code_block = root_code_block
|
92
|
+
|
93
|
+
root_element.children.each{ |child|
|
94
|
+
if(child.text?)
|
95
|
+
# Do something with this text
|
96
|
+
text_procedure.call(current_parent_code_block, child)
|
97
|
+
elsif(child.element?)
|
98
|
+
case child.name
|
99
|
+
when 'if'
|
100
|
+
cond_attribute = child.args.first
|
101
|
+
if_term = IfTree::IfTerm.new(current_parent_code_block)
|
102
|
+
current_parent_code_block.terms << if_term
|
103
|
+
if_cond = create_if_cond_for_if_term(child.line, if_term, cond_attribute)
|
104
|
+
current_parent_code_block = if_cond.child_code_block
|
105
|
+
|
106
|
+
when 'elsif'
|
107
|
+
|
108
|
+
cond_attribute = child.args.first
|
109
|
+
if_term = current_parent_code_block.parent_if_cond.parent_if_term
|
110
|
+
|
111
|
+
if !if_term
|
112
|
+
@mode.errors << Glaeml::Error.new(child.line, " 'elsif' without a 'if'.")
|
113
|
+
return
|
114
|
+
end
|
115
|
+
|
116
|
+
# TODO : check that precendent one is a elsif, not a else
|
117
|
+
if_cond = create_if_cond_for_if_term(child.line, if_term, cond_attribute)
|
118
|
+
current_parent_code_block = if_cond.child_code_block
|
119
|
+
|
120
|
+
when 'else'
|
121
|
+
|
122
|
+
if_term = current_parent_code_block.parent_if_cond.parent_if_term
|
123
|
+
|
124
|
+
if !if_term
|
125
|
+
@mode.errors << Glaeml::Error.new(child.line, " 'else' without a 'if'.")
|
126
|
+
return
|
127
|
+
end
|
128
|
+
|
129
|
+
if_cond = create_if_cond_for_if_term(child.line, if_term, "true")
|
130
|
+
current_parent_code_block = if_cond.child_code_block
|
131
|
+
|
132
|
+
when 'endif'
|
133
|
+
if_term = current_parent_code_block.parent_if_cond.parent_if_term
|
134
|
+
|
135
|
+
if !if_term
|
136
|
+
@mode.errors << Glaeml::Error.new(child.line, " 'endif' without a 'if'.")
|
137
|
+
return
|
138
|
+
end
|
139
|
+
|
140
|
+
current_parent_code_block = if_term.parent_code_block
|
141
|
+
|
142
|
+
else
|
143
|
+
# Do something with this child element
|
144
|
+
element_procedure.call(current_parent_code_block, child)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
}
|
148
|
+
|
149
|
+
if(current_parent_code_block.parent_if_cond)
|
150
|
+
@mode.errors << Glaeml::Error.new(root_element.line, "Unended 'if' at the end of this '#{root_element.name}' element.")
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def parse_pre_post_processor(processor_element, pre_not_post)
|
155
|
+
# Do nothing with text elements
|
156
|
+
text_procedure = Proc.new { |current_parent_code_block, element| }
|
157
|
+
|
158
|
+
element_procedure = Proc.new { |current_parent_code_block, element|
|
159
|
+
|
160
|
+
# A block of code lines. Put them in a codelinesterm.
|
161
|
+
term = current_parent_code_block.terms.last
|
162
|
+
if(!term || !term.is_pre_post_processor_operators?)
|
163
|
+
term = IfTree::PrePostProcessorOperatorsTerm.new(current_parent_code_block)
|
164
|
+
current_parent_code_block.terms << term
|
165
|
+
end
|
166
|
+
|
167
|
+
operator_name = element.name
|
168
|
+
operator_class = if pre_not_post
|
169
|
+
ResourceManager::class_for_pre_processor_operator_name(operator_name)
|
170
|
+
else
|
171
|
+
ResourceManager::class_for_post_processor_operator_name(operator_name)
|
172
|
+
end
|
173
|
+
|
174
|
+
if !operator_class
|
175
|
+
@mode.errors << Glaeml::Error.new(element.line,"Operator #{operator_name} is unknown.")
|
176
|
+
else
|
177
|
+
arg0 = element.args[0]
|
178
|
+
arg1 = element.args[1]
|
179
|
+
arg2 = element.args[2]
|
180
|
+
arg3 = element.args[3]
|
181
|
+
|
182
|
+
term.operators << operator_class.new([arg0,arg1,arg2,arg3])
|
183
|
+
end
|
184
|
+
}
|
185
|
+
|
186
|
+
root_code_block = ((pre_not_post)?(@mode.pre_processor.root_code_block):(@mode.post_processor.root_code_block))
|
187
|
+
|
188
|
+
self.traverse_if_tree(root_code_block, processor_element, text_procedure, element_procedure )
|
189
|
+
end
|
190
|
+
|
191
|
+
def parse(file_path, mode_options = {})
|
192
|
+
|
193
|
+
@mode = Mode.new(ResourceManager::mode_name_from_file_path(file_path))
|
194
|
+
|
195
|
+
raw = File.open(file_path,"rb:utf-8").read
|
196
|
+
doc = Glaeml::Parser.new.parse(raw)
|
197
|
+
|
198
|
+
if(doc.errors.any?)
|
199
|
+
@mode.errors = doc.errors
|
200
|
+
return @mode
|
201
|
+
end
|
202
|
+
|
203
|
+
verify_mode_glaeml(doc)
|
204
|
+
|
205
|
+
return @mode if(@mode.errors.any?)
|
206
|
+
|
207
|
+
# Get the attributes of the mode
|
208
|
+
@mode.language = doc.root_node.gpath('language').first.args.first
|
209
|
+
@mode.writing = doc.root_node.gpath('writing').first.args.first
|
210
|
+
@mode.human_name = doc.root_node.gpath('mode').first.args.first
|
211
|
+
@mode.authors = doc.root_node.gpath('authors').first.args.first
|
212
|
+
@mode.version = doc.root_node.gpath('version').first.args.first
|
213
|
+
|
214
|
+
doc.root_node.gpath("options.option").each{ |option_element|
|
215
|
+
values = {}
|
216
|
+
option_element.gpath("value").each{ |value_element|
|
217
|
+
value_name = value_element.args.first
|
218
|
+
values[value_name] = value_element.args.last.to_i
|
219
|
+
}
|
220
|
+
|
221
|
+
option_name_at = option_element.args[0]
|
222
|
+
option_default_val_at = option_element.args[1]
|
223
|
+
# TODO: check syntax of the option name
|
224
|
+
|
225
|
+
if(option_default_val_at.nil?)
|
226
|
+
@mode.errors << Glaeml::Error.new(option_element.line, "Missing option default value.")
|
227
|
+
end
|
228
|
+
|
229
|
+
option = Option.new(option_name_at, option_default_val_at, values)
|
230
|
+
@mode.options[option.name] = option
|
231
|
+
}
|
232
|
+
|
233
|
+
# Read the supported font list
|
234
|
+
doc.root_node.gpath("charset").each { |charset_element|
|
235
|
+
charset_name = charset_element.args.first
|
236
|
+
charset = ResourceManager::charset(charset_name)
|
237
|
+
|
238
|
+
# Load the charset if we don't have it
|
239
|
+
if !charset
|
240
|
+
ResourceManager::load_charsets([charset_name])
|
241
|
+
charset = ResourceManager::charset(charset_name)
|
242
|
+
end
|
243
|
+
if charset
|
244
|
+
if charset.errors.any?
|
245
|
+
charset.errors.each{ |e|
|
246
|
+
@mode.errors << Glaeml::Error.new(charset_element.line, "#{charset_name}:#{e.line}:#{e.text}")
|
247
|
+
}
|
248
|
+
return @mode
|
249
|
+
end
|
250
|
+
|
251
|
+
@mode.supported_charsets[charset_name] = charset
|
252
|
+
@mode.default_charset = charset if charset_element.args[1] && charset_element.args[1] == "true"
|
253
|
+
else
|
254
|
+
@mode.warnings << Glaeml::Error.new(charset_element.line,"Failed to load charset '#{charset_name}'.")
|
255
|
+
end
|
256
|
+
}
|
257
|
+
|
258
|
+
if !@mode.default_charset
|
259
|
+
@mode.warnings << Glaeml::Error.new(0,"No default charset defined!!")
|
260
|
+
end
|
261
|
+
|
262
|
+
# Read the preprocessor
|
263
|
+
doc.root_node.gpath("preprocessor").each{ |preprocessor_element|
|
264
|
+
self.parse_pre_post_processor(preprocessor_element, true)
|
265
|
+
}
|
266
|
+
|
267
|
+
# Read the postprocessor
|
268
|
+
doc.root_node.gpath("postprocessor").each{ |postprocessor_element|
|
269
|
+
self.parse_pre_post_processor(postprocessor_element, false)
|
270
|
+
}
|
271
|
+
|
272
|
+
# Read the processor
|
273
|
+
doc.root_node.gpath("processor.outspace").each{ |outspace_element|
|
274
|
+
val = outspace_element.args[0]
|
275
|
+
@mode.processor.out_space = val.split.reject{|token| token.empty? }
|
276
|
+
}
|
277
|
+
|
278
|
+
doc.root_node.gpath("processor.rules").each{ |rules_element|
|
279
|
+
|
280
|
+
rule_group_name = rules_element.args.first
|
281
|
+
rule_group = RuleGroup.new(@mode,rule_group_name)
|
282
|
+
@mode.processor.rule_groups[rule_group_name] = rule_group
|
283
|
+
|
284
|
+
text_procedure = Proc.new { |current_parent_code_block, element|
|
285
|
+
# A block of code lines. Put them in a codelinesterm.
|
286
|
+
term = current_parent_code_block.terms.last
|
287
|
+
if(!term || !term.is_code_lines?)
|
288
|
+
term = IfTree::CodeLinesTerm.new(current_parent_code_block)
|
289
|
+
current_parent_code_block.terms << term
|
290
|
+
end
|
291
|
+
|
292
|
+
lcount = element.line
|
293
|
+
element.args[0].lines.to_a.each{ |l|
|
294
|
+
l = l.strip
|
295
|
+
term.code_lines << IfTree::CodeLine.new(l, lcount)
|
296
|
+
lcount += 1
|
297
|
+
}
|
298
|
+
}
|
299
|
+
|
300
|
+
element_procedure = Proc.new { |current_parent_code_block, element|
|
301
|
+
# This is fatal.
|
302
|
+
@mode.errors << Glaeml::Error.new(element.line, "Unknown directive #{element.name}.")
|
303
|
+
}
|
304
|
+
|
305
|
+
self.traverse_if_tree( rule_group.root_code_block, rules_element, text_procedure, element_procedure )
|
306
|
+
}
|
307
|
+
|
308
|
+
@mode.finalize(mode_options) if !@mode.errors.any?
|
309
|
+
|
310
|
+
@mode
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|