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 }