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   Node[] children(); /// Gets all children of a block
47   NodeType type(); /// Returns a `NodeType` that corresponds to the type
48   Node opIndex(size_t i); /// Should be equivalent to `node.children[i]`
49   int opApply(int delegate(Node) dg); /// equivalent to looping over node.children
50   string key(); /// Gets the key of an assignment
51   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
52   /// Gets the value of an assignment or the value of a value
53   T value(T)() {
54     return value_().get!T();
55   }
56   /// Equivalent to .value!Block
57   final Block block() {
58     return value!Block;
59   }
60   string toString();
61 }
62 
63 /// Two values seperated by an equals. Usually an effect or condition
64 /// ex:
65 /// `culture = albanian`
66 class Assignment : Node {
67   string _key;
68   Variant _value;
69   this(string k, Variant v) {
70     this._key = k;
71     this._value = v;
72   }
73 
74   Node[] children() {
75     throw new PDXInvalidTypeException;
76   }
77   NodeType type() {
78     return NodeType.ASSIGNMENT;
79   }
80   Node opIndex(size_t i) {
81     throw new PDXInvalidTypeException;
82   }
83   int opApply(int delegate(Node) dg) {
84     throw new PDXInvalidTypeException;
85   }
86   string key() {
87     return _key;
88   }
89   Variant value_() {
90     return _value;
91   }
92   override string toString() {
93     return _key~" = "~_value.toString();
94   }
95 }
96 
97 /// Represents a series of ret surrounded by curly braces (typically the value of an assignment)
98 class Block : Node {
99   Node[] _children;
100   this(Node[] children) {
101     _children = children;
102   }
103 
104   Node[] children() {
105     return _children;
106   }
107   NodeType type() {
108     return NodeType.BLOCK;
109   }
110   Node opIndex(size_t i) {
111     return _children[i];
112   }
113   int opApply(int delegate(Node) dg) {
114     foreach(ref Node child; _children) {
115       if(dg(child))
116         return 1;
117     }
118     return 0;
119   }
120   string key() {
121     throw new PDXInvalidTypeException;
122   }
123   Variant value_() {
124     throw new PDXInvalidTypeException;
125   }
126   override string toString() {
127     auto ap = appender!string;
128     ap.put("{\n");
129     foreach(child; this) {
130       ap.put(child.toString()~"\n");
131     }
132     ap.put("\n}");
133     return ap[];
134   }
135 }
136 
137 /// Represents a single value with no = after it.
138 class Value : Node {
139   Variant _value;
140   this(Variant v) {
141     _value = v;
142   }
143 
144   Node[] children() {
145     throw new PDXInvalidTypeException;
146   }
147   NodeType type() {
148     return NodeType.VALUE;
149   }
150   Node opIndex(size_t i) {
151     throw new PDXInvalidTypeException;
152   }
153   int opApply(int delegate(Node) dg) {
154     throw new PDXInvalidTypeException;
155   }
156   string key() {
157     throw new PDXInvalidTypeException;
158   }
159   Variant value_() {
160     return _value;
161   }
162   override string toString() {
163     return _value.toString();
164   }
165 }
166 
167 /// Takes a paradox script and returns a Block representing all nodes within it
168 Node parse(string script) {
169   int loc = 0;
170   return parse(script~"\n}", &loc);
171 }
172 
173 Block parse(string script, int* l) {
174   Node[] ret;
175   string value = ""; // stores value currently working on
176   string buf = ""; // stores previous value when parsing assignment (buf != "" means it's currently in an assignment)
177   bool seenSpace = false;
178   Variant get() {
179     // test whether it's a number
180     bool number = true;
181     bool dot = false;
182     foreach(char c; value) {
183       if(c == '.') {
184         if(dot) {
185           number = false;
186           break;
187         }
188         else dot = true;
189       } else if(c < '0' || c > '9') {
190         number = false;
191         break;
192       }
193     }
194     // TODO: maybe add date as a type? (e.g. 1444.4.4 would return a custom struct Date or something)
195     // TODO: "yes" and "no" still get converted to bools when surrounded by quotes, fix
196     if(number && dot)
197       return cast(Variant)(value.to!float);
198     else if(number)
199       return cast(Variant)(value.to!int);
200     else if(value == "yes")
201       return cast(Variant)true;
202     else if(value == "no")
203       return cast(Variant)false;
204     else
205       return cast(Variant)(value);
206   }
207   while(true) {
208     char c = script[*l];
209     *l += 1;
210     if(*l > script.length)
211       throw new PDXParsingException("Unbalanced braces");
212     switch(c) {
213       case ' ':
214       case '\t':
215       case '\n':
216         if(value != "") {
217           if(buf != "") {
218             // finish assignment
219             ret ~= new Assignment(buf, get);
220             buf = "";
221             value = "";
222             break;
223           }
224           seenSpace = true;
225         }
226         break;
227       case '{':
228         if(buf != "") {
229           *l += 1;
230           Block b = parse(script, l);
231           ret ~= new Assignment(buf, cast(Variant)b);
232           buf = "";
233           value = "";
234           break;
235         }
236         ret ~= parse(script, l);
237         break;
238       case '}':
239         return new Block(ret);
240       case '#':
241         while(script[*l] != '\n')
242           *l += 1;
243         break;
244       case '\r':
245         break; // windows
246       case '=':
247         buf = value;
248         value = "";
249         seenSpace = false;
250         break;
251       case '"':
252         while(script[*l] != '"') {
253           value ~= script[*l];
254           *l += 1;
255         }
256         *l += 1;
257         break;
258       default:
259         if(seenSpace) {
260           ret ~= new Value(get);
261           value = c.to!string;
262           seenSpace = false;
263           break;
264         }
265         value ~= c;
266         break;
267     }
268   }
269 }
270 
271 unittest {
272   string albania = `government = monarchy
273 add_government_reform = autocracy_reform
274 government_rank = 1
275 primary_culture = albanian
276 religion = catholic
277 technology_group = eastern
278 capital = 4175 # Lezhe
279 
280 # The League of Lezhe
281 1443.3.4 = {
282 	monarch = {
283 		name = "Gjergj Skanderbeg"
284 		dynasty = "Kastrioti"
285 		birth_date = 1405.1.1		
286 		adm = 6
287 		dip = 5
288 		mil = 6
289 		leader = {	name = "Skanderbeg"            	type = general	fire = 5	shock = 5	manuever = 5	siege = 0}
290 	}
291 	clear_scripted_personalities = yes
292 	add_ruler_personality = inspiring_leader_personality
293 	add_ruler_personality = silver_tongue_personality
294 }`; // excerpt of "history/countries/ALB - Albania.txt" from EU4
295   auto res = parse(albania);
296   assert(res[0].key == "government");
297   assert(res[0].value!string == "monarchy");
298   assert(res[2].value!int == 1);
299   assert(res[7].key == "1443.3.4");
300   assert(res[7].block[1].key == "clear_scripted_personalities");
301   assert(res[7].block[0].block[1].value!string == "Kastrioti");
302   assert(res[7].block[0].block[2].value!string != "aaa");
303 }
304 
305 unittest {
306   string valueTest = `values = { 1 1 1 }`;
307   auto res = parse(valueTest);
308   foreach(v; res[0].block) {
309     assert(v.value!int == 1);
310   }
311 }