1 module argon.parser; 2 3 import std.string; 4 import std.array; 5 import std.uni; 6 7 import argon.ast; 8 9 ASTTemplateNode parseArgonTemplate(string file, string source) 10 { 11 auto state = new ParserState(file, source.replace("\r\n", "\n")); 12 auto node = new ASTTemplateNode; 13 state.parent = node; 14 node.file = file; 15 16 node.children = parseArgonNodes(state); 17 18 state.parent = node.parent; 19 return node; 20 } 21 22 ASTHTMLNode parseArgonHTML(ParserState state) 23 { 24 Appender!(string) html; 25 auto node = new ASTHTMLNode; 26 node.parent = state.parent; 27 28 while(!state.testFor("{include=") && !state.testFor("{element=") && !state.testFor("{list=") && !state.testFor("{/list}") && state.remaining > 0) 29 { 30 html.put(state.pop()); 31 } 32 33 if (state.testFor("{include=")) 34 { 35 auto lines = html.data.splitLines; 36 lines[$-1] = lines[$-1].stripRight; 37 node.html = lines.join("\n"); 38 } 39 else 40 node.html = html.data.stripRight; 41 return node; 42 } 43 44 ASTIncludeNode parseArgonInclude(ParserState state) 45 { 46 auto node = new ASTIncludeNode; 47 node.parent = state.parent; 48 49 state.expect("{include="); 50 node.file = parseString(state); 51 state.expect("}"); 52 53 return node; 54 } 55 56 ASTElementNode parseArgonElement(ParserState state) 57 { 58 auto node = new ASTElementNode; 59 node.parent = state.parent; 60 state.parent = node; 61 62 state.expect("{element="); 63 node.identifier = parseArgonIdentifier(state); 64 state.expect("}"); 65 66 state.parent = node.parent; 67 return node; 68 } 69 70 ASTListNode parseArgonList(ParserState state) 71 { 72 auto node = new ASTListNode; 73 node.parent = state.parent; 74 state.parent = node; 75 76 state.expect("{list="); 77 node.identifier = parseArgonIdentifier(state); 78 state.expect("}"); 79 //state.pos += state.source[state.pos..$].length-state.source[state.pos..$].stripLeft().length; 80 81 node.children = parseArgonNodes(state, "{/list}"); 82 state.expect("{/list}"); 83 state.parent = node.parent; 84 return node; 85 } 86 87 ASTIdentifierNode parseArgonIdentifier(ParserState state) 88 { 89 auto node = new ASTIdentifierNode; 90 node.parent = state.parent; 91 node.callChain.length = 1; 92 93 while(state.peek().isAlphaNum() || state.peek() == '_') 94 node.namespace ~= state.pop(); 95 state.expect(":"); 96 while(state.peek().isAlphaNum() || state.peek() == '_' || state.peek() == '[' || state.peek() == '.') 97 { 98 node.callChain[$-1] ~= state.pop(); 99 if (state.peek() == '.') 100 { 101 state.pop(); 102 node.callChain.length++; 103 } 104 else if (state.peek == '[') 105 { 106 node.callChain.length++; 107 state.pop(); 108 node.callChain[$-1] ~= "[" ~ state.peek() ~ "]"; 109 if (state.peek != '#') 110 state.expect("$"); 111 else 112 state.pop(); 113 state.expect("]"); 114 } 115 } 116 117 return node; 118 } 119 120 ASTNode[] parseArgonNodes(ParserState state, string stopString = null) 121 { 122 ASTNode[] children; 123 for(auto child = parseArgonNode(state, stopString); child !is null; child = parseArgonNode(state, stopString)) 124 children ~= child; 125 return children; 126 } 127 128 ASTNode parseArgonNode(ParserState state, string stopString = null) 129 { 130 if ((stopString !is null && state.testFor(stopString)) || state.remaining == 0) 131 return null; 132 else if (state.testFor("{include=")) 133 return parseArgonInclude(state); 134 else if (state.testFor("{element=")) 135 return parseArgonElement(state); 136 else if (state.testFor("{list=")) 137 return parseArgonList(state); 138 else 139 return parseArgonHTML(state); 140 } 141 142 string parseString(ParserState state) 143 { 144 state.expect(`"`); 145 Appender!string str; 146 while(state.peek() != '"') 147 { 148 str.put(state.pop()); 149 } 150 state.expect(`"`); 151 return str.data; 152 } 153 154 class ParserState 155 { 156 size_t line = 1, colunm; 157 string file; 158 159 size_t pos; 160 string source; 161 ASTNode parent; 162 163 this(string file, string source) 164 { 165 this.file = file; 166 this.source = source; 167 } 168 169 bool testFor(string test) 170 { 171 return source.length >= pos + test.length && source[pos..pos+test.length] == test; 172 } 173 174 void advance(size_t count = 1) 175 { 176 if (pos+count > source.length) 177 throw new ArgonParserException("Unexpected end of file", getSourceLocation()); 178 foreach(i; 0..count) 179 { 180 if (peek == '\n') 181 { 182 colunm = 0; 183 line++; 184 } 185 else 186 { 187 colunm++; 188 } 189 pos++; 190 } 191 } 192 193 void expect(string str) 194 { 195 if (!testFor(str)) 196 throw new ArgonParserException("Expected "~str, getSourceLocation()); 197 advance(str.length); 198 } 199 200 char peek() 201 { 202 return source[pos]; 203 } 204 205 char pop() 206 { 207 char c = peek(); 208 advance(); 209 return c; 210 } 211 212 SourceLocation getSourceLocation() 213 { 214 return new SourceLocation(file, line, colunm); 215 } 216 217 @property size_t remaining() 218 { 219 return source.length - pos; 220 } 221 } 222 223 class ArgonParserException : Exception 224 { 225 this(string msg, SourceLocation sourceLocation, string file = __FILE__, size_t line = __LINE__) 226 { 227 super(msg ~ ' ' ~ sourceLocation.getErrorMessage(), file, line); 228 } 229 }