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 get(Value x, Value key, Value def) {
59 		Value result;
60 		if (x.has(key, &result))
61 			return result;
62 		return def;
63 	}
64 
65 	static Value def(Value x, Value def) {
66 		if ((x.type == Value.Type.Undefined) || (x.type == Value.Type.Null))
67 			return def;
68 		return x;
69 	}
70 
71 	static long tointeger(Value x) {
72 		return x.get!long;
73 	}
74 
75 	static double tofloat(Value x) {
76 		return x.get!double;
77 	}
78 
79 	static string tostring(Value x) {
80 		return x.toString();
81 	}
82 
83 	static bool tobool(Value x) {
84 		return x.get!bool;
85 	}
86 
87 	static string type(Value x) {
88 		final switch (x.type) with (Value.Type) {
89 		case Undefined:
90 			return "undefined";
91 		case Null:
92 			return "null";
93 		case Bool:
94 			return "bool";
95 		case Integer:
96 			return "integer";
97 		case Float:
98 			return "float";
99 		case Function:
100 			return "function";
101 		case String:
102 			return "string";
103 		case Array:
104 			return "array";
105 		case AssocArray:
106 			return "assocarray";
107 		case Object:
108 			return "object";
109 		case Pointer:
110 			return "pointer";
111 		}
112 	}
113 
114 	static bool exists(Value x) {
115 		// 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.
116 		return x.type.to!string != "undefined";
117 	}
118 
119 	static string escape(Value[] args) {
120 		auto value = args[0].get!string;
121 
122 		foreach (f; args[1..$]) {
123 			switch (f.get!string) {
124 			case "html":
125 				value = escapeHTML(value);
126 				break;
127 			case "js":
128 				value = escapeJS(value);
129 				break;
130 			case "uri":
131 				value = escapeURI(value);
132 				break;
133 			default:
134 				assert("unimplemented escape filter: " ~ f.get!string);
135 				break;
136 			}
137 		}
138 
139 		return value;
140 	}
141 
142 	static string translate(Value[] args) {
143 		return args[0].get!string;
144 	}
145 
146 	static string replace(string x, string from, string to) {
147 		return x.replace(from, to);
148 	}
149 
150 	static string replaceAll(string x, string from, string to) {
151 		return x.replaceAll(regex(from), to);
152 	}
153 
154 	globals["length"] = Value(&length);
155 	globals["empty"] = Value(&empty);
156 	globals["keys"] = Value(&keys);
157 	globals["values"] = Value(&values);
158 	globals["front"] = Value(&front);
159 	globals["back"] = Value(&back);
160 
161 	globals["integer"] = Value(&tointeger);
162 	globals["float"] = Value(&tofloat);
163 	globals["string"] = Value(&tostring);
164 	globals["bool"] = Value(&tobool);
165 	globals["type"] = Value(&type);
166 
167 	globals["get"] = Value(&get);
168 	globals["has"] = Value((Value x, Value key) => x.has(key));
169 	globals["default"] = Value(&def);
170 
171 	globals["escape"] = Value(&escape);
172 
173 	globals["replace"] = Value(&replace);
174 	globals["replaceAll"] = Value(&replaceAll);
175 	globals["existsIn"] = Value(&existsIn);
176 
177 	globals["__escape"] = Value(&escape);
178 	globals["__translate"] = Value(&translate);
179 }
180 
181 
182 void bindLibMath(ref Value[string] globals) {
183 	import std.math;
184 
185 	static Value abs(Value x) {
186 		final switch (x.type) with (Value.Type) {
187 		case Undefined:
188 		case Function:
189 		case String:
190 		case Array:
191 		case AssocArray:
192 		case Object:
193 		case Pointer:
194 		case Null:
195 		case Bool:
196 			return x;
197 		case Integer:
198 			return Value(std.math.abs(x.get!long));
199 		case Float:
200 			return Value(std.math.abs(x.get!double));
201 		}
202 	}
203 
204 	static Value min(Value[] args) {
205 		size_t argMin;
206 
207 		foreach (i; 1..args.length) {
208 			if (args[argMin].compareOp!">"(args[i]))
209 				argMin = i;
210 		}
211 
212 		return args[argMin];
213 	}
214 
215 	static Value max(Value[] args) {
216 		size_t argMax;
217 
218 		foreach (i; 1..args.length) {
219 			if (args[argMax].compareOp!"<"(args[i]))
220 				argMax = i;
221 		}
222 
223 		return args[argMax];
224 	}
225 
226 	globals["abs"] = Value(&abs);
227 	globals["min"] = Value(&min);
228 	globals["max"] = Value(&max);
229 }
230 
231 
232 void bindLibString(ref Value[string] globals) {
233 	static string join(Value[] x) {
234 		auto app = appender!string;
235 		auto value = x[0];
236 		auto len = value.length;
237 		auto seps = (x.length > 1) ? x[1].get!string : ",";
238 		foreach (size_t i, v; value) {
239 			app.put(v.get!string);
240 			if (i + 1 != len)
241 				app.put(seps);
242 		}
243 		return app.data;
244 	}
245 
246 	static string[] split(Value[] x) {
247 		auto value = x[0].get!string;
248 		auto sep = (x.length > 1) ? x[1].get!string : " ";
249 
250 		return value.split(sep);
251 	}
252 
253 	static string strip(Value x) {
254 		return x.get!string.strip;
255 	}
256 
257 	static string lower(Value x) {
258 		return x.get!string.toLower();
259 	}
260 
261 	static string upper(Value x) {
262 		return x.get!string.toUpper();
263 	}
264 
265 	static string capitalize(Value x) {
266 		return x.get!string.capitalize();
267 	}
268 
269 	static long indexOf(Value[] args) {
270 		if (!args[0].length)
271 			return -1;
272 
273 		if (args[0].type == Value.Type.String) {
274 			auto haystack = args[0].get!string;
275 			auto needle = args[1].get!string;
276 			auto start = (args.length > 2) ? args[2].get!size_t : 0;
277 			return haystack.indexOf(needle, start);
278 		}
279 
280 		foreach (size_t i, v; args[0]) {
281 			if (v.compareOp!"=="(args[1]))
282 				return cast(long)i;
283 		}
284 
285 		return -1;
286 	}
287 
288 	static string format(Value[] args) {
289 		auto fmt = args[0].get!string;
290 		auto spec = FormatSpec!char(fmt);
291 
292 		auto app = appender!string;
293 		app.reserve(max(32, fmt.length + fmt.length >> 1));
294 
295 		size_t arg = 1;
296     	while (spec.writeUpToNextSpec(app)) {
297 			if (arg >= args.length)
298 				break;
299 
300 			auto value = args[arg++];
301 			final switch (value.type) with (Value.Type) {
302 			case Undefined:
303 			case Function:
304 			case String:
305 			case Array:
306 			case AssocArray:
307 			case Object:
308 			case Pointer:
309 				formatValue(&app, value.get!string, spec);
310 				break;
311 			case Null:
312 				formatValue(&app, null, spec);
313 				break;
314 			case Bool:
315 				formatValue(&app, value.get!bool, spec);
316 				break;
317 			case Integer:
318 				formatValue(&app, value.get!long, spec);
319 				break;
320 			case Float:
321 				formatValue(&app, value.get!double, spec);
322 				break;
323 			}
324     	}
325 
326 		if (arg != args.length)
327 			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));
328 		return app.data;
329 	}
330 
331 	globals["join"] = Value(&join);
332 	globals["split"] = Value(&split);
333 	globals["format"] = Value(&format);
334 	globals["strip"] = Value(&strip);
335 	globals["lower"] = Value(&lower);
336 	globals["upper"] = Value(&upper);
337 	globals["capitalize"] = Value(&capitalize);
338 	globals["indexOf"] = Value(&indexOf);
339 }
340 
341 
342 void bindLibDefault(ref Value[string] globals) {
343 	bindLibBasic(globals);
344 	bindLibString(globals);
345 	bindLibMath(globals);
346 }
347 
348 
349 string escapeHTML(string x) {
350 	auto app = appender!string;
351 	app.reserve(8 + x.length + (x.length >> 1));
352 
353 	foreach (ch; x.byDchar) {
354 		switch (ch) {
355 		case '"':
356 			app.put("&quot;");
357 			break;
358 		case '\'':
359 			app.put("&#39;");
360 			break;
361 		case 'a': .. case 'z':
362 			goto case;
363 		case 'A': .. case 'Z':
364 			goto case;
365 		case '0': .. case '9':
366 			goto case;
367 		case ' ', '\t', '\n', '\r', '-', '_', '.', ':', ',', ';',
368 			'#', '+', '*', '?', '=', '(', ')', '/', '!',
369 			'%' , '{', '}', '[', ']', '$', '^', '~':
370 			app.put(cast(char)ch);
371 			break;
372 		case '<':
373 			app.put("&lt;");
374 			break;
375 		case '>':
376 			app.put("&gt;");
377 			break;
378 		case '&':
379 			app.put("&amp;");
380 			break;
381 		default:
382 			formattedWrite(&app, "&#x%02X;", cast(uint)ch);
383 			break;
384 		}
385 	}
386 	return app.data;
387 }
388 
389 
390 string escapeJS(string x) {
391 	auto app = appender!string;
392 	app.reserve(x.length + (x.length >> 1));
393 
394 	foreach (ch; x.byDchar) {
395 		switch (ch) {
396 		case '\\':
397 			app.put(`\\`);
398 			break;
399 		case '\'':
400 			app.put(`\'`);
401 			break;
402 		case '\"':
403 			app.put(`\"`);
404 			break;
405 		case '\r':
406 			break;
407 		case '\n':
408 			app.put(`\n`);
409 			break;
410 		default:
411 			app.put(ch);
412 			break;
413 		}
414 	}
415 	return app.data;
416 }
417 
418 
419 string escapeURI(string x) {
420 	auto app = appender!string;
421 	app.reserve(8 + x.length + (x.length >> 1));
422 
423 	foreach (i; 0..x.length) {
424 		switch (x.ptr[i]) {
425 		case 'A': .. case 'Z':
426 		case 'a': .. case 'z':
427 		case '0': .. case '9':
428 		case '-': case '_': case '.': case '~':
429 			app.put(x.ptr[i]);
430 			break;
431 		default:
432 			formattedWrite(&app, "%%%02X", x.ptr[i]);
433 			break;
434 		}
435 	}
436 
437 	return app.data;
438 }