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