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