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 }