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 }