-
Notifications
You must be signed in to change notification settings - Fork 0
/
phrase.lua
316 lines (264 loc) · 9.09 KB
/
phrase.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
--[[
Phrase generator.
--]]
--[[
This module is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This module is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this module. If not, see <http://www.gnu.org/licenses/>.
Copyright © 2024 OOTA, Masato
--]]
local phrase_data = require "phrase.data"
local phrase_parser = require "phrase.parser"
local phrase = {}
local phrase_mt = { __index = phrase }
phrase.utf8_is_required = true
local check_encoding_consistency
--[[
Create an instance of the phrase generator.
param: text_or_compiled A text or a compiled data of the phrase syntax, or nil to request the empty instance.
start_condition A string that has the name of the nonterminal where is the start condition, or nil that means "main".
return: The phrase generator if no errors are detected.
An empty phrase generator if non fatal errors are detected.
nil if a fatal error is detected.
note: output_error() and output_compile_error() is called if some errors are detected.
--]]
function phrase.new(text_or_compiled, start_condition)
if not check_encoding_consistency() then
return nil
end
local instance = {}
setmetatable(instance, phrase_mt)
instance.data = phrase_data.new_phrase_data()
instance.type_phrase = true
if text_or_compiled then
instance:add(text_or_compiled, start_condition)
end
return instance
end
--[[
Add a phrase syntax into the instance.
param: text_or_compiled A text or a compiled data of the phrase syntax.
start_condition A string that has the name of the nonterminal where is the start condition, or nil that means "main".
return: ID for the syntax added into the phrase, or nil if the phrase syntax fail to add into the phrase generator due to some errors.
note: output_error() and output_compile_error() is if when some errors are detected.
--]]
function phrase:add(text_or_compiled, start_condition)
local text, syntax, err_msg
if type(text_or_compiled) == "string" then
text = text_or_compiled
syntax, err_msg = phrase_parser.parse(text)
elseif type(text_or_compiled) == "table" and text_or_compiled.type_compiled_syntax then
text = nil
err_msg = ""
syntax = text_or_compiled.data:clone()
else
phrase.output_error("Invalid parameter type.\n")
return nil
end
if err_msg == "" then
if start_condition == nil then
start_condition = "main"
end
err_msg = syntax:bind_syntax(start_condition)
if err_msg == "" and not self.data:add(syntax) then
err_msg = "Fail to add the syntax.\n"
end
end
if err_msg ~= "" then
self.output_compile_error(text, err_msg)
return nil
else
return syntax
end
end
--[[
Remove the phrase syntax from the instance.
param: ID for the syntax.
return: true if the syntax is removed.
note: This is an O(n) function, because it's assumed that the function is not used frequently.
--]]
function phrase:remove(id)
return self.data:remove(id)
end
--[[
Generate a phrase.
param: ext_context nil, or a table that has a set of the nonterminal and the value.
return: A phrase text.
note: 'ext_context' is used only for the unsolved nonterminals.
--]]
function phrase:generate(ext_context)
return self.data:generate(ext_context)
end
local compiled_syntax_add
--[[
Compile a phrase syntax to generate the compiled data.
param: text A string of a phrase syntax, or nil to request the empty instance.
return: The compiled data if no errors are detected.
nil if some errors are detected.
note: output_error() and output_compile_error() is if when some errors are detected.
--]]
function phrase.compile(text)
if not check_encoding_consistency() then
return nil
end
local syntax, err_msg
if text ~= nil then
syntax, err_msg = phrase_parser.parse(text)
if err_msg ~= "" then
phrase.output_compile_error(text, err_msg)
return nil
end
else
syntax = phrase_data.new_syntax()
end
local compiled = {}
compiled.data = syntax
compiled.type_compiled_syntax = true
compiled.add = compiled_syntax_add
return compiled
end
--[[
A instance method of the compiled data that adds a phrase syntax into "self" after compiling the syntax if needed.
param: text_or_compiled A text or a compiled data of the phrase syntax.
return: false if the phrase syntax fail to add into the instance due to some errors.
note: output_error() and output_compile_error() is if when some errors are detected.
note: If "text_or_compiled" has the nonterminal that self already contains, then
- The nonterminal in "text_or_compiled" overwrites it.
- Output an error message.
- Return true unless other error is detected.
--]]
function compiled_syntax_add(self, text_or_compiled)
local compiled_to_add = nil
if type(text_or_compiled) == "string" then
compiled_to_add = phrase.compile(text_or_compiled)
elseif type(text_or_compiled) == "table" and text_or_compiled.type_compiled_syntax then
compiled_to_add = {}
compiled_to_add.data = text_or_compiled.data:clone()
else
phrase.output_error("Invalid parameter type.\n")
end
if compiled_to_add then
local err_msg = self.data:add(compiled_to_add.data)
if err_msg ~= "" then
phrase.output_error(err_msg)
end
return true
else
return false
end
end
--[[
Equalize the chance to select each phrase syntax.
param: enable It equalizes the chance to select each phrase syntax if "enable" is true or nil. If 'enable' is false, the chance depends on the weight of the phrase syntax.
--]]
function phrase:equalize_chance(enable)
self.data:equalize_chance(enable)
end
--[[
Get the number of the syntaxes in the instance..
return: The number of the phrase syntaxes in the instance.
--]]
function phrase:get_number_of_syntax()
return self.data:get_number_of_syntax()
end
--[[
Get the sum of the weight of the syntaxes in the instance.
return: The sum of the weight of the syntaxes in the instance.
--]]
function phrase:get_weight()
return self.data:get_weight()
end
--[[
Get the number of the possible phrases generated by the instance.
return: The number of the possible phrases generated by the instance.
--]]
function phrase:get_combination_number()
return self.data:get_combination_number()
end
--[[
Set a random function to generate a phrase text.
param: f The function that is compatible with math.random() and math.random(n).
--]]
function phrase.set_random_function(f)
phrase_data.rand = f
end
--[[
Configure the requirement for the character encoding, and try to enable it.
param: enable if it's true or nil, UTF-8 support is required and tried to enable. (default)
if it's false, The plain 8 bit encoding support is required and tried to enable.
return: false if fail to set the required encoding.
--]]
function phrase.require_utf8(enable)
if enable == nil then
enable = true
end
phrase.utf8_is_required = enable
return phrase_parser.set_encoding_utf8(enable)
end
-- Check the character encoding is consistent.
function check_encoding_consistency()
if phrase.utf8_is_required then
if not phrase_parser.check_encoding_utf8() then
phrase.output_error('The module "phrase" requires the character encoding UTF-8.\n')
return false
end
else
if phrase_parser.check_encoding_utf8() then
phrase.output_error('The module "phrase" requires the plain 8 bit character encoding.\n')
return false
end
end
return true
end
--[[
Default function to output the error messages.
--]]
function phrase.output_error(err_msg)
io.stderr:write(err_msg)
end
--[[
Truncate a program text of a phrase syntax to output it in the error message.
--]]
local function trunc_syntax(text)
local target_len = 40
text = text:gsub("^%s+(.+)%s$", "%1")
local s = ""
local count = 0
local len = text:len()
while count < len do
local c = string.char(text:byte(count, count))
if c == "\n" then
break
end
s = s .. c
count = count + 1
if count >= target_len and string.find(" \t=|~", c, 1, true) then
break
end
end
if count < len then
s = s .. "..."
else
s = text
end
return s
end
--[[
Default function to output the compile error messages.
This function uses output_error() to output.
--]]
function phrase.output_compile_error(text, err_msg)
if text ~= nil then
phrase.output_error('Error in the phrase "' .. trunc_syntax(text) .. '":\n' .. err_msg)
else
phrase.output_error('Error at compiling:\n' .. err_msg)
end
end
return phrase