1 /** 2 * Syntax analyzer 3 * 4 * Copyright: © 2017 Andrey Kabylin 5 * License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 6 */ 7 8 module rpdl.parser; 9 10 import std.stdio; 11 import std.format : formattedWrite; 12 import std.array : appender; 13 import std.container; 14 import std.conv; 15 16 import rpdl.lexer; 17 import rpdl.token; 18 import rpdl.stream; 19 import rpdl.node; 20 import rpdl.value; 21 import rpdl.tree; 22 import rpdl.exception; 23 import rpdl.accessors; 24 25 class ParseError : Exception { 26 this(in uint line, in uint pos, in string details) { 27 auto writer = appender!string(); 28 formattedWrite(writer, "line %d, pos %d: %s", line, pos, details); 29 super(writer.data); 30 } 31 } 32 33 /** 34 * Parser checks if the declared data is syntactically correct 35 * and convert declared data to the `rpdl.tree.RpdlTree` 36 */ 37 class Parser { 38 /** 39 * Parsing file into the `tree` 40 */ 41 this(Lexer lexer, RpdlTree tree) { 42 this.lexer = lexer; 43 this.data = tree; 44 } 45 46 void parse() { 47 lexer.nextToken(); 48 49 while (lexer.currentToken.code != Token.Code.none) { 50 switch (lexer.currentToken.code) { 51 case Token.Code.id: parseObject(data.root); break; 52 case Token.Code.include: parseInclude(data.root); break; 53 default: 54 throw new ParseError(line, pos, "unknown identifier"); 55 } 56 } 57 } 58 59 @property int indent() { 60 return lexer.currentToken.indent; 61 } 62 63 @property int line() { 64 return lexer.currentToken.line; 65 } 66 67 @property int pos() { 68 return lexer.currentToken.pos; 69 } 70 71 private: 72 RpdlTree data; 73 Lexer lexer; 74 75 void parseObject(Node parent) { 76 string name = lexer.currentToken.identifier; 77 string type = ""; 78 Array!Parameter parameters; 79 80 const objectPath = parent.isRoot ? name : parent.path ~ "." ~ name; 81 auto node = data.data.optObjectNode(objectPath, new ObjectNode(name, parent)); 82 83 int objectIndent = indent; 84 lexer.nextToken(); 85 86 if (lexer.currentToken.symbol == '(') { 87 lexer.nextToken(); 88 89 if (lexer.currentToken.code != Token.Code.id) 90 throw new ParseError(line, pos, "expected identifier"); 91 92 type = lexer.currentToken.identifier; 93 node.inherit = data.data.getObjectNode(type); 94 lexer.nextToken(); 95 96 if (lexer.currentToken.symbol != ')') 97 throw new ParseError(line, pos, "expected ')'"); 98 99 lexer.nextToken(); 100 } 101 102 parseParameters(objectIndent, node); 103 } 104 105 void parseParameters(in int objectIndent, Node node, bool trace = false) { 106 assert(node !is null); 107 108 const initialTargetIndent = indent - 1; 109 const initialLine = lexer.currentToken.line; 110 111 lexer.prevToken(); 112 113 // Skip objects without parameters 114 if (objectIndent != initialTargetIndent && lexer.currentToken.line != initialLine) { 115 lexer.nextToken(); 116 return; 117 } 118 119 lexer.nextToken(); 120 121 Token objectToken = lexer.currentToken; 122 int counter = 0; 123 124 while (true) { 125 string paramName = lexer.currentToken.identifier; 126 int targetIndent = indent - 1; 127 128 if (lexer.currentToken.code == Token.Code.include) { 129 if (objectIndent != targetIndent && lexer.currentToken.line != objectToken.line) { 130 lexer.nextToken(); 131 break; 132 } 133 134 parseInclude(node); 135 continue; 136 } 137 138 lexer.nextToken(); 139 140 if (objectIndent != targetIndent && lexer.currentToken.line != objectToken.line) 141 break; 142 143 const code = lexer.currentToken.code; 144 const symbol = lexer.currentToken.symbol; 145 146 if (code != Token.Code.id && symbol != ':' && symbol != '(') 147 break; 148 149 if (lexer.currentToken.symbol != ':') { 150 lexer.prevToken(); 151 parseObject(node); 152 continue; 153 } 154 155 counter++; 156 parseParameter(paramName, node); 157 } 158 159 lexer.prevToken(); 160 } 161 162 void parseParameter(in string name, Node node) { 163 auto parameter = new Parameter(name); 164 165 while (true) { 166 string valueName = to!string(parameter.children.length); 167 parseValue(valueName, parameter); 168 lexer.nextToken(); 169 170 if (lexer.currentToken.symbol != ',') 171 break; 172 } 173 174 node.insert(parameter); 175 } 176 177 void parseValue(in string name, Node parent) { 178 lexer.nextToken(); 179 180 if (lexer.currentToken.symbol == '$') { 181 parseInjectedParam(name, parent); 182 return; 183 } 184 185 if (lexer.currentToken.symbol == '[') { 186 parseArray(name, parent); 187 return; 188 } 189 190 Value value; 191 192 switch (lexer.currentToken.code) { 193 case Token.Code.number: 194 value = new NumberValue(name, lexer.currentToken.number); 195 break; 196 197 case Token.Code..string: 198 value = new StringValue(name, lexer.currentToken.str, lexer.currentToken.utfStr); 199 break; 200 201 case Token.Code.id: 202 value = new IdentifierValue(name, lexer.currentToken.identifier); 203 break; 204 205 case Token.Code.boolean: 206 value = new BooleanValue(name, lexer.currentToken.boolean); 207 break; 208 209 default: 210 throw new ParseError(line, pos, "value error"); 211 } 212 213 parent.insert(value); 214 } 215 216 void parseArray(in string name, Node parent) { 217 const auto code = lexer.currentToken.code; 218 ArrayValue array = new ArrayValue(name); 219 220 lexer.nextToken(); 221 222 if (lexer.currentToken.symbol == ']') { 223 parent.insert(array); 224 return; 225 } 226 227 lexer.prevToken(); 228 229 while (code != ']' || code != Token.Code.none) { 230 string valueName = to!string(array.children.length); 231 parseValue(valueName, array); 232 lexer.nextToken(); 233 234 const auto symbol = lexer.currentToken.symbol; 235 236 if (symbol != ',' && symbol != ']') 237 throw new ParseError(line, pos, "expected ',' or ']'"); 238 239 if (symbol == ']') 240 break; 241 } 242 243 parent.insert(array); 244 } 245 246 void parseInjectedParam(in string name, Node parent) { 247 lexer.nextToken(); 248 249 if (lexer.currentToken.code != Token.Code.id) { 250 throw new ParseError(line, pos, "expected identifier"); 251 } 252 253 const injectParamName = lexer.currentToken.identifier; 254 255 assert(data.injectParams !is null); 256 assert(parent !is null); 257 258 Node paramNode = data.injectParams.getNode(injectParamName); 259 260 if (paramNode is null) { 261 throw new ParseError(line, pos, "inject parameter with name '" ~ injectParamName ~ "' hasn't found"); 262 } 263 264 foreach (child; paramNode.children) { 265 auto value = cast(Value) child; 266 267 if (value is null) { 268 throw new ParseError(line, pos, "inject parameter '" ~ injectParamName ~ "' should be valid value"); 269 } 270 271 auto cloned = value.clone(to!string(parent.children.length)); 272 parent.insert(cloned); 273 writeln("PATH: ", cloned.path); 274 } 275 } 276 277 void parseInclude(Node parent) { 278 const indent = lexer.currentToken.indent; 279 lexer.nextToken(); 280 281 if (lexer.currentToken.code != Token.Code..string) 282 throw new ParseError(line, pos, "expected '\"'"); 283 284 if (!data.isStaticLoaded) { 285 const fileName = lexer.currentToken.str; 286 287 Node injectedParams = new Node("", true); 288 lexer.nextToken(); 289 parseParameters(indent, injectedParams, true); 290 lexer.prevToken(); 291 292 RpdlTree includeTree = new RpdlTree(data.p_rootDirectory); 293 includeTree.injectParams = injectedParams; 294 includeTree.parse(fileName); 295 296 foreach (Node child; includeTree.root.children) { 297 parent.insert(child); 298 } 299 } else { 300 throw new IncludeNotAllowedAtCTException(); 301 } 302 303 lexer.nextToken(); 304 } 305 }