1// tScript.cpp 
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, 2020 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#include <Foundation/tString.h> 
23#include "System/tFile.h" 
24#include "System/tScript.h" 
25using namespace tMath; 
26 
27 
28namespace tScript 
29{ 
30 // Block comment begin and end characters. Putting them here in case we need to change them (again). 
31 static char BCB = '{'; 
32 static char BCE = '}'; 
33}; 
34using namespace tScript; 
35 
36 
37tExpression tExpression::Car() const 
38{ 
39 tAssert( IsValid() ); 
40 
41 const char* c = ValueData + 1; 
42 
43 if (*c == '\0') 
44 return tExpression(); 
45 
46 int lineCount; 
47 c = EatWhiteAndComments(c, lineCount); 
48 
49 // Look for empty list. 
50 if (*c == ']') 
51 return tExpression(); 
52 
53 return tExpression(c, LineNumber+lineCount); 
54} 
55 
56 
57tExpression tExpression::CarCdrN(int n) const 
58{ 
59 tExpression e = Car(); 
60 
61 for (int i = 0; i < n; i++) 
62 { 
63 if (!e.Valid()) 
64 return e; 
65 
66 e = e.Next(); 
67 } 
68 
69 return e; 
70} 
71 
72 
73tExpression tExpression::Next() const 
74{ 
75 tAssert( IsValid() ); 
76 
77 const char* c = ValueData; 
78 int count = 0; 
79 int lineNum = LineNumber; 
80 
81 if (!IsAtom()) 
82 { 
83 // Skip the expression by counting square brackets till we are at zero again. Opening bracket adds 1, closing subtracts 1. Simple. 
84 // @todo Slight bug here. If there is a string inside the expression with []s, they must match. 
85 do 
86 { 
87 if (*c == '[') 
88 count++; 
89 
90 if (*c == ']') 
91 count--; 
92 
93 if (*c == '\0') 
94 return tExpression(); 
95 
96 if (*c == '\r') 
97 lineNum++; 
98 
99 c++; 
100 } 
101 while (count); 
102 } 
103 else 
104 { 
105 // It's an atom. If it begins with a quote we must find where it ends. 
106 if (*c == '"') 
107 { 
108 c++; 
109 
110 // Keep going until next quote. 
111 c = tStd::tStrchr(c, '"'); 
112 
113 if (!c) 
114 { 
115 if (lineNum != -1) 
116 throw tScriptError("Begin quote found but no end quote on line %d.", lineNum); 
117 else 
118 throw tScriptError("Begin quote found but no end quote."); 
119 } 
120 c++; 
121 } 
122 
123 // If it begins with an open bracket (not a square bracket, a British bracket... a parenthesis in "American") we must find the closing bracket. 
124 else if (*c == '(') 
125 { 
126 c++; 
127 
128 // Keep going until closing bracket. 
129 c = tStd::tStrchr(c, ')'); 
130 
131 if (!c) 
132 { 
133 if (lineNum != -1) 
134 throw tScriptError("Opening bracket found but no closing bracket on line %d.", lineNum); 
135 else 
136 throw tScriptError("Opening bracket found but no closing bracket."); 
137 } 
138 c++; 
139 } 
140 else 
141 { 
142 // The ';' and BCB should also be terminators for the current argument so that EatWhiteAndComments will get everything. 
143 char c1 = *c; 
144 while ((c1 != ' ') && (c1 != '\t') && (c1 != '[') && (c1 != ']') && (c1 != '\0') && (c1 != ';') && (c1 != BCB) && (c1 != '"')) 
145 { 
146 c++; 
147 
148 // Newline is also considered a terminator. 
149 if (c1 == '\n') 
150 { 
151 lineNum++; 
152 break; 
153 } 
154 c1 = *c; 
155 } 
156 } 
157 } 
158 
159 int lineCount; 
160 c = EatWhiteAndComments(c, lineCount); 
161 lineNum += lineCount; 
162 
163 if ((*c == ']') || (*c == '\0')) 
164 // We're at the end of the argument list. 
165 return tExpression(); 
166 
167 return tExpression(c, lineNum); 
168} 
169 
170 
171bool tExpression::IsAtom() const 
172{ 
173 if (ValueData && (*ValueData != '[')) 
174 return true; 
175 else 
176 return false; 
177} 
178 
179 
180tString tExpression::GetExpressionString() const 
181{ 
182 tAssert( IsValid() ); 
183 if (!ValueData) 
184 return tString(); 
185 
186 if (IsAtom()) 
187 return GetAtomString(); 
188 
189 tAssert(*ValueData == '['); 
190 const char* start = ValueData; 
191 const char* end = start; 
192 int bracketCount = 1; 
193 while(*++end) 
194 { 
195 if(*end == '[') 
196 bracketCount++; 
197 else if(*end == ']' && !--bracketCount) 
198 break; 
199 } 
200 
201 if (*end) 
202 end++; 
203 else 
204 throw tScriptError(LineNumber, "Begin bracket found but no end bracket."); 
205 
206 // Creates a tString full of '\0's. 
207 tString estr(int(end - start)); 
208 tStd::tStrncpy(estr.Text(), start, int(end - start)); 
209 return estr; 
210} 
211 
212 
213tString tExpression::GetAtomString() const 
214{ 
215 if (!IsAtom()) 
216 throw tScriptError(LineNumber, "Atom expected near: %s", GetContext().Pod()); 
217 
218 const char* start; 
219 const char* end; 
220 if (*ValueData == '"') 
221 { 
222 start = ValueData + 1; 
223 end = tStd::tStrchr(start, '"'); 
224 
225 // Keep going until next quote. 
226 if (!end) 
227 throw tScriptError(LineNumber, "Begin quote found but no end quote."); 
228 } 
229 
230 // It may be a tuple atom in form (a, b, c) 
231 else if (*ValueData == '(') 
232 { 
233 start = ValueData; 
234 end = tStd::tStrchr(start, ')'); 
235 
236 // Keep going until next paren. 
237 if (end) 
238 end++; 
239 else 
240 throw tScriptError(LineNumber, "Begin paren found but no end paren."); 
241 } 
242 else 
243 { 
244 start = ValueData; 
245 end = ValueData; 
246 while 
247 ( 
248 (*end != ' ') && (*end != '\t') && (*end != '[') && (*end != ']') && (*end != '\0') && 
249 (*end != '\r') && (*end != '\n') && (*end != ';') && (*end != BCB) && (*end != '"') 
250 ) end++; 
251 } 
252 
253 // Creates a tString full of '\0's. 
254 tString atom(int(end - start)); 
255 tStd::tStrncpy(atom.Text(), start, int(end - start)); 
256 
257 return atom; 
258} 
259 
260 
261tString tExpression::GetAtomTupleString() const 
262{ 
263 if (!IsAtom()) 
264 throw tScriptError(LineNumber, "Atom expected near: %s", GetContext().Pod()); 
265 
266 if (*ValueData != '(') 
267 throw tScriptError(LineNumber, "Tuple atom expected near: %s", GetContext().Pod()); 
268 
269 const char* start = ValueData + 1; 
270 const char* end = tStd::tStrchr(start, ')'); 
271 
272 // If no end paren was found we're in trouble. 
273 if (!end) 
274 throw tScriptError(LineNumber, "Opening paren but no corresponding closing paren."); 
275 
276 // Creates a tString full of '\0's. 
277 tString tuple(int(end - start)); 
278 tStd::tStrncpy(tuple.Text(), start, int(end - start)); 
279 
280 // Now remove the '(', the ')', and any spaces. 
281 tuple.Remove('('); 
282 tuple.Remove(')'); 
283 tuple.Remove(' '); 
284 
285 return tuple; 
286} 
287 
288 
289tVector2 tExpression::GetAtomVector2() const 
290{ 
291 // Vectors should be of the form (x, y). Spaces or not after and before the comma. 
292 tString str = GetAtomTupleString(); 
293 tVector2 v; 
294 
295 tString component = str.ExtractLeft(','); 
296 v.x = component.GetAsFloat(); 
297 
298 v.y = str.GetAsFloat(); 
299 return v; 
300} 
301 
302 
303tVector3 tExpression::GetAtomVector3() const 
304{ 
305 // Vectors should be of the form (x, y, z). Spaces or not after and before the commas. 
306 tString str = GetAtomTupleString(); 
307 tVector3 v; 
308 
309 tString component = str.ExtractLeft(','); 
310 v.x = component.GetAsFloat(); 
311 
312 component = str.ExtractLeft(','); 
313 v.y = component.GetAsFloat(); 
314 
315 v.z = str.GetAsFloat(); 
316 return v; 
317} 
318 
319 
320tVector4 tExpression::GetAtomVector4() const 
321{ 
322 // Vectors should be of the form (x, y, z, w). Spaces or not after and before the commas. 
323 tString str = GetAtomTupleString(); 
324 tVector4 v; 
325 
326 tString component = str.ExtractLeft(','); 
327 v.x = component.GetAsFloat(); 
328 
329 component = str.ExtractLeft(','); 
330 v.y = component.GetAsFloat(); 
331 
332 component = str.ExtractLeft(','); 
333 v.z = component.GetAsFloat(); 
334 
335 v.w = str.GetAsFloat(); 
336 return v; 
337} 
338 
339 
340tQuaternion tExpression::GetAtomQuaternion() const 
341{ 
342 // Quaternions should be of the form (w, x, y, z). Spaces or not after and before the commas. 
343 tString str = GetAtomTupleString(); 
344 tQuaternion q; 
345 
346 tString component = str.ExtractLeft(','); 
347 q.x = component.GetAsFloat(); 
348 
349 component = str.ExtractLeft(','); 
350 q.y = component.GetAsFloat(); 
351 
352 component = str.ExtractLeft(','); 
353 q.z = component.GetAsFloat(); 
354 
355 q.w = str.GetAsFloat(); 
356 return q; 
357} 
358 
359 
360tMatrix2 tExpression::GetAtomMatrix2() const 
361{ 
362 // Matrices should be of the form (a11, a21, a12, a22). Spaces or not after and before the commas. 
363 tString str = GetAtomTupleString(); 
364 tMatrix2 m; 
365 
366 tString component = str.ExtractLeft(','); 
367 m.C1.x = component.GetAsFloat(); 
368 
369 component = str.ExtractLeft(','); 
370 m.C1.y = component.GetAsFloat(); 
371 
372 component = str.ExtractLeft(','); 
373 m.C2.x = component.GetAsFloat(); 
374 
375 m.C2.y = str.GetAsFloat(); 
376 return m; 
377} 
378 
379 
380tMatrix4 tExpression::GetAtomMatrix4() const 
381{ 
382 // Matrices should be of the form (a11, a21, a31, a41, a12, a22, ..., a44). 
383 // Spaces or not after and before the commas. 
384 tString str = GetAtomTupleString(); 
385 tMatrix4 m; 
386 
387 tString component = str.ExtractLeft(','); 
388 m.E[0] = component.GetAsFloat(); 
389 
390 for (int e = 1; e < 15; e++) 
391 { 
392 component = str.ExtractLeft(','); 
393 m.E[e] = component.GetAsFloat(); 
394 } 
395 
396 m.E[15] = str.GetAsFloat(); 
397 return m; 
398} 
399 
400 
401tColouri tExpression::GetAtomColour() const 
402{ 
403 // Colours should be of the form (r, g, b, a). Spaces or not after and before the commas. 
404 tString str = GetAtomTupleString(); 
405 tColouri c; 
406 
407 tString component = str.ExtractLeft(','); 
408 c.R = component.GetAsInt(); 
409 
410 component = str.ExtractLeft(','); 
411 c.G = component.GetAsInt(); 
412 
413 component = str.ExtractLeft(','); 
414 c.B = component.GetAsInt(); 
415 
416 c.A = str.GetAsInt(); 
417 return c; 
418} 
419 
420 
421const char* tExpression::EatWhiteAndComments(const char* c, int& lineCount) 
422{ 
423 // There are two types of comment. Single-line comments using a semi-colon go to the end of the current line. 
424 // Block (multi-line) comments are delimited with { and }. Note that { } are still allowed inside a string 
425 // such as "this { string" without being considered as begin or end comment markers. 
426 bool inSingleLineComment = false; 
427 int inMultiLineComment = 0; 
428 bool inString = false; 
429 
430 lineCount = 0; 
431 
432 while 
433 ( 
434 (*c == ' ') || (*c == '\t') || (*c == '\n') || (*c == '\r') || (*c == 9) || 
435 (*c == ';') || (*c == BCB) || (*c == BCE) || inSingleLineComment || inMultiLineComment 
436 ) 
437 { 
438 if ((*c == BCB) && !inSingleLineComment && !inString) 
439 inMultiLineComment++; 
440 
441 else if ((*c == BCE) && !inSingleLineComment && inMultiLineComment && !inString) 
442 inMultiLineComment--; 
443 
444 else if ((*c == ';') && !inMultiLineComment) 
445 inSingleLineComment = true; 
446 
447 else if (inSingleLineComment && ((*c == '\r') || (*c == '\n') || (*c == '\0'))) 
448 inSingleLineComment = false; 
449 
450 else if (inMultiLineComment && (*c == '"')) 
451 inString = !inString; 
452 
453 if (*c == '\n') 
454 lineCount++; 
455 
456 c++; 
457 } 
458 
459 return c; 
460} 
461 
462 
463tString tExpression::GetContext() const 
464{ 
465 tString context(ContextSize); 
466 
467 // We're allowed to do this because as soon as strncpy sees a 0 in the src string it stops reading from it. 
468 tStd::tStrncpy(context.Text(), ValueData, ContextSize); 
469 
470 int newLine = context.FindChar('\r'); 
471 
472 // Only go to the end of the current line. 
473 if (newLine != -1) 
474 context[newLine] = '\0'; 
475 
476 return context; 
477} 
478 
479 
480tScriptReader::tScriptReader(int argc, char** argv) : 
481 tExpression(), 
482 ReadBuffer(0) 
483{ 
484 // Here we're just concatenating all the argv strings into one. 
485 tString scriptString = "["; 
486 
487 for (int i = 0; i < argc; i++) 
488 { 
489 tString itemToAdd(argv[i]); 
490 if (itemToAdd.IsEmpty()) 
491 continue; 
492 
493 // OK, here we need to interpret the item as a string atom if there is a space. If there is a space, we may 
494 // need to add quotes if they aren't already there. Normally there won't be any spaces because spaces are 
495 // precisely what separates the argv arguments from each other. However, sometimes argv[0] can be a single 
496 // string something like "C:\Program File\SomeDir\SomeFile.txt" and it needs to be treated as a single item even 
497 // though it has a space. 
498 // 
499 // The reason we check for the double quotes is because it is perfectly ok for the command line to contain escaped 
500 // quotes, in which case the work of inserting them is already done. 
501 // 
502 // There is one last caveat: the command line: a.exe [b "c d"] will come in as: 'a.exe', '[b', 'c d]' because there 
503 // was no space after the last double quote. It turns out it is safe to ignore this if the atom does not have a 
504 // space, and if it does, we can simply replace the FIRST occurence of a ']' with a '"]' We don't need to handle 
505 // multiple close brackets and subsequent open brackets are also ok because the tScript syntax demands a space 
506 // between all arguments. Come to think of it we also need to deal with ["a b" c] going to '[a b' and 'c]' but 
507 // whatever, this is getting too complicated, I'm just going to require escaped quotes in the command line. 
508 if ((itemToAdd.FindChar(' ') != -1) && (itemToAdd[0] != '"') && (itemToAdd[itemToAdd.Length()-1] != '"')) 
509 { 
510 // Add a beginning quote. 
511 itemToAdd = "\"" + itemToAdd + "\""; 
512 } 
513 
514 scriptString += itemToAdd; 
515 
516 // Add a space if it's not the last argv string. 
517 if (i < argc-1) 
518 scriptString += " "; 
519 } 
520 
521 scriptString += "]"; 
522 
523 ReadBuffer = new char[scriptString.Length() + 1]; 
524 tStd::tStrcpy(ReadBuffer, scriptString); 
525 
526 ValueData = ReadBuffer; 
527 LineNumber = 1; 
528} 
529 
530 
531void tScriptReader::Load(const tString& name, bool isFile) 
532{ 
533 Clear(); 
534 if (name.IsEmpty()) 
535 return; 
536 
537 if (isFile) 
538 { 
539 tFileHandle file = tSystem::tOpenFile(name, "rb"); 
540 
541 // @todo Consider just becoming an invalid expression. 
542 tAssert(file); 
543 
544 int fileSize = tSystem::tGetFileSize(file); 
545 
546 // Create a buffer big enough for the file, the uber []'s, two line-endings (one for each square bracket), and a terminating 0. 
547 int bufferSize = fileSize + 7; 
548 
549 ReadBuffer = new char[bufferSize]; 
550 ReadBuffer[0] = '['; 
551 ReadBuffer[1] = '\r'; 
552 ReadBuffer[2] = '\n'; 
553 
554 // Load the entire thing into memory. 
555 int numRead = tSystem::tReadFile(file, (uint8*)(ReadBuffer+3), fileSize); 
556 if (numRead != fileSize) 
557 throw tScriptError("Cannot read file [%s].", name.Pod()); 
558 tSystem::tCloseFile(file); 
559 
560 ReadBuffer[bufferSize-4] = '\r'; 
561 ReadBuffer[bufferSize-3] = '\n'; 
562 ReadBuffer[bufferSize-2] = ']'; 
563 ReadBuffer[bufferSize-1] = '\0'; 
564 } 
565 else 
566 { 
567 int stringSize = name.Length(); 
568 int bufferSize = stringSize + 7; 
569 ReadBuffer = new char[bufferSize]; 
570 
571 ReadBuffer[0] = '['; 
572 ReadBuffer[1] = '\r'; 
573 ReadBuffer[2] = '\n'; 
574 tStd::tStrcpy(ReadBuffer+3, name); 
575 ReadBuffer[bufferSize-4] = '\r'; 
576 ReadBuffer[bufferSize-3] = '\n'; 
577 ReadBuffer[bufferSize-2] = ']'; 
578 ReadBuffer[bufferSize-1] = '\0'; 
579 } 
580 
581 LineNumber = 1; 
582 ValueData = EatWhiteAndComments(ReadBuffer, LineNumber); 
583} 
584 
585 
586tScriptWriter::tScriptWriter(const tString& filename) : 
587 CurrIndent(0) 
588{ 
589 ScriptFile = tSystem::tOpenFile(filename, "wt"); 
590 
591 if (!ScriptFile) 
592 throw tScriptError("Cannot open file [%s].", tPod(filename)); 
593} 
594 
595 
596void tScriptWriter::BeginExpression() 
597{ 
598 char beg[] = "[ "; 
599 int numWritten = tSystem::tWriteFile(ScriptFile, beg, 2); 
600 if (numWritten != 2) 
601 throw tScriptError("Cannot write to script file."); 
602} 
603 
604 
605void tScriptWriter::EndExpression() 
606{ 
607 char end[] = "] "; 
608 int numWritten = tSystem::tWriteFile(ScriptFile, end, 2); 
609 if (numWritten != 2) 
610 throw tScriptError("Cannot write to script file."); 
611} 
612 
613 
614void tScriptWriter::WriteAtom(const tString& atom) 
615{ 
616 char qu = '"'; 
617 
618 bool hasSpace = true; 
619 if (atom.FindChar(' ') == -1) 
620 hasSpace = false; 
621 
622 bool isTuple = true; 
623 if (atom.FindChar('(') == -1) 
624 isTuple = false; 
625 
626 // Here we determine whether to use quotes if necessary. If the atom is a tuple (a vector or matrix etc) then we do 
627 // not use quotes even if spaces are present. 
628 bool useQuotes = (hasSpace && !isTuple) || atom.IsEmpty(); 
629 int numWritten = 0; 
630 if (useQuotes) 
631 numWritten += tSystem::tWriteFile(ScriptFile, &qu, 1); 
632 
633 int atomLen = atom.Length(); 
634 numWritten += tSystem::tWriteFile(ScriptFile, atom, atomLen); 
635 if (useQuotes) 
636 numWritten += tSystem::tWriteFile(ScriptFile, &qu, 1); 
637 
638 char sp = ' '; 
639 numWritten += tSystem::tWriteFile(ScriptFile, &sp, 1); 
640 
641 if (numWritten != (1 + (useQuotes ? 2 : 0) + atomLen)) 
642 throw tScriptError("Cannot write atom [%s] to script file.", tPod(atom)); 
643} 
644 
645 
646void tScriptWriter::WriteAtom(const char* atom) 
647{ 
648 char qu = '"'; 
649 
650 bool hasSpace = false; 
651 if (tStd::tStrchr(atom, ' ')) 
652 hasSpace = true; 
653 
654 bool isTuple = false; 
655 if (tStd::tStrchr(atom, '(')) 
656 isTuple = true; 
657 
658 // Here we determine whether to use quotes if necessary. If the atom is a tuple (a vector or matrix etc) then we do 
659 // not use quotes even if spaces are present. 
660 bool useQuotes = hasSpace && !isTuple; 
661 int numWritten = 0; 
662 if (useQuotes) 
663 numWritten += tSystem::tWriteFile(ScriptFile, &qu, 1); 
664 
665 int atomLen = tStd::tStrlen(atom); 
666 numWritten += tSystem::tWriteFile(ScriptFile, atom, atomLen); 
667 if (useQuotes) 
668 numWritten += tSystem::tWriteFile(ScriptFile, &qu, 1); 
669 
670 char sp = ' '; 
671 numWritten += tSystem::tWriteFile(ScriptFile, &sp, 1); 
672 
673 if (numWritten != (1 + (useQuotes ? 2 : 0) + atomLen)) 
674 throw tScriptError("Cannot write atom '%s' to script file.", atom); 
675} 
676 
677 
678void tScriptWriter::WriteAtom(const bool atom) 
679{ 
680 if (atom) 
681 WriteAtom("True"); 
682 else 
683 WriteAtom("False"); 
684} 
685 
686 
687void tScriptWriter::WriteAtom(const uint32 atom) 
688{ 
689 char val[36]; 
690 tStd::tItoa(atom, val, 36, 10); 
691 WriteAtom(val); 
692} 
693 
694 
695void tScriptWriter::WriteAtom(const uint64 atom) 
696{ 
697 char val[48]; 
698 tStd::tItoa(atom, val, 48, 10); 
699 WriteAtom(val); 
700} 
701 
702 
703void tScriptWriter::WriteAtom(const int atom) 
704{ 
705 char val[36]; 
706 tStd::tItoa(atom, val, 36, 10); 
707 WriteAtom(val); 
708} 
709 
710 
711void tScriptWriter::WriteAtom(const float atom, bool incBitRep) 
712{ 
713 float f = atom; 
714 if (tStd::tIsSpecial(f)) 
715 f = 0.0f; 
716 
717 char val[64]; 
718 char* cval = val; 
719 cval += tsPrintf(cval, "%8.8f", f); 
720 
721 // Add a trailing 0 because it looks better. 
722 if (*(cval-1) == '.') 
723 { 
724 *cval++ = '0'; 
725 *cval = '\0'; 
726 } 
727 
728 if (incBitRep) 
729 cval += tsPrintf(cval, "#%08X", *((uint32*)&f)); 
730 
731 WriteAtom(val); 
732} 
733 
734 
735void tScriptWriter::WriteAtom(const double atom, bool incBitRep) 
736{ 
737 double d = atom; 
738 if (tStd::tIsSpecial(d)) 
739 d = 0.0; 
740 
741 char val[128]; 
742 char* cval = val; 
743 cval += tsPrintf(cval, "%16.16f", d); 
744 
745 // Add a trailing 0 because it looks better. 
746 if (*(cval-1) == '.') 
747 { 
748 *cval++ = '0'; 
749 *cval = '\0'; 
750 } 
751 
752 if (incBitRep) 
753 cval += tsPrintf(cval, "#%016|64X", *((uint64*)&d)); 
754 
755 WriteAtom(val); 
756} 
757 
758 
759void tScriptWriter::WriteAtom(const tVector2& v, bool incBitRep) 
760{ 
761 tString str("("); 
762 for (int e = 0; e < 2; e++) 
763 { 
764 float f = v.E[e]; 
765 if (tStd::tIsSpecial(f)) 
766 f = 0.0f; 
767 
768 char val[64]; 
769 char* cval = val; 
770 cval += tsPrintf(cval, "%8.8f", f); 
771 
772 // Add a trailing 0 because it looks better. 
773 if (*(cval-1) == '.') 
774 { 
775 *cval++ = '0'; 
776 *cval = '\0'; 
777 } 
778 if (incBitRep) 
779 cval += tsPrintf(cval, "#%08X", *((uint32*)&f)); 
780 
781 str += tString(val); 
782 if (e != 1) 
783 str += ", "; 
784 } 
785 str += ")"; 
786 WriteAtom(str); 
787} 
788 
789 
790void tScriptWriter::WriteAtom(const tVector3& v, bool incBitRep) 
791{ 
792 tString str("("); 
793 for (int e = 0; e < 3; e++) 
794 { 
795 float f = v.E[e]; 
796 if (tStd::tIsSpecial(f)) 
797 f = 0.0f; 
798 
799 char val[64]; 
800 char* cval = val; 
801 cval += tsPrintf(cval, "%8.8f", f); 
802 
803 // Add a trailing 0 because it looks better. 
804 if (*(cval-1) == '.') 
805 { 
806 *cval++ = '0'; 
807 *cval = '\0'; 
808 } 
809 if (incBitRep) 
810 cval += tsPrintf(cval, "#%08X", *((uint32*)&f)); 
811 
812 str += tString(val); 
813 if (e != 2) 
814 str += ", "; 
815 } 
816 str += ")"; 
817 WriteAtom(str); 
818} 
819 
820 
821void tScriptWriter::WriteAtom(const tVector4& v, bool incBitRep) 
822{ 
823 tString str("("); 
824 for (int e = 0; e < 4; e++) 
825 { 
826 float f = v.E[e]; 
827 if (tStd::tIsSpecial(f)) 
828 f = 0.0f; 
829 
830 char val[64]; 
831 char* cval = val; 
832 cval += tsPrintf(cval, "%8.8f", f); 
833 
834 // Add a trailing 0 because it looks better. 
835 if (*(cval-1) == '.') 
836 { 
837 *cval++ = '0'; 
838 *cval = '\0'; 
839 } 
840 if (incBitRep) 
841 cval += tsPrintf(cval, "#%08X", *((uint32*)&f)); 
842 
843 str += tString(val); 
844 if (e != 3) 
845 str += ", "; 
846 } 
847 str += ")"; 
848 WriteAtom(str); 
849} 
850 
851 
852void tScriptWriter::WriteAtom(const tQuaternion& q, bool incBitRep) 
853{ 
854 tString str("("); 
855 for (int e = 0; e < 4; e++) 
856 { 
857 float f = q.E[e]; 
858 if (tStd::tIsSpecial(f)) 
859 f = 0.0f; 
860 
861 char val[64]; 
862 char* cval = val; 
863 cval += tsPrintf(cval, "%8.8f", f); 
864 
865 // Add a trailing 0 because it looks better. 
866 if (*(cval-1) == '.') 
867 { 
868 *cval++ = '0'; 
869 *cval = '\0'; 
870 } 
871 if (incBitRep) 
872 cval += tsPrintf(cval, "#%08X", *((uint32*)&f)); 
873 
874 str += tString(val); 
875 if (e != 3) 
876 str += ", "; 
877 } 
878 str += ")"; 
879 WriteAtom(str); 
880} 
881 
882 
883void tScriptWriter::WriteAtom(const tMatrix2& m, bool incBitRep) 
884{ 
885 tString str("("); 
886 for (int e = 0; e < 4; e++) 
887 { 
888 float f = m.E[e]; 
889 if (tStd::tIsSpecial(f)) 
890 f = 0.0f; 
891 
892 char val[64]; 
893 char* cval = val; 
894 cval += tsPrintf(cval, "%8.8f", f); 
895 
896 // Add a trailing 0 because it looks better. 
897 if (*(cval-1) == '.') 
898 { 
899 *cval++ = '0'; 
900 *cval = '\0'; 
901 } 
902 if (incBitRep) 
903 cval += tsPrintf(cval, "#%08X", *((uint32*)&f)); 
904 
905 str += tString(val); 
906 if (e != 3) 
907 str += ", "; 
908 } 
909 str += ")"; 
910 WriteAtom(str); 
911} 
912 
913 
914void tScriptWriter::WriteAtom(const tMatrix4& m, bool incBitRep) 
915{ 
916 tString str("("); 
917 for (int e = 0; e < 16; e++) 
918 { 
919 float f = m.E[e]; 
920 if (tStd::tIsSpecial(f)) 
921 f = 0.0f; 
922 
923 char val[64]; 
924 char* cval = val; 
925 cval += tsPrintf(cval, "%8.8f", f); 
926 
927 // Add a trailing 0 because it looks better. 
928 if (*(cval-1) == '.') 
929 { 
930 *cval++ = '0'; 
931 *cval = '\0'; 
932 } 
933 if (incBitRep) 
934 cval += tsPrintf(cval, "#%08X", *((uint32*)&f)); 
935 
936 str += tString(val); 
937 if (e != 15) 
938 str += ", "; 
939 } 
940 str += ")"; 
941 WriteAtom(str); 
942} 
943 
944 
945void tScriptWriter::WriteAtom(const tColouri& c) 
946{ 
947 tString str("("); 
948 for (int e = 0; e < 4; e++) 
949 { 
950 char val[36]; 
951 tStd::tItoa(c.E[e], val, 36, 10); 
952 
953 str += val; 
954 if (e != 3) 
955 str += ", "; 
956 } 
957 str += ")"; 
958 WriteAtom(str); 
959} 
960 
961 
962void tScriptWriter::WriteComment(const char* comment) 
963{ 
964 char sc[] = "; "; 
965 int numWritten = tSystem::tWriteFile(ScriptFile, sc, 2); 
966 int commentLen = 0; 
967 if (comment) 
968 { 
969 commentLen = tStd::tStrlen(comment); 
970 numWritten += tSystem::tWriteFile(ScriptFile, comment, commentLen); 
971 } 
972 
973 if (numWritten != (2 + commentLen)) 
974 throw tScriptError("Cannot write to script file."); 
975 
976 NewLine(); 
977} 
978 
979 
980void tScriptWriter::WriteCommentBegin() 
981{ 
982 char sc[] = "<\n"; 
983 sc[0] = BCB; 
984 int numWritten = tSystem::tWriteFile(ScriptFile, sc, 2); 
985 if (numWritten != 2) 
986 throw tScriptError("Cannot write to script file."); 
987} 
988 
989 
990void tScriptWriter::WriteCommentLine(const char* comment) 
991{ 
992 int numWritten = 0; 
993 int commentLen = 0; 
994 if (comment) 
995 { 
996 commentLen = tStd::tStrlen(comment); 
997 numWritten += tSystem::tWriteFile(ScriptFile, comment, commentLen); 
998 } 
999 
1000 if (numWritten != commentLen) 
1001 throw tScriptError("Cannot write to script file."); 
1002 
1003 NewLine(); 
1004} 
1005 
1006 
1007void tScriptWriter::WriteCommentEnd() 
1008{ 
1009 char sc[] = ">\n"; 
1010 sc[0] = BCE; 
1011 int numWritten = tSystem::tWriteFile(ScriptFile, sc, 2); 
1012 if (numWritten != 2) 
1013 throw tScriptError("Cannot write to script file."); 
1014} 
1015 
1016 
1017void tScriptWriter::NewLine() 
1018{ 
1019 char nl = '\n'; 
1020 char sp = ' '; 
1021 
1022 int numWritten = tSystem::tWriteFile(ScriptFile, &nl, 1); 
1023 for (int s = 0; s < CurrIndent; s++) 
1024 numWritten += tSystem::tWriteFile(ScriptFile, &sp, 1); 
1025 
1026 if (numWritten != CurrIndent + 1) 
1027 throw tScriptError("Cannot write to script file."); 
1028} 
1029 
1030 
1031// Next follow the types for the functional scripts of the form f(a, b). 
1032tFunExtression::tFunExtression(const char* function) 
1033{ 
1034 const int maxExpressionSize = 512; 
1035 
1036 tString buffer(maxExpressionSize); 
1037 tStd::tStrncpy(buffer.Text(), function, maxExpressionSize); 
1038 buffer[maxExpressionSize-1] = '\0'; 
1039 
1040 // We need to find the trailing ')' that is at the end of our expression. We do this by incrementing and decrementing 
1041 // based on '(' or ')' 
1042 int beginningParen = buffer.FindChar('('); 
1043 int endParen = beginningParen; 
1044 int openCount = 0; 
1045 
1046 for (int c = 0; c < maxExpressionSize-1; c++) 
1047 { 
1048 if (buffer[endParen] == '(') 
1049 openCount++; 
1050 
1051 if (buffer[endParen] == ')') 
1052 openCount--; 
1053 
1054 if (!openCount) 
1055 break; 
1056 
1057 endParen++; 
1058 } 
1059 
1060 if (openCount) 
1061 throw tScriptError("Expression too long. Missing bracket? Max size is %d chars. Look for [%s]", maxExpressionSize, buffer.Pod()); 
1062 
1063 buffer[endParen] = '\0'; 
1064 int origLength = buffer.Length(); 
1065 buffer[beginningParen] = '\0'; 
1066 
1067 // Now replace all occurences of space, tab, and comma with nulls. Double quote string arguments are handled correctly. 
1068 bool inString = false; 
1069 openCount = 0; 
1070 for (int c = 0; c < origLength; c++) 
1071 { 
1072 int ch = buffer[c]; 
1073 if (ch == '"') 
1074 { 
1075 inString = !inString; 
1076 buffer[c] = '\0'; 
1077 } 
1078 
1079 if (ch == '(') 
1080 openCount++; 
1081 
1082 if (ch == ')') 
1083 openCount--; 
1084 
1085 if ((!inString) && (!openCount)) 
1086 { 
1087 if ((ch == '\t') || (ch == ',') || (ch == '\n') || (ch == '\r') || (ch == 9) || (ch == ' ')) 
1088 buffer[c] = '\0'; 
1089 } 
1090 } 
1091 
1092 // We now have a string of the form: name00arga000argb0argc00. The function name is the first word. 
1093 Function = buffer; 
1094 int pos = Function.Length(); 
1095 
1096 // Now lets get arguments until there are no more. 
1097 while (1) 
1098 { 
1099 while ((buffer[pos] == '\0') && (pos < endParen)) 
1100 pos++; 
1101 
1102 if (pos == endParen) 
1103 break; 
1104 
1105 tStringItem* arg = new tStringItem( &buffer[pos] ); 
1106 Arguments.Append(arg); 
1107 pos += arg->Length(); 
1108 } 
1109} 
1110 
1111 
1112void tFunScript::Load(const tString& fileName) 
1113{ 
1114 Clear(); 
1115 tFileHandle file = tSystem::tOpenFile(fileName.ConstText() , "rb"); 
1116 tAssert(file); 
1117 
1118 // Create a buffer big enough for the file. 
1119 int fileSize = tSystem::tGetFileSize(file); 
1120 char* buffer = new char[fileSize + 1]; 
1121 
1122 // Load the entire thing into memory. 
1123 int numRead = tSystem::tReadFile(file, (uint8*)buffer, fileSize); 
1124 tAssert(numRead == fileSize); 
1125 
1126 // This makes buffer a valid null terminated string. 
1127 buffer[fileSize] = '\0'; 
1128 tSystem::tCloseFile(file); 
1129 
1130 char* currChar = EatWhiteAndComments(buffer); 
1131 while (*currChar != '\0') 
1132 { 
1133 Expressions.Append(new tFunExtression(currChar)); 
1134 
1135 // Get to the next expression. 
1136 currChar = tStd::tStrchr(currChar, '('); 
1137 int openCount = 0; 
1138 while (1) 
1139 { 
1140 if (*currChar == '(') 
1141 openCount++; 
1142 
1143 if (*currChar == ')') 
1144 openCount--; 
1145 
1146 currChar++; 
1147 if (!openCount) 
1148 break; 
1149 } 
1150 
1151 currChar = EatWhiteAndComments(currChar); 
1152 } 
1153 
1154 delete[] buffer; 
1155} 
1156 
1157 
1158void tFunScript::Save(const tString& fileName) 
1159{ 
1160 tFileHandle file = tSystem::tOpenFile(fileName, "wt"); 
1161 
1162 if (!file) 
1163 throw tScriptError("Cannot open file '%s'.", fileName.ConstText()); 
1164 
1165 // All we need to do is traverse the expression list and write out each one. 
1166 for (tFunExtression* exp = Expressions.First(); exp; exp = exp->Next()) 
1167 { 
1168 tfPrintf(file, "%s(", exp->Function.Text()); 
1169 for (tStringItem* arg = exp->Arguments.First(); arg; arg = arg->Next()) 
1170 { 
1171 tfPrintf(file, "%s", arg->Text()); 
1172 
1173 // If it's not the last argument we put a comma there. 
1174 if (arg->Next()) 
1175 tfPrintf(file, ", "); 
1176 } 
1177 tfPrintf(file, ")\n"); 
1178 } 
1179 
1180 tSystem::tCloseFile(file); 
1181} 
1182 
1183 
1184char* tFunScript::EatWhiteAndComments(char* c) 
1185{ 
1186 bool inComment = false; 
1187 while ((*c == ' ') || (*c == '\t') || (*c == '\n') || (*c == '\r') || (*c == 9) || (*c == '/') || inComment) 
1188 { 
1189 if (*c == '/') 
1190 inComment = true; 
1191 
1192 if ((*c == '\r') || (*c == '\n') || (*c == '\0')) 
1193 inComment = false; 
1194 
1195 c++; 
1196 } 
1197 
1198 return c; 
1199} 
1200