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, 2022-2024 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 char8_t* c = ExprData + 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 char8_t* c = ExprData
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(s: c, 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(s: c, 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 (ExprData && (*ExprData != '[')) 
174 return true
175 else 
176 return false
177
178 
179 
180tString tExpression::GetExpressionString() const 
181
182 tAssert( IsValid() ); 
183 if (!ExprData
184 return tString(); 
185 
186 if (IsAtom()) 
187 return GetAtomString(); 
188 
189 tAssert(*ExprData == '['); 
190 const char8_t* start = ExprData
191 const char8_t* 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. Note that 'end' is one passed the ending ']' so numChars is correct. 
207 int numChars = int(end - start); 
208 tString estr(numChars); 
209 tStd::tMemcpy(dest: estr.Text(), src: start, numBytes: numChars); 
210 return estr
211
212 
213 
214tString tExpression::GetAtomString() const 
215
216 if (!IsAtom()) 
217 throw tScriptError(LineNumber, "Atom expected near: %s", GetContext().Pod()); 
218 
219 const char8_t* start
220 const char8_t* end
221 if (*ExprData == '"'
222
223 start = ExprData + 1
224 end = tStd::tStrchr(s: start, c: '"'); 
225 
226 // Keep going until next quote. 
227 if (!end
228 throw tScriptError(LineNumber, "Begin quote found but no end quote."); 
229
230 
231 // It may be a tuple atom in form (a, b, c) 
232 else if (*ExprData == '('
233
234 start = ExprData
235 end = tStd::tStrchr(s: start, c: ')'); 
236 
237 // Keep going until next paren. 
238 if (end
239 end++; 
240 else 
241 throw tScriptError(LineNumber, "Begin paren found but no end paren."); 
242
243 else 
244
245 start = ExprData
246 end = ExprData
247 while 
248
249 (*end != ' ') && (*end != '\t') && (*end != '[') && (*end != ']') && (*end != '\0') && 
250 (*end != '\r') && (*end != '\n') && (*end != ';') && (*end != BCB) && (*end != '"'
251 ) end++; 
252
253 
254 // Creates a tString full of '\0's. End is one bigger so we get the right numChars. 
255 int numChars = int(end - start); 
256 tString atom(numChars); 
257 tStd::tStrncpy(dst: atom.Text(), src: start, n: numChars); 
258 
259 return atom
260
261 
262 
263tString tExpression::GetAtomTupleString() const 
264
265 if (!IsAtom()) 
266 throw tScriptError(LineNumber, "Atom expected near: %s", GetContext().Pod()); 
267 
268 if (*ExprData != '('
269 throw tScriptError(LineNumber, "Tuple atom expected near: %s", GetContext().Pod()); 
270 
271 const char8_t* start = ExprData + 1
272 const char8_t* end = tStd::tStrchr(s: start, c: ')'); 
273 
274 // If no end paren was found we're in trouble. 
275 if (!end
276 throw tScriptError(LineNumber, "Opening paren but no corresponding closing paren."); 
277 
278 // Creates a tString full of '\0's. 
279 int numChars = int(end - start); 
280 tString tuple(numChars); 
281 tStd::tStrncpy(dst: tuple.Text(), src: start, n: numChars); 
282 
283 // Now remove the '(', the ')', and any spaces. 
284 tuple.RemoveAny(theseChars: "() "); 
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(divider: ','); 
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(divider: ','); 
310 v.x = component.GetAsFloat(); 
311 
312 component = str.ExtractLeft(divider: ','); 
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(divider: ','); 
327 v.x = component.GetAsFloat(); 
328 
329 component = str.ExtractLeft(divider: ','); 
330 v.y = component.GetAsFloat(); 
331 
332 component = str.ExtractLeft(divider: ','); 
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(divider: ','); 
347 q.x = component.GetAsFloat(); 
348 
349 component = str.ExtractLeft(divider: ','); 
350 q.y = component.GetAsFloat(); 
351 
352 component = str.ExtractLeft(divider: ','); 
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(divider: ','); 
367 m.C1.x = component.GetAsFloat(); 
368 
369 component = str.ExtractLeft(divider: ','); 
370 m.C1.y = component.GetAsFloat(); 
371 
372 component = str.ExtractLeft(divider: ','); 
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(divider: ','); 
388 m.E[0] = component.GetAsFloat(); 
389 
390 for (int e = 1; e < 15; e++) 
391
392 component = str.ExtractLeft(divider: ','); 
393 m.E[e] = component.GetAsFloat(); 
394
395 
396 m.E[15] = str.GetAsFloat(); 
397 return m
398
399 
400 
401tColour4b 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 tColour4b c
406 
407 tString component = str.ExtractLeft(divider: ','); 
408 c.R = component.GetAsInt(); 
409 
410 component = str.ExtractLeft(divider: ','); 
411 c.G = component.GetAsInt(); 
412 
413 component = str.ExtractLeft(divider: ','); 
414 c.B = component.GetAsInt(); 
415 
416 c.A = str.GetAsInt(); 
417 return c
418
419 
420 
421const char8_t* tExpression::EatWhiteAndComments(const char8_t* 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(dst: context.Text(), src: ExprData, n: ContextSize); 
469 context.SetLength( length: tStd::tStrlen(s: context.Text()) ); 
470 
471 int newLine = context.FindChar(c: '\r'); 
472 
473 // Only go to the end of the current line. The set-length will end up removing the '\r' cuz it's one less. 
474 if (newLine != -1
475 context.SetLength(length: newLine); 
476 
477 return context
478
479 
480 
481tExprReader::tExprReader(int argc, char** argv) : 
482 tExpression(), 
483 ExprBuffer(nullptr
484
485 // Here we're just concatenating all the argv strings into one. 
486 tString scriptString = "["
487 
488 for (int i = 0; i < argc; i++) 
489
490 tString itemToAdd(argv[i]); 
491 if (itemToAdd.IsEmpty()) 
492 continue
493 
494 // OK, here we need to interpret the item as a string atom if there is a space. If there is a space, we may 
495 // need to add quotes if they aren't already there. Normally there won't be any spaces because spaces are 
496 // precisely what separates the argv arguments from each other. However, sometimes argv[0] can be a single 
497 // string something like "C:\Program File\SomeDir\SomeFile.txt" and it needs to be treated as a single item even 
498 // though it has a space. 
499 // 
500 // The reason we check for the double quotes is because it is perfectly ok for the command line to contain escaped 
501 // quotes, in which case the work of inserting them is already done. 
502 // 
503 // There is one last caveat: the command line: a.exe [b "c d"] will come in as: 'a.exe', '[b', 'c d]' because there 
504 // was no space after the last double quote. It turns out it is safe to ignore this if the atom does not have a 
505 // space, and if it does, we can simply replace the FIRST occurence of a ']' with a '"]' We don't need to handle 
506 // multiple close brackets and subsequent open brackets are also ok because the tScript syntax demands a space 
507 // between all arguments. Come to think of it we also need to deal with ["a b" c] going to '[a b' and 'c]' but 
508 // whatever, this is getting too complicated, I'm just going to require escaped quotes in the command line. 
509 if ((itemToAdd.FindChar(c: ' ') != -1) && (itemToAdd[0] != '"') && (itemToAdd[itemToAdd.Length()-1] != '"')) 
510
511 // Add a beginning quote. 
512 itemToAdd = "\"" + itemToAdd + "\""
513
514 
515 scriptString += itemToAdd
516 
517 // Add a space if it's not the last argv string. 
518 if (i < argc-1
519 scriptString += " "
520
521 
522 scriptString += "]"
523 
524 ExprBuffer = new char8_t[scriptString.Length() + 1]; 
525 tStd::tStrcpy(dst: ExprBuffer, src: scriptString); 
526 
527 ExprData = ExprBuffer
528 LineNumber = 1
529
530 
531 
532void tExprReader::Load(const tString& name, bool isFile
533
534 Clear(); 
535 if (name.IsEmpty()) 
536 return
537 
538 if (isFile
539
540 tFileHandle file = tSystem::tOpenFile(file: name, mode: "rb"); 
541 
542 // @todo Consider just becoming an invalid expression. 
543 tAssert(file); 
544 
545 int fileSize = tSystem::tGetFileSize(file); 
546 
547 // Create a buffer big enough for the file, the uber []'s, two line-endings (one for each square bracket), and a terminating 0. 
548 int bufferSize = fileSize + 7
549 
550 ExprBuffer = new char8_t[bufferSize]; 
551 ExprBuffer[0] = '['
552 ExprBuffer[1] = '\r'
553 ExprBuffer[2] = '\n'
554 
555 // Load the entire thing into memory. 
556 int numRead = tSystem::tReadFile(handle: file, buffer: (uint8*)(ExprBuffer+3), sizeBytes: fileSize); 
557 if (numRead != fileSize
558 throw tScriptError("Cannot read file [%s].", name.Pod()); 
559 tSystem::tCloseFile(f: file); 
560 
561 ExprBuffer[bufferSize-4] = '\r'
562 ExprBuffer[bufferSize-3] = '\n'
563 ExprBuffer[bufferSize-2] = ']'
564 ExprBuffer[bufferSize-1] = '\0'
565
566 else 
567
568 int stringSize = name.Length(); 
569 int bufferSize = stringSize + 7
570 ExprBuffer = new char8_t[bufferSize]; 
571 
572 ExprBuffer[0] = '['
573 ExprBuffer[1] = '\r'
574 ExprBuffer[2] = '\n'
575 tStd::tStrcpy(dst: ExprBuffer+3, src: name); 
576 ExprBuffer[bufferSize-4] = '\r'
577 ExprBuffer[bufferSize-3] = '\n'
578 ExprBuffer[bufferSize-2] = ']'
579 ExprBuffer[bufferSize-1] = '\0'
580
581 
582 LineNumber = 1
583 ExprData = EatWhiteAndComments(c: ExprBuffer, lineCount&: LineNumber); 
584
585 
586 
587tExprWriter::tExprWriter(const tString& filename) : 
588 CurrIndent(0), 
589 TabWidth(0
590
591 ExprFile = tSystem::tOpenFile(file: filename, mode: "wt"); 
592 
593 if (!ExprFile
594 throw tScriptError("Cannot open file [%s].", tPod(filename)); 
595
596 
597 
598void tExprWriter::BeginExpression() 
599
600 char beg[] = "[ "
601 int numWritten = tSystem::tWriteFile(handle: ExprFile, buffer: beg, sizeBytes: 2); 
602 if (numWritten != 2
603 throw tScriptError("Cannot write to script file."); 
604
605 
606 
607void tExprWriter::EndExpression() 
608
609 char end[] = "] "
610 int numWritten = tSystem::tWriteFile(handle: ExprFile, buffer: end, sizeBytes: 2); 
611 if (numWritten != 2
612 throw tScriptError("Cannot write to script file."); 
613
614 
615 
616void tExprWriter::WriteAtom(const tString& atom
617
618 char qu = '"'
619 
620 bool hasSpace = true
621 if (atom.FindChar(c: ' ') == -1
622 hasSpace = false
623 
624 // Here we determine whether to use quotes. If the atom has a space, we need them. 
625 bool useQuotes = hasSpace || atom.IsEmpty(); 
626 int numWritten = 0
627 if (useQuotes
628 numWritten += tSystem::tWriteFile(handle: ExprFile, buffer: &qu, sizeBytes: 1); 
629 
630 int atomLen = atom.Length(); 
631 numWritten += tSystem::tWriteFile(handle: ExprFile, buffer: atom, length: atomLen); 
632 if (useQuotes
633 numWritten += tSystem::tWriteFile(handle: ExprFile, buffer: &qu, sizeBytes: 1); 
634 
635 char sp = ' '
636 numWritten += tSystem::tWriteFile(handle: ExprFile, buffer: &sp, sizeBytes: 1); 
637 
638 if (numWritten != (1 + (useQuotes ? 2 : 0) + atomLen)) 
639 throw tScriptError("Cannot write atom [%s] to script file.", tPod(atom)); 
640
641 
642 
643void tExprWriter::WriteAtom(const char* atom
644
645 char qu = '"'
646 
647 bool hasSpace = false
648 if (tStd::tStrchr(s: atom, c: ' ')) 
649 hasSpace = true
650 
651 // Here we determine whether to use quotes if necessary. If the atom is a tuple (a vector or matrix etc) then we do 
652 // not use quotes even if spaces are present. 
653 bool useQuotes = hasSpace
654 int numWritten = 0
655 if (useQuotes
656 numWritten += tSystem::tWriteFile(handle: ExprFile, buffer: &qu, sizeBytes: 1); 
657 
658 int atomLen = tStd::tStrlen(s: atom); 
659 numWritten += tSystem::tWriteFile(handle: ExprFile, buffer: atom, sizeBytes: atomLen); 
660 if (useQuotes
661 numWritten += tSystem::tWriteFile(handle: ExprFile, buffer: &qu, sizeBytes: 1); 
662 
663 char sp = ' '
664 numWritten += tSystem::tWriteFile(handle: ExprFile, buffer: &sp, sizeBytes: 1); 
665 
666 if (numWritten != (1 + (useQuotes ? 2 : 0) + atomLen)) 
667 throw tScriptError("Cannot write atom '%s' to script file.", atom); 
668
669 
670 
671void tExprWriter::WriteRaw(const tString& atom
672
673 int numWritten = 0
674 
675 int atomLen = atom.Length(); 
676 numWritten += tSystem::tWriteFile(handle: ExprFile, buffer: atom, length: atomLen); 
677 
678 char sp = ' '
679 numWritten += tSystem::tWriteFile(handle: ExprFile, buffer: &sp, sizeBytes: 1); 
680 
681 if (numWritten != (1 + atomLen)) 
682 throw tScriptError("Cannot write atom [%s] to script file.", tPod(atom)); 
683
684 
685 
686void tExprWriter::WriteRaw(const char* atom
687
688 int numWritten = 0
689 
690 int atomLen = tStd::tStrlen(s: atom); 
691 numWritten += tSystem::tWriteFile(handle: ExprFile, buffer: atom, sizeBytes: atomLen); 
692 
693 char sp = ' '
694 numWritten += tSystem::tWriteFile(handle: ExprFile, buffer: &sp, sizeBytes: 1); 
695 
696 if (numWritten != (1 + atomLen)) 
697 throw tScriptError("Cannot write atom '%s' to script file.", atom); 
698
699 
700 
701void tExprWriter::WriteAtom(const bool atom
702
703 if (atom
704 WriteAtom(atom: "True"); 
705 else 
706 WriteAtom(atom: "False"); 
707
708 
709 
710void tExprWriter::WriteAtom(const uint32 atom
711
712 char val[36]; 
713 tStd::tItoa(str: val, strSize: 36, value: atom, base: 10); 
714 WriteAtom(atom: val); 
715
716 
717 
718void tExprWriter::WriteAtom(const uint64 atom
719
720 char val[48]; 
721 tStd::tItoa(str: val, strSize: 48, value: atom, base: 10); 
722 WriteAtom(atom: val); 
723
724 
725 
726void tExprWriter::WriteAtom(const int atom
727
728 char val[36]; 
729 tStd::tItoa(str: val, strSize: 36, value: atom, base: 10); 
730 WriteAtom(atom: val); 
731
732 
733 
734void tExprWriter::WriteAtom(const float atom, bool incBitRep
735
736 tString str
737 tSystem::tFtostr(dest&: str, value: atom, incBitRep); 
738 WriteAtom(atom: str); 
739
740 
741 
742void tExprWriter::WriteAtom(const double atom, bool incBitRep
743
744 tString str
745 tSystem::tDtostr(dest&: str, value: atom, incBitRep); 
746 WriteAtom(atom: str); 
747
748 
749 
750void tExprWriter::WriteAtom(const tVector2& v, bool incBitRep
751
752 tString str("("); 
753 for (int e = 0; e < 2; e++) 
754
755 float f = v.E[e]; 
756 if (tStd::tIsSpecial(v: f)) 
757 f = 0.0f
758 
759 char val[64]; 
760 char* cval = val
761 cval += tsPrintf(dest: cval, format: "%8.8f", f); 
762 
763 // Add a trailing 0 because it looks better. 
764 if (*(cval-1) == '.'
765
766 *cval++ = '0'
767 *cval = '\0'
768
769 if (incBitRep
770 cval += tsPrintf(dest: cval, format: "#%08X", *((uint32*)&f)); 
771 
772 str += tString(val); 
773 if (e != 1
774 str += ", "
775
776 str += ")"
777 WriteRaw(atom: str); 
778
779 
780 
781void tExprWriter::WriteAtom(const tVector3& v, bool incBitRep
782
783 tString str("("); 
784 for (int e = 0; e < 3; e++) 
785
786 float f = v.E[e]; 
787 if (tStd::tIsSpecial(v: f)) 
788 f = 0.0f
789 
790 char val[64]; 
791 char* cval = val
792 cval += tsPrintf(dest: cval, format: "%8.8f", f); 
793 
794 // Add a trailing 0 because it looks better. 
795 if (*(cval-1) == '.'
796
797 *cval++ = '0'
798 *cval = '\0'
799
800 if (incBitRep
801 cval += tsPrintf(dest: cval, format: "#%08X", *((uint32*)&f)); 
802 
803 str += tString(val); 
804 if (e != 2
805 str += ", "
806
807 str += ")"
808 WriteRaw(atom: str); 
809
810 
811 
812void tExprWriter::WriteAtom(const tVector4& v, bool incBitRep
813
814 tString str("("); 
815 for (int e = 0; e < 4; e++) 
816
817 float f = v.E[e]; 
818 if (tStd::tIsSpecial(v: f)) 
819 f = 0.0f
820 
821 char val[64]; 
822 char* cval = val
823 cval += tsPrintf(dest: cval, format: "%8.8f", f); 
824 
825 // Add a trailing 0 because it looks better. 
826 if (*(cval-1) == '.'
827
828 *cval++ = '0'
829 *cval = '\0'
830
831 if (incBitRep
832 cval += tsPrintf(dest: cval, format: "#%08X", *((uint32*)&f)); 
833 
834 str += tString(val); 
835 if (e != 3
836 str += ", "
837
838 str += ")"
839 WriteRaw(atom: str); 
840
841 
842 
843void tExprWriter::WriteAtom(const tQuaternion& q, bool incBitRep
844
845 tString str("("); 
846 for (int e = 0; e < 4; e++) 
847
848 float f = q.E[e]; 
849 if (tStd::tIsSpecial(v: f)) 
850 f = 0.0f
851 
852 char val[64]; 
853 char* cval = val
854 cval += tsPrintf(dest: cval, format: "%8.8f", f); 
855 
856 // Add a trailing 0 because it looks better. 
857 if (*(cval-1) == '.'
858
859 *cval++ = '0'
860 *cval = '\0'
861
862 if (incBitRep
863 cval += tsPrintf(dest: cval, format: "#%08X", *((uint32*)&f)); 
864 
865 str += tString(val); 
866 if (e != 3
867 str += ", "
868
869 str += ")"
870 WriteRaw(atom: str); 
871
872 
873 
874void tExprWriter::WriteAtom(const tMatrix2& m, bool incBitRep
875
876 tString str("("); 
877 for (int e = 0; e < 4; e++) 
878
879 float f = m.E[e]; 
880 if (tStd::tIsSpecial(v: f)) 
881 f = 0.0f
882 
883 char val[64]; 
884 char* cval = val
885 cval += tsPrintf(dest: cval, format: "%8.8f", f); 
886 
887 // Add a trailing 0 because it looks better. 
888 if (*(cval-1) == '.'
889
890 *cval++ = '0'
891 *cval = '\0'
892
893 if (incBitRep
894 cval += tsPrintf(dest: cval, format: "#%08X", *((uint32*)&f)); 
895 
896 str += tString(val); 
897 if (e != 3
898 str += ", "
899
900 str += ")"
901 WriteRaw(atom: str); 
902
903 
904 
905void tExprWriter::WriteAtom(const tMatrix4& m, bool incBitRep
906
907 tString str("("); 
908 for (int e = 0; e < 16; e++) 
909
910 float f = m.E[e]; 
911 if (tStd::tIsSpecial(v: f)) 
912 f = 0.0f
913 
914 char val[64]; 
915 char* cval = val
916 cval += tsPrintf(dest: cval, format: "%8.8f", f); 
917 
918 // Add a trailing 0 because it looks better. 
919 if (*(cval-1) == '.'
920
921 *cval++ = '0'
922 *cval = '\0'
923
924 if (incBitRep
925 cval += tsPrintf(dest: cval, format: "#%08X", *((uint32*)&f)); 
926 
927 str += tString(val); 
928 if (e != 15
929 str += ", "
930
931 str += ")"
932 WriteRaw(atom: str); 
933
934 
935 
936void tExprWriter::WriteAtom(const tColour4b& c
937
938 tString str("("); 
939 for (int e = 0; e < 4; e++) 
940
941 char val[36]; 
942 tStd::tItoa(str: val, strSize: 36, value: c.E[e], base: 10); 
943 
944 str += val
945 if (e != 3
946 str += ", "
947
948 str += ")"
949 WriteRaw(atom: str); 
950
951 
952 
953void tExprWriter::WriteComment(const char* comment
954
955 char sc[] = "; "
956 int numWritten = tSystem::tWriteFile(handle: ExprFile, buffer: sc, sizeBytes: 2); 
957 int commentLen = 0
958 if (comment
959
960 commentLen = tStd::tStrlen(s: comment); 
961 numWritten += tSystem::tWriteFile(handle: ExprFile, buffer: comment, sizeBytes: commentLen); 
962
963 
964 if (numWritten != (2 + commentLen)) 
965 throw tScriptError("Cannot write to script file."); 
966 
967 NewLine(); 
968
969 
970 
971void tExprWriter::WriteCommentLine(const char* comment
972
973 int numWritten = 0
974 int commentLen = 0
975 if (comment
976
977 commentLen = tStd::tStrlen(s: comment); 
978 numWritten += tSystem::tWriteFile(handle: ExprFile, buffer: comment, sizeBytes: commentLen); 
979
980 
981 if (numWritten != commentLen
982 throw tScriptError("Cannot write to script file."); 
983 
984 NewLine(); 
985
986 
987 
988void tExprWriter::WriteCommentEnd() 
989
990 char sc[] = ">\n"
991 sc[0] = BCE
992 int numWritten = tSystem::tWriteFile(handle: ExprFile, buffer: sc, sizeBytes: 2); 
993 if (numWritten != 2
994 throw tScriptError("Cannot write to script file."); 
995
996 
997 
998void tExprWriter::WriteCommentInlineBegin() 
999
1000 char sc[] = "< "
1001 sc[0] = BCB
1002 int numWritten = tSystem::tWriteFile(handle: ExprFile, buffer: sc, sizeBytes: 2); 
1003 if (numWritten != 2
1004 throw tScriptError("Cannot write to script file."); 
1005
1006 
1007 
1008void tExprWriter::WriteCommentInline(const char* comment
1009
1010 int numWritten = 0
1011 int commentLen = 0
1012 if (comment
1013
1014 commentLen = tStd::tStrlen(s: comment); 
1015 numWritten += tSystem::tWriteFile(handle: ExprFile, buffer: comment, sizeBytes: commentLen); 
1016
1017 
1018 if (numWritten != commentLen
1019 throw tScriptError("Cannot write to script file."); 
1020
1021 
1022 
1023void tExprWriter::WriteCommentInlineEnd() 
1024
1025 char sc[] = " > "
1026 sc[1] = BCE
1027 int numWritten = tSystem::tWriteFile(handle: ExprFile, buffer: sc, sizeBytes: 3); 
1028 if (numWritten != 3
1029 throw tScriptError("Cannot write to script file."); 
1030
1031 
1032 
1033void tExprWriter::NewLine() 
1034
1035 char nl = '\n'
1036 char tb = '\t'
1037 
1038 int numWritten = tSystem::tWriteFile(handle: ExprFile, buffer: &nl, sizeBytes: 1); 
1039 numWritten += WriteIndents(); 
1040 int expectedWritten = 1 + (TabWidth ? CurrIndent*TabWidth : CurrIndent); 
1041 
1042 if (numWritten != expectedWritten
1043 throw tScriptError("Cannot write to script file."); 
1044
1045 
1046 
1047// Next follow the types for the functional scripts of the form f(a, b). 
1048tFunExpression::tFunExpression(const char8_t* function
1049
1050 // @todo There are better ways to do this. 
1051 const int maxExpressionSize = 512
1052 char buffer[maxExpressionSize]; 
1053 tStd::tStrncpy(dst: buffer, src: (char*)function, n: maxExpressionSize); 
1054 buffer[maxExpressionSize-1] = '\0'
1055 
1056 // We need to find the trailing ')' that is at the end of our expression. We do this by incrementing and decrementing 
1057 // based on '(' or ')' 
1058 char* beginningParen = (char*)tStd::tStrchr(s: buffer, c: '('); 
1059 char* endParen = beginningParen
1060 int openCount = 0
1061 
1062 for (int c = 0; c < maxExpressionSize-1; c++) 
1063
1064 if (*endParen == '('
1065 openCount++; 
1066 else if (*endParen == ')'
1067 openCount--; 
1068 
1069 if (!openCount
1070 break
1071 
1072 endParen++; 
1073
1074 
1075 if (openCount
1076 throw tScriptError("Expression too long. Missing bracket? Max size is %d chars. Look for [%s]", maxExpressionSize, buffer); 
1077 
1078 *endParen = '\0'
1079 int origLength = tStd::tStrlen(s: buffer); 
1080 *beginningParen = '\0'
1081 
1082 // Now replace all occurences of space, tab, and comma with nulls. Double quote string arguments are handled correctly. 
1083 bool inString = false
1084 openCount = 0
1085 for (int c = 0; c < origLength; c++) 
1086
1087 int ch = buffer[c]; 
1088 if (ch == '"'
1089
1090 inString = !inString
1091 buffer[c] = '\0'
1092
1093 
1094 if (ch == '('
1095 openCount++; 
1096 
1097 if (ch == ')'
1098 openCount--; 
1099 
1100 if ((!inString) && (!openCount)) 
1101
1102 if ((ch == '\t') || (ch == ',') || (ch == '\n') || (ch == '\r') || (ch == 9) || (ch == ' ')) 
1103 buffer[c] = '\0'
1104
1105
1106 
1107 // We now have a string of the form: name00arga000argb0argc00. The function name is the first word. 
1108 Function = buffer
1109 int pos = Function.Length(); 
1110 
1111 // Now lets get arguments until there are no more. 
1112 int endIdx = int(endParen-beginningParen); 
1113 while (1
1114
1115 while ((buffer[pos] == '\0') && (pos < endIdx)) 
1116 pos++; 
1117 
1118 if (pos == endIdx
1119 break
1120 
1121 tStringItem* arg = new tStringItem( &buffer[pos] ); 
1122 Arguments.Append(item: arg); 
1123 pos += arg->Length(); 
1124
1125
1126 
1127 
1128void tFunScript::Load(const tString& fileName
1129
1130 Clear(); 
1131 tFileHandle file = tSystem::tOpenFile(file: fileName.Chars() , mode: "rb"); 
1132 tAssert(file); 
1133 
1134 // Create a buffer big enough for the file. 
1135 int fileSize = tSystem::tGetFileSize(file); 
1136 char8_t* buffer = new char8_t[fileSize + 1]; 
1137 
1138 // Load the entire thing into memory. 
1139 int numRead = tSystem::tReadFile(handle: file, buffer: (uint8*)buffer, sizeBytes: fileSize); 
1140 tAssert(numRead == fileSize); 
1141 
1142 // This makes buffer a valid null terminated string. 
1143 buffer[fileSize] = '\0'
1144 tSystem::tCloseFile(f: file); 
1145 
1146 char8_t* currChar = EatWhiteAndComments(c: buffer); 
1147 while (*currChar != '\0'
1148
1149 Expressions.Append(item: new tFunExpression(currChar)); 
1150 
1151 // Get to the next expression. 
1152 currChar = tStd::tStrchr(s: currChar, c: '('); 
1153 int openCount = 0
1154 while (1
1155
1156 if (*currChar == '('
1157 openCount++; 
1158 
1159 if (*currChar == ')'
1160 openCount--; 
1161 
1162 currChar++; 
1163 if (!openCount
1164 break
1165
1166 
1167 currChar = EatWhiteAndComments(c: currChar); 
1168
1169 
1170 delete[] buffer
1171
1172 
1173 
1174void tFunScript::Save(const tString& fileName
1175
1176 tFileHandle file = tSystem::tOpenFile(file: fileName, mode: "wt"); 
1177 
1178 if (!file
1179 throw tScriptError("Cannot open file '%s'.", fileName.Chr()); 
1180 
1181 // All we need to do is traverse the expression list and write out each one. 
1182 for (tFunExpression* exp = Expressions.First(); exp; exp = exp->Next()) 
1183
1184 tfPrintf(dest: file, format: "%s(", exp->Function.Text()); 
1185 for (tStringItem* arg = exp->Arguments.First(); arg; arg = arg->Next()) 
1186
1187 tfPrintf(dest: file, format: "%s", arg->Text()); 
1188 
1189 // If it's not the last argument we put a comma there. 
1190 if (arg->Next()) 
1191 tfPrintf(dest: file, format: ", "); 
1192
1193 tfPrintf(dest: file, format: ")\n"); 
1194
1195 
1196 tSystem::tCloseFile(f: file); 
1197
1198 
1199 
1200char8_t* tFunScript::EatWhiteAndComments(char8_t* c
1201
1202 bool inComment = false
1203 while ((*c == ' ') || (*c == '\t') || (*c == '\n') || (*c == '\r') || (*c == 9) || (*c == '/') || inComment
1204
1205 if (*c == '/'
1206 inComment = true
1207 
1208 if ((*c == '\r') || (*c == '\n') || (*c == '\0')) 
1209 inComment = false
1210 
1211 c++; 
1212
1213 
1214 return c
1215
1216