1 /*
2   Paradox Script Parser
3   Copyright (C) 2022 TheZipCreator
4 
5   This program is free software: you can redistribute it and/or modify
6   it under the terms of the GNU General Public License as published by
7   the Free Software Foundation, either version 3 of the License, or
8   (at your option) any later version.
9 
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14 
15   You should have received a copy of the GNU General Public License
16   along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18 
19 module pdxparser;
20 
21 import std.variant, std.conv, std.array;
22 
23 /// Thrown whenever a function is called on a node when it's not appropriate
24 class PDXInvalidTypeException : Exception {
25   this() {
26     super("Invalid type for operation");
27   }
28 }
29 
30 /// Thrown whenever parsing fails
31 class PDXParsingException : Exception {
32   this(string msg) {
33     super(msg);
34   }
35 }
36 
37 /// Type of a node
38 enum NodeType {
39   ASSIGNMENT, ///
40   BLOCK, ///
41   VALUE ///
42 }
43 
44 /// Represents a single usable thing in the given script
45 interface Node {
46   /// Gets all children of a block
47   Node[] children();
48   /// Returns a `NodeType` that corresponds to the type
49   NodeType type(); 
50   /// Should be equivalent to `node.children[i]`
51   Node opIndex(size_t i);
52   /// equivalent to looping over node.children
53   int opApply(int delegate(Node) dg);
54   /// Gets the key of an assignment
55   string key(); 
56   Variant value_(); // for some reason (seriously why the f**k did they do this) template functions in interfaces are final by default. So I have to do this instead
57   /// Gets the value of an assignment or the value of a value
58   T value(T)() {
59     return value_().get!T();
60   }
61   /// Equivalent to .value!Block
62   final Block block() {
63     return value!Block;
64   }
65   string toString();
66 }
67 
68 /// Two values seperated by an equals. Usually an effect or condition
69 /// ex:
70 /// `culture = albanian`
71 class Assignment : Node {
72   string _key;
73   Variant _value;
74   this(string k, Variant v) {
75     this._key = k;
76     this._value = v;
77   }
78 
79   Node[] children() {
80     throw new PDXInvalidTypeException;
81   }
82   NodeType type() {
83     return NodeType.ASSIGNMENT;
84   }
85   Node opIndex(size_t i) {
86     throw new PDXInvalidTypeException;
87   }
88   int opApply(int delegate(Node) dg) {
89     throw new PDXInvalidTypeException;
90   }
91   string key() {
92     return _key;
93   }
94   Variant value_() {
95     return _value;
96   }
97   override string toString() {
98     return _key~" = "~_value.toString();
99   }
100 }
101 
102 /// Represents a series of ret surrounded by curly braces (typically the value of an assignment)
103 class Block : Node {
104   Node[] _children;
105   this(Node[] children) {
106     _children = children;
107   }
108 
109   Node[] children() {
110     return _children;
111   }
112   NodeType type() {
113     return NodeType.BLOCK;
114   }
115   Node opIndex(size_t i) {
116     return _children[i];
117   }
118   int opApply(int delegate(Node) dg) {
119     foreach(ref Node child; _children) {
120       if(dg(child))
121         return 1;
122     }
123     return 0;
124   }
125   string key() {
126     throw new PDXInvalidTypeException;
127   }
128   Variant value_() {
129     throw new PDXInvalidTypeException;
130   }
131   override string toString() {
132     auto ap = appender!string;
133     ap.put("{\n");
134     foreach(child; this) {
135       ap.put(child.toString()~"\n");
136     }
137     ap.put("\n}");
138     return ap[];
139   }
140 }
141 
142 /// Represents a single value with no = after it.
143 class Value : Node {
144   Variant _value;
145   this(Variant v) {
146     _value = v;
147   }
148 
149   Node[] children() {
150     throw new PDXInvalidTypeException;
151   }
152   NodeType type() {
153     return NodeType.VALUE;
154   }
155   Node opIndex(size_t i) {
156     throw new PDXInvalidTypeException;
157   }
158   int opApply(int delegate(Node) dg) {
159     throw new PDXInvalidTypeException;
160   }
161   string key() {
162     throw new PDXInvalidTypeException;
163   }
164   Variant value_() {
165     return _value;
166   }
167   override string toString() {
168     return _value.toString();
169   }
170 }
171 
172 /// Takes a paradox script and returns a Block representing all nodes within it
173 Node parse(string script) {
174   int loc = 0;
175   return parse(script~"\n}", &loc);
176 }
177 
178 Block parse(string script, int* l) {
179   Node[] ret;
180   string value = ""; // stores value currently working on
181   string buf = ""; // stores previous value when parsing assignment (buf != "" means it's currently in an assignment)
182   bool seenSpace = false;
183   Variant get() {
184     // test whether it's a number
185     bool number = true;
186     bool dot = false;
187     foreach(char c; value) {
188       if(c == '.') {
189         if(dot) {
190           number = false;
191           break;
192         }
193         else dot = true;
194       } else if(c < '0' || c > '9') {
195         number = false;
196         break;
197       }
198     }
199     // TODO: maybe add date as a type? (e.g. 1444.4.4 would return a custom struct Date or something)
200     // TODO: "yes" and "no" still get converted to bools when surrounded by quotes, fix
201     if(number && dot)
202       return cast(Variant)(value.to!float);
203     else if(number)
204       return cast(Variant)(value.to!int);
205     else if(value == "yes")
206       return cast(Variant)true;
207     else if(value == "no")
208       return cast(Variant)false;
209     else
210       return cast(Variant)(value);
211   }
212   while(true) {
213     char c = script[*l];
214     *l += 1;
215     if(*l > script.length)
216       throw new PDXParsingException("Unbalanced braces");
217     switch(c) {
218       case ' ':
219       case '\t':
220       case '\n':
221         if(value != "") {
222           if(buf != "") {
223             // finish assignment
224             ret ~= new Assignment(buf, get);
225             buf = "";
226             value = "";
227             break;
228           }
229           seenSpace = true;
230         }
231         break;
232       case '{':
233         if(buf != "") {
234           *l += 1;
235           Block b = parse(script, l);
236           ret ~= new Assignment(buf, cast(Variant)b);
237           buf = "";
238           value = "";
239           break;
240         }
241         ret ~= parse(script, l);
242         break;
243       case '}':
244         if(seenSpace) {
245           ret ~= new Value(get);
246           value = c.to!string;
247           seenSpace = false;
248         }
249         return new Block(ret);
250       case '#':
251         while(script[*l] != '\n')
252           *l += 1;
253         break;
254       case '\r':
255         break; // windows
256       case '=':
257         buf = value;
258         value = "";
259         seenSpace = false;
260         break;
261       case '"':
262         while(script[*l] != '"') {
263           value ~= script[*l];
264           *l += 1;
265         }
266         *l += 1;
267         break;
268       default:
269         if(seenSpace) {
270           ret ~= new Value(get);
271           value = c.to!string;
272           seenSpace = false;
273           break;
274         }
275         value ~= c;
276         break;
277     }
278   }
279 }
280 
281 unittest {
282   string albania = `government = monarchy
283 add_government_reform = autocracy_reform
284 government_rank = 1
285 primary_culture = albanian
286 religion = catholic
287 technology_group = eastern
288 capital = 4175 # Lezhe
289 
290 # The League of Lezhe
291 1443.3.4 = {
292 	monarch = {
293 		name = "Gjergj Skanderbeg"
294 		dynasty = "Kastrioti"
295 		birth_date = 1405.1.1		
296 		adm = 6
297 		dip = 5
298 		mil = 6
299 		leader = {	name = "Skanderbeg"            	type = general	fire = 5	shock = 5	manuever = 5	siege = 0}
300 	}
301 	clear_scripted_personalities = yes
302 	add_ruler_personality = inspiring_leader_personality
303 	add_ruler_personality = silver_tongue_personality
304 }`; // excerpt of "history/countries/ALB - Albania.txt" from EU4
305   auto res = parse(albania);
306   assert(res[0].key == "government");
307   assert(res[0].value!string == "monarchy");
308   assert(res[2].value!int == 1);
309   assert(res[7].key == "1443.3.4");
310   assert(res[7].block[1].key == "clear_scripted_personalities");
311   assert(res[7].block[0].block[1].value!string == "Kastrioti");
312   assert(res[7].block[0].block[2].value!string != "aaa");
313 }
314 
315 unittest {
316   string valueTest = `values = { 1 1 1 }`;
317   auto res = parse(valueTest);
318   import std.stdio;
319   writeln(res[0].block);
320   assert(res[0].block.children.length == 3);
321   foreach(v; res[0].block) {
322     assert(v.value!int == 1);
323   }
324 }