1 module vayne.lib;
2 
3 
4 import std.algorithm;
5 import std.array;
6 import std.conv;
7 import std.format;
8 import std..string;
9 import std.utf;
10 import std.regex;
11 
12 
13 import vayne.value;
14 
15 
16 template bindVars(size_t i, alias Container, Vars...) {
17 	static if(i < Vars.length) {
18 		enum bindVars = Vars.length ? (__traits(identifier, Container) ~ "[\"" ~ __traits(identifier, Vars[i]) ~ "\"]=Value(Vars[" ~ i.to!string ~ "]);\n" ~ bindVars!(i + 1, Container, Vars)) : "";
19 	} else {
20 		enum bindVars = "";
21 	}
22 }
23 
24 
25 void bindLibBasic(ref Value[string] globals) {
26 	static long length(Value x) {
27 		return cast(long)x.length;
28 	}
29 
30 	static bool existsIn(Value[] args) {
31 		auto value = args[0].get!string;
32 		foreach (f; args[1..$])
33 			if(value == f.get!string)
34 				return true;
35 		return false;
36 	}
37 
38 	static bool empty(Value x) {
39 		return x.length == 0;
40 	}
41 
42 	static Value keys(Value x) {
43 		return x.keys();
44 	}
45 
46 	static Value values(Value x) {
47 		return x.values();
48 	}
49 
50 	static Value front(Value x) {
51 		return x[0];
52 	}
53 
54 	static Value back(Value x) {
55 		return x[x.length - 1];
56 	}
57 
58 	static Value take(Value x, Value y) {
59 		return x[0..min(x.length, y.get!long)];
60 	}
61 
62 	static Value get(Value x, Value key, Value def) {
63 		Value result;
64 		if (x.has(key, &result))
65 			return result;
66 		return def;
67 	}
68 
69 	static Value def(Value x, Value def) {
70 		if ((x.type == Value.Type.Undefined) || (x.type == Value.Type.Null))
71 			return def;
72 		return x;
73 	}
74 
75 	static long tointeger(Value x) {
76 		return x.get!long;
77 	}
78 
79 	static double tofloat(Value x) {
80 		return x.get!double;
81 	}
82 
83 	static string tostring(Value x) {
84 		return x.toString();
85 	}
86 
87 	static bool tobool(Value x) {
88 		return x.get!bool;
89 	}
90 
91 	static string type(Value x) {
92 		final switch (x.type) with (Value.Type) {
93 		case Undefined:
94 			return "undefined";
95 		case Null:
96 			return "null";
97 		case Bool:
98 			return "bool";
99 		case Integer:
100 			return "integer";
101 		case Float:
102 			return "float";
103 		case Function:
104 			return "function";
105 		case String:
106 			return "string";
107 		case Array:
108 			return "array";
109 		case AssocArray:
110 			return "assocarray";
111 		case Object:
112 			return "object";
113 		case Pointer:
114 			return "pointer";
115 		}
116 	}
117 
118 	static bool exists(Value x) {
119 		// Json objects also have a type property that is not a string, so in the case our Value is also a Json we have to convert it to string first.
120 		return x.type.to!string != "undefined";
121 	}
122 
123 	static string escape(Value[] args) {
124 		auto value = args[0].get!string;
125 		if (value.empty())
126 			return null;
127 
128 		foreach (f; args[1..$]) {
129 			switch (f.get!string) {
130 			case "html":
131 				value = escapeHTML(value);
132 				break;
133 			case "js":
134 				value = escapeJS(value);
135 				break;
136 			case "uri":
137 				value = escapeURI(value);
138 				break;
139 			default:
140 				assert("unimplemented escape filter: " ~ f.get!string);
141 				break;
142 			}
143 		}
144 
145 		return value;
146 	}
147 
148 	static string translate(Value[] args) {
149 		return args[0].get!string;
150 	}
151 
152 	static string replace(string x, string from, string to) {
153 		return x.replace(from, to);
154 	}
155 
156 	static string replaceAll(string x, string from, string to) {
157 		return x.replaceAll(regex(from), to);
158 	}
159 
160 	globals["length"] = Value(&length);
161 	globals["empty"] = Value(&empty);
162 	globals["keys"] = Value(&keys);
163 	globals["values"] = Value(&values);
164 	globals["front"] = Value(&front);
165 	globals["back"] = Value(&back);
166 	globals["take"] = Value(&take);
167 
168 	globals["integer"] = Value(&tointeger);
169 	globals["float"] = Value(&tofloat);
170 	globals["string"] = Value(&tostring);
171 	globals["bool"] = Value(&tobool);
172 	globals["type"] = Value(&type);
173 
174 	globals["get"] = Value(&get);
175 	globals["has"] = Value((Value x, Value key) => x.has(key));
176 	globals["default"] = Value(&def);
177 
178 	globals["escape"] = Value(&escape);
179 
180 	globals["replace"] = Value(&replace);
181 	globals["replaceAll"] = Value(&replaceAll);
182 	globals["existsIn"] = Value(&existsIn);
183 
184 	globals["__escape"] = Value(&escape);
185 	globals["__translate"] = Value(&translate);
186 }
187 
188 
189 void bindLibMath(ref Value[string] globals) {
190 	import std.math;
191 
192 	static Value abs(Value x) {
193 		final switch (x.type) with (Value.Type) {
194 		case Undefined:
195 		case Function:
196 		case String:
197 		case Array:
198 		case AssocArray:
199 		case Object:
200 		case Pointer:
201 		case Null:
202 		case Bool:
203 			return x;
204 		case Integer:
205 			return Value(std.math.abs(x.get!long));
206 		case Float:
207 			return Value(std.math.abs(x.get!double));
208 		}
209 	}
210 
211 	static Value min(Value[] args) {
212 		size_t argMin;
213 
214 		foreach (i; 1..args.length) {
215 			if (args[argMin].compareOp!">"(args[i]))
216 				argMin = i;
217 		}
218 
219 		return args[argMin];
220 	}
221 
222 	static Value max(Value[] args) {
223 		size_t argMax;
224 
225 		foreach (i; 1..args.length) {
226 			if (args[argMax].compareOp!"<"(args[i]))
227 				argMax = i;
228 		}
229 
230 		return args[argMax];
231 	}
232 
233 	globals["abs"] = Value(&abs);
234 	globals["min"] = Value(&min);
235 	globals["max"] = Value(&max);
236 }
237 
238 
239 void bindLibString(ref Value[string] globals) {
240 	static string join(Value[] x) {
241 		auto app = appender!string;
242 		auto value = x[0];
243 		auto len = value.length;
244 		auto seps = (x.length > 1) ? x[1].get!string : ",";
245 		foreach (size_t i, v; value) {
246 			app.put(v.get!string);
247 			if (i + 1 != len)
248 				app.put(seps);
249 		}
250 		return app.data;
251 	}
252 
253 	static string[] split(Value[] x) {
254 		auto value = x[0].get!string;
255 		auto sep = (x.length > 1) ? x[1].get!string : " ";
256 
257 		return value.split(sep);
258 	}
259 
260 	static string strip(Value x) {
261 		return x.get!string.strip;
262 	}
263 
264 	static string lower(Value x) {
265 		return x.get!string.toLower();
266 	}
267 
268 	static string upper(Value x) {
269 		return x.get!string.toUpper();
270 	}
271 
272 	static string capitalize(Value x) {
273 		return x.get!string.capitalize();
274 	}
275 
276 	static long indexOf(Value[] args) {
277 		if (!args[0].length)
278 			return -1;
279 
280 		if (args[0].type == Value.Type.String) {
281 			auto haystack = args[0].get!string;
282 			auto needle = args[1].get!string;
283 			auto start = (args.length > 2) ? args[2].get!size_t : 0;
284 			return haystack.indexOf(needle, start);
285 		}
286 
287 		foreach (size_t i, v; args[0]) {
288 			if (v.compareOp!"=="(args[1]))
289 				return cast(long)i;
290 		}
291 
292 		return -1;
293 	}
294 
295 	static string format(Value[] args) {
296 		auto fmt = args[0].get!string;
297 		auto spec = FormatSpec!char(fmt);
298 
299 		auto app = appender!string;
300 		app.reserve(max(32, fmt.length + fmt.length >> 1));
301 
302 		size_t arg = 1;
303     	while (spec.writeUpToNextSpec(app)) {
304 			if (arg >= args.length)
305 				break;
306 
307 			auto value = args[arg++];
308 			final switch (value.type) with (Value.Type) {
309 			case Undefined:
310 			case Function:
311 			case String:
312 			case Array:
313 			case AssocArray:
314 			case Object:
315 			case Pointer:
316 				formatValue(&app, value.get!string, spec);
317 				break;
318 			case Null:
319 				formatValue(&app, null, spec);
320 				break;
321 			case Bool:
322 				formatValue(&app, value.get!bool, spec);
323 				break;
324 			case Integer:
325 				formatValue(&app, value.get!long, spec);
326 				break;
327 			case Float:
328 				formatValue(&app, value.get!double, spec);
329 				break;
330 			}
331 		}
332 
333 		if (arg != args.length)
334 			throw new Exception(std.format.format("number of arguments doesn't match number of format specifiers - expected %d, got %d", arg - 1, args.length - 1));
335 		return app.data;
336 	}
337 
338 	globals["join"] = Value(&join);
339 	globals["split"] = Value(&split);
340 	globals["format"] = Value(&format);
341 	globals["strip"] = Value(&strip);
342 	globals["lower"] = Value(&lower);
343 	globals["upper"] = Value(&upper);
344 	globals["capitalize"] = Value(&capitalize);
345 	globals["indexOf"] = Value(&indexOf);
346 }
347 
348 
349 void bindLibDefault(ref Value[string] globals) {
350 	bindLibBasic(globals);
351 	bindLibString(globals);
352 	bindLibMath(globals);
353 }
354 
355 
356 string escapeHTML(string x) {
357 	auto ptr = x.ptr;
358 	const end = x.ptr + x.length;
359 
360 	while (ptr != end) {
361 		auto ch = *ptr++;
362 		if (ch > '>') continue;
363 
364 		switch (ch) {
365 		case '"':
366 		case '\'':
367 		case '<':
368 		case '>':
369 		case '&':
370 			const prefix = ptr - x.ptr - 1;
371 
372 			auto app = appender!string;
373 			app.reserve(prefix + (x.length - prefix) * 2);
374 			app.put(x.ptr[0..prefix]);
375 			--ptr;
376 
377 			while (ptr != end) {
378 				const pch = *ptr++;
379 				switch (pch) {
380 				case '"':
381 					app.put("&#34;");
382 					break;
383 				case '\'':
384 					app.put("&#39;");
385 					break;
386 				case '<':
387 					app.put("&lt;");
388 					break;
389 				case '>':
390 					app.put("&gt;");
391 					break;
392 				case '&':
393 					app.put("&amp;");
394 					break;
395 				default:
396 					app.put(pch);
397 					break;
398 				}
399 			}
400 			return app.data;
401 		default:
402 			continue;
403 		}
404 	}
405 	return x;
406 }
407 
408 
409 string escapeJS(string x) {
410 	auto ptr = x.ptr;
411 	const end = x.ptr + x.length;
412 
413 	while (ptr != end) {
414 		auto ch = *ptr++;
415 		if (ch > '\\') continue;
416 
417 		switch (ch) {
418 		case '\\':
419 		case '\'':
420 		case '\"':
421 		case '\r':
422 		case '\n':
423 			const prefix = ptr - x.ptr - 1;
424 
425 			auto app = appender!string;
426 			app.reserve(prefix + 1 + (x.length - prefix) + (x.length - prefix) / 2);
427 			app.put(x.ptr[0..prefix]);
428 			--ptr;
429 
430 			while (ptr != end) {
431 				const pch = *ptr++;
432 				switch (pch) {
433 				case '\\':
434 					app.put(`\\`);
435 					break;
436 				case '\'':
437 					app.put(`\'`);
438 					break;
439 				case '\"':
440 					app.put(`\"`);
441 					break;
442 				case '\r':
443 					break;
444 				case '\n':
445 					app.put(`\n`);
446 					break;
447 				default:
448 					app.put(pch);
449 					break;
450 				}
451 			}
452 			return app.data;
453 		default:
454 			continue;
455 		}
456 	}
457 	return x;
458 }
459 
460 
461 string escapeURI(string x) {
462 	auto ptr = x.ptr;
463 	const end = x.ptr + x.length;
464 
465 	while (ptr != end) {
466 		auto ch = *ptr++;
467 
468 		switch (ch) {
469 		case 'A': .. case 'Z':
470 		case 'a': .. case 'z':
471 		case '0': .. case '9':
472 		case '-': case '_': case '.': case '~':
473 			continue;
474 		default:
475 			const prefix = ptr - x.ptr - 1;
476 
477 			auto app = appender!string;
478 			app.reserve(prefix + 1 + (x.length - prefix) + (x.length - prefix) / 2);
479 			app.put(x.ptr[0..prefix]);
480 			--ptr;
481 
482 			while (ptr != end) {
483 				const pch = *ptr++;
484 				switch (pch) {
485 				case 'A': .. case 'Z':
486 				case 'a': .. case 'z':
487 				case '0': .. case '9':
488 				case '-': case '_': case '.': case '~':
489 					app.put(pch);
490 					break;
491 				default:
492 					formattedWrite(&app, "%%%02X", pch);
493 					break;
494 				}
495 			}
496 			return app.data;
497 		}
498 	}
499 	return x;
500 }