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 /*
23  sourcereader : Faust source file reader
24  This component is in charge of mapping filenames to
25  the list of Faust definitions they contain.
26 */
27 
28 #include <iostream>
29 #include <map>
30 #include <list>
31 #include <string>
32 #include <sstream>
33 
34 #ifndef _WIN32
35 #include <unistd.h>
36 #endif
37 
38 #ifdef EMCC
39 #include <emscripten.h>
40 #endif
41 
42 #include "compatibility.hh"
43 #include "sourcereader.hh"
44 #include "sourcefetcher.hh"
45 #include "enrobage.hh"
46 #include "ppbox.hh"
47 #include "exception.hh"
48 #include "global.hh"
49 #include "Text.hh"
50 
51 using namespace std;
52 
53 /****************************************************************
54  Parser variables
55  *****************************************************************/
56 
57 int yyparse();
58 int yylex_destroy(void);
59 void yyrestart(FILE* new_file);
60 struct yy_buffer_state* yy_scan_string(const char *yy_str); // In principle YY_BUFFER_STATE
61 
62 extern int yyerr;
63 extern int yydebug;
64 extern FILE* yyin;
65 extern int yylineno;
66 extern const char* yyfilename;
67 
68 /**
69  * Checks an argument list for containing only
70  * standard identifiers, no patterns and
71  * is linear.
72  * @param args the argument list to check
73  * @return true if it contains only identifiers
74  */
75 
standardArgList(Tree args)76 static bool standardArgList(Tree args)
77 {
78 	map<Tree,int> L;
79 	while (isList(args)) {
80 		if (!isBoxIdent(hd(args))) return false;
81 		if (++L[hd(args)] > 1) return false;
82 		args = tl(args);
83 	}
84 	return true;
85 }
86 
printPatternError(Tree symbol,Tree lhs1,Tree rhs1,Tree lhs2,Tree rhs2)87 static string printPatternError(Tree symbol, Tree lhs1, Tree rhs1, Tree lhs2, Tree rhs2)
88 {
89     stringstream error;
90 
91     if (!symbol) {
92         error << "ERROR : inconsistent number of parameters in pattern-matching rule: "
93         << boxpp(reverse(lhs2)) << " => " << boxpp(rhs2) << ";"
94         << " previous rule was: "
95         << boxpp(reverse(lhs1)) << " => " << boxpp(rhs1) << ";"
96         << endl;
97     } else {
98         error << "ERROR (file " << yyfilename << ":" << yylineno << ") : in the definition of " << boxpp(symbol) << endl
99         << "Inconsistent number of parameters in pattern-matching rule: "
100         << boxpp(reverse(lhs2)) << " => " << boxpp(rhs2) << ";"
101         << " previous rule was: "
102         << boxpp(reverse(lhs1)) << " => " << boxpp(rhs1) << ";"
103         << endl;
104     }
105 
106     return error.str();
107 }
108 
printRedefinitionError(Tree symbol,list<Tree> & variants)109 static string printRedefinitionError(Tree symbol, list<Tree>& variants)
110 {
111     stringstream error;
112 
113     error << "ERROR (file " << yyfilename << ":" << yylineno << ") : multiple definitions of symbol " << boxpp(symbol) << endl;
114     for (list<Tree>::iterator p = variants.begin(); p != variants.end(); p++) {
115         Tree params = hd(*p);
116         Tree body = tl(*p);
117         if (isNil(params)) {
118             error << boxpp(symbol) << " = " << boxpp(body) << ";" << endl;
119         } else {
120             error << boxpp(symbol) << boxpp(params) << " = " << boxpp(body) << ";" << endl;
121         }
122     }
123 
124     return error.str();
125 }
126 
127 /**
128  * Transforms a list of variants (arglist.body)
129  * into an abstraction or a boxCase.
130  * @param symbol name only used in case of error
131  * @param variants list of variants (arglist.body)
132  * @return the corresponding box expression
133  */
134 
makeDefinition(Tree symbol,list<Tree> & variants)135 static Tree makeDefinition(Tree symbol, list<Tree>& variants)
136 {
137 	if (variants.size() == 1) {
138 		Tree rhs = *(variants.begin());
139 		Tree args= hd(rhs);
140 		Tree body= tl(rhs);
141 
142 		if (isNil(args)) {
143 			return body;
144 		} else if (standardArgList(args)) {
145 			return buildBoxAbstr(args, body);
146 		} else {
147 			return boxCase(cons(rhs,gGlobal->nil));
148 		}
149 	} else {
150 		list<Tree>::iterator p;
151 		Tree l = gGlobal->nil;
152 		Tree prev = *variants.begin();
153 		int npat = len(hd(prev));
154 
155         if (npat == 0) {
156             throw faustexception(printRedefinitionError(symbol, variants));
157         }
158 
159 		for (p = variants.begin(); p != variants.end(); p++) {
160 			Tree cur = *p;
161 			if ((npat == 0) || (npat != len(hd(cur)))) {
162                 throw faustexception(printPatternError(symbol, hd(prev), tl(prev), hd(cur), tl(cur)));
163 			}
164 			prev = cur;
165 			l = cons(*p,l);
166 		}
167 		return boxCase(l);
168 	}
169 }
170 
171 // Add function metadata (using a boxMetadata construction) to a list of definitions
addFunctionMetadata(Tree ldef,FunMDSet & M)172 static Tree addFunctionMetadata(Tree ldef, FunMDSet& M)
173 {
174     Tree lresult = gGlobal->nil; // the transformed list of definitions
175 
176     // for each definition def of ldef
177     for ( ;!isNil(ldef); ldef = tl(ldef)) {
178         Tree def = hd(ldef);
179         Tree fname;
180         if (isNil(def)) {
181             // skip null definitions produced by declarations
182         } else if (isImportFile(def, fname)) {
183             lresult = cons(def, lresult);
184         } else {
185             Tree foo = hd(def);
186             Tree exp = tl(def);
187             for (const auto& m : M[foo]) {
188                 exp = boxMetadata(exp, m);
189             }
190             lresult = cons(cons(foo,exp), lresult);
191         }
192     }
193     return lresult;
194 }
195 
checkName()196 void SourceReader::checkName()
197 {
198     if (gGlobal->gMasterDocument == yyfilename) {
199         Tree name = tree("name");
200         if (gGlobal->gMetaDataSet.find(name) == gGlobal->gMetaDataSet.end()) {
201             gGlobal->gMetaDataSet[name].insert(tree(quote(stripEnd(basename((char*)yyfilename), ".dsp"))));
202         }
203         gGlobal->gMetaDataSet[tree("filename")].insert(tree(quote(basename((char*)yyfilename))));
204     }
205 }
206 
207 /**
208  * Parse a single Faust source file, returns the list of
209  * definitions it contains.
210  *
211  * @param fname the name of the file to parse
212  * @return the list of definitions it contains
213  */
214 
isURL(const char * name)215 inline bool isURL(const char* name) { return (strstr(name, "http://") != 0) || (strstr(name, "https://") != 0); }
isFILE(const char * name)216 inline bool isFILE(const char* name) { return strstr(name, "file://") != 0; }
217 
parseFile(const char * fname)218 Tree SourceReader::parseFile(const char* fname)
219 {
220     yyerr = 0;
221     yylineno = 1;
222     yyfilename = fname;
223 
224     // We are requested to parse an URL file
225     if (isURL(yyfilename)) {
226         char* buffer = nullptr;
227     #ifdef EMCC
228         // Call JS code to load URL
229         buffer = (char*)EM_ASM_INT({
230             var dsp_code = "";
231             try {
232                 var xmlhttp = new XMLHttpRequest();
233                 xmlhttp.open("GET", Module.UTF8ToString($0), false);
234                 xmlhttp.send();
235                 if (xmlhttp.status == 200) {
236                     dsp_code = xmlhttp.responseText;
237                 }
238             } catch(e) {
239                 console.log(e);
240             }
241             return allocate(intArrayFromString(dsp_code), 'i8', ALLOC_STACK);
242         }, yyfilename);
243 
244         Tree res = nullptr;
245         if (strlen(buffer) == 0) {
246             stringstream error;
247             error << "ERROR : unable to access URL '" << fname << "'" << endl;
248             throw faustexception(error.str());
249         } else {
250             yy_scan_string(buffer);
251             res = parseLocal(yyfilename);
252         }
253     #else
254         // Otherwise use http URL fetch code
255         if (http_fetch(yyfilename, &buffer) == -1) {
256             stringstream error;
257             error << "ERROR : unable to access URL '" << fname << "' : " << http_strerror() << endl;
258             throw faustexception(error.str());
259         }
260         yy_scan_string(buffer);
261         Tree res = parseLocal(yyfilename);
262         // 'http_fetch' result must be deallocated
263         free(buffer);
264     #endif
265         return res;
266 
267     } else {
268 
269         // Test for local url
270         if (isFILE(yyfilename)) {
271             yyfilename = &yyfilename[7]; // skip 'file://'
272         }
273 
274         // Try to open local file
275         string fullpath1;
276         FILE* tmp_file = yyin = fopenSearch(yyfilename, fullpath1); // Keep file to properly close it
277         if (yyin) {
278             Tree res = parseLocal(fullpath1.c_str());
279             fclose(tmp_file);
280             return res;
281         } else {
282         #ifdef EMCC
283             // Try to open with the complete URL
284             Tree res = nullptr;
285             for (size_t i = 0; i < gGlobal->gImportDirList.size(); i++) {
286                 if (isURL(gGlobal->gImportDirList[i].c_str())) {
287                     // Keep the created filename in the global state, so that the 'yyfilename'
288                     // global variable always points to a valid string
289                     gGlobal->gImportFilename = gGlobal->gImportDirList[i] + fname;
290                     if ((res = parseFile(gGlobal->gImportFilename.c_str()))) return res;
291                 }
292             }
293         #endif
294             stringstream error;
295             error << "ERROR : unable to open file " << yyfilename << endl;
296             throw faustexception(error.str());
297         }
298     }
299 }
300 
parseString(const char * fname)301 Tree SourceReader::parseString(const char* fname)
302 {
303     yyerr = 0;
304     yylineno = 1;
305     yyfilename = fname;
306     yy_scan_string(gGlobal->gInputString);
307 
308     // Clear global "inputstring" so that imported files will be correctly parsed with "parse"
309     gGlobal->gInputString = nullptr;
310     return parseLocal(fname);
311 }
312 
parseLocal(const char * fname)313 Tree SourceReader::parseLocal(const char* fname)
314 {
315     int r = yyparse();
316     stringstream error;
317 
318     if (r) {
319         error << "ERROR : parse code = " << r << endl;
320         throw faustexception(error.str());
321     }
322     if (yyerr > 0) {
323         error << "ERROR : parse code = " << yyerr << endl;
324         throw faustexception(error.str());
325     }
326 
327     yylex_destroy();
328 
329     // We have parsed a valid file
330     checkName();
331     fFilePathnames.push_back(fname);
332     return gGlobal->gResult;
333 }
334 
335 /**
336  * Check if a file as been read and is in the "cache"
337  *
338  * @param fname the name of the file to check
339  * @return true if the file is in the cache
340  */
341 
cached(string fname)342 bool SourceReader::cached(string fname)
343 {
344 	return fFileCache.find(fname) != fFileCache.end();
345 }
346 
347 /**
348  * Return the list of definitions file contains. Cache the result.
349  *
350  * @param fname the name of the file to check
351  * @return the list of definitions it contains
352  */
353 
getList(const char * fname)354 Tree SourceReader::getList(const char* fname)
355 {
356 	if (!cached(fname)) {
357         // Previous metadata need to be cleared before parsing a file
358         gGlobal->gFunMDSet.clear();
359         Tree ldef = (gGlobal->gInputString) ? parseString(fname) : parseFile(fname);
360         // Definitions with metadata have to be wrapped into a boxMetadata construction
361         fFileCache[fname] = addFunctionMetadata(ldef, gGlobal->gFunMDSet);
362 	}
363     return fFileCache[fname];
364 }
365 
366 /**
367  * Return a vector of pathnames representing the list
368  * of all the source files that have been required
369  * to evaluate process (those in fFileCache)
370  */
371 
listSrcFiles()372 vector<string> SourceReader::listSrcFiles()
373 {
374     return fFilePathnames;
375 }
376 
377 /**
378  * Return a vector of pathnames representing the list
379  * of all the source files that have been required
380  * to evaluate process, without the DSP file itself
381  */
382 
listLibraryFiles()383 vector<string> SourceReader::listLibraryFiles()
384 {
385     vector<string> tmp = fFilePathnames;
386     if (tmp.size() > 0) tmp.erase(tmp.begin());
387     return tmp;
388 }
389 
390 /**
391  * Return the list of definitions where all imports have been expanded.
392  *
393  * @param ldef the list of definitions to expand
394  * @return the expanded list of definitions
395  */
396 
expandList(Tree ldef)397 Tree SourceReader::expandList(Tree ldef)
398 {
399 	set<string> visited;
400 	return expandRec(ldef, visited, gGlobal->nil);
401 }
402 
expandRec(Tree ldef,set<string> & visited,Tree lresult)403 Tree SourceReader::expandRec(Tree ldef, set<string>& visited, Tree lresult)
404 {
405 	for (;!isNil(ldef); ldef = tl(ldef)) {
406 		Tree d = hd(ldef);
407 		Tree fname;
408 		if (isNil(d)) {
409 			// skill null definitions produced by declarations
410 		} else if (isImportFile(d, fname)) {
411 			const char* f = tree2str(fname);
412 			if (visited.find(f) == visited.end()) {
413 				visited.insert(f);
414 				lresult = expandRec(getList(f), visited, lresult);
415 			}
416 		} else {
417 			lresult = cons(d, lresult);
418 		}
419 	}
420 	return lresult;
421 }
422 
423 // =================
424 // Public functions
425 // =================
426 
427 /**
428  * Formats a list of raw definitions represented by triplets
429  * <name, arglist, body> into abstractions or pattern
430  * matching rules when appropriate.
431  *
432  * @param rldef list of raw definitions in reverse order
433  * @return the list of formatted definitions
434  */
435 
formatDefinitions(Tree rldef)436 Tree formatDefinitions(Tree rldef)
437 {
438     map<Tree, list<Tree> > dic;
439     map<Tree, list<Tree> >::iterator p;
440     Tree ldef2 = gGlobal->nil;
441     Tree file;
442 
443     // Collects the definitions in a dictionnary
444     while (!isNil(rldef)) {
445         Tree def = hd(rldef);
446         rldef = tl(rldef);
447         if (isImportFile(def, file)) {
448             ldef2 = cons(def,ldef2);
449         } else if (!isNil(def)) {
450             //cout << " def : " << *def << endl;
451             dic[hd(def)].push_front(tl(def));
452         }
453     }
454 
455     // Produces the definitions
456     for (p = dic.begin(); p != dic.end(); p++) {
457         ldef2 = cons(cons(p->first, makeDefinition(p->first, p->second)), ldef2);
458     }
459 
460     return ldef2;
461 }
462 
checkRulelist(Tree lr)463 Tree checkRulelist(Tree lr)
464 {
465     Tree lrules = lr;
466     if (isNil(lrules)) {
467         stringstream error;
468         error << "ERROR (file " << yyfilename << ":" << yylineno << ") : a case expression can't be empty" << endl;
469         throw faustexception(error.str());
470     }
471     // first pattern used as a reference
472     Tree lhs1 = hd(hd(lrules));
473     Tree rhs1 = tl(hd(lrules));
474     int npat = len(lhs1);
475     lrules = tl(lrules);
476     while (!isNil(lrules)) {
477         Tree lhs2 = hd(hd(lrules));
478         Tree rhs2 = tl(hd(lrules));
479         if (npat != len(lhs2)) {
480             throw faustexception(printPatternError(nullptr, lhs1, rhs1, lhs2, rhs2));
481         }
482         lhs1 = lhs2;
483         rhs1 = rhs2;
484         lrules = tl(lrules);
485     }
486     return lr;
487 }
488 
declareMetadata(Tree key,Tree value)489 void declareMetadata(Tree key, Tree value)
490 {
491     if (gGlobal->gMasterDocument == yyfilename) {
492         // Inside master document, no prefix needed to declare metadata
493         gGlobal->gMetaDataSet[key].insert(value);
494     } else {
495         string fkey(yyfilename);
496         if (fkey != "") {
497             fkey += "/";
498         }
499         fkey += tree2str(key);
500         gGlobal->gMetaDataSet[tree(fkey.c_str())].insert(value);
501     }
502 }
503 
504 /*
505 fun -> (file*fun -> {key*value,...})
506 
507 gGlobal->gFunMetaDataSet[fun].insert(file*fun*key*value);
508 gFunMetaDataSet = map<tree, tuple<Tree,Tree,Tree,Tree>>
509 */
510 
511 // Called by parser to create function's metadata
declareDefinitionMetadata(Tree id,Tree key,Tree value)512 void declareDefinitionMetadata(Tree id, Tree key, Tree value)
513 {
514     stringstream fullkeystream;
515     fullkeystream << yyfilename << "/" << tree2str(id) << ":" << tree2str(key);
516     string fullkey = fullkeystream.str();
517     Tree md = cons(tree(fullkey), value);
518     //cout << "Creation of a function metadata : " << *md << endl;
519     gGlobal->gFunMDSet[boxIdent(tree2str(id))].insert(md);
520 }
521 
declareDoc(Tree t)522 void declareDoc(Tree t)
523 {
524 	gGlobal->gDocVector.push_back(t);
525 }
526