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 }