1 /++
2  + Module defining the base for all parser combinators.
3  +
4  + Copyright: Copyright © 2015, Christian Köstlin
5  + License: MIT
6  + Authors: Christian Koestlin, Christian Köstlin
7  +/
8 module pc4d.parser;
9 
10 version (unittest) import unit_threaded;
11 
12 public import std.variant;
13 
14 import pc4d.parsers;
15 import std.array;
16 import std.ascii;
17 import std.conv;
18 import std.functional;
19 import std.stdio;
20 import std.string;
21 import std.string;
22 
23 /**
24  * Result of a parsing step.
25  * use fResults to get to the results.
26  * use fRest to get to the not consumed part of the input.
27  */
28 class ParseResult(T)
29 {
30     T[] fRest;
31     Variant[] fResults;
32     string fMessage;
33     bool fSuccess;
34 
35     private this(bool success)
36     {
37         fRest = null;
38         fResults = null;
39         fMessage = null;
40         fSuccess = success;
41     }
42 
43     public static ParseResult!(T) ok(T[] rest, Variant[] results)
44     {
45         auto res = new ParseResult!(T)(true);
46         res.fRest = rest;
47         res.fResults = results;
48         return res;
49     }
50 
51     public static ParseResult!(T) error(string message)
52     {
53         auto res = new ParseResult!(T)(false);
54         res.fMessage = message;
55         return res;
56     }
57 
58     /// errormessage
59     @property string message()
60     {
61         return fMessage;
62     }
63 
64     /// true if parsing was successfull
65     @property bool success()
66     {
67         return fSuccess;
68     }
69 
70     /// unconsumed input
71     @property T[] rest()
72     {
73         return fRest;
74     }
75 
76     @property T[] rest(T[] rest)
77     {
78         return fRest = rest;
79     }
80 
81     /// the results
82     @property Variant[] results()
83     {
84         if (!success)
85         {
86             throw new Exception("no results available");
87         }
88         return fResults;
89     }
90 }
91 
92 /**
93  * interface for all parser combinators
94  * parse must be implemented.
95  */
96 class Parser(T)
97 {
98     Variant[]delegate(Variant[]) fCallable = null;
99 
100     /// helper to create a successfull result
101     static success(U...)(T[] rest, U args)
102     {
103         return ParseResult!(T).ok(rest, variantArray(args));
104     }
105 
106     ParseResult!(T) parseAll(T[] s)
107     {
108         auto res = parse(s);
109         if (res.success)
110         {
111             if ((res.rest is null) || (res.rest.length == 0))
112             {
113                 return res;
114             }
115             else
116             {
117                 return ParseResult!(T).error("string not completely consumed" /*, res.rest*/ );
118             }
119         }
120         else
121         {
122             return res;
123         }
124     }
125 
126     /++
127    + this must be implemented by subclasses
128    + Params:
129    +   input = the data to process
130    + Returns: ParseResult with (success, result and rest) or (not success and optional error message)
131    +/
132     ParseResult!(T) parse(T[] input)
133     {
134         throw new Exception("must be implemented in childs");
135     }
136 
137     /// dsl for repetition of a parser e.g. (*match("a")) matches sequences of a
138     Parser opUnary(string op)() if (op == "*")
139     {
140         return new Repetition!(T)(this);
141     }
142 
143     /// dsl for optional parser e.g. (-match("abc")) matches "abc" and "efg"
144     Parser opUnary(string op)() if (op == "-")
145     {
146         return new Optional!(T)(this);
147     }
148 
149     /// dsl for transforming results of a parser
150     Parser opBinary(string op)(Variant[]function(Variant[] objects) toCall)
151             if (op == "^^")
152     {
153         return setCallback(toCall);
154     }
155 
156     /// dsl for alternatives e.g. match("abc") | match("def") matches "abc" or "def"
157     Parser opBinary(string op)(Parser rhs) if (op == "|")
158     {
159         return or(this, rhs);
160     }
161 
162     /// dsl for sequences e.g. match("a") ~ match("b") matches "ab"
163     Parser opBinary(string op)(Parser rhs) if (op == "~")
164     {
165         return sequence(this, rhs);
166     }
167 
168     Parser setCallback(Variant[]function(Variant[] objects) tocall)
169     {
170         fCallable = toDelegate(tocall);
171         return this;
172     }
173 
174     Parser setCallback(Variant[]delegate(Variant[] objects) tocall)
175     {
176         fCallable = tocall;
177         return this;
178     }
179 
180     ParseResult!(T) transform(ParseResult!(T) result)
181     {
182         if (result.success)
183         {
184             return fCallable ? ParseResult!(T).ok(result.rest, fCallable(result.results)) : result;
185         }
186         else
187         {
188             return result;
189         }
190     }
191 
192 }
193 
194 /// transforming from regexp string to integer
195 @("regexp to integer") unittest
196 {
197     import std.conv;
198 
199     auto res = (regex("\\d+") ^^ (input) {
200         return variantArray(input[0].get!string
201             .to!int);
202     }).parse("123");
203     res.success.should == true;
204     res.results[0].should == 123;
205 }
206 
207 /// the pc4d.alternative parser and its dsl '|'
208 @("alternative dsl") unittest
209 {
210     auto parser = match("abc") | match("def");
211     auto res = parser.parse("abc");
212     res.success.should == true;
213 
214     res = parser.parse("def");
215     res.success.should == true;
216 
217     res = parser.parse("ghi");
218     res.success.should == false;
219 }
220 
221 /// trying to parse all of the input
222 @("parseAll") unittest
223 {
224     auto parser = match("test");
225     auto res = parser.parseAll("test");
226 
227     res.success.should == true;
228     res.rest.length.should == 0;
229 
230     res = parser.parseAll("test1");
231     res.success.should == false;
232 }
233 
234 /// trying to parse part of the input
235 @("parse") unittest
236 {
237     auto parser = match("test");
238     auto res = parser.parse("test");
239 
240     res.success.should == true;
241     res.rest.length.should == 0;
242 
243     res = parser.parse("test1");
244     res.success.should == true;
245 
246     res.rest.should == "1";
247 }