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