1 /************************************************************************
2  ************************************************************************
3  FAUST compiler
4  Copyright (C) 2003-2018 GRAME, Centre National de Creation Musicale
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., 675 Mass Ave, Cambridge, MA 02139, USA.
19  ************************************************************************
20  ************************************************************************/
21 
22 #include <ctype.h>
23 #include <errno.h>
24 #include <limits.h>
25 #include <stdlib.h>
26 #include <cctype>
27 #include <climits>
28 
29 #include "enrobage.hh"
30 #include "compatibility.hh"
31 #include "exception.hh"
32 #include "garbageable.hh"
33 #include "global.hh"
34 #include "sourcefetcher.hh"
35 
36 using namespace std;
37 
38 /**
39  * Returns true is a line is blank (contains only white caracters)
40  */
isBlank(const string & s)41 static bool isBlank(const string& s)
42 {
43     for (size_t i = 0; i < s.size(); i++) {
44         if (s[i] != ' ' && s[i] != '\t') return false;
45     }
46     return true;
47 }
48 
49 /**
50  * Check that a substring defined by its starting position and its length is a 'word'.
51  * That is: it should not be preceeded or followed by an alphanumerical or a _ character.
52  */
wordBoundaries(const string & str,string::size_type pos,string::size_type len)53 static bool wordBoundaries(const string& str, string::size_type pos, string::size_type len)
54 {
55     if ((pos > 0) && (isalnum(str[pos - 1]) || (str[pos - 1] == '_'))) return false;
56     if ((pos + len < str.length()) && (isalnum(str[pos + len]) || (str[pos + len] == '_'))) return false;
57     return true;
58 }
59 
60 /**
61  * Replace every occurrence of oldstr by newstr inside str. str is modified
62  * and returned as reference for convenience.
63  */
replaceOccurrences(string & str,const string & oldstr,const string & newstr,bool force)64 static string& replaceOccurrences(string& str, const string& oldstr, const string& newstr, bool force)
65 {
66     string::size_type l1 = oldstr.length();
67     string::size_type l2 = newstr.length();
68 
69     string::size_type pos = str.find(oldstr);
70     while (pos != string::npos) {
71         if (force || wordBoundaries(str, pos, l1)) {
72             // cerr << "'" << str << "'@" << pos << " replace '" << oldstr << "' by '" << newstr << "'" << endl;
73             str.replace(pos, l1, newstr);
74             pos = str.find(oldstr, pos + l2);
75         } else {
76             // cerr << "'" << str << "'@" << pos << " DON'T REPLACE '" << oldstr << "' by '" << newstr << "'" << endl;
77             pos = str.find(oldstr, pos + l1);
78         }
79     }
80     return str;
81 }
82 
83 /**
84  * Used when copying architecture files to replace default mydsp
85  * class name with the user specified one
86  */
replaceClassName(string & str)87 static string& replaceClassName(string& str)
88 {
89     // "mydsp" can be replaced as the DSP class name, or appearing anywhere in the file
90     replaceOccurrences(str, "mydsp", gGlobal->gClassName, true);
91     // But "dsp" string has to be replaced in a strict manner
92     replaceOccurrences(str, "dsp", gGlobal->gSuperClassName, false);
93     return str;
94 }
95 
96 /**
97  * A minimalistic parser used to recognize '#include <faust/...>' patterns when copying
98  * architecture files
99  */
100 class myparser {
101 
102    private:
103 
104     string str;
105     size_t N;
106     size_t p;
107 
108    public:
109 
myparser(const string & s)110     myparser(const string& s) : str(s), N(s.length()), p(0) {}
111 
skip()112     bool skip()
113     {
114         while (p < N && isspace(str[p])) p++;
115         return true;
116     }
117 
parse(const string & s)118     bool parse(const string& s)
119     {
120         bool f;
121         if ((f = (p == str.find(s, p)))) p += s.length();
122         return f;
123     }
124 
filename(string & fname)125     bool filename(string& fname)
126     {
127         size_t saved = p;
128         if (p < N) {
129             char c = str[p++];
130             if ((c == '<') | (c == '"')) {
131                 fname = "";
132                 while (p < N && (str[p] != '>') && (str[p] != '"')) fname += str[p++];
133                 p++;
134                 return true;
135             }
136         }
137         p = saved;
138         return false;
139     }
140 };
141 
142 /**
143  * True if string s match '#include <faust/fname>' or include("/usr/local/share/faust/julia/fname")
144  */
isFaustInclude(const string & line,string & fname)145 static bool isFaustInclude(const string& line, string& fname)
146 {
147     myparser P(line);
148     // C/C++ case
149     if (P.skip() && P.parse("#include") && P.skip() && P.filename(fname)) {
150         myparser Q(fname);
151         return Q.parse("faust/");
152     // Julia case
153     } else if (P.skip() && P.parse("include(") && P.skip() && P.filename(fname)) {
154         myparser Q(fname);
155         return Q.parse("/usr/local/share/faust/julia");
156     } else {
157         return false;
158     }
159 }
160 
161 /**
162  * Inject file fname into dst ostream
163  */
164 
inject(ostream & dst,const string & fname)165 static void inject(ostream& dst, const string& fname)
166 {
167     if (gGlobal->gAlreadyIncluded.find(fname) == gGlobal->gAlreadyIncluded.end()) {
168         gGlobal->gAlreadyIncluded.insert(fname);
169         unique_ptr<istream> src = unique_ptr<istream>(openArchStream(fname.c_str()));
170         if (src) {
171             streamCopyUntilEnd(*src, dst);
172         } else {
173             gGlobal->gErrorMsg = "ERROR : " + fname + " not found\n";
174         }
175     }
176 }
177 
removeSpaces(const string & line)178 static string removeSpaces(const string& line)
179 {
180     string res;
181     for (char c : line) {
182         if (c != ' ') res.push_back(c);
183     }
184     return res;
185 }
186 
187 #define TRY_OPEN(filename)           \
188     unique_ptr<ifstream> f = unique_ptr<ifstream>(new ifstream());    \
189     f->open(filename, ifstream::in); \
190     err = chdir(old);                \
191     if (f->is_open())                \
192         return f;                    \
193 
194 /**
195  * Check if an URL exists.
196  * @return true if the URL exist, throw on exception otherwise
197  */
checkFile(const char * filename)198 static bool checkFile(const char* filename)
199 {
200     // Otherwise tries to open as a regular file
201     FILE* f = fopen(filename, "r");
202     if (f) {
203         fclose(f);
204         return true;
205     } else {
206         stringstream error;
207         error << "ERROR : cannot open file '" << ((filename) ? filename : "null") << "' : " << strerror(errno) << endl;
208         throw faustexception(error.str());
209     }
210 }
211 
212 /**
213  * Try to open the file '<dir>/<filename>'. If it succeed, it stores the full pathname
214  * of the file into <fullpath>
215  */
fopenAt(string & fullpath,const char * dir,const char * filename)216 static FILE* fopenAt(string& fullpath, const char* dir, const char* filename)
217 {
218     int  err;
219     char olddirbuffer[FAUST_PATH_MAX];
220     char newdirbuffer[FAUST_PATH_MAX];
221 
222     char* olddir = getcwd(olddirbuffer, FAUST_PATH_MAX);
223 
224     if (chdir(dir) == 0) {
225         FILE* f      = fopen(filename, "r");
226         char* newdir = getcwd(newdirbuffer, FAUST_PATH_MAX);
227         if (!newdir) {
228             fclose(f);
229             stringstream error;
230             error << "ERROR : getcwd : " << strerror(errno) << endl;
231             throw faustexception(error.str());
232         }
233         fullpath = newdir;
234         fullpath += '/';
235         fullpath += filename;
236         err = chdir(olddir);
237         if (err != 0) {
238             fclose(f);
239             stringstream error;
240             error << "ERROR : cannot change back directory to '" << ((olddir) ? olddir : "null")
241                   << "' : " << strerror(errno) << endl;
242             throw faustexception(error.str());
243         }
244         return f;
245     }
246     err = chdir(olddir);
247     if (err != 0) {
248         stringstream error;
249         error << "ERROR : cannot change back directory to '" << ((olddir) ? olddir : "null")
250               << "' : " << strerror(errno) << endl;
251         throw faustexception(error.str());
252     }
253     return nullptr;
254 }
255 
256 /**
257  * Try to open the file '<dir>/<filename>'. If it succeed, it stores the full pathname
258  * of the file into <fullpath>
259  */
fopenAt(string & fullpath,const string & dir,const char * filename)260 static FILE* fopenAt(string& fullpath, const string& dir, const char* filename)
261 {
262     return fopenAt(fullpath, dir.c_str(), filename);
263 }
264 
265 /**
266  * Test absolute pathname.
267  */
isAbsolutePathname(const string & filename)268 static bool isAbsolutePathname(const string& filename)
269 {
270     // test windows absolute pathname "x:xxxxxx"
271     if (filename.size() > 1 && filename[1] == ':') return true;
272 
273     // test unix absolute pathname "/xxxxxx"
274     if (filename.size() > 0 && filename[0] == '/') return true;
275 
276     return false;
277 }
278 
279 /**
280  * Build a full pathname of <filename>.
281  * <fullpath> = <currentdir>/<filename>
282  */
buildFullPathname(string & fullpath,const char * filename)283 static void buildFullPathname(string& fullpath, const char* filename)
284 {
285     char old[FAUST_PATH_MAX];
286 
287     if (isAbsolutePathname(filename)) {
288         fullpath = filename;
289     } else {
290         char* newdir = getcwd(old, FAUST_PATH_MAX);
291         if (!newdir) {
292             stringstream error;
293             error << "ERROR : getcwd : " << strerror(errno) << endl;
294             throw faustexception(error.str());
295         }
296         fullpath = newdir;
297         fullpath += '/';
298         fullpath += filename;
299     }
300 }
301 
302 //---------------------------
303 // Exported public functions
304 //---------------------------
305 
306 /**
307  * Try to open an architecture file searching in various directories
308  */
openArchStream(const char * filename)309 unique_ptr<ifstream> openArchStream(const char* filename)
310 {
311     char  buffer[FAUST_PATH_MAX];
312     char* old = getcwd(buffer, FAUST_PATH_MAX);
313     int   err;
314 
315     TRY_OPEN(filename);
316     for (string dirname : gGlobal->gArchitectureDirList) {
317         if ((err = chdir(dirname.c_str())) == 0) {
318             TRY_OPEN(filename);
319         }
320     }
321 
322     return nullptr;
323 }
324 
325 /**
326  * Try to open the file <filename> searching in various directories. If succesful
327  * place its full pathname in the string <fullpath>
328  */
fopenSearch(const char * filename,string & fullpath)329 FILE* fopenSearch(const char* filename, string& fullpath)
330 {
331     FILE* f;
332 
333     // tries to open file with its filename
334     if ((f = fopen(filename, "r"))) {
335         buildFullPathname(fullpath, filename);
336         // enrich the supplied directories paths with the directory containing the loaded file,
337         // so that local files relative to this added directory can then be loaded
338         gGlobal->gImportDirList.push_back(fileDirname(fullpath));
339         return f;
340     }
341 
342     // otherwise search file in user supplied directories paths
343     for (string dirname : gGlobal->gImportDirList) {
344         if ((f = fopenAt(fullpath, dirname, filename))) {
345             return f;
346         }
347     }
348     return nullptr;
349 }
350 
351 /**
352  * filebasename returns the basename of a path.
353  * (adapted by kb from basename.c)
354  *
355  * @param[in]	The path to parse.
356  * @return		The last component of the given path.
357  */
358 #ifndef DIR_SEPARATOR
359 #define DIR_SEPARATOR '/'
360 #endif
361 
362 #ifdef _WIN32
363 #define HAVE_DOS_BASED_FILE_SYSTEM
364 #ifndef DIR_SEPARATOR_2
365 #define DIR_SEPARATOR_2 '\\'
366 #endif
367 #endif
368 
369 /* Define IS_DIR_SEPARATOR.  */
370 #ifndef DIR_SEPARATOR_2
371 #define IS_DIR_SEPARATOR(ch) ((ch) == DIR_SEPARATOR)
372 #else /* DIR_SEPARATOR_2 */
373 #define IS_DIR_SEPARATOR(ch) (((ch) == DIR_SEPARATOR) || ((ch) == DIR_SEPARATOR_2))
374 #endif /* DIR_SEPARATOR_2 */
375 
376 /**
377  * returns a pointer on the basename part of name
378  */
fileBasename(const char * name)379 const char* fileBasename(const char* name)
380 {
381 #if defined(HAVE_DOS_BASED_FILE_SYSTEM)
382     /* Skip over the disk name in MSDOS pathnames. */
383     if (isalpha(name[0]) && name[1] == ':') name += 2;
384 #endif
385     const char* base;
386     for (base = name; *name; name++) {
387         if (IS_DIR_SEPARATOR(*name)) {
388             base = name + 1;
389         }
390     }
391     return base;
392 }
393 
394 /**
395  * returns a string containing the dirname of name
396  * If no dirname, returns "."
397  */
fileDirname(const string & name)398 string fileDirname(const string& name)
399 {
400     const char*        base = fileBasename(name.c_str());
401     const unsigned int size = base - name.c_str();
402     string             dirname;
403 
404     if (size == 0) {
405         dirname += '.';
406     } else if (size == 1) {
407         dirname += name[0];
408     } else {
409         for (unsigned int i = 0; i < size - 1; i++) {
410             dirname += name[i];
411         }
412     }
413     return dirname;
414 }
415 
stripEnd(const string & name,const string & ext)416 string stripEnd(const string& name, const string& ext)
417 {
418     if (name.length() >= 4 && name.substr(name.length() - ext.length()) == ext) {
419         return name.substr(0, name.length() - ext.length());
420     } else {
421         return name;
422     }
423 }
424 
checkURL(const char * filename)425 bool checkURL(const char* filename)
426 {
427     char* fileBuf = nullptr;
428 
429     // Tries to open as an URL for a local file
430     if (strstr(filename, "file://") != 0) {
431         // Tries to open as a regular file after removing 'file://'
432         return checkFile(&filename[7]);
433         // Tries to open as a http URL
434     } else if ((strstr(filename, "http://") != 0) || (strstr(filename, "https://") != 0)) {
435         if (http_fetch(filename, &fileBuf) != -1) {
436             return true;
437         } else {
438             stringstream error;
439             error << "ERROR : unable to access URL '" << ((filename) ? filename : "null") << "' : ";
440             error << http_strerror() << endl;
441             throw faustexception(error.str());
442         }
443     } else {
444         // Otherwise tries to open as a regular file
445         return checkFile(filename);
446     }
447 }
448 
449 /**
450  * Copy or remove license header. Architecture files can contain a header specifying
451  * the license. If this header contains an exception tag (for example "FAUST COMPILER EXCEPTION")
452  * it is an indication for the compiler to remove the license header from the resulting code.
453  * A header is the first non blank line that begins a comment.
454  */
streamCopyLicense(istream & src,ostream & dst,const string & exceptiontag)455 void streamCopyLicense(istream& src, ostream& dst, const string& exceptiontag)
456 {
457     string         line;
458     vector<string> H;
459 
460     // skip blank lines
461     while (getline(src, line) && isBlank(line)) dst << line << endl;
462 
463     // first non blank should start a comment
464     if (line.find("/*") == string::npos) {
465         dst << line << endl;
466         return;
467     }
468 
469     // copy the header into H
470     bool remove = false;
471     H.push_back(line);
472 
473     while (getline(src, line) && line.find("*/") == string::npos) {
474         H.push_back(line);
475         if (line.find(exceptiontag) != string::npos) remove = true;
476     }
477 
478     // copy the header unless explicitely granted to remove it
479     if (!remove) {
480         // copy the header
481         for (size_t i = 0; i < H.size(); i++) {
482             dst << H[i] << endl;
483         }
484         dst << line << endl;
485     }
486 }
487 
488 /**
489  * Copy src to dst until a specific line
490  */
491 
streamCopyUntil(istream & src,ostream & dst,const string & until)492 void streamCopyUntil(istream& src, ostream& dst, const string& until)
493 {
494     string fname, line;
495     while (getline(src, line) && (removeSpaces(line) != until)) {
496         if (gGlobal->gInlineArchSwitch && isFaustInclude(line, fname)) {
497             inject(dst, fname);
498         } else {
499             dst << replaceClassName(line) << endl;
500         }
501     }
502 }
503 
504 /**
505  * Copy src to dst until end
506  */
streamCopyUntilEnd(istream & src,ostream & dst)507 void streamCopyUntilEnd(istream& src, ostream& dst)
508 {
509     streamCopyUntil(src, dst, "<<<FORBIDDEN LINE IN A FAUST ARCHITECTURE FILE>>>");
510 }
511 
makeOutputFile(const std::string & fname)512 std::string makeOutputFile(const std::string& fname)
513 {
514     return (gGlobal->gOutputDir != "") ? (gGlobal->gOutputDir + "/" + fname) : fname;
515 }
516