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.  
43class tExpression 
44
45public
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 
142protected
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 
156private
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. 
164typedef tExpression tExpr
165 
166 
167// Convenience. Get the value and advance the expression to the next. 
168inline tString GetAtomString(tExpression& e) { tString str = e.GetAtomString(); e = e.Next(); return str;} 
169inline bool GetAtomBool(tExpression& e) { return GetAtomString(e).GetAsBool(); } 
170inline uint GetAtomUint(tExpression& e) { return GetAtomString(e).GetAsUInt(); } 
171inline uint64 GetAtomUint64(tExpression& e) { return GetAtomString(e).GetAsUInt64(); } 
172inline int GetAtomInt(tExpression& e) { return GetAtomString(e).GetAsInt(); } 
173inline float GetAtomFloat(tExpression& e) { return GetAtomString(e).GetAsFloat(); } 
174inline double GetAtomDouble(tExpression& e) { return GetAtomString(e).GetAsDouble(); } 
175inline 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 
184class tScriptReader : public tExpression 
185
186public
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 
207private
208 char* ReadBuffer
209}; 
210 
211 
212// Use this to create a script file. 
213class tScriptWriter 
214
215public
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 WriteComment(const char* = 0); 
243 
244 // Use these for multiline comments. They use the < > characters. They are not indented. 
245 void WriteCommentBegin(); 
246 void WriteCommentLine(const char* = 0); 
247 void WriteCommentEnd(); 
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 
292private
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 
297protected
298 tFileHandle ScriptFile
299}; 
300 
301 
302// A tFunExtression takes the form FunctionName(Arg1, Arg2, Arg3...) 
303class tFunExtression : public tLink<tFunExtression
304
305public
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 
317class tFunScript 
318
319public
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 
334private
335 char* EatWhiteAndComments(char* c); 
336}; 
337 
338 
339// The following error objects may be thrown by script parsing functions. 
340struct 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