1 /** 2 * Parsed tree 3 * 4 * Copyright: © 2017 Andrey Kabylin 5 * License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 6 */ 7 8 module rpdl.tree; 9 10 import std.file; 11 import std.stdio; 12 import std.math; 13 import std.conv; 14 import std.path; 15 import std.traits; 16 17 import rpdl.lexer; 18 import rpdl.parser; 19 import rpdl.stream; 20 import rpdl.node; 21 import rpdl.value; 22 import rpdl.exception; 23 import rpdl.file_formats.bin; 24 import rpdl.file_formats.text; 25 import rpdl.writer; 26 import rpdl.accessors; 27 28 import gl3n.linalg; 29 30 /// Tree with loaded data 31 class RpdlTree { 32 /// Read/Write type 33 enum FileType { 34 /** 35 * Parsing tree for loading 36 * and using `rpdl.file_formats.text.TextWriter` for saving 37 */ 38 text, 39 40 /** 41 * Using `rpdl.file_formats.bin.BinReader` loading 42 * and using `rpdl.file_formats.bin.BinWriter` for saving 43 */ 44 bin 45 } 46 47 /** 48 * Create tree with base directory relative to this dierectory 49 * will be loaded files via `load` and `staticLoad` 50 */ 51 this(in string rootDirectory) { 52 this.p_rootDirectory = rootDirectory; 53 p_root = new Node("", true); 54 injectParams = new Node("", true); 55 } 56 57 /// Insert all nodes from `tree` 58 void merge(RpdlTree tree) { 59 foreach (node; tree.root.children) { 60 p_root.insert(node); 61 } 62 } 63 64 /// Load file in runtime 65 void load(in string fileName, in FileType rt = FileType.text) { 66 const string fullPath = rootDirectory ~ dirSeparator ~ fileName; 67 68 switch (rt) { 69 case FileType.text: parse(fileName); break; 70 case FileType.bin: new BinReader(p_root).read(fullPath); break; 71 default: 72 break; 73 } 74 } 75 76 /// Load file in compile time 77 void staticLoad(string fileName, FileType rt = FileType.text)() { 78 p_staticLoad = true; 79 80 switch (rt) { 81 case FileType.text: staticParse!(fileName)(); break; 82 case FileType.bin: break; 83 default: 84 break; 85 } 86 } 87 88 /// Save data tree to the external file 89 void save(in string fileName, in FileType wt = FileType.text) { 90 Writer writer; 91 92 switch (wt) { 93 case FileType.text: writer = new TextWriter(p_root); break; 94 case FileType.bin: writer = new BinWriter(p_root); break; 95 default: 96 return; 97 } 98 99 const fullPath = rootDirectory ~ dirSeparator ~ fileName; 100 writer.save(fullPath); 101 } 102 103 /// If true then this tree was loaded in compile time 104 @property bool isStaticLoaded() { return p_staticLoad; } 105 106 /// Root node 107 @property Node root() { return p_root; } 108 109 /** 110 * Base direcotry. Relative to this dierectory 111 * will be loaded files via `load` and `staticLoad` 112 */ 113 @property string rootDirectory() { return p_rootDirectory; } 114 115 /// Alias to the `root` 116 @property Node data() { 117 return p_root; 118 } 119 120 private: 121 Lexer lexer; 122 Parser parser; 123 124 package string p_rootDirectory; 125 Node p_root; 126 bool p_staticLoad = false; 127 package Node injectParams; 128 129 package: 130 void parse(in string fileName) { 131 SymbolStream stream = new SymbolStream(rootDirectory ~ dirSeparator ~ fileName); 132 133 this.lexer = new Lexer(stream); 134 this.parser = new Parser(lexer, this); 135 136 this.parser.parse(); 137 } 138 139 void staticParse(string fileName)() { 140 const fullPath = rootDirectory ~ dirSeparator ~ fileName; 141 SymbolStream stream = CTSymbolStream.createFromFile!(fullPath)(); 142 143 this.lexer = new Lexer(stream); 144 this.parser = new Parser(lexer, this); 145 146 this.parser.parse(); 147 } 148 } 149 150 version(unittest) { 151 import std.path; 152 import std.file; 153 } 154 155 /// See all accessors in `rpdl.accessors.Accessors` 156 unittest { 157 const binDirectory = dirName(thisExePath()); 158 const testsDirectory = buildPath(binDirectory, "tests"); 159 160 auto tree = new RpdlTree(testsDirectory); 161 tree.load("simple.rdl"); 162 163 with (tree.data) { 164 auto writer = new TextWriter(tree.root); 165 writer.save("__test.rdl"); 166 167 assert(getString("Test.Test5.name.0") == "Test"); 168 assert(getVec4f("Test.Test5.vec") == Vector!(float, 4)(0, 1, 2, 1)); 169 assert(getNumber("Test.Test5.test.4") == 3.5); 170 assert(getNumber("Test.Test5.test.5") == 1.5); 171 assert(getNumber("Test.Test4.Test5.test.6.0") == 1); 172 assert(getNumber("Test.Test4.Test5.test.6.3.1") == 6); 173 assert(getString("Test.Test4.Test5.name.0") == "Test"); 174 assert(getNumber("Test.Test2.p2.0") == 2); 175 assert(getBoolean("Test.Test2.p2.1") == true); 176 assert(getString("Test.Test2.p2.2") == "Hello"); 177 assert(getString("TestInclude.Linux.0") == "Arch"); 178 assert(getInteger("TestInclude.Test2.param.3.2") == 4); 179 assert(getString("TestInclude.testString.0") == "test", getString("TestInclude.testString.0")); 180 assert(getString("TestInclude.Test3.name.0") == "y", getString("TestInclude.Test3.name.0")); 181 182 // Non standart types 183 assert(getVec2f("Rombik.position") == Vector!(float, 2)(1, 3)); 184 assert(getVec2i("Rombik.position") == Vector!(int, 2)(1, 3)); 185 assert(getVec2ui("Rombik.position") == Vector!(uint, 2)(1, 3)); 186 187 assert(getVec2f("Rombik.size.0") == Vector!(float, 2)(320, 128)); 188 assert(getVec2f("Rombik.size2") == Vector!(float, 2)(64, 1024)); 189 try { getVec2f("Rombik.size.1"); assert(false); } catch(WrongNodeType) {} 190 191 assert(getVec4f("Rombik.texCoord.0") == Vector!(float, 4)(10, 15, 32, 64)); 192 assert(getVec4f("Rombik.texCoord2") == Vector!(float, 4)(5, 3, 16, 24)); 193 194 assert(optVec4f("Rombik.texCoord2", Vector!(float, 4)(0, 1, 2, 3)) == Vector!(float, 4)(5, 3, 16, 24)); 195 assert(optVec4f("Rombik.texCoord3", Vector!(float, 4)(0, 1, 2, 3)) == Vector!(float, 4)(0, 1, 2, 3)); 196 197 // Inheritance 198 assert(getVec2f("Test.p1") == Vector!(float, 2)(1.231, 3)); 199 assert(getString("Test.p0.0") == "Hello world!"); 200 assert(getVec4f("Test.Nest.color") == Vector!(float, 4)(255, 0, 0, 100)); 201 202 // Nest include 203 assert(getString("Test.TestInclude.Linux.0") == "Arch"); 204 assert(getString("Test.Nest.TestInclude.Linux.0") == "Arch"); 205 } 206 207 // TODO: Tests for relative pathes 208 }