6e3624c800979a9c4eec411e50e6d670728ab34b
[Faustine.git] / interpretor / preprocessor / faust-0.9.47mr3 / compiler / draw / drawschema.cpp
1 /************************************************************************
2 ************************************************************************
3 FAUST compiler
4 Copyright (C) 2003-2004 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 * @file drawschema.cpp
24 * Implement block-diagram schema generation in svg or postscript format.
25 * The result is a folder containing one or more schema files in svg or
26 * ps format. Complex block-diagrams are automatically splitted.
27 */
28
29
30 #include <stdio.h>
31 #include <ctype.h>
32 #include <sys/stat.h>
33 #include <sys/types.h>
34 #include <errno.h>
35 #include <string.h>
36
37 #include <ostream>
38 #include <sstream>
39 #include <set>
40 #include <utility>
41 #include <map>
42 #include <stack>
43 #include <string>
44
45 #include "boxes.hh"
46 #include "ppbox.hh"
47 #include "prim2.hh"
48
49 #include <vector>
50 #include "devLib.h"
51 #include "ppbox.hh"
52 #include "xtended.hh"
53 #include "occurrences.hh"
54 #include "boxcomplexity.h"
55
56 #include "schema.h"
57 #include "drawschema.hh"
58 #include "compatibility.hh"
59 #include "names.hh"
60 #include "description.hh"
61 #include "property.hh"
62
63
64
65 #if 0
66 #define linkcolor "#b3d1dc"
67 #define normalcolor "#ffeaa2"
68 #define uicolor "#F1CFA1"
69 #define slotcolor "#ffffd7"
70 #define numcolor "#ffffff"
71 #endif
72
73 #if 0
74 #define linkcolor "#F57900"
75 #define normalcolor "#4B71A1"
76 #define uicolor "#47945E"
77 #define slotcolor "#EDD400"
78 #define numcolor "#4B71A1"
79 #endif
80
81 #if 0
82 #define linkcolor "#47945E"
83 #define normalcolor "#4B71A1"
84 #define uicolor "#f44800"
85 #define slotcolor "#EDD400"
86 #define numcolor "#f44800"
87 #endif
88
89 #if 0
90 #define linkcolor "#47945E"
91 #define normalcolor "#4B71A1"
92 #define uicolor "#816647"
93 #define slotcolor "#EDD400"
94 #define numcolor "#f44800"
95 #endif
96
97 #if 0
98 #define linkcolor "#003366"
99 #define normalcolor "#4B71A1"
100 #define uicolor "#816647"
101 #define slotcolor "#EDD400"
102 #define numcolor "#f44800"
103 #endif
104
105 #if 0
106 #define linkcolor "#003366"
107 #define normalcolor "#4B71A1"
108 #define uicolor "#477881"
109 #define slotcolor "#816647"
110 #define numcolor "#f44800"
111 #endif
112
113
114 #if 1
115 #define linkcolor "#003366"
116 #define normalcolor "#4B71A1"
117 #define uicolor "#477881"
118 #define slotcolor "#47945E"
119 #define numcolor "#f44800"
120 #define invcolor "#ffffff"
121 #endif
122
123 using namespace std;
124
125 // external parameters
126 extern int gFoldThreshold; // max diagram complexity before folding
127
128
129 // internal state during drawing
130 static Occurrences* gOccurrences;
131 static bool sFoldingFlag; // true with complex block-diagrams
132 static stack<Tree> gPendingExp; // Expressions that need to be drawn
133 static set<Tree> gDrawnExp; // Expressions drawn or scheduled so far
134 static const char* gDevSuffix; // .svg or .ps used to choose output device
135 static char gCurrentDir[512]; // room to save current directory name
136 static string gSchemaFileName; // name of schema file beeing generated
137 static map<Tree,string> gBackLink; // link to enclosing file for sub schema
138
139 // prototypes of internal functions
140 static void writeSchemaFile(Tree bd);
141 static schema* generateDiagramSchema (Tree bd);
142 static schema* generateInsideSchema(Tree t);
143 static void scheduleDrawing(Tree t);
144 static bool pendingDrawing(Tree& t);
145 static schema* generateAbstractionSchema(schema* x, Tree t);
146 static schema* generateOutputSlotSchema(Tree a);
147 static schema* generateInputSlotSchema(Tree a);
148 static schema* generateBargraphSchema(Tree t);
149 static schema* generateUserInterfaceSchema(Tree t);
150 static char* legalFileName(Tree t, int n, char* dst);
151 static int cholddir ();
152 static int mkchdir(const char* dirname);
153
154
155
156
157 /**
158 *The entry point to generate from a block diagram as a set of
159 *svg files stored in the directory "<projname>-svg/" or
160 *"<projname>-ps/" depending of <dev>.
161 */
162 void drawSchema(Tree bd, const char* projname, const char* dev)
163 {
164 gDevSuffix = dev;
165 sFoldingFlag = boxComplexity(bd) > gFoldThreshold;
166
167 mkchdir(projname); // create a directory to store files
168
169 scheduleDrawing(bd); // schedule the initial drawing
170
171 Tree t; while (pendingDrawing(t)) {
172 writeSchemaFile(t); // generate all the pending drawing
173 }
174
175 cholddir(); // return to current directory
176 }
177
178
179 /************************************************************************
180 ************************************************************************
181 IMPLEMENTATION
182 ************************************************************************
183 ************************************************************************/
184
185
186 //------------------- to schedule and retreive drawing ------------------
187
188 /**
189 * Schedule a makeBlockSchema diagram to be drawn.
190 */
191 static void scheduleDrawing(Tree t)
192 {
193 if (gDrawnExp.find(t) == gDrawnExp.end()) {
194 gDrawnExp.insert(t);
195 gBackLink.insert(make_pair(t,gSchemaFileName)); // remember the enclosing filename
196 gPendingExp.push(t);
197 }
198 }
199
200 /**
201 * Retrieve next block diagram that must be drawn
202 */
203 static bool pendingDrawing(Tree& t)
204 {
205 if (gPendingExp.empty()) return false;
206 t = gPendingExp.top();
207 gPendingExp.pop();
208 return true;
209 }
210
211
212
213 //------------------------ dealing with files -------------------------
214
215 /**
216 * Write a top level diagram. A top level diagram
217 * is decorated with its definition name property
218 * and is drawn in an individual file
219 */
220 static void writeSchemaFile(Tree bd)
221 {
222 Tree id;
223 schema* ts;
224
225 char temp[1024];
226
227 gOccurrences = new Occurrences(bd);
228
229 bool hasname = getDefNameProperty(bd, id);
230
231 //assert(hasname);
232 if (!hasname) {
233 // create an arbitrary name
234 id = tree(Node(unique("diagram_")));
235 }
236
237 // generate legal file name for the schema
238 stringstream s1; s1 << legalFileName(bd, 1024, temp) << "." << gDevSuffix;
239 gSchemaFileName = s1.str();
240
241 // generate the label of the schema
242 stringstream s2; s2 << tree2str(id);
243 string link = gBackLink[bd];
244 ts = makeTopSchema(generateInsideSchema(bd), 20, s2.str(), link);
245 // draw to the device defined by gDevSuffix
246 if (strcmp(gDevSuffix, "svg") == 0) {
247 SVGDev dev(s1.str().c_str(), ts->width(), ts->height());
248 ts->place(0,0, kLeftRight);
249 ts->draw(dev);
250 { collector c; ts->collectTraits(c); c.draw(dev); }
251 } else {
252 PSDev dev(s1.str().c_str(), ts->width(), ts->height());
253 ts->place(0,0, kLeftRight);
254 ts->draw(dev);
255 {
256 collector c;
257 ts->collectTraits(c);
258 c.draw(dev);
259 }
260 }
261 }
262
263
264
265 /**
266 * Create a new directory in the current one to store the diagrams.
267 * The current directory is saved to be later restaured.
268 */
269 static int mkchdir(const char* dirname)
270 {
271 //cerr << "mkchdir of " << dirname << endl;
272 if (getcwd(gCurrentDir, 512) != 0) {
273 int status = mkdir(dirname, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
274 if (status == 0 || errno == EEXIST) {
275 if (chdir(dirname) == 0) {
276 return 0;
277 }
278 }
279 }
280 perror("mkchdir");
281 exit(errno);
282 //return errno;
283 }
284
285
286 /**
287 *Switch back to the previously stored current directory
288 */
289 static int cholddir ()
290 {
291 if (chdir(gCurrentDir) == 0) {
292 return 0;
293 } else {
294 perror("cholddir");
295 exit(errno);
296 }
297 }
298
299
300 /**
301 * Transform the definition name property of tree <t> into a
302 * legal file name. The resulting file name is stored in
303 * <dst> a table of at least <n> chars. Returns the <dst> pointer
304 * for convenience.
305 */
306 static char* legalFileName(Tree t, int n, char* dst)
307 {
308 Tree id;
309 int i=0;
310 if (getDefNameProperty(t, id)) {
311 const char* src = tree2str(id);
312 for (i=0; isalnum(src[i]) && i<16; i++) {
313 dst[i] = src[i];
314 }
315 }
316 dst[i] = 0;
317 if (strcmp(dst, "process") != 0) {
318 // if it is not process add the hex address to make the name unique
319 snprintf(&dst[i], n-i, "-%p", t);
320 }
321 return dst;
322 }
323
324
325
326 //------------------------ generating the schema -------------------------
327
328
329 /**
330 * isInverter(t) returns true if t == '*(-1)'. This test is used
331 * to simplify diagram by using a special symbol for inverters.
332 */
333 Tree gInverter[6];
334
335 static bool isInverter(Tree t)
336 {
337 // init gInverted table. For some reason doesn't work if done outside
338 if (gInverter[0] == 0) {
339 gInverter[0] = boxSeq(boxPar(boxWire(), boxInt(-1)),boxPrim2(sigMul));
340 gInverter[1] = boxSeq(boxPar(boxInt(-1), boxWire()),boxPrim2(sigMul));
341 gInverter[2] = boxSeq(boxPar(boxWire(), boxReal(-1.0)),boxPrim2(sigMul));
342 gInverter[3] = boxSeq(boxPar(boxReal(-1.0), boxWire()),boxPrim2(sigMul));
343 gInverter[4] = boxSeq(boxPar(boxInt(0), boxWire()),boxPrim2(sigSub));
344 gInverter[5] = boxSeq(boxPar(boxReal(0.0), boxWire()),boxPrim2(sigSub));
345 };
346
347 //cerr << "isInverter " << t << '$' << boxpp(t) << endl;
348 for (int i=0; i<6; i++) {
349 if (t == gInverter[i]) return true;
350 }
351 return false;
352 }
353
354
355 /**
356 * Compute the Pure Routing property, that is expressions
357 * only made of cut, wires and slots. No labels will be
358 * dispayed for pure routing expressions.
359 */
360 property<bool> gPureRoutingProperty;
361
362 static bool isPureRouting(Tree t)
363 {
364 bool r;
365 int ID;
366 Tree x,y;
367
368 if (gPureRoutingProperty.get(t,r)) {
369 return r;
370 } else if ( isBoxCut(t)
371 || isBoxWire(t)
372 || isInverter(t)
373 || isBoxSlot(t, &ID)
374 || (isBoxPar(t,x,y) && isPureRouting(x) && isPureRouting(y))
375 || (isBoxSeq(t,x,y) && isPureRouting(x) && isPureRouting(y))
376 || (isBoxSplit(t,x,y) && isPureRouting(x) && isPureRouting(y))
377 || (isBoxMerge(t,x,y) && isPureRouting(x) && isPureRouting(y))
378 ) {
379 gPureRoutingProperty.set(t,true);
380 return true;
381 } else {
382 gPureRoutingProperty.set(t,false);
383 return false;
384 }
385 }
386
387
388 /**
389 * Generate an appropriate schema according to
390 * the type of block diagram. When folding is requiered,
391 * instead of going down block-diagrams with a name,
392 * schedule them for an individual file.
393 */
394 static schema* generateDiagramSchema(Tree t)
395 {
396 Tree id;
397 int ins, outs;
398
399 //cerr << t << " generateDiagramSchema " << boxpp(t)<< endl;
400
401 if (getDefNameProperty(t, id)) {
402 stringstream s; s << tree2str(id);
403 //cerr << t << "\tNAMED : " << s.str() << endl;
404 }
405
406 if ( sFoldingFlag && /*(gOccurrences->getCount(t) > 0) &&*/
407 (boxComplexity(t) > 2) && getDefNameProperty(t, id)) {
408 char temp[1024];
409 getBoxType(t, &ins, &outs);
410 stringstream s, l;
411 s << tree2str(id);
412 l << legalFileName(t,1024,temp) << "." << gDevSuffix;
413 scheduleDrawing(t);
414 return makeBlockSchema(ins, outs, s.str(), linkcolor, l.str());
415
416 } else if (getDefNameProperty(t, id) && ! isPureRouting(t)) {
417 // named case : not a slot, with a name
418 // draw a line around the object with its name
419 stringstream s; s << tree2str(id);
420 return makeDecorateSchema(generateInsideSchema(t), 10, s.str());
421
422 } else {
423 // normal case
424 return generateInsideSchema(t);
425 }
426 }
427
428
429
430 /**
431 * Generate the inside schema of a block diagram
432 * according to its type
433 */
434 static schema* generateInsideSchema(Tree t)
435 {
436 Tree a, b, ff, l, type,name,file;
437 int i;
438 double r;
439 prim0 p0;
440 prim1 p1;
441 prim2 p2;
442 prim3 p3;
443 prim4 p4;
444 prim5 p5;
445
446
447 xtended* xt = (xtended*)getUserData(t);
448
449 if (xt) { return makeBlockSchema(xt->arity(), 1, xt->name(), normalcolor, ""); }
450
451 else if (isInverter(t)) { return makeInverterSchema(invcolor); }
452
453 else if (isBoxInt(t, &i)) { stringstream s; s << i; return makeBlockSchema(0, 1, s.str(), numcolor, "" ); }
454 else if (isBoxReal(t, &r)) { stringstream s; s << r; return makeBlockSchema(0, 1, s.str(), numcolor, "" ); }
455 else if (isBoxWire(t)) { return makeCableSchema(); }
456 else if (isBoxCut(t)) { return makeCutSchema(); }
457
458 else if (isBoxPrim0(t, &p0)) { return makeBlockSchema(0, 1, prim0name(p0), normalcolor, ""); }
459 else if (isBoxPrim1(t, &p1)) { return makeBlockSchema(1, 1, prim1name(p1), normalcolor, ""); }
460 else if (isBoxPrim2(t, &p2)) { return makeBlockSchema(2, 1, prim2name(p2), normalcolor, ""); }
461 else if (isBoxPrim3(t, &p3)) { return makeBlockSchema(3, 1, prim3name(p3), normalcolor, ""); }
462 else if (isBoxPrim4(t, &p4)) { return makeBlockSchema(4, 1, prim4name(p4), normalcolor, ""); }
463 else if (isBoxPrim5(t, &p5)) { return makeBlockSchema(5, 1, prim5name(p5), normalcolor, ""); }
464
465 else if (isBoxFFun(t, ff)) { return makeBlockSchema(ffarity(ff), 1, ffname(ff), normalcolor, ""); }
466 else if (isBoxFConst(t, type,name,file)) { return makeBlockSchema(0, 1, tree2str(name), normalcolor, ""); }
467 else if (isBoxFVar (t, type, name,file)) { return makeBlockSchema(0, 1, tree2str(name), normalcolor, ""); }
468
469 else if (isBoxButton(t)) { return generateUserInterfaceSchema(t); }
470 else if (isBoxCheckbox(t)) { return generateUserInterfaceSchema(t); }
471 else if (isBoxVSlider(t)) { return generateUserInterfaceSchema(t); }
472 else if (isBoxHSlider(t)) { return generateUserInterfaceSchema(t); }
473 else if (isBoxNumEntry(t)) { return generateUserInterfaceSchema(t); }
474 else if (isBoxVBargraph(t)) { return generateBargraphSchema(t); }
475 else if (isBoxHBargraph(t)) { return generateBargraphSchema(t); }
476
477 // don't draw group rectangle when labels are empty (ie "")
478 else if (isBoxVGroup(t,l,a)) { stringstream s; s << "vgroup(" << extractName(l) << ")";
479 schema* r = generateDiagramSchema(a);
480 return makeDecorateSchema(r, 10, s.str()); }
481 else if (isBoxHGroup(t,l,a)) { stringstream s; s << "hgroup(" << extractName(l) << ")";
482 schema* r = generateDiagramSchema(a);
483 return makeDecorateSchema(r, 10, s.str()); }
484 else if (isBoxTGroup(t,l,a)) { stringstream s; s << "tgroup(" << extractName(l) << ")";
485 schema* r = generateDiagramSchema(a);
486 return makeDecorateSchema(r, 10, s.str()); }
487
488 else if (isBoxSeq(t, a, b)) { return makeSeqSchema(generateDiagramSchema(a), generateDiagramSchema(b)); }
489 else if (isBoxPar(t, a, b)) { return makeParSchema(generateDiagramSchema(a), generateDiagramSchema(b)); }
490 else if (isBoxSplit(t, a, b)) { return makeSplitSchema(generateDiagramSchema(a), generateDiagramSchema(b)); }
491 else if (isBoxMerge(t, a, b)) { return makeMergeSchema(generateDiagramSchema(a), generateDiagramSchema(b)); }
492 else if (isBoxRec(t, a, b)) { return makeRecSchema(generateDiagramSchema(a), generateDiagramSchema(b)); }
493
494 else if (isBoxSlot(t, &i)) { return generateOutputSlotSchema(t); }
495 else if (isBoxSymbolic(t,a,b)) {
496 Tree id;
497 if (getDefNameProperty(t, id)) {
498 return generateAbstractionSchema(generateInputSlotSchema(a), b);
499 } else {
500 return makeDecorateSchema(generateAbstractionSchema(generateInputSlotSchema(a), b), 10, "Abstraction");
501 }
502 }
503
504 else {
505
506 fprintf(stderr, "Internal Error, box expression not recognized : "); print(t, stderr); fprintf(stderr, "\n");
507 exit(1);
508
509 }
510 }
511
512 /**
513 * Convert User interface element into a textual representation
514 */
515 static void UserInterfaceDescription(Tree box, string& d)
516 {
517 Tree t1, label, cur, min, max, step;
518 stringstream fout;
519 // user interface
520 if (isBoxButton(box, label)) fout << "button(" << extractName(label) << ')';
521 else if (isBoxCheckbox(box, label)) fout << "checkbox(" << extractName(label) << ')';
522 else if (isBoxVSlider(box, label, cur, min, max, step)) {
523 fout << "vslider("
524 << extractName(label) << ", "
525 << boxpp(cur) << ", "
526 << boxpp(min) << ", "
527 << boxpp(max) << ", "
528 << boxpp(step)<< ')';
529 }
530 else if (isBoxHSlider(box, label, cur, min, max, step)) {
531 fout << "hslider("
532 << extractName(label) << ", "
533 << boxpp(cur) << ", "
534 << boxpp(min) << ", "
535 << boxpp(max) << ", "
536 << boxpp(step)<< ')';
537 }
538 else if (isBoxVGroup(box, label, t1)) {
539 fout << "vgroup(" << extractName(label) << ", " << boxpp(t1, 0) << ')';
540 }
541 else if (isBoxHGroup(box, label, t1)) {
542 fout << "hgroup(" << extractName(label) << ", " << boxpp(t1, 0) << ')';
543 }
544 else if (isBoxTGroup(box, label, t1)) {
545 fout << "tgroup(" << extractName(label) << ", " << boxpp(t1, 0) << ')';
546 }
547 else if (isBoxHBargraph(box, label, min, max)) {
548 fout << "hbargraph("
549 << extractName(label) << ", "
550 << boxpp(min) << ", "
551 << boxpp(max) << ')';
552 }
553 else if (isBoxVBargraph(box, label, min, max)) {
554 fout << "vbargraph("
555 << extractName(label) << ", "
556 << boxpp(min) << ", "
557 << boxpp(max) << ')';
558 }
559 else if (isBoxNumEntry(box, label, cur, min, max, step)) {
560 fout << "nentry("
561 << extractName(label) << ", "
562 << boxpp(cur) << ", "
563 << boxpp(min) << ", "
564 << boxpp(max) << ", "
565 << boxpp(step)<< ')';
566 }
567 else {
568 cerr << "INTERNAL ERROR : unknow user interface element " << endl;
569 exit(0);
570 }
571 d = fout.str();
572 }
573
574
575 /**
576 * Generate a 0->1 block schema for a user interface element
577 */
578 static schema* generateUserInterfaceSchema(Tree t)
579 {
580 string s; UserInterfaceDescription(t,s);
581 return makeBlockSchema(0, 1, s, uicolor, "");
582 }
583
584
585 /**
586 * Generate a 1->1 block schema for a user interface bargraph
587 */
588 static schema* generateBargraphSchema(Tree t)
589 {
590 string s; UserInterfaceDescription(t,s);
591 return makeBlockSchema(1, 1, s, uicolor, "");
592 }
593
594
595
596 /**
597 * Generate a 1->0 block schema for an input slot
598 */
599 static schema* generateInputSlotSchema(Tree a)
600 {
601 Tree id; assert(getDefNameProperty(a, id));
602 stringstream s; s << tree2str(id);
603 return makeBlockSchema(1, 0, s.str(), slotcolor, "");
604 }
605
606
607
608 /**
609 * Generate a 0->1 block schema for an output slot
610 */
611 static schema* generateOutputSlotSchema(Tree a)
612 {
613 Tree id; assert(getDefNameProperty(a, id));
614 stringstream s; s << tree2str(id);
615 return makeBlockSchema(0, 1, s.str(), slotcolor, "");
616 }
617
618
619
620 /**
621 * Generate an abstraction schema by placing in sequence
622 * the input slots and the body
623 */
624 static schema* generateAbstractionSchema(schema* x, Tree t)
625 {
626 Tree a,b;
627
628 while (isBoxSymbolic(t,a,b)) {
629 x = makeParSchema(x, generateInputSlotSchema(a));
630 t = b;
631 }
632 return makeSeqSchema(x, generateDiagramSchema(t));
633 }
634