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