1 /*************************************************************************/
2 /* Copyright (C) 2019-2020 Amir Plivatsky */
3 /* All rights reserved */
4 /* */
5 /* Use of the link grammar parsing system is subject to the terms of the */
6 /* license set forth in the LICENSE file included with this software. */
7 /* This license allows free redistribution and use in source and binary */
8 /* forms, with or without modification, subject to certain conditions. */
9 /* */
10 /*************************************************************************/
11
12 #include <string.h>
13
14 #include "api-structures.h"
15 #include "dialect.h"
16 #include "dict-api.h" // cost_stringify
17 #include "dict-common.h"
18 #include "dict-structures.h"
19 #include "dict-file/read-dialect.h" // dialect_read_from_one_line_str
20 #include "file-utils.h"
21 #include "string-id.h"
22 #include "string-set.h"
23
24 #define D_DIALECT 7 /* Verbosity level for this file (also 8). */
25
free_dialect_table(Dialect * di)26 static void free_dialect_table(Dialect *di)
27 {
28 free(di->table);
29 free(di->kept_input);
30 }
31
free_dialect(Dialect * di)32 void free_dialect(Dialect *di)
33 {
34 if (di == NULL) return;
35 free_dialect_table(di);
36 free(di->section);
37 string_id_delete(di->section_set);
38 free(di);
39 }
40
dialect_alloc(void)41 Dialect *dialect_alloc(void)
42 {
43 Dialect *di = malloc(sizeof(*di));
44 memset(di, 0, sizeof(*di));
45 di->section_set = string_id_create();
46 return di;
47 }
48
exptag_dialect_add(Dictionary dict,const char * tag)49 unsigned int exptag_dialect_add(Dictionary dict, const char *tag)
50 {
51 expression_tag *dt = &dict->dialect_tag;
52 unsigned int tag_index = string_id_lookup(tag, dt->set);
53
54 if (tag_index != SID_NOTFOUND) return tag_index;
55 tag_index = string_id_add(tag, dt->set);
56 tag = string_set_add(tag, dict->string_set); /* FIXME: Refer to string-id */
57
58 if (dt->num == dt->size)
59 {
60 if (dt->num == 0)
61 dt->size = EXPTAG_SZ;
62 else
63 dt->size *= 2;
64
65 dt->name = realloc(dt->name, dt->size * sizeof(*dt->name));
66 }
67 dt->name[tag_index] = tag;
68 dt->num++;
69 assert(dt->num == tag_index);
70
71 return tag_index;
72 }
73
74 /**
75 * Set in the cost table the dialect component cost at \p table_index.
76 * @retrun \c false iff the component doesn't exist in the dictionary.
77 */
apply_component(Dictionary dict,Dialect * di,unsigned int table_index,float * cost_table)78 static bool apply_component(Dictionary dict, Dialect *di,
79 unsigned int table_index, float *cost_table)
80 {
81 expression_tag *dt = &dict->dialect_tag;
82 unsigned int cost_index =
83 string_id_lookup(di->table[table_index].name, dt->set);
84
85 if (cost_index == SID_NOTFOUND)
86 {
87 prt_error("Error: Dialect component \"%s\" is not in the dictionary.\n",
88 di->table[table_index].name);
89 return false;
90 }
91 cost_table[cost_index] = di->table[table_index].cost;
92
93 return true;
94 }
95
96 /**
97 * Apply the dialect settings at \p from to the dialect table index
98 * \p table_index at \p to.
99 * The dialect table at \p from has been created from the "4.0.dialect"
100 * file or from the user setting kept in the dialect_info option.
101 * The dialect table at \p to is always from "4.0.dialect".
102 * Recursive (for applying sub-dialects).
103 * @retrun \c true on success, \c false on failure.
104 */
apply_table_entry(Dictionary dict,Dialect * from,unsigned int table_index,Dialect * to,dialect_info * dinfo,bool * encountered)105 static bool apply_table_entry(Dictionary dict, Dialect *from,
106 unsigned int table_index, Dialect *to,
107 dialect_info *dinfo, bool *encountered)
108 {
109 int skip = (int)(to == from); /* Skip an initial section header. */
110
111 /* Apply everything until the next section (or "from" table end). */
112 for (unsigned int i = table_index + skip; i < from->num_table_tags; i++)
113 {
114 if (cost_eq(from->table[i].cost, DIALECT_SECTION)) break;
115
116 /* Apply components and sub-dialects. */
117 lgdebug(+D_DIALECT, "Apply %s %s%s\n",
118 from->table[i].name, cost_stringify(from->table[i].cost),
119 (to == from) ? "" : " (user setup");
120 if (!cost_eq(from->table[i].cost, DIALECT_SUB))
121 {
122 if (!apply_component(dict, from, i, dinfo->cost_table)) return false;
123 }
124 else
125 {
126 unsigned int sub_index = SID_NOTFOUND;
127 if (to != NULL)
128 sub_index = string_id_lookup(from->table[i].name, to->section_set);
129 if (sub_index == SID_NOTFOUND)
130 {
131 prt_error("Error: Undefined dialect \"%s\"\n", from->table[i].name);
132 return false;
133 }
134 if (encountered[sub_index])
135 {
136 prt_error("Error: Loop detected at sub-dialect \"%s\" "
137 "(of dialect \"%s\").\n",
138 to->table[i].name, to->table[table_index].name);
139 return false;
140 }
141 encountered[sub_index] = true;
142 if (!apply_table_entry(dict, to, to->section[sub_index].index, to,
143 dinfo, encountered))
144 return false;
145 }
146 }
147
148 return true;
149 }
150
apply_dialect(Dictionary dict,Dialect * from,unsigned int table_index,Dialect * to,dialect_info * dinfo)151 bool apply_dialect(Dictionary dict, Dialect *from, unsigned int table_index,
152 Dialect *to, dialect_info *dinfo)
153 {
154 /* Loop detection (needed only if there are "4.0.dialect" definitions). */
155 bool *loopdet;
156 if (to == NULL)
157 {
158 loopdet = NULL;
159 }
160 else
161 {
162 loopdet = alloca(to->num_sections + 1);
163 memset(loopdet, 0, to->num_sections + 1);
164 }
165
166 if (!apply_table_entry(dict, from, table_index, to, dinfo, loopdet))
167 return false;
168
169 return true;
170 }
171
print_cost_table(Dictionary dict,Dialect * di,dialect_info * dinfo)172 static void print_cost_table(Dictionary dict, Dialect *di, dialect_info *dinfo)
173 {
174 expression_tag *dt = &dict->dialect_tag;
175
176 if (dt->num == 0)
177 {
178 assert(dinfo->cost_table == NULL, "Unexpected cost table.");
179 prt_error("Debug: No dialect cost table (no tags in the dict).\n");
180 return;
181 }
182
183 if (dinfo->cost_table == NULL)
184 {
185 prt_error("Debug: No dialect cost table.\n");
186 return;
187 }
188
189 prt_error("Dialect cost table (%u components%s):\n\\",
190 dt->num, dt->num == 1 ? "" : "s");
191 prt_error("%-15s %s\n", "component", "cost");
192
193 for (unsigned int i = 1; i <= dt->num; i++)
194 {
195 prt_error("%-15s %s\n\\",
196 dt->name[i], cost_stringify(dinfo->cost_table[i]));
197 }
198 lg_error_flush();
199 }
200
free_cost_table(Parse_Options opts)201 void free_cost_table(Parse_Options opts)
202 {
203 free(opts->dialect.cost_table);
204 opts->dialect.cost_table = NULL;
205 }
206
dialect_conf_exists(dialect_info * dinfo)207 static bool dialect_conf_exists(dialect_info *dinfo)
208 {
209 for (const char *p = dinfo->conf; *p != '\0'; p++)
210 if (!lg_isspace(*p)) return true;
211 return false;
212 }
213
214 const char no_dialect[] = "(unset the dialect option)\n";
215
216 /**
217 * Build the dialect cost table if it doesn't exist.
218 * Note: All IDs start from 1, so the 0'th elements are ignored
219 * (but section[0] which points to the default section).
220 * @retrun \c true on success, \c false on failure.
221 */
setup_dialect(Dictionary dict,Parse_Options opts)222 bool setup_dialect(Dictionary dict, Parse_Options opts)
223 {
224 Dialect *di = dict->dialect;
225 dialect_info *dinfo = &opts->dialect;
226 expression_tag *dt = &dict->dialect_tag;
227
228 if (dt->num == 0)
229 {
230 if (!dialect_conf_exists(dinfo)) return true;
231 prt_error("Error: Dialect setup failed: No dialects in the \"%s\" "
232 "dictionary %s.\n", dict->lang, no_dialect);
233 return false;
234 }
235
236 if (dinfo->cost_table != NULL)
237 {
238 /* Cached table. */
239 if ((dinfo->dict != dict) || (dict->cached_dialect != dinfo))
240 {
241 /* XXX It may still be a stale cache if the dictionary got the
242 * same address as a previously-closed one and also "opts" got the
243 * same address as a previously-deleted "opts" (very unlikely).
244 * Can be fixed by adding dictionary_create() ordinal number +
245 * stack address at the time of grabbing the ordinal number:
246 * dict_id = ++static_id; dict_stack_address_stamp = &auto_var; */
247 lgdebug(+D_DIALECT,
248 "Debug: Resetting dialect cache of a different dictionary.\n");
249 free_cost_table(opts);
250 }
251 else
252 {
253 lgdebug(+D_DIALECT, "Debug: Cached cost table found\n");
254
255 if (verbosity_level(+D_DIALECT+1))
256 print_cost_table(dict, di, dinfo);
257
258 return true;
259 }
260 }
261
262 dinfo->dict = dict;
263 dict->cached_dialect = dinfo;
264
265 if (dt->num != 0)
266 {
267 dinfo->cost_table = malloc((dt->num + 1) * sizeof(*dinfo->cost_table));
268
269 for (unsigned int i = 1; i <= dt->num; i++)
270 dinfo->cost_table[i] = DIALECT_COST_DISABLE;
271 }
272
273 /* Apply the default section of the dialect table, if exists. */
274 if ((di != NULL) && (di->section != NULL) &&
275 (di->section[0].index != NO_INDEX))
276 {
277 if (!apply_dialect(dict, di, di->section[0].index, di, dinfo))
278 return false;
279 }
280
281 if (dialect_conf_exists(dinfo))
282 {
283 Dialect user_setup = (Dialect){ 0 };
284 if (!dialect_read_from_one_line_str(dict, &user_setup, dinfo->conf))
285 {
286 free_dialect_table(&user_setup);
287 return false;
288 }
289
290 if (!apply_dialect(dict, &user_setup, /*table index*/0, di, dinfo))
291 {
292 free_dialect_table(&user_setup);
293 return false;
294 }
295 free_dialect_table(&user_setup);
296 }
297
298 if (verbosity_level(+D_DIALECT+1))
299 print_cost_table(dict, di, dinfo);
300
301 return true;
302 }
303