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 }