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