1 | // tScript.h  |
2 | //  |
3 | // This module contains script file readers and writers. Tacent supports two text script formats. The main one is in  |
4 | // the spirit of Church's lambda calculus and used 'symbolic expressions'. ex. [a b c]. See tExpression. I personally  |
5 | // find this notation much more elegant than stuff like the lame XML notation. This site agrees:  |
6 | // http://c2.com/cgi/wiki?XmlIsaPoorCopyOfEssExpressions  |
7 | // The tExpression reader class in this file parses 'in-place'. That is, the entire file is just read into memory once  |
8 | // and accessed as const data. This reduces memory fragmentation but may have made implementation more complex.   |
9 | //  |
10 | // The second format is a functional format. ex. a(b,c) See tFunExtression.  |
11 | //  |
12 | // Copyright (c) 2006, 2017, 2019 Tristan Grimmer.  |
13 | // Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby  |
14 | // granted, provided that the above copyright notice and this permission notice appear in all copies.  |
15 | //  |
16 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL  |
17 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,  |
18 | // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN  |
19 | // AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR  |
20 | // PERFORMANCE OF THIS SOFTWARE.  |
21 |   |
22 | #pragma once  |
23 | #include <Foundation/tList.h>  |
24 | #include <Foundation/tString.h>  |
25 | #include <Foundation/tHash.h>  |
26 | #include <Foundation/tFundamentals.h>  |
27 | #include <Math/tQuaternion.h>  |
28 | #include <Math/tVector2.h>  |
29 | #include <Math/tVector3.h>  |
30 | #include <Math/tVector4.h>  |
31 | #include <Math/tMatrix2.h>  |
32 | #include <Math/tMatrix4.h>  |
33 | #include <Math/tColour.h>  |
34 | #include "System/tThrow.h"  |
35 | #include "System/tPrint.h"  |
36 | #include "System/tFile.h"  |
37 |   |
38 |   |
39 | // An s-expression has the following syntax: [command argument1 argument2 argument3] where both the commands and  |
40 | // arguments are also expressions. At the leaf level an expression is an Atom. This is the essence of symbolic  |
41 | // expressions. ex. (a.(b.(c.()))) == [a b c] is supported but (a.b) is not. Cdr is not much use in proper lists, but  |
42 | // Car, Cadr, Caddr, etc are. i.e. Car([a b c]) = a. Cadr([a b c]) = b. etc.   |
43 | class tExpression  |
44 | {  |
45 | public:  |
46 | tExpression() : ValueData(nullptr), LineNumber(0) { }  |
47 | tExpression(const char* v) : ValueData(v), LineNumber(0) { }  |
48 |   |
49 | // If you want the expression to keep track of what line number it's on then you should supply the current line  |
50 | // number. Thrown error messages will include the line number if it's set.  |
51 | tExpression(const char* v, int lineNumber) : ValueData(v), LineNumber(lineNumber) { }  |
52 | virtual ~tExpression() { }  |
53 |   |
54 | // Like in scheme. Contents of the Address Register from the old IBM days.  |
55 | tExpression Car() const;  |
56 | tExpression Cadr() const { return CarCdrN(1); }  |
57 | tExpression Caddr() const { return CarCdrN(2); }  |
58 | tExpression Cadddr() const { return CarCdrN(3); }  |
59 | tExpression Caddddr() const { return CarCdrN(4); }  |
60 | tExpression Cadddddr() const { return CarCdrN(5); }  |
61 | tExpression Caddddddr() const { return CarCdrN(6); }  |
62 | tExpression CarCdrN(int n) const;  |
63 |   |
64 | // If there aren't enough d's in the above commands or there are a variable number of items, use this until you get  |
65 | // an invalid expression.  |
66 | virtual tExpression Next() const;  |
67 |   |
68 | bool IsValid() const { return ValueData ? true : false; }  |
69 | bool IsAtom() const;  |
70 |   |
71 | tString GetExpressionString() const;  |
72 | tString GetAtomString() const;  |
73 | bool GetAtomBool() const { return GetAtomString().GetAsBool(); }  |
74 | uint GetAtomUint() const { return GetAtomString().GetAsUInt(); }  |
75 | uint64 GetAtomUint64() const { return GetAtomString().GetAsUInt64(); }  |
76 | int GetAtomInt() const { return GetAtomString().GetAsInt(); }  |
77 | float GetAtomFloat() const { return GetAtomString().GetAsFloat(); }  |
78 | double GetAtomDouble() const { return GetAtomString().GetAsDouble(); }  |
79 | uint32 GetAtomHash() const { return tHash::tHashString(GetAtomString()); }  |
80 | uint32 Hash() const { return GetAtomHash(); }  |
81 |   |
82 | // Vectors, quaternions, matrices, and colours should be of the form (x, y, z). Colours are represented as  |
83 | // (r, g, b, a) where all elements are in [0, 255]. Matrices currently need to be specified similarly to vectors  |
84 | // except with 16 values. The values must be specified in column-major format.  |
85 | tMath::tVector2 GetAtomVector2() const;  |
86 | tMath::tVector3 GetAtomVector3() const;  |
87 | tMath::tVector4 GetAtomVector4() const;  |
88 | tMath::tQuaternion GetAtomQuaternion() const;  |
89 | tMath::tMatrix2 GetAtomMatrix2() const;  |
90 | tMath::tMatrix4 GetAtomMatrix4() const;  |
91 | tColouri GetAtomColour() const;  |
92 |   |
93 | // Implicit casting to various types. They can be used instead of the GetAtom methods. Notice that some of the  |
94 | // common math types, like vectors, are supported also.  |
95 | operator tString() const { if (IsAtom()) return GetAtomString(); else return GetExpressionString(); }  |
96 | operator bool() const { if (IsAtom()) return GetAtomBool(); else return false; }  |
97 | operator int() const { if (IsAtom()) return GetAtomInt(); else return 0; }  |
98 | operator uint32() const { if (IsAtom()) return GetAtomUint(); else return 0; }  |
99 | operator long() const { if (IsAtom()) return long(GetAtomInt()); else return 0; }  |
100 | operator float() const { if (IsAtom()) return GetAtomFloat(); else return 0.0f; }  |
101 | operator double() const { if (IsAtom()) return GetAtomDouble(); else return 0.0; }  |
102 | operator tMath::tVector2() const { if (IsAtom()) return GetAtomVector2(); else return tMath::tVector2::zero; }  |
103 | operator tMath::tVector3() const { if (IsAtom()) return GetAtomVector3(); else return tMath::tVector3::zero; }  |
104 | operator tMath::tVector4() const { if (IsAtom()) return GetAtomVector4(); else return tMath::tVector4::zero; }  |
105 | operator tMath::tQuaternion() const { if (IsAtom()) return GetAtomQuaternion(); else return tMath::tQuaternion::zero; }  |
106 | operator tMath::tMatrix2() const { if (IsAtom()) return GetAtomMatrix2(); else return tMath::tMatrix2::zero; }  |
107 | operator tMath::tMatrix4() const { if (IsAtom()) return GetAtomMatrix4(); else return tMath::tMatrix4::zero; }  |
108 | operator tColouri() const { if (IsAtom()) return GetAtomColour(); else return tColouri::black; }  |
109 |   |
110 | // Here are some alternate names for the functions above.  |
111 | tExpression Arg0() const /* Arg0 is the command. */ { return Car(); }  |
112 | tExpression Arg1() const { return Cadr(); }  |
113 | tExpression Arg2() const { return Caddr(); }  |
114 | tExpression Arg3() const { return Cadddr(); }  |
115 | tExpression Arg4() const { return Caddddr(); }  |
116 | tExpression Arg5() const { return Cadddddr(); }  |
117 | tExpression Arg6() const { return Caddddddr(); }  |
118 | tExpression ArgN(int n) const { return CarCdrN(n); }  |
119 | int CountArgs() const /* Not fast. */ { if (!IsValid()) return 0; int c = 0; while (CarCdrN(c).IsValid()) c++; return c; }  |
120 |   |
121 | tExpression Item0() const { return Car(); }  |
122 | tExpression Item1() const { return Cadr(); }  |
123 | tExpression Item2() const { return Caddr(); }  |
124 | tExpression Item3() const { return Cadddr(); }  |
125 | tExpression Item4() const { return Caddddr(); }  |
126 | tExpression Item5() const { return Cadddddr(); }  |
127 | tExpression Item6() const { return Caddddddr(); }  |
128 | tExpression ItemN(int n) const { return CarCdrN(n); }  |
129 | int CountItems() const /* Not fast. */ { return CountArgs(); }  |
130 |   |
131 | tExpression Cmd() const { return Car(); }  |
132 | tExpression Command() const { return Car(); }  |
133 | tExpression First() const { return Car(); }  |
134 |   |
135 | bool Valid() const { return IsValid(); }  |
136 | bool Atom() const { return IsAtom(); }  |
137 |   |
138 | // Some stuff for error reporting. In an error condition this returns the context of the problem.  |
139 | tString GetContext() const;  |
140 | int GetLineNumber() const { return LineNumber; }  |
141 |   |
142 | protected:  |
143 | // Chugs along the in-memory data ignoring stuff that is allowed to be ignored. Returns the number of new lines  |
144 | // encountered along the way.  |
145 | static const char* EatWhiteAndComments(const char*, int& lineCount);  |
146 |   |
147 | // The memory for this is owned by the tScriptRead class.  |
148 | const char* ValueData;  |
149 |   |
150 | // The first valid line number starts at 1.  |
151 | int LineNumber;  |
152 |   |
153 | // When throwing an error this is how much of the file is supplied to give a context.  |
154 | static const int ContextSize = 32;  |
155 |   |
156 | private:  |
157 | // Parses atom strings of the form (a, b, c, ...). There may or may not be spaces. Returns the part inside the  |
158 | // brackets and removes all spaces. This function is a helper for getting atoms that are vectors, quaternions,  |
159 | // matrices, or colours.  |
160 | tString GetAtomTupleString() const;  |
161 | };  |
162 |   |
163 | // If you don't like to type.  |
164 | typedef tExpression tExpr;  |
165 |   |
166 |   |
167 | // Convenience. Get the value and advance the expression to the next.  |
168 | inline tString GetAtomString(tExpression& e) { tString str = e.GetAtomString(); e = e.Next(); return str;}  |
169 | inline bool GetAtomBool(tExpression& e) { return GetAtomString(e).GetAsBool(); }  |
170 | inline uint GetAtomUint(tExpression& e) { return GetAtomString(e).GetAsUInt(); }  |
171 | inline uint64 GetAtomUint64(tExpression& e) { return GetAtomString(e).GetAsUInt64(); }  |
172 | inline int GetAtomInt(tExpression& e) { return GetAtomString(e).GetAsInt(); }  |
173 | inline float GetAtomFloat(tExpression& e) { return GetAtomString(e).GetAsFloat(); }  |
174 | inline double GetAtomDouble(tExpression& e) { return GetAtomString(e).GetAsDouble(); }  |
175 | inline uint32 GetAtomHash(tExpression& e) { return tHash::tHashString(GetAtomString(e)); }  |
176 |   |
177 |   |
178 | // Use this to read and parse an existing script. A script file is a list of expressions without []'s around the  |
179 | // entire file. e.g. This is a valid script:  |
180 | //  |
181 | // [a b c] ; Arg0  |
182 | // d ; Arg1  |
183 | // [e f] ; Arg2  |
184 | class tScriptReader : public tExpression  |
185 | {  |
186 | public:  |
187 | // Constructs an initially invalid tScriptReader.  |
188 | tScriptReader() : tExpression(), ReadBuffer(nullptr) { }  |
189 |   |
190 | // If isFile is true then the file 'name' is loaded, otherwise treats 'name' as the actual script string.  |
191 | tScriptReader(const tString& name, bool isFile = true) : tExpression(), ReadBuffer(nullptr) { Load(name, isFile); }  |
192 |   |
193 | // Useful for command line utilities. Makes a script from standard command line argc and argv parameters. Honestly,  |
194 | // I'm not sure how useful this is now that we have tOption for parsing command lines in a nice way that is a bit  |
195 | // more standard.  |
196 | tScriptReader(int argc, char** argv);  |
197 | ~tScriptReader() { delete[] ReadBuffer; }  |
198 |   |
199 | // If isFile is true then the file 'name' is loaded, otherwise treats 'name' as the actual script string. The  |
200 | // object is cleared before the new information is loaded. Any previous information is lost.  |
201 | void Load(const tString& name, bool isFile = true);  |
202 |   |
203 | // The object will be invalid after this call.  |
204 | void Clear() { delete[] ReadBuffer; ReadBuffer = 0; }  |
205 | bool IsValid() const { return ReadBuffer ? true : false; }  |
206 |   |
207 | private:  |
208 | char* ReadBuffer;  |
209 | };  |
210 |   |
211 |   |
212 | // Use this to create a script file.  |
213 | class tScriptWriter  |
214 | {  |
215 | public:  |
216 | // Creates the file if it doesn't exist, overwrites it if it does.  |
217 | tScriptWriter(const tString& filename);  |
218 | ~tScriptWriter() { tSystem::tCloseFile(ScriptFile); }  |
219 |   |
220 | void BeginExpression();  |
221 | void EndExpression();  |
222 |   |
223 | // For the WriteAtom calls any attempt to write a floating point value that is a "special" IEEE bit-pattern (like  |
224 | // Infinity, NAN, etc) will result in a value of 0.0 being written.  |
225 | void WriteAtom(const tString&);  |
226 | void WriteAtom(const char*);  |
227 | void WriteAtom(const bool);  |
228 | void WriteAtom(const uint32);  |
229 | void WriteAtom(const uint64);  |
230 | void WriteAtom(const int);  |
231 | void WriteAtom(const float, bool incBitRep = true);  |
232 | void WriteAtom(const double, bool incBitRep = true);  |
233 | void WriteAtom(const tMath::tVector2&, bool incBitRep = true);  |
234 | void WriteAtom(const tMath::tVector3&, bool incBitRep = true);  |
235 | void WriteAtom(const tMath::tVector4&, bool incBitRep = true);  |
236 | void WriteAtom(const tMath::tQuaternion&, bool incBitRep = true);  |
237 | void WriteAtom(const tMath::tMatrix2&, bool incBitRep = true);  |
238 | void WriteAtom(const tMath::tMatrix4&, bool incBitRep = true);  |
239 | void WriteAtom(const tColouri&);  |
240 |   |
241 | // Writes a single line comment to the script file.  |
242 | void (const char* = 0);  |
243 |   |
244 | // Use these for multiline comments. They use the < > characters. They are not indented.  |
245 | void ();  |
246 | void (const char* = 0);  |
247 | void ();  |
248 |   |
249 | void Indent() { CurrIndent += IndentDelta; }  |
250 | void Dedent() { CurrIndent -= IndentDelta; if (CurrIndent < 0) CurrIndent = 0; }  |
251 | void NewLine();  |
252 |   |
253 | // Here are shortened versions of the commands so you don't have to type as much.  |
254 | void Beg() { BeginExpression(); }  |
255 | void Begin() { BeginExpression(); }  |
256 | void End() { EndExpression(); }  |
257 | void Atom(const tString& s) { WriteAtom(s); }  |
258 | void Atom(const char* c) { WriteAtom(c); }  |
259 | void Atom(const bool b) { WriteAtom(b); }  |
260 | void Atom(const uint32 u) { WriteAtom(u); }  |
261 | void Atom(const int i) { WriteAtom(i); }  |
262 | void Atom(const float f, bool incBitRep = true) { WriteAtom(f, incBitRep); }  |
263 | void Atom(const double d, bool incBitRep = true) { WriteAtom(d, incBitRep); }  |
264 | void Atom(const tMath::tVector2& v, bool incBitRep = true) { WriteAtom(v, incBitRep); }  |
265 | void Atom(const tMath::tVector3& v, bool incBitRep = true) { WriteAtom(v, incBitRep); }  |
266 | void Atom(const tMath::tVector4& v, bool incBitRep = true) { WriteAtom(v, incBitRep); }  |
267 | void Atom(const tMath::tQuaternion& q, bool incBitRep = true) { WriteAtom(q, incBitRep); }  |
268 | void Atom(const tMath::tMatrix2& m, bool incBitRep = true) { WriteAtom(m, incBitRep); }  |
269 | void Atom(const tMath::tMatrix4& m, bool incBitRep = true) { WriteAtom(m, incBitRep); }  |
270 | void Atom(const tColouri& c) { WriteAtom(c); }  |
271 |   |
272 | void Rem(const char* c = 0) { WriteComment(c); }  |
273 | void RemBegin() { WriteCommentBegin(); }  |
274 | void RemLine(const char* l = 0) { WriteCommentLine(l); }  |
275 | void RemEnd() { WriteCommentEnd(); }  |
276 |   |
277 | void Ind() { Indent(); }  |
278 | void DInd() { Dedent(); }  |
279 | void CR() { NewLine(); }  |
280 | void Return() { NewLine(); }  |
281 |   |
282 | // The Comp (Compose) functions write a list of 2 to 5 atoms (a command followed by 1 to 4 arguments). They are  |
283 | // convenience functions to write expressions of the form [s a b] followed by a carraige return. The first atom is  |
284 | // always a string and remaining atoms are always of the same type. Before the expression is written, the correct  |
285 | // number of indents is inserted. Ex. To write 4 integers:  |
286 | // Comp("LeftRightTopBottom", 4, 2, -2, 1);  |
287 | template<typename T> void Comp(const tString& s, const T& a) { WriteIndents(); Begin(); Atom(s); Atom(a); End(); CR(); }  |
288 | template<typename T> void Comp(const tString& s, const T& a, const T& b) { WriteIndents(); Begin(); Atom(s); Atom(a); Atom(b); End(); CR(); }  |
289 | template<typename T> void Comp(const tString& s, const T& a, const T& b, const T& c) { WriteIndents(); Begin(); Atom(s); Atom(a); Atom(b); Atom(c); End(); CR(); }  |
290 | template<typename T> void Comp(const tString& s, const T& a, const T& b, const T& c, const T& d) { WriteIndents(); Begin(); Atom(s); Atom(a); Atom(b); Atom(c); Atom(d); End(); CR(); }  |
291 |   |
292 | private:  |
293 | void WriteIndents() { int tabs = CurrIndent / IndentDelta; for (int t = 0; t < tabs; t++) tfPrintf(ScriptFile, "\t" ); }  |
294 | int CurrIndent;  |
295 | static const int IndentDelta = 4;  |
296 |   |
297 | protected:  |
298 | tFileHandle ScriptFile;  |
299 | };  |
300 |   |
301 |   |
302 | // A tFunExtression takes the form FunctionName(Arg1, Arg2, Arg3...)  |
303 | class tFunExtression : public tLink<tFunExtression>  |
304 | {  |
305 | public:  |
306 | tFunExtression() : Function(), Arguments() { }  |
307 |   |
308 | // Argument must point to the first character of the function name.  |
309 | tFunExtression(const char*);  |
310 | virtual ~tFunExtression() { while (tStringItem* arg = Arguments.Remove()) delete arg; }  |
311 |   |
312 | tString Function;  |
313 | tList<tStringItem> Arguments;  |
314 | };  |
315 |   |
316 |   |
317 | class tFunScript  |
318 | {  |
319 | public:  |
320 | tFunScript() : Expressions() { }  |
321 | tFunScript(const tString& fileName) : Expressions() { Load(fileName); }  |
322 | ~tFunScript() { Clear(); }  |
323 |   |
324 | void Clear() { while (tFunExtression* exp = Expressions.Remove()) delete exp; }  |
325 | void Load(const tString& fileName);  |
326 | void Save(const tString& fileName);  |
327 |   |
328 | tFunExtression* First() const { return Expressions.First(); }  |
329 | tFunExtression* Last() const { return Expressions.Last(); }  |
330 |   |
331 | // A tFunScript is just a list of expressions. A tree may be more powerful?  |
332 | tList<tFunExtression> Expressions;   |
333 |   |
334 | private:  |
335 | char* EatWhiteAndComments(char* c);  |
336 | };  |
337 |   |
338 |   |
339 | // The following error objects may be thrown by script parsing functions.  |
340 | struct tScriptError : public tError  |
341 | {  |
342 | tScriptError(const char* format, ...) :  |
343 | tError("tScript Module. " )  |
344 | {  |
345 | va_list marker;  |
346 | va_start(marker, format);  |
347 | tString msg;  |
348 | Message += tvsPrintf(msg, format, marker);  |
349 | }  |
350 |   |
351 | tScriptError(int lineNumber, const char* format, ...) :  |
352 | tError("tScript Module. " )  |
353 | {  |
354 | va_list marker;  |
355 | va_start(marker, format);  |
356 | tString msg;  |
357 | tvsPrintf(msg, format, marker);  |
358 | if (lineNumber > 0)  |
359 | {  |
360 | tString line;  |
361 | Message += tsPrintf(line, "Line %d. " , lineNumber);  |
362 | }  |
363 | Message += msg;  |
364 | }  |
365 | tScriptError() : tError("tScript Module." ) { }  |
366 | };  |
367 | |