1 /**
2   Simple parsers
3 
4   Here are some commonly used parsers. Also as an example for how
5   to use the combinators.
6 
7   Copyright: 2017 Yuxuan Shui
8 */
9 module sdpc.parsers;
10 import sdpc.combinators,
11        sdpc.primitives;
12 import std.traits,
13        std.string,
14        std.conv,
15        std.range;
16 public @safe :
17 
18 ///Consumes nothing, always return OK
19 auto nop(R)(ref R i) if (isForwardRange!R) {
20 	return ParseResult!R(i);
21 }
22 
23 struct ParseError(R) {
24 	dstring msg;
25 	R err_range;
26 }
27 
28 /// Match a string, return the matched string
29 struct token(string t) {
30 	import std.algorithm.comparison;
31 	static auto opCall(R)(R i) if (isForwardRange!R && is(typeof(equal(i, t)))) {
32 		import std.array;
33 		alias RT = ParseResult!(R, string, ParseError!R);
34 		auto str = take(i, t.length);
35 		auto retr = i.save.drop(t.length);
36 		if (equal(str.save, t))
37 			return RT(retr, t);
38 		return RT(ParseError!R("expecting \""~t~"\", got \""~str.array.idup~"\"", retr));
39 	}
40 }
41 
42 /// Match any character in accept
43 struct ch(alias accept) if (isSomeChar!(ElementType!(typeof(accept)))){
44 	static auto opCall(R)(R i) if (isForwardRange!R) {// && is(typeof(ElementType!R == accept[0]))) {
45 		alias RT = ParseResult!(R, ElementType!(typeof(accept)), ParseError!R);
46 		if (i.empty || accept.indexOf(i.front) == -1) {
47 			if (!i.empty)
48 				i.popFront;
49 			return RT(ParseError!R("expecting one of \""~accept~"\"", i));
50 		}
51 
52 		auto ch = i.front;
53 		i.popFront;
54 		return RT(i, ch);
55 	}
56 }
57 
58 /// Match any character except those in reject
59 struct not_ch(alias reject) if (isSomeChar!(ElementType!(typeof(reject)))){
60 	static auto opCall(R)(R i) if (isForwardRange!R) {
61 		alias RT = ParseResult!(R, ElementType!(typeof(reject)), ParseError!R);
62 		if (i.empty || reject.indexOf(i.front) != -1) {
63 			if (!i.empty)
64 				i.popFront;
65 			return RT(ParseError!R("not expecting one of \""~reject~"\"", i));
66 		}
67 
68 		auto ch = i.front;
69 		i.popFront;
70 		return RT(i, ch);
71 	}
72 }
73 
74 /// Parse a sequences of digits, return an array of number
75 alias digit(string _digits) = transform!(ch!_digits, (ch) => cast(int)_digits.indexOf(ch));
76 
77 immutable string lower = "qwertyuiopasdfghjklzxcvbnm";
78 immutable string upper = "QWERTYUIOPASDFGHJKLZXCVBNM";
79 immutable string alphabet = lower ~ upper;
80 immutable string digits = "0123456789";
81 
82 /**
83   Parse a number
84   Params:
85 	accept = digits allowed in the number, i-th character corresponds to digit i
86 	base = base
87 */
88 template number(string accept = digits, int base = 10) if (accept.length == base) {
89 	import std.algorithm.iteration;
90 	alias number = transform!(many!(digit!accept), (x) => x.reduce!((a,b) => a*base+b));
91 }
92 
93 ///
94 unittest {
95 	auto i = "12354";
96 	auto rx = number!()(i);
97 	assert(rx.ok);
98 	assert(rx.v == 12354);
99 
100 	i = "ffabc";
101 	auto rx1 = number!(digits~"abcdef", 16)(i);
102 	assert(rx1.ok);
103 	assert(rx1.v == 1047228);
104 }
105 
106 /**
107   Parse a sequence of characters
108   Params:
109 	accept = an array of acceptable characters
110 */
111 alias word(alias accept = alphabet) = transform!(many!(ch!accept), (x) => x.idup);
112 
113 /// Parse an identifier, starts with a letter or _, followed by letter, _, or digits
114 auto identifier(R)(R i) if (isForwardRange!R) {
115 	auto ret = ch!(alphabet~"_")(i);
116 	alias RT = ParseResult!(R, ElementType!R[], ParseError!R);
117 	if (!ret.ok)
118 		return RT(ParseError!R("Failed to parse a identifier", i));
119 	auto ret2 = word!(alphabet~"_"~digits)(ret.cont);
120 	ElementType!R[] str = [ret.v];
121 	if (ret2.ok)
122 		str ~= ret2.v;
123 	return RT(ret2.cont, str);
124 }
125 
126 ///
127 unittest {
128 	auto i = "_asd1234a";
129 	auto rx2 = identifier(i);
130 	assert(rx2.ok);
131 	assert(!rx2.cont.length);
132 	assert(rx2.v == "_asd1234a");
133 }
134 
135 /// Parse escaped character, \n, \r, \b, \" and \\
136 auto parse_escape1(R)(R i) if (isForwardRange!R) {
137 	alias RT = ParseResult!(R, dchar, ParseError!R);
138 	auto r = seq!(
139 		discard!(token!"\\"),
140 		transform_err!(choice!(
141 			token!"n",
142 			token!"b",
143 			token!"r",
144 			token!"\"",
145 			token!"\\"
146 		), (x) => x[0])
147 	)(i);
148 	if (!r.ok)
149 		return RT(ParseError!R("Failed to parse escape sequence", i));
150 	dchar res;
151 	final switch(r.v.v!1) {
152 	case "n":
153 		res = '\n';
154 		break;
155 	case "b":
156 		res = '\b';
157 		break;
158 	case "r":
159 		res = '\r';
160 		break;
161 	case "\"":
162 		res = '\"';
163 		break;
164 	case "\\":
165 		res = '\\';
166 		break;
167 	}
168 	return RT(r.cont, res);
169 }
170 
171 /// Parse a string enclosed by a pair of quotes, and containing escape sequence
172 auto parse_string(R)(R i) if (isForwardRange!R) {
173 	alias RT = ParseResult!(R, dchar[], ParseError!R);
174 	auto r = between!(token!"\"",
175 		transform_err!(many!(choice!(
176 			parse_escape1,
177 			not_ch!"\""
178 		)), (x) => x[0]),
179 	token!"\"")(i);
180 	if (!r.ok)
181 		return RT(ParseError!R("Failed to parse a string", i));
182 	return r;
183 }
184 
185 ///
186 unittest {
187 	auto i = "\"asdf\\n\\b\"";
188 	auto r = parse_string(i);
189 	import std.format;
190 	assert(r.ok);
191 	assert(r.v == "asdf\n\b", format("%s", r.v));
192 }
193 
194 /// Skip white spaces
195 alias skip_whitespace = skip!(choice!(token!" ", token!"\n", token!"\t"));
196 
197 ///
198 unittest {
199 	auto i = " \n\t    ";
200 	auto r = skip_whitespace(i);
201 	assert(r.ok);
202 	assert(!r.cont.length);
203 }