1 module dini.utils;
2 
3 import std.format : format, formatElement, FormatSpec, FormatException, formattedRead;
4 import std.traits : arity, isCallable, Parameters, ReturnType;
5 
6 
7 enum bool isBoxer(alias boxer) = isCallable!boxer
8         && arity!boxer == 1
9         && is(Parameters!boxer[0] == string);
10 
11 alias BoxerType(alias boxer) = ReturnType!boxer;
12 
13 
14 static char[char] escapeSequences;
15 static this() {
16 	escapeSequences = [
17 		'n': '\n', 'r': '\r', 't': '\t', 'b': '\b', '\\': '\\',
18 		'#': '#', ';': ';', '=': '=', ':': ':', '"': '"', '\'': '\''
19 	];
20 }
21 
22 string parseEscapeSequences(string input)
23 {
24 	bool inEscape;
25 	const(char)[] result = [];
26 	result.reserve(input.length);
27 
28 	for(auto i = 0; i < input.length; i++) {
29 		char c = input[i];
30 
31 		if (inEscape) {
32 			if (c in escapeSequences)
33 				result ~= escapeSequences[c];
34 			else if (c == 'x') {
35 				ubyte n;
36 				if (i + 3 > input.length)
37 					throw new FormatException("Invalid escape sequence (\\x)");
38 				string s = input[i+1..i+3];
39 				if (formattedRead(s, "%x", &n) < 1)
40 					throw new FormatException("Invalid escape sequence (\\x)");
41 				result ~= cast(char)n;
42 				i += 2;
43 			}
44 			else {
45 				throw new FormatException("Invalid escape sequence (\\%s..)".format(c));
46 			}
47 		}
48 		else if (!inEscape && c == '\\') {
49 			inEscape = true;
50 			continue;
51 		}
52 		else result ~= c;
53 
54 		inEscape = false;
55 	}
56 
57 	return cast(string)result;
58 }
59 
60 unittest {
61 	assert(parseEscapeSequences("abc wef ' n r ;a") == "abc wef ' n r ;a");
62 	assert(parseEscapeSequences(`\\n \\\\\\\\\\r`) == `\n \\\\\r`);
63 	assert(parseEscapeSequences(`hello\nworld`) == "hello\nworld");
64 	assert(parseEscapeSequences(`multi\r\nline \#notacomment`) == "multi\r\nline #notacomment");
65 	assert(parseEscapeSequences(`welp \x5A\x41\x7a`) == "welp ZAz");
66 }