1 /*
2     SuperCollider real time audio synthesis system
3     Copyright (c) 2002 James McCartney. All rights reserved.
4     http://www.audiosynth.com
5 
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10 
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15 
16     You should have received a copy of the GNU General Public License
17     along with this program; if not, write to the Free Software
18     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
19 */
20 /*
21 
22 Primitives for String.
23 
24 */
25 
26 #include "PyrPrimitive.h"
27 #include "PyrKernel.h"
28 #include "GC.h"
29 #include "Hash.h"
30 #include <string.h>
31 #include <stdlib.h>
32 #include <ctype.h>
33 #include <vector>
34 #include "PyrLexer.h"
35 #include "SC_Filesystem.hpp"
36 #include "SC_Codecvt.hpp" // path_to_utf8_str
37 #ifdef _WIN32
38 #    include <direct.h>
39 #    include "SC_Win32Utils.h"
40 #else
41 #    include <sys/param.h>
42 #endif
43 
44 #include <boost/regex.hpp>
45 #include <boost/intrusive/list.hpp>
46 #include <boost/intrusive/unordered_set.hpp>
47 #include <boost/filesystem/fstream.hpp> // ifstream
48 #include <boost/filesystem/path.hpp> // path
49 
50 #include <yaml-cpp/yaml.h>
51 
52 using namespace std;
53 namespace bfs = boost::filesystem;
54 
55 int prStringAsSymbol(struct VMGlobals* g, int numArgsPushed);
56 int prStringAsSymbol(struct VMGlobals* g, int numArgsPushed) {
57     PyrSlot* a;
58     char str[1024], *strp = nullptr;
59     int len;
60 
61     a = g->sp;
62     len = slotRawObject(a)->size;
63     strp = len > 1023 ? (char*)malloc(len + 1) : str;
64 
65     memcpy(strp, slotRawString(a)->s, len);
66     strp[len] = 0;
67 
68     SetSymbol(a, getsym(strp));
69 
70     if (len > 1023)
71         free(strp);
72 
73     return errNone;
74 }
75 
76 int prString_AsInteger(struct VMGlobals* g, int numArgsPushed);
77 int prString_AsInteger(struct VMGlobals* g, int numArgsPushed) {
78     PyrSlot* a = g->sp;
79 
80     char str[256];
81     int err = slotStrVal(a, str, 255);
82     if (err)
83         return err;
84 
85     SetInt(a, atoi(str));
86 
87     return errNone;
88 }
89 
90 int prString_AsFloat(struct VMGlobals* g, int numArgsPushed);
91 int prString_AsFloat(struct VMGlobals* g, int numArgsPushed) {
92     PyrSlot* a = g->sp;
93 
94     char str[256];
95     int err = slotStrVal(a, str, 255);
96     if (err)
97         return err;
98 
99     SetFloat(a, atof(str));
100 
101     return errNone;
102 }
103 
104 int prString_AsCompileString(struct VMGlobals* g, int numArgsPushed) {
105     PyrSlot* a = g->sp;
106     PyrString* scstr = slotRawString(a);
107     char* chars1 = scstr->s;
108     int newSize = scstr->size + 2;
109     for (int i = 0; i < scstr->size; ++i) {
110         if (chars1[i] == '"' || chars1[i] == '\\')
111             newSize++;
112     }
113     PyrString* newString = newPyrStringN(g->gc, newSize, 0, true);
114     char* chars2 = newString->s;
115     chars2[0] = '"';
116     chars2[newSize - 1] = '"';
117     int k = 1;
118     for (int i = 0; i < scstr->size; ++i) {
119         int c = chars1[i];
120         if (c == '"' || c == '\\')
121             chars2[k++] = '\\';
122         chars2[k++] = c;
123     }
124     SetObject(a, newString);
125     return errNone;
126 }
127 
128 int prString_Format(struct VMGlobals* g, int numArgsPushed) {
129     PyrSlot* a = g->sp - 1;
130     PyrSlot* b = g->sp;
131 
132     if (!isKindOfSlot(b, class_array))
133         return errWrongType;
134 
135     char* fmt = slotRawString(a)->s;
136 
137     int asize = slotRawObject(a)->size;
138     int bsize = slotRawObject(b)->size;
139     int csize = asize;
140 
141     PyrSlot* slots = slotRawObject(b)->slots;
142     for (int i = 0; i < bsize; ++i) {
143         PyrSlot* slot = slots + i;
144         if (!isKindOfSlot(slot, class_string))
145             return errWrongType;
146         csize += slotRawString(slot)->size;
147     }
148     PyrString* newString = newPyrStringN(g->gc, csize, 0, true);
149     char* buf = newString->s;
150 
151     int k = 0;
152     int index = 0;
153     for (int i = 0; i < asize;) {
154         char ch = fmt[i++];
155         if (ch == '%') {
156             if (index < bsize) {
157                 PyrString* bstring = slotRawString(&slots[index]);
158                 memcpy(buf + k, bstring->s, bstring->size);
159                 k += bstring->size;
160                 index++;
161             }
162         } else if (ch == '\\') {
163             if (i >= asize)
164                 break;
165             ch = fmt[i++];
166             if (ch == '%') {
167                 buf[k++] = '%';
168             } else {
169                 i--;
170                 buf[k++] = '\\';
171             }
172         } else {
173             buf[k++] = ch;
174         }
175     }
176     newString->size = k;
177     SetObject(a, newString);
178     return errNone;
179 };
180 
181 namespace detail {
182 
183 namespace bin = boost::intrusive;
184 
185 class regex_lru_cache {
186     int regex_flags;
187 
188     struct regex_node : bin::list_base_hook<>, bin::unordered_set_base_hook<> {
189     public:
190         regex_node(const char* str, size_t size, int regex_flags): pattern(str, size, regex_flags) {}
191 
192         boost::regex const& get(void) const { return pattern; }
193 
194     private:
195         boost::regex pattern;
196     };
197 
198     struct regex_equal {
199         bool operator()(regex_node const& lhs, regex_node const& rhs) const { return lhs.get() == rhs.get(); }
200 
201         bool operator()(const char* lhs, regex_node const& rhs) const {
202             return strcmp(lhs, rhs.get().str().c_str()) == 0;
203         }
204     };
205 
206     static inline std::size_t string_hash(const char* str) {
207         std::size_t ret = 0;
208 
209         // sdbm hash
210         int c;
211         while ((c = *str++))
212             ret = c + (ret << 6) + (ret << 16) - ret;
213 
214         return ret;
215     }
216 
217     struct regex_hash {
218         size_t operator()(regex_node const& arg) const { return string_hash(arg.get().str().c_str()); }
219 
220         size_t operator()(const char* arg) const { return string_hash(arg); }
221     };
222 
223     typedef bin::unordered_set<regex_node, bin::equal<regex_equal>, bin::hash<regex_hash>, bin::power_2_buckets<true>,
224                                bin::constant_time_size<false>>
225         re_set_t;
226     typedef re_set_t::bucket_type bucket_type;
227     typedef re_set_t::bucket_traits bucket_traits;
228     bucket_type buckets[128];
229     re_set_t re_set;
230 
231     bin::list<regex_node> re_list;
232 
233     void pop_lru() {
234         regex_node& rlu = re_list.back();
235         re_list.pop_back();
236         re_set.erase(rlu);
237         delete &rlu;
238     }
239 
240 public:
241     regex_lru_cache(int regex_flags = boost::regex_constants::ECMAScript):
242         re_set(bucket_traits(buckets, 128)),
243         re_list() {}
244 
245     ~regex_lru_cache() {
246         while (!re_list.empty()) {
247             pop_lru();
248         }
249     }
250 
251     boost::regex const& get_regex(const char* str, size_t size) {
252         re_set_t::iterator re_in_cache = re_set.find(str, regex_hash(), regex_equal());
253         if (re_in_cache != re_set.end()) {
254             regex_node& node = *re_in_cache;
255             bin::list<regex_node>::iterator re_in_list = bin::list<regex_node>::s_iterator_to(node);
256 
257             re_list.splice(re_list.begin(), re_list, re_in_list); // move to the begin of the list
258             assert(&re_list.front() == &node);
259             return node.get();
260         }
261 
262         if (re_list.size() >= 64)
263             pop_lru();
264 
265         regex_node* new_node = new regex_node(str, size, regex_flags);
266         re_set.insert(*new_node);
267         re_list.push_front(*new_node);
268         return new_node->get();
269     }
270 };
271 
272 }
273 
274 int prString_Regexp(struct VMGlobals* g, int numArgsPushed) {
275     /* not reentrant */
276     static detail::regex_lru_cache regex_lru_cache(boost::regex_constants::ECMAScript | boost::regex_constants::nosubs);
277 
278     using namespace boost;
279 
280     int start, end, len;
281 
282     PyrSlot* a = g->sp - 3;
283     PyrSlot* b = g->sp - 2;
284     PyrSlot* c = g->sp - 1;
285     PyrSlot* d = g->sp;
286 
287     if (!isKindOfSlot(b, class_string))
288         return errWrongType;
289     if (NotInt(c) || (NotInt(d) && NotNil(d)))
290         return errWrongType;
291     start = slotRawInt(c);
292 
293     len = slotRawObject(b)->size; // last char index instead of size
294 
295     if (IsNil(d)) {
296         end = len;
297     } else {
298         end = slotRawInt(d);
299     }
300 
301     if (end > len)
302         end = len;
303 
304     if (end - start <= 0) {
305         SetFalse(a);
306         return errNone;
307     }
308 
309     int stringlen = end - start;
310 
311     try {
312         regex const& pattern = regex_lru_cache.get_regex(slotRawString(a)->s, slotRawObject(a)->size);
313         match_flag_type flags = match_nosubs | match_any;
314 
315         const char* stringStart = slotRawString(b)->s + start;
316         const char* stringEnd = stringStart + stringlen;
317         bool res = regex_search(stringStart, stringEnd, pattern, flags);
318 
319         if (res)
320             SetTrue(a);
321         else
322             SetFalse(a);
323 
324         return errNone;
325     } catch (std::exception const& e) {
326         postfl("Warning: Exception in _String_Regexp - %s\n", e.what());
327         return errFailed;
328     }
329 }
330 
331 struct sc_regexp_match {
332     int pos;
333     int len;
334 };
335 
336 
337 static int prString_FindRegexp(struct VMGlobals* g, int numArgsPushed) {
338     /* not reentrant */
339     static detail::regex_lru_cache regex_lru_cache(boost::regex_constants::ECMAScript);
340 
341     using namespace boost;
342 
343     PyrSlot* a = g->sp - 2; // source string
344     PyrSlot* b = g->sp - 1; // pattern
345     PyrSlot* c = g->sp; // offset
346 
347     if (!isKindOfSlot(b, class_string) || (NotInt(c))) {
348         SetNil(a);
349         return errWrongType;
350     }
351 
352     int offset = slotRawInt(c);
353     int stringlen = std::max(slotRawObject(a)->size - offset, 0);
354 
355     std::vector<sc_regexp_match> matches;
356     const char* const stringBegin = slotRawString(a)->s + offset;
357     try {
358         regex const& pattern = regex_lru_cache.get_regex(slotRawString(b)->s, slotRawObject(b)->size);
359         match_flag_type flags = match_default;
360 
361         match_results<const char*> what;
362         const char* start = stringBegin;
363         const char* end = start + stringlen;
364         while (start <= end && regex_search(start, end, what, pattern, flags)) {
365             for (int i = 0; i < what.size(); ++i) {
366                 sc_regexp_match match;
367                 if (what[i].matched) {
368                     match.pos = what[i].first - stringBegin;
369                     match.len = what[i].second - what[i].first;
370                 } else {
371                     match.pos = 0;
372                     match.len = 0;
373                 }
374                 matches.push_back(match);
375             }
376             start = what[0].second;
377             if (what[0].first == what[0].second)
378                 ++start;
379         }
380     } catch (std::exception const& e) {
381         postfl("Warning: Exception in _String_FindRegexp - %s\n", e.what());
382         SetNil(a);
383         return errFailed;
384     }
385 
386     int match_count = matches.size();
387 
388     PyrObject* result_array = newPyrArray(g->gc, match_count, 0, true);
389     ++g->sp; // advance the stack to avoid overwriting receiver
390     SetObject(g->sp, result_array); // push result to make reachable
391 
392     for (int i = 0; i < match_count; ++i) {
393         int pos = matches[i].pos;
394         int len = matches[i].len;
395 
396         PyrObject* array = newPyrArray(g->gc, 2, 0, true);
397         SetObject(result_array->slots + i, array);
398         result_array->size++;
399         g->gc->GCWriteNew(result_array, array); // we know array is white so we can use GCWriteNew
400 
401         PyrString* matched_string = newPyrStringN(g->gc, len, 0, true);
402         memcpy(matched_string->s, stringBegin + pos, len);
403 
404         array->size = 2;
405         SetInt(array->slots, pos + offset);
406         SetObject(array->slots + 1, matched_string);
407         g->gc->GCWriteNew(array, matched_string); // we know matched_string is white so we can use GCWriteNew
408     };
409 
410     --g->sp; // pop the stack back to the receiver slot since we stored result_array there above
411     SetObject(a, result_array); // now we can set the result in a
412 
413     return errNone;
414 }
415 
416 static int prString_FindRegexpAt(struct VMGlobals* g, int numArgsPushed) {
417     /* not reentrant */
418     static detail::regex_lru_cache regex_lru_cache(boost::regex_constants::ECMAScript);
419 
420     using namespace boost;
421 
422     PyrSlot* a = g->sp - 2; // source string
423     PyrSlot* b = g->sp - 1; // pattern
424     PyrSlot* c = g->sp; // offset
425 
426     if (!isKindOfSlot(b, class_string) || (NotInt(c))) {
427         SetNil(a);
428         return errWrongType;
429     }
430 
431     int offset = slotRawInt(c);
432     int stringlen = std::max(slotRawObject(a)->size - offset, 0);
433 
434     int matched_len = 0;
435 
436     const char* const stringBegin = slotRawString(a)->s + offset;
437     try {
438         regex const& pattern = regex_lru_cache.get_regex(slotRawString(b)->s, slotRawObject(b)->size);
439 
440         // match_continuous: the match must begin at the offset start
441         match_flag_type flags = match_continuous;
442 
443         match_results<const char*> what;
444         const char* start = stringBegin;
445         const char* end = start + stringlen;
446         if (regex_search(start, end, what, pattern, flags)) {
447             assert(what[0].first == stringBegin);
448             matched_len = what[0].second - what[0].first;
449         } else {
450             SetNil(a);
451             return errNone;
452         }
453     } catch (std::exception const& e) {
454         postfl("Warning: Exception in _String_FindRegexpAt - %s\n", e.what());
455         return errFailed;
456     }
457 
458     PyrObject* array = newPyrArray(g->gc, 2, 0, true);
459     ++g->sp;
460     SetObject(g->sp, array); // push on stack to make reachable
461 
462     PyrString* matched_string = newPyrStringN(g->gc, matched_len, 0, true);
463     memcpy(matched_string->s, stringBegin, (size_t)matched_len);
464 
465     array->size = 2;
466     SetInt(array->slots + 1, matched_len);
467     SetObject(array->slots, matched_string);
468     g->gc->GCWriteNew(array, matched_string); // we know matched_string is white so we can use GCWriteNew
469     --g->sp; // pop the stack back to the receiver slot since we stored array there above
470     SetObject(a, array); // now we can set the result in a
471 
472     return errNone;
473 }
474 
475 int memcmpi(char* a, char* b, int len) {
476     for (int i = 0; i < len; ++i) {
477         char aa = toupper(a[i]);
478         char bb = toupper(b[i]);
479         if (aa < bb)
480             return -1;
481         if (aa > bb)
482             return 1;
483     }
484     return 0;
485 }
486 
487 int prStringCompare(struct VMGlobals* g, int numArgsPushed);
488 int prStringCompare(struct VMGlobals* g, int numArgsPushed) {
489     PyrSlot *a, *b, *c;
490     int cmp, length;
491 
492     a = g->sp - 2;
493     b = g->sp - 1;
494     c = g->sp;
495 
496     if (NotObj(b) || !isKindOf(slotRawObject(b), class_string))
497         return errWrongType;
498 
499     length = sc_min(slotRawObject(a)->size, slotRawObject(b)->size);
500     if (IsTrue(c))
501         cmp = memcmpi(slotRawString(a)->s, slotRawString(b)->s, length);
502     else
503         cmp = memcmp(slotRawString(a)->s, slotRawString(b)->s, length);
504     if (cmp == 0) {
505         if (slotRawObject(a)->size < slotRawObject(b)->size)
506             cmp = -1;
507         else if (slotRawObject(a)->size > slotRawObject(b)->size)
508             cmp = 1;
509     }
510     SetInt(a, cmp);
511     return errNone;
512 }
513 
514 int prStringHash(struct VMGlobals* g, int numArgsPushed);
515 int prStringHash(struct VMGlobals* g, int numArgsPushed) {
516     PyrSlot* a = g->sp;
517     int hash = Hash(slotRawString(a)->s, slotRawString(a)->size);
518     SetInt(a, hash);
519     return errNone;
520 }
521 
522 int prString_PathMatch(struct VMGlobals* g, int numArgsPushed);
523 int prString_PathMatch(struct VMGlobals* g, int numArgsPushed) {
524     PyrSlot* a = g->sp;
525     char pattern[PATH_MAX];
526     int err = slotStrVal(a, pattern, PATH_MAX - 1);
527     if (err)
528         return err;
529 
530     SC_Filesystem::Glob* glob = SC_Filesystem::makeGlob(pattern);
531 
532     // exit early with empty array if no matches found
533     if (!glob) {
534         SetObject(a, newPyrArray(g->gc, 0, 0, true));
535         return errNone;
536     }
537 
538     // read all paths into a vector
539     std::vector<bfs::path> paths;
540     while (true) {
541         const bfs::path& matched_path = SC_Filesystem::globNext(glob);
542         if (matched_path.empty())
543             break;
544         else
545             paths.push_back(matched_path);
546     };
547 
548     // create array with appropriate reserved size
549     PyrObject* array = newPyrArray(g->gc, paths.size(), 0, true);
550     SetObject(a, array); // this is okay here as we don't use the receiver below
551 
552     // convert paths and copy into sclang array.
553     for (int i = 0; i < paths.size(); ++i) {
554         const std::string& matched_path_utf8 = SC_Codecvt::path_to_utf8_str(paths[i]);
555         PyrObject* string = (PyrObject*)newPyrString(g->gc, matched_path_utf8.c_str(), 0, true);
556         SetObject(array->slots + i, string);
557         g->gc->GCWriteNew(array, string); // we know string is white so we can use GCWriteNew
558         array->size++;
559     }
560 
561     SC_Filesystem::freeGlob(glob);
562     return errNone;
563 }
564 
565 int prString_Getenv(struct VMGlobals* g, int numArgsPushed);
566 int prString_Getenv(struct VMGlobals* g, int /* numArgsPushed */) {
567     PyrSlot* arg = g->sp;
568     char key[256];
569     char* value;
570     int err;
571 
572     err = slotStrVal(arg, key, 256);
573     if (err)
574         return err;
575 
576 #ifdef _WIN32
577     char buf[1024];
578     DWORD size = GetEnvironmentVariable(key, buf, 1024);
579     if (size == 0 || size > 1024)
580         value = 0;
581     else
582         value = buf;
583 #else
584     value = getenv(key);
585 #endif
586 
587     if (value) {
588         PyrString* pyrString = newPyrString(g->gc, value, 0, true);
589         if (!pyrString)
590             return errFailed;
591         SetObject(arg, pyrString);
592     } else {
593         SetNil(arg);
594     }
595 
596     return errNone;
597 }
598 
599 int prString_Setenv(struct VMGlobals* g, int numArgsPushed);
600 int prString_Setenv(struct VMGlobals* g, int /* numArgsPushed */) {
601     PyrSlot* args = g->sp - 1;
602     char key[256];
603     int err;
604 
605     err = slotStrVal(args + 0, key, 256);
606     if (err)
607         return err;
608 
609     if (IsNil(args + 1)) {
610 #ifdef _WIN32
611         SetEnvironmentVariable(key, NULL);
612 #else
613         unsetenv(key);
614 #endif
615     } else {
616         char value[1024];
617         err = slotStrVal(args + 1, value, 1024);
618         if (err)
619             return err;
620 #ifdef _WIN32
621         SetEnvironmentVariable(key, value);
622 #else
623         setenv(key, value, 1);
624 #endif
625     }
626 
627     return errNone;
628 }
629 
630 int prStripRtf(struct VMGlobals* g, int numArgsPushed);
631 int prStripRtf(struct VMGlobals* g, int numArgsPushed) {
632     PyrSlot* a = g->sp;
633     int len = slotRawObject(a)->size;
634     char* chars = (char*)malloc(len + 1);
635     memcpy(chars, slotRawString(a)->s, len);
636     chars[len] = 0;
637     rtf2txt(chars);
638 
639     PyrString* string = newPyrString(g->gc, chars, 0, false);
640     SetObject(a, string);
641     free(chars);
642 
643     return errNone;
644 }
645 
646 int prStripHtml(struct VMGlobals* g, int numArgsPushed);
647 int prStripHtml(struct VMGlobals* g, int numArgsPushed) {
648     PyrSlot* a = g->sp;
649     int len = slotRawObject(a)->size;
650     char* chars = (char*)malloc(len + 1);
651     memcpy(chars, slotRawString(a)->s, len);
652     chars[len] = 0;
653     html2txt(chars);
654 
655     PyrString* string = newPyrString(g->gc, chars, 0, false);
656     SetObject(a, string);
657     free(chars);
658 
659     return errNone;
660 }
661 
662 int prString_Find(struct VMGlobals* g, int numArgsPushed);
663 int prString_Find(struct VMGlobals* g, int numArgsPushed) {
664     PyrSlot* a = g->sp - 3; // source string
665     PyrSlot* b = g->sp - 2; // search string
666     PyrSlot* c = g->sp - 1; // ignoreCase
667     PyrSlot* d = g->sp; // offset
668 
669     int offset;
670     int err = slotIntVal(d, &offset);
671     if (err)
672         return err;
673 
674     int alength = slotRawObject(a)->size - offset;
675     if (alength <= 0) {
676         SetNil(a);
677         return errNone;
678     }
679 
680     if (isKindOfSlot(b, class_string)) {
681         int blength = slotRawObject(b)->size;
682 
683         if ((blength == 0)
684             // should also return nil if search string is longer than source
685             || (blength > alength)) {
686             SetNil(a);
687             return errNone;
688         }
689 
690         int cmp = 1; // assume contains will be false
691         char* achar = slotRawString(a)->s + offset;
692         char* bchar = slotRawString(b)->s;
693         char bchar0 = bchar[0];
694         int scanlength = alength - blength;
695         if (IsTrue(c)) {
696             bchar0 = toupper(bchar0);
697             for (int i = 0; i <= scanlength; ++i, ++achar) {
698                 if (toupper(*achar) == bchar0) {
699                     cmp = memcmpi(achar + 1, bchar + 1, blength - 1);
700                     if (cmp == 0)
701                         break;
702                 }
703             }
704         } else {
705             for (int i = 0; i <= scanlength; ++i, ++achar) {
706                 if (*achar == bchar0) {
707                     cmp = memcmp(achar + 1, bchar + 1, blength - 1);
708                     if (cmp == 0)
709                         break;
710                 }
711             }
712         }
713         if (cmp == 0) {
714             SetInt(a, achar - slotRawString(a)->s);
715         } else {
716             SetNil(a);
717         }
718         return errNone;
719 
720     } else if (IsChar(b)) {
721         char* achar = slotRawString(a)->s + offset;
722         char bchar = slotRawChar(b);
723         int scanlength = alength - 1;
724         if (IsTrue(c)) {
725             bchar = toupper(bchar);
726             for (int i = 0; i <= scanlength; ++i, ++achar) {
727                 if (toupper(*achar) == bchar) {
728                     SetInt(a, achar - slotRawString(a)->s);
729                     return errNone;
730                 }
731             }
732         } else {
733             for (int i = 0; i <= scanlength; ++i, ++achar) {
734                 if (*achar == bchar) {
735                     SetInt(a, achar - slotRawString(a)->s);
736                     return errNone;
737                 }
738             }
739         }
740         SetNil(a);
741         return errNone;
742     } else
743         return errWrongType;
744 }
745 
746 int prString_FindBackwards(struct VMGlobals* g, int numArgsPushed);
747 int prString_FindBackwards(struct VMGlobals* g, int numArgsPushed) {
748     PyrSlot* a = g->sp - 3; // source string
749     PyrSlot* b = g->sp - 2; // search string
750     PyrSlot* c = g->sp - 1; // ignoreCase
751     PyrSlot* d = g->sp; // offset
752 
753     int offset;
754     int err = slotIntVal(d, &offset);
755     if (err)
756         return err;
757 
758     int alength = sc_min(offset + 1, slotRawObject(a)->size);
759     if (alength <= 0) {
760         SetNil(a);
761         return errNone;
762     }
763 
764     if (isKindOfSlot(b, class_string)) {
765         int blength = slotRawObject(b)->size;
766 
767         if ((blength == 0)
768             // should also return nil if search string is longer than source
769             || (blength > alength)) {
770             SetNil(a);
771             return errNone;
772         }
773 
774         int cmp = 1; // assume contains will be false
775         char* achar = slotRawString(a)->s + (alength - blength);
776         char* bchar = slotRawString(b)->s;
777         char bchar0 = bchar[0];
778         int scanlength = alength - blength;
779         if (IsTrue(c)) {
780             bchar0 = toupper(bchar0);
781             for (int i = scanlength; i >= 0; --i, --achar) {
782                 if (toupper(*achar) == bchar0) {
783                     cmp = memcmpi(achar + 1, bchar + 1, blength - 1);
784                     if (cmp == 0)
785                         break;
786                 }
787             }
788         } else {
789             for (int i = scanlength; i >= 0; --i, --achar) {
790                 if (*achar == bchar0) {
791                     cmp = memcmp(achar + 1, bchar + 1, blength - 1);
792                     if (cmp == 0)
793                         break;
794                 }
795             }
796         }
797         if (cmp == 0) {
798             SetInt(a, achar - slotRawString(a)->s);
799         } else {
800             SetNil(a);
801         }
802         return errNone;
803     } else if (IsChar(b)) {
804         char* achar = slotRawString(a)->s + (alength - 1);
805         char bchar = slotRawChar(b);
806         int scanlength = alength - 1;
807         if (IsTrue(c)) {
808             bchar = toupper(bchar);
809             for (int i = scanlength; i >= 0; --i, --achar) {
810                 if (toupper(*achar) == bchar) {
811                     SetInt(a, achar - slotRawString(a)->s);
812                     return errNone;
813                 }
814             }
815         } else {
816             for (int i = scanlength; i >= 0; --i, --achar) {
817                 if (*achar == bchar) {
818                     SetInt(a, achar - slotRawString(a)->s);
819                     return errNone;
820                 }
821             }
822         }
823         SetNil(a);
824         return errNone;
825     } else
826         return errWrongType;
827 }
828 
829 /** \brief Expand `~` to home directory and resolve aliases
830  *
831  * Prints an error message if alias resolution failed.
832  */
833 int prString_StandardizePath(struct VMGlobals* g, int numArgsPushed);
834 int prString_StandardizePath(struct VMGlobals* g, int /* numArgsPushed */) {
835     PyrSlot* arg = g->sp;
836     char ipath[PATH_MAX];
837 
838     int err = slotStrVal(arg, ipath, PATH_MAX);
839     if (err != errNone)
840         return err;
841 
842     bfs::path p = SC_Codecvt::utf8_str_to_path(ipath);
843     p = SC_Filesystem::instance().expandTilde(p);
844     bool isAlias;
845     p = SC_Filesystem::resolveIfAlias(p, isAlias);
846 
847     // Don't consider alias resolution a failure condition, but print an error
848     if (isAlias && p.empty())
849         error("standardizePath: symlink resolution failed for '%s'\n", ipath);
850 
851     const std::string& utf8_str = SC_Codecvt::path_to_utf8_str(p);
852     PyrString* pyrString = newPyrString(g->gc, utf8_str.c_str(), 0, true);
853     SetObject(arg, pyrString);
854 
855     return errNone;
856 }
857 
858 int prString_EscapeChar(struct VMGlobals* g, int numArgsPushed) {
859     PyrSlot* arg = g->sp - 1;
860     PyrSlot* charToEscapeSlot = g->sp;
861 
862     assert(isKindOfSlot(arg, class_string));
863 
864     if (!IsChar(charToEscapeSlot))
865         return errWrongType;
866 
867     char charToEscape = slotRawChar(charToEscapeSlot);
868 
869     PyrString* argString = slotRawString(arg);
870     int length = argString->size;
871     PyrString* resultString = newPyrStringN(g->gc, length * 2 + 1, 0, 1); // pressimize
872 
873     char* original = argString->s;
874     char* result = resultString->s;
875 
876     int resultLength = length;
877     for (int i = 0; i != length; ++i) {
878         char current = *original++;
879         if (current == charToEscape) {
880             *result++ = '\\';
881             resultLength += 1;
882         }
883         *result++ = current;
884     }
885     *result = 0;
886 
887     resultString->size = resultLength;
888 
889     SetRaw(arg, (PyrObject*)resultString);
890 
891     return errNone;
892 }
893 
894 static void yaml_traverse(struct VMGlobals* g, const YAML::Node& node, PyrObject* parent, PyrSlot* slot) {
895     YAML::NodeType::value type = node.Type();
896     string out;
897     PyrObject* result = nullptr;
898 
899     switch (type) {
900     case YAML::NodeType::Scalar:
901         out = node.as<string>();
902         result = (PyrObject*)newPyrString(g->gc, out.c_str(), 0, true);
903         SetObject(slot, result);
904         if (parent)
905             g->gc->GCWriteNew(parent, result); // we know result is white so we can use GCWriteNew
906         break;
907 
908     case YAML::NodeType::Sequence:
909         result = newPyrArray(g->gc, node.size(), 0, true);
910         SetObject(slot, result);
911         if (parent)
912             g->gc->GCWriteNew(parent, result); // we know result is white so we can use GCWriteNew
913         for (unsigned int i = 0; i < node.size(); i++) {
914             const YAML::Node& subnode = node[i];
915             result->size++;
916             yaml_traverse(g, subnode, result, result->slots + i);
917         }
918         break;
919 
920     case YAML::NodeType::Map: {
921         result = instantiateObject(g->gc, s_dictionary->u.classobj, 0, false, true);
922         SetObject(slot, result);
923         if (parent)
924             g->gc->GCWriteNew(parent, result); // we know result is white so we can use GCWriteNew
925 
926         PyrObject* array = newPyrArray(g->gc, node.size() * 2, 0, true);
927         result->size = 2;
928         SetObject(result->slots, array); // array
929         SetInt(result->slots + 1, node.size()); // size
930         g->gc->GCWriteNew(result, array); // we know array is white so we can use GCWriteNew
931 
932         int j = 0;
933         for (auto const& element : node) {
934             const YAML::Node& key = element.first;
935             const YAML::Node& value = element.second;
936             out = key.as<string>();
937             PyrObject* pkey = (PyrObject*)newPyrString(g->gc, out.c_str(), 0, true);
938             SetObject(array->slots + j, pkey);
939             array->size++;
940             g->gc->GCWriteNew(array, pkey); // we know pkey is white so we can use GCWriteNew
941 
942             array->size++;
943             yaml_traverse(g, value, array, array->slots + j + 1);
944 
945             j += 2;
946         }
947         break;
948     }
949 
950     case YAML::NodeType::Null:
951         SetNil(slot);
952         break;
953 
954     default:
955         postfl("WARNING: yaml_traverse(): unknown/unsupported node type\n");
956         SetNil(slot);
957     }
958 }
959 
960 int prString_ParseYAML(struct VMGlobals* g, int numArgsPushed) {
961     PyrSlot* arg = g->sp;
962 
963     if (!isKindOfSlot(arg, class_string))
964         return errWrongType;
965 
966     string str((const char*)slotRawString(arg)->s, slotRawString(arg)->size);
967 
968     std::istringstream fin(str);
969     YAML::Node doc = YAML::Load(fin);
970     yaml_traverse(g, doc, nullptr, arg);
971 
972     return errNone;
973 }
974 
975 int prString_ParseYAMLFile(struct VMGlobals* g, int numArgsPushed) {
976     PyrSlot* arg = g->sp;
977 
978     if (!isKindOfSlot(arg, class_string))
979         return errWrongType;
980 
981     string str((const char*)slotRawString(arg)->s, slotRawString(arg)->size);
982 
983     const bfs::path& path = SC_Codecvt::utf8_str_to_path(str);
984     bfs::ifstream fin(path);
985     YAML::Node doc = YAML::Load(fin);
986     yaml_traverse(g, doc, nullptr, arg);
987 
988     return errNone;
989 }
990 
991 void initStringPrimitives();
992 void initStringPrimitives() {
993     int base, index = 0;
994 
995     base = nextPrimitiveIndex();
996 
997     definePrimitive(base, index++, "_StringCompare", prStringCompare, 3, 0);
998     definePrimitive(base, index++, "_StringHash", prStringHash, 1, 0);
999     definePrimitive(base, index++, "_StringPathMatch", prString_PathMatch, 1, 0);
1000     definePrimitive(base, index++, "_StringAsSymbol", prStringAsSymbol, 1, 0);
1001     definePrimitive(base, index++, "_String_AsInteger", prString_AsInteger, 1, 0);
1002     definePrimitive(base, index++, "_String_AsFloat", prString_AsFloat, 1, 0);
1003     definePrimitive(base, index++, "_String_AsCompileString", prString_AsCompileString, 1, 0);
1004     definePrimitive(base, index++, "_String_Getenv", prString_Getenv, 1, 0);
1005     definePrimitive(base, index++, "_String_Setenv", prString_Setenv, 2, 0);
1006     definePrimitive(base, index++, "_String_Find", prString_Find, 4, 0);
1007     definePrimitive(base, index++, "_String_FindBackwards", prString_FindBackwards, 4, 0);
1008     definePrimitive(base, index++, "_String_Format", prString_Format, 2, 0);
1009     definePrimitive(base, index++, "_String_Regexp", prString_Regexp, 4, 0);
1010     definePrimitive(base, index++, "_String_FindRegexp", prString_FindRegexp, 3, 0);
1011     definePrimitive(base, index++, "_String_FindRegexpAt", prString_FindRegexpAt, 3, 0);
1012     definePrimitive(base, index++, "_StripRtf", prStripRtf, 1, 0);
1013     definePrimitive(base, index++, "_StripHtml", prStripHtml, 1, 0);
1014     definePrimitive(base, index++, "_String_StandardizePath", prString_StandardizePath, 1, 0);
1015     definePrimitive(base, index++, "_String_EscapeChar", prString_EscapeChar, 2, 0);
1016     definePrimitive(base, index++, "_String_ParseYAML", prString_ParseYAML, 1, 0);
1017     definePrimitive(base, index++, "_String_ParseYAMLFile", prString_ParseYAMLFile, 1, 0);
1018 }
1019 
1020 #if _SC_PLUGINS_
1021 
1022 
1023 #    include "SCPlugin.h"
1024 
1025 // export the function that SC will call to load the plug in.
1026 #    pragma export on
1027 extern "C" {
1028 SCPlugIn* loadPlugIn(void);
1029 }
1030 #    pragma export off
1031 
1032 
1033 // define plug in object
1034 class APlugIn : public SCPlugIn {
1035 public:
1036     APlugIn();
1037     virtual ~APlugIn();
1038 
1039     virtual void AboutToCompile();
1040 };
1041 
1042 APlugIn::APlugIn() {
1043     // constructor for plug in
1044 }
1045 
1046 APlugIn::~APlugIn() {
1047     // destructor for plug in
1048 }
1049 
1050 void APlugIn::AboutToCompile() {
1051     // this is called each time the class library is compiled.
1052     initStringPrimitives();
1053 }
1054 
1055 // This function is called when the plug in is loaded into SC.
1056 // It returns an instance of APlugIn.
1057 SCPlugIn* loadPlugIn() { return new APlugIn(); }
1058 
1059 #endif
1060