adapton_lab/
labviz.rs

1use std::fs;
2//use std::io;
3use std::io::prelude::*;
4use std::io::BufWriter;
5use std::fs::File;
6use std::collections::HashMap;
7
8use adapton::engine::Name;
9use adapton::reflect::*;
10//use adapton::engine::reflect::{trace, string_of_name, string_of_loc};
11use labdef::{LabParams,Lab,LabResults, Sample};
12
13/// The `Div` struct represents a restricted form of a `<div>` element
14/// in HTML.  The field `tag` is a string, which corresponds to a
15/// distinguished `tag` CSS class that indicates the Rust datatype
16/// reflected into this `Div`.  The other CSS `classes` hold bits that
17/// signal various subcases (e.g., of `enum`s in the `reflect`
18/// module).  For Rust structures that have subfields and/or
19/// substructure, the `Div`'s `extent` field lists their reflections
20/// into `Div`s.  In principle, the produced `Div` structure has an
21/// equivalent amount of information to the corresponding Rust
22/// datatype, and could be "parsed" back into this Rust datatype later
23/// (let's not do that, though!).  The text field is useful for `Div`
24/// versions of `Name`s, for giving the text of the name.
25#[derive(Debug,Clone)]
26pub struct Div {
27  pub tag:     String,
28  pub classes: Vec<String>,
29  pub extent:  Box<Vec<Div>>,
30  pub text:    Option<String>,
31}
32
33// Questions:
34// Reflections of names? 
35// Do we expose their internal structure as `div`s, or stringify them?
36// For paths, clearly we wanted to expose their structure.
37// Perhaps for forked names such as `11.L.r.L` we'll want something similar?
38
39// Thoughts:
40
41// Both: We want names to be like strings when they are used as CSS
42// classes that control when, during user interaction, certain div
43// elements highlight, focus or toggle between hide/show.  On the
44// other hand, we may also want the user to be able to see and inspect
45// the internal structure of names, e.g., to select/highlight related
46// names in the lineage.  E.g., `11.L.r.L` is likely related to
47// `11.L.r.r` since a single `fork` operation produced them both.
48
49pub fn div_of_name (n:&Name) -> Div {
50  Div{ tag: String::from("name"),
51       // TODO: Remove illegal chars for CSS classes (check spec)
52       // classes: vec![ format!("{:?}", n) ],
53       classes: vec![ string_of_name(n) ],
54       extent: Box::new( vec![ ] ),
55       text: Some( format!("{}", string_of_name(n) ) ) }
56}
57
58pub fn div_of_path (p:&Path) -> Div {
59  Div{ tag: String::from("path"),
60       //classes: vec![ format!("{:?}", p) ],
61       classes: vec![ ],
62       extent: Box::new(
63         p.iter().map( div_of_name ).collect()
64       ),
65       text: None }
66}
67
68pub fn div_of_loc (l:&Loc) -> Div {
69  Div{ tag: String::from("loc"),
70       // TODO: Remove illegal chars for CSS classes (check spec)
71       //classes: vec![ format!("{:?}", l) ],
72       classes: vec![ ],       
73       extent: Box::new(vec![ div_of_path(&l.path), div_of_name(&l.name) ]),
74       //text: Some( format!("{:?}",l) )
75       text:None,
76  }
77}
78
79pub fn div_of_oploc (ol:&Option<Loc>) -> Div {
80  if true {
81    Div{ tag: String::from("oploc"), 
82         classes: vec![],
83         extent: Box::new(vec![]),
84         text: None,
85    }
86  } else {
87    Div{ tag: String::from("oploc"),
88         classes: vec![],
89         extent: Box::new(match *ol { 
90           None => vec![],
91           Some(ref l) => vec![ div_of_loc(l)]}),
92         text: None
93    }
94  }
95}
96
97pub fn div_of_succ (s:&Succ) -> Div {
98  Div{ tag: String::from("succ"),
99       classes: vec![
100         String::from(match s.effect {
101           Effect::Alloc => "succ-alloc",
102           Effect::Force => "succ-force"
103         }),
104         String::from(match s.dirty {
105           true  => "succ-dirty",
106           false => "succ-not-dirty"
107         }),
108       ],
109       text: None,
110       extent: Box::new(vec![
111         div_of_loc(&s.loc),
112       ])}
113}
114
115pub fn div_of_edge (e:&trace::Edge) -> Div {
116  Div{ tag: String::from("edge"),
117       classes: vec![],
118       text: None,
119       extent: Box::new(
120         vec![ div_of_oploc(&e.loc),
121               div_of_succ(&e.succ) ]) }
122}
123
124pub fn div_of_value_tree (dcg:&DCG, visited:&mut HashMap<Loc, ()>, val:&Val) -> Div {
125  let div = Div {
126    tag: match *val {
127      Val::Constr(ref n, _) => { format!("val-constr constr-{}", string_of_name(n) ) },
128      Val::Struct(ref n, _) => { format!("val-struct struct-{}", string_of_name(n) ) },
129      Val::Const( ref c )     => { format!("val-const  const-{}" , match *c {
130        Const::Nat( ref n ) => format!("{}", n),
131        Const::Num( ref n ) => format!("{}", n),
132        Const::String( ref s ) => s.clone(),
133      })},
134      Val::Tuple(ref vs) => { format!("val-tuple tuple-{}", vs.len()) },
135      Val::Vec(ref vs) => { format!("val-vec vec-{}", vs.len()) },
136      Val::Art(ref loc, _) => { format!("val-art {}", string_of_loc( loc ) ) },
137      Val::ValTODO => { format!("val-TODO") },
138      Val::Name(ref n) => { format!("name val-name {}", string_of_name(n)) },
139    },
140    classes: vec![],
141    text: 
142      match *val {
143        Val::Constr(ref n, _) => { Some(string_of_name(n)) },
144        Val::Struct(ref n, _) => { Some(string_of_name(n)) },
145        Val::Const( ref c )     => Some(match *c {
146          Const::Nat( ref n ) => format!("{}", n),
147          Const::Num( ref n ) => format!("{}", n),
148          Const::String( ref s ) => format!("{:?}", s),
149        }),
150        Val::Tuple( _ ) => None,
151        Val::Vec( _ ) => None,
152        Val::ValTODO => None,
153        Val::Art( ref l, _ ) => {
154          Some(format!("{}", string_of_loc(l)))
155        }
156        Val::Name(ref n) => {
157          Some(format!("{}", string_of_name(n)))
158        }
159      },
160    
161    extent: Box::new(
162      match *val {
163        Val::Constr(_, ref vs) => { let ds : Vec<_> = vs.iter().map( |v| div_of_value_tree(dcg, visited,  v) ).collect() ; ds },
164        Val::Struct(_, ref fs) => { let ds : Vec<_> = fs.iter().map(  |&(ref _f, ref v) | 
165                                                                         div_of_value_tree(dcg, visited, &v) ).collect() ; ds },
166        Val::Tuple(ref vs) =>     { let ds : Vec<_> = vs.iter().map( |v| div_of_value_tree(dcg, visited,  v) ).collect() ; ds },
167        Val::Vec(ref vs) =>       { let ds : Vec<_> = vs.iter().map( |v| div_of_value_tree(dcg, visited,  v) ).collect() ; ds },
168        Val::Const( _ ) => vec![],
169        Val::ValTODO => vec![],
170        Val::Name(_) => vec![],
171        Val::Art( ref l, _ ) => vec![
172          div_of_loc(l), 
173          match dcg.table.get(l) {
174            None => Div{ tag: String::from("dangling"), 
175                         classes:vec![String::from("no-extent")], 
176                         text:Some(String::from("Dangling")), 
177                         extent:Box::new(vec![]),
178            },
179            Some(node) => {
180              match *node {
181                Node::Pure(ref p) => div_of_value_tree(dcg, visited, &p.value),
182                Node::Ref(ref n) => div_of_value_tree(dcg, visited, &n.value),
183                Node::Comp(ref n) => match n.value {
184                  None => 
185                    Div{ tag: String::from("Unevald"), 
186                         classes:vec![String::from("no-extent")], 
187                         text:Some(String::from("Uneval'd")), 
188                         extent:Box::new(vec![]),
189                    },
190                  Some(ref v) => div_of_value_tree(dcg, visited, v),
191                }
192              }
193            }
194          }
195        ],
196      }
197    )
198  }
199  ;
200  div
201}
202
203pub fn div_of_force_tree (dcg:&DCG, visited:&mut HashMap<Loc, ()>, loc:&Loc) -> Div {  
204  let mut div = Div {
205    tag:String::from("force-tree"),
206    text:None,
207    classes: vec![],
208    extent: Box::new(vec![ div_of_loc( loc ) ]),
209  };
210  visited.insert( loc.clone(), () );
211  let no_extent = match dcg.table.get( loc ) {
212    None => panic!("dangling pointer in reflected DCG!"),
213    Some( nd ) => {
214      match succs_of_node( nd ) {
215        None => true, // No succs; E.g., ref cells have no succs
216        Some( succs ) => {
217          let mut no_extent = true;
218          for succ in succs {
219            if succ.effect == Effect::Force {
220              no_extent = false;
221              let succ_div = div_of_force_tree (dcg, visited, &succ.loc);
222              div.extent.push( succ_div )
223            }
224          };
225          no_extent
226        }
227      }
228    }
229  };
230  if no_extent {
231    div.classes.push(String::from("no-extent"))
232  };
233  div
234}
235
236pub fn div_of_alloc_tree (dcg:&DCG, visited:&mut HashMap<Loc, ()>, loc:&Loc) -> Div {  
237  let mut div = Div {
238    tag:String::from("alloc-tree"),
239    text:None,
240    classes: vec![],
241    extent: Box::new(vec![ div_of_loc( loc ) ]),
242  };
243  visited.insert( loc.clone(), () );
244  let no_extent = match dcg.table.get( loc ) {
245    None => panic!("dangling pointer in reflected DCG!"),
246    Some( nd ) => {
247      match succs_of_node( nd ) {
248        None => true, // No succs; E.g., ref cells have no succs
249        Some( succs ) => {
250          let mut no_extent = true;
251          for succ in succs {
252            if succ.effect == Effect::Alloc {
253              no_extent = false;
254              let succ_div = div_of_alloc_tree (dcg, visited, &succ.loc);
255              div.extent.push( succ_div )
256            }
257          };
258          no_extent
259        }
260      }
261    }
262  };
263  if no_extent {
264    div.classes.push(String::from("no-extent"))
265  };
266  div
267}
268
269
270pub fn class_of_dcg_node (nd:&Node) -> String {
271  match *nd {
272    Node::Comp(_) => String::from("dcg-node-comp"),
273    Node::Ref(_) => String::from("dcg-node-ref"),
274    Node::Pure(_) => String::from("dcg-node-pure"),
275  }
276}
277
278pub fn div_of_dcg_alloc_edge (src:Option<&Loc>, loc:&Loc, nd:&Node, is_dirty:bool) -> Div {
279  let div = Div {
280    tag:String::from("dcg-alloc-edge"),
281    text:None,
282    classes: vec![ if is_dirty { String::from("dirty") } else { String::from("clean") },
283                   if src == None { String::from("editor-edge") } else { String::from("dcg-edge") },
284                   class_of_dcg_node( nd ) ],
285    extent: Box::new(vec![ div_of_loc( loc ) ]),
286  };
287  div
288}
289
290pub fn div_of_dcg_succs (dcg:&DCG, visited:&mut HashMap<Loc, ()>, loc:Option<&Loc>, 
291                         succs: &Vec<Succ>,
292                         extent: &mut Vec<Div>) {  
293  for succ in succs {
294    match succ.effect {
295      Effect::Alloc => {
296        let node = dcg.table.get( &succ.loc ).unwrap();
297        let succ_div = div_of_dcg_alloc_edge (loc, &succ.loc, &node, succ.dirty);
298        extent.push( succ_div )
299      },
300      Effect::Force => {
301        let succ_div = div_of_dcg_force_edge (loc, dcg, visited, &succ.loc, succ.dirty, succ.is_dup);
302        extent.push( succ_div )
303      }
304    }     
305  }
306}
307
308pub fn div_of_dcg_force_edge (src:Option<&Loc>, dcg:&DCG, visited:&mut HashMap<Loc, ()>, 
309                              loc:&Loc, is_dirty:bool, is_dup:bool) -> Div 
310{  
311  let mut div = Div {
312    tag:String::from("dcg-force-edge"),
313    text:None,
314    classes: vec![ 
315      if is_dup   { String::from("dup-edge") } else { String::from("not-dup-edge") },
316      if is_dirty { String::from("dirty") } else { String::from("clean") }, 
317      if src == None { String::from("editor-edge") } else { String::from("dcg-edge") },
318    ],    
319    extent: Box::new(vec![ div_of_loc( loc ) ]),
320  };
321  visited.insert( loc.clone(), () );
322  let no_extent = match dcg.table.get( loc ) {
323    None => panic!("dangling pointer in reflected DCG!"),
324    Some( nd ) => {
325      div.classes.push( class_of_dcg_node(nd) );
326      match succs_of_node( nd ) {
327        None => true, // No succs; E.g., ref cells have no succs
328        Some( succs ) => { 
329          div_of_dcg_succs(dcg, visited, Some(loc), succs, &mut div.extent);
330          false
331        }
332      }
333    }
334  };
335  if no_extent {
336    div.classes.push(String::from("no-extent"))
337  };
338  div
339}
340
341pub fn div_of_trace (tr:&trace::Trace) -> Div {
342  // For linking to rustdoc documentation from the output HTML
343  let tr_eff_url = "http://adapton.org/rustdoc/adapton/engine/reflect/trace/enum.Effect.html";
344
345  let mut div = 
346    Div{ 
347      tag: String::from("trace"),
348      text: None,
349      classes: vec![
350        String::from(match tr.effect {
351          trace::Effect::CleanRec  => "tr-clean-rec",
352          trace::Effect::CleanEval => "tr-clean-eval",
353          trace::Effect::CleanEdge => "tr-clean-edge",
354          trace::Effect::Dirty     => "tr-dirty",
355          trace::Effect::Remove    => "tr-remove",
356          trace::Effect::Alloc(trace::AllocCase::LocFresh,_)     => "tr-alloc-loc-fresh",
357          trace::Effect::Alloc(trace::AllocCase::LocExists(trace::ChangeFlag::ContentSame),_) => "tr-alloc-loc-exists-same",
358          trace::Effect::Alloc(trace::AllocCase::LocExists(trace::ChangeFlag::ContentDiff),_) => "tr-alloc-loc-exists-diff",
359          trace::Effect::Force(_) if tr.edge.succ.is_dup         => "tr-force-dup",
360          trace::Effect::Force(trace::ForceCase::CompCacheMiss)  => "tr-force-compcache-miss",
361          trace::Effect::Force(trace::ForceCase::CompCacheHit)   => "tr-force-compcache-hit",
362          trace::Effect::Force(trace::ForceCase::RefGet)         => "tr-force-refget",
363        })
364      ],
365      extent: Box::new(
366        vec![
367          Div{ 
368            tag: String::from("tr-effect"),
369            text: Some(              
370              format!("<a href={:?}>{}</a>", tr_eff_url, match tr.effect {
371                trace::Effect::CleanRec  => "CleanRec",
372                trace::Effect::CleanEval => "CleanEval",
373                trace::Effect::CleanEdge => "CleanEdge",
374                trace::Effect::Dirty     => "Dirty",
375                trace::Effect::Remove    => "Remove",
376                trace::Effect::Alloc(trace::AllocCase::LocFresh,_)     => "Alloc(LocFresh)",
377                trace::Effect::Alloc(trace::AllocCase::LocExists(trace::ChangeFlag::ContentSame),_) => "Alloc(LocExists(SameContent))",
378                trace::Effect::Alloc(trace::AllocCase::LocExists(trace::ChangeFlag::ContentDiff),_) => "Alloc(LocExists(DiffContent))",
379                trace::Effect::Force(_) if tr.edge.succ.is_dup         => "ForceDup",
380                trace::Effect::Force(trace::ForceCase::CompCacheMiss)  => "Force(CompCacheMiss)",
381                trace::Effect::Force(trace::ForceCase::CompCacheHit)   => "Force(CompCacheHit)",
382                trace::Effect::Force(trace::ForceCase::RefGet)         => "Force(RefGet)",
383              })),
384            classes: vec![],
385            extent: Box::new(vec![]),
386          },
387          Div{
388            tag: String::from("tr-symbols"),
389            text: match tr.effect {
390              trace::Effect::Alloc(_,trace::AllocKind::RefCell) => Some(String::from("▣")),
391              trace::Effect::Alloc(_,trace::AllocKind::Thunk)   => Some(String::from("◯")),
392              _ => None,              
393            },
394            classes:vec![],
395            extent: Box::new(vec![]),
396          },
397          // This is where the location (path and name) are DIV-ified
398          // If the effect is a CleanEval, then we should use the
399          // location at the *source* of the edge, which is the
400          // location we re-evaluate.
401          match (tr.effect.clone(), tr.edge.loc.clone()) {            
402            (trace::Effect::CleanEval, Some(loc)) => div_of_loc(&loc),
403            // TODO: Figure out why/when we get None here; seems like a reflection issue in the Engine
404            _ => div_of_edge(&tr.edge)
405          }
406        ])}
407  ;
408  match tr.effect {
409    trace::Effect::Alloc(_,trace::AllocKind::RefCell) => div.classes.push(String::from("alloc-kind-refcell")),
410    trace::Effect::Alloc(_,trace::AllocKind::Thunk)   => div.classes.push(String::from("alloc-kind-thunk")),
411    _ => ()
412  };
413  if tr.extent.len() > 0 {
414    div.classes.push( String::from("has-extent") );
415    div.extent.push(
416      Div{ tag: String::from("tr-extent"),
417           text: None,
418           classes: vec![],
419           extent: 
420           Box::new(tr.extent.iter().map(div_of_trace).collect())
421      }
422    )
423  } else {
424    div.classes.push( String::from("no-extent") );
425  };
426  return div
427}
428
429pub trait WriteHTML {
430  fn write_html<Wr:Write>(&self, wr: &mut Wr);
431}
432
433impl WriteHTML for Div {
434  fn write_html<Wr:Write>(&self, wr: &mut Wr) {    
435    writeln!(wr, "<div class=\"{} {}\">", 
436             self.tag, 
437             self.classes.iter().fold(
438               String::new(), 
439               |mut cs,c|{cs.push_str(" ");
440                          cs.push_str(c.as_str()); cs}
441             )
442    ).unwrap();
443    match self.text {
444      None => (),
445      Some(ref text) => writeln!(wr, "{}", text).unwrap()
446    };
447    for div in self.extent.iter() {
448      div.write_html(wr);
449    };
450    writeln!(wr, "</div>").unwrap();
451  }
452}
453
454impl<T:WriteHTML> WriteHTML for Vec<T> {
455  fn write_html<Wr:Write>(&self, wr:&mut Wr) {
456    for x in self.iter() {
457      x.write_html(wr);
458    }
459  }
460}
461
462pub fn write_lab_results_summary
463  (_params:&LabParams, 
464   labs:&Vec<Box<Lab>>, 
465   results:&Vec<LabResults>) 
466{
467  // Create directories and files on local filesystem:
468  fs::create_dir_all("lab-results").unwrap();
469  let f = File::create(format!("lab-results/index.html")).unwrap();
470  let mut writer = BufWriter::new(f);
471
472  writeln!(writer, "{}", style_string()).unwrap();
473  writeln!(writer, "<style> .tool-label-toggles {{ display: none }} </style>").unwrap();
474
475  assert!( labs.len() == results.len() );
476
477  writeln!(writer, "<div class={:?}>Lab results summary</div>", "labsum-title").unwrap();
478
479  for ((_i,lab),(_j,_result)) in 
480    labs.iter().enumerate().zip(results.iter().enumerate()) 
481  {
482    writeln!(&mut writer, "<div class={:?}>", "labsum-row").unwrap();
483    writeln!(&mut writer, "<div class={:?}>", "labsum-name").unwrap();
484    write_lab_name(&mut writer, lab, false);
485    writeln!(&mut writer, "</div>").unwrap();
486    
487    writeln!(&mut writer, "<a class={:?} href=./{}/index.html>detailed results</a>", 
488             "lab-details", 
489             string_of_name(&lab.name())
490    ).unwrap();
491
492    writeln!(&mut writer, "</div>").unwrap();        
493    write_cr(&mut writer);
494  }
495}
496
497pub fn write_cr<W:Write>(writer:&mut W) {
498  /// We style this with clear:both, and without any appearance
499  writeln!(writer, "<hr/>").unwrap();
500}
501
502pub fn write_lab_name<W:Write>(writer:&mut W, lab:&Box<Lab>, is_title:bool) {
503  let catalog_url = String::from("http://adapton.org/rustdoc/adapton_lab/catalog/index.html");
504
505  let labname = string_of_name( &lab.name() );
506  let laburl  = lab.url();
507
508  writeln!(writer, "<div class={:?}><a href={:?} class={:?}>{}</a></div>", 
509           "lab-name",
510           match *laburl {
511             Some(ref url) => url,
512             None => & catalog_url
513           },
514           format!("lab-name {}", if is_title { "page-title" } else { "" }), 
515           labname
516  ).unwrap();
517}
518
519pub fn write_dcg_tree<W:Write> (writer:&mut W, dcg:&DCG, traces:&Vec<trace::Trace>) {
520  let mut visited = HashMap::new();
521  let mut extent : Vec<_> = Vec::new();
522  let succs : Vec<_> = traces.iter().map(|t| t.edge.succ.clone()).collect();
523  div_of_dcg_succs(dcg, &mut visited, None, &succs, &mut extent);
524  for d in extent.iter() {
525    d.write_html(writer);
526  }
527}
528
529pub fn write_dcg_edge_tree<W:Write> (writer:&mut W, dcg:&DCG, traces:&Vec<trace::Trace>, effect:Effect) {
530  for tr in traces.iter() {
531    if tr.edge.succ.effect == effect {
532      match effect {
533        Effect::Alloc =>
534          div_of_alloc_tree(dcg, &mut HashMap::new(), &tr.edge.succ.loc)
535          .write_html(writer),
536        Effect::Force =>
537          div_of_force_tree(dcg,&mut HashMap::new(),  &tr.edge.succ.loc)
538          .write_html(writer),
539      }
540    }
541  }
542}
543
544pub fn write_sample_dcg<W:Write>
545  (writer:&mut W,
546   _lab:&Box<Lab>, 
547   prev_sample:Option<&Sample>,
548   this_sample:&Sample)
549{
550  write_cr(writer)
551    ;
552  match this_sample.dcg_sample.process_input.reflect_dcg {
553    None => { },
554    Some(ref dcg_post_edit) => {
555      match this_sample.dcg_sample.input {
556        None => { },
557        Some(ref input) => {
558          writeln!(writer, "<div class=\"input-value\">").unwrap();
559          writeln!(writer, "<div class=\"label\">{}</div>", "Input:").unwrap();
560          div_of_value_tree(dcg_post_edit, &mut HashMap::new(), input)
561            .write_html( writer );
562          writeln!(writer, "</div>").unwrap();
563        }
564      }
565    }
566  }
567  ;
568  match this_sample.dcg_sample.compute_output.reflect_dcg {
569    None => { },
570    Some(ref dcg_post_update) => {      
571      match this_sample.dcg_sample.output {
572        None => { },
573        Some(ref output) => {
574          writeln!(writer, "<div class=\"output-value\">").unwrap();
575          writeln!(writer, "<div class=\"label\">{}</div>", "Output:").unwrap();
576          div_of_value_tree(dcg_post_update, &mut HashMap::new(), output)
577            .write_html( writer );            
578          writeln!(writer, "</div>").unwrap();            
579        }
580      }
581    }
582  }
583  ;
584  // Separate the input and output from the DCG trees, below
585  write_cr(writer);
586  ;
587  match this_sample.dcg_sample.process_input.reflect_dcg {
588    Some(ref dcg_post_edit) => {
589      match prev_sample {
590        Some(ref prev_sample) => {
591        
592          // // 0/4: dcg for compute, after this edit, but before the update
593          writeln!(writer, "<div class=\"archivist-dcg-tree-post-edit\">").unwrap();
594          writeln!(writer, "<div class=\"label\">{}</div>", "DCG, post-edit:").unwrap();
595          write_dcg_tree
596            (writer, 
597             dcg_post_edit,
598             &prev_sample.dcg_sample.compute_output.reflect_traces,
599            );
600          writeln!(writer, "</div>").unwrap();
601          
602          if false {
603          // 1/4: alloc tree for compute, after this edit, but before the update
604          writeln!(writer, "<div class=\"archivist-alloc-tree-post-edit\">").unwrap();
605          writeln!(writer, "<div class=\"label\">{}</div>", "Allocs, post-edit:").unwrap();
606          write_dcg_edge_tree
607            (writer, 
608             dcg_post_edit,
609             &prev_sample.dcg_sample.compute_output.reflect_traces,
610             Effect::Alloc
611            );
612          writeln!(writer, "</div>").unwrap();
613          
614          // 2/4: force tree for compute, after this edit, but before the update
615          writeln!(writer, "<div class=\"archivist-force-tree-post-edit\">").unwrap();
616          writeln!(writer, "<div class=\"label\">{}</div>", "Forces, post-edit:").unwrap();
617          write_dcg_edge_tree
618            (writer, 
619             dcg_post_edit,
620             &prev_sample.dcg_sample.compute_output.reflect_traces,
621             Effect::Force,         
622            );
623          writeln!(writer, "</div>").unwrap();
624          }
625
626        },    
627        _ => {
628          writeln!(writer,"<div class=\"archivist-alloc-tree-post-edit\"></div>").unwrap();
629          writeln!(writer,"<div class=\"archivist-force-tree-post-edit\"></div>").unwrap();
630        }}
631    },    
632    _ => {
633      //writeln!(writer,"<div class=\"archivist-alloc-tree-post-edit\"></div>").unwrap();
634      //writeln!(writer,"<div class=\"archivist-force-tree-post-edit\"></div>").unwrap();
635    }
636  }
637  ;
638  writeln!(writer,"<div class=\"archivist-update-sep\"></div>").unwrap();
639 
640  match this_sample.dcg_sample.compute_output.reflect_dcg {
641    Some(ref dcg_post_update) => {
642
643      // // 0/4: dcg for compute, after this edit, but before the update
644      writeln!(writer, "<div class=\"archivist-dcg-tree-post-update\">").unwrap();
645      writeln!(writer, "<div class=\"label\">{}</div>", "DCG, post-compute:").unwrap();
646      write_dcg_tree
647        (writer, 
648         dcg_post_update,
649         &this_sample.dcg_sample.compute_output.reflect_traces,
650        );
651      writeln!(writer, "</div>").unwrap();
652      
653      if false {
654      // 3/4: alloc tree for compute, after the update
655      writeln!(writer, "<div class=\"archivist-alloc-tree-post-update\">").unwrap();
656      writeln!(writer, "<div class=\"label\">{}</div>", "Allocs, post-update:").unwrap();
657      write_dcg_edge_tree
658        (writer, 
659         dcg_post_update,
660         &this_sample.dcg_sample.compute_output.reflect_traces,
661         Effect::Alloc
662        );
663      writeln!(writer, "</div>").unwrap();
664      
665      // 4/4: force tree for compute, after the update
666      writeln!(writer, "<div class=\"archivist-force-tree-post-update\">").unwrap();
667      writeln!(writer, "<div class=\"label\">{}</div>", "Forces, post-update:").unwrap();
668      write_dcg_edge_tree
669        (writer, 
670         dcg_post_update,
671         &this_sample.dcg_sample.compute_output.reflect_traces,
672         Effect::Force,         
673        );
674      writeln!(writer, "</div>").unwrap();
675      }
676      
677      write_cr(writer);
678    },    
679    _ => {
680      //writeln!(writer,"<div class=\"archivist-alloc-tree-post-update\"></div>").unwrap();
681      //writeln!(writer,"<div class=\"archivist-force-tree-post-update\"></div>").unwrap();
682    }
683  };
684
685}
686
687pub fn write_lab_results(params:&LabParams, lab:&Box<Lab>, results:&LabResults) {
688  
689  // If we are reflecting the trace, do not bother writing out the
690  // times; the purpose was probably visualization.
691  // TODO: Make this logic better.
692  let write_times = if params.sample_params.reflect_trace { false } else { true };    
693
694  let labname = string_of_name( &lab.name() );
695  //let laburl  = lab.url();
696
697  // For linking to rustdoc documentation from the output HTML
698  //let trace_url   = "http://adapton.org/rustdoc/adapton/engine/reflect/trace/struct.Trace.html";
699  
700  // Create directories and files on local filesystem:
701  fs::create_dir_all(format!("lab-results/{}/", labname)).unwrap();
702  let f = File::create(format!("lab-results/{}/index.html", labname)).unwrap();
703  let mut writer = BufWriter::new(f);
704  writeln!(writer, "{}", style_string()).unwrap();  
705  writeln!(writer, "<a href=\"../index.html\">↰ Results summary</a>").unwrap();
706  write_cr(&mut writer);
707  write_lab_name(&mut writer, lab, true);
708  writeln!(writer, "<div style=\"font-size:12px\" class=\"batch-name\"> step</div>").unwrap();  
709  if write_times {
710    writeln!(writer, "<div style=\"font-size:20px\" class=\"editor\">Editor</div>").unwrap();
711    writeln!(writer, "<div style=\"font-size:20px\" class=\"archivist\">Archivist</div>").unwrap();
712  }  
713  let mut prev_sample = None;
714  for sample in results.samples.iter() {
715    write_cr(&mut writer);
716    // - - - - - - - 
717    // 0. Write batch name (a counter); and write timing information for this edit batch.
718    writeln!(writer, "<div class=\"batch-name-lab\">batch name<div class=\"batch-name\">{:?}</div></div>", 
719             sample.batch_name).unwrap();
720    
721    if write_times {
722      writeln!(writer, "<div class=\"editor\">").unwrap();
723      
724      writeln!(writer, "<div class=\"time-ns-lab\">time (ns): <div class=\"time-ns\">{:?}</div></div>", 
725               sample.dcg_sample.process_input.time_ns).unwrap();    
726      writeln!(writer, "</div>").unwrap();
727      
728      writeln!(writer, "<div class=\"archivist\">").unwrap();
729      
730      writeln!(writer, "<div class=\"row\">").unwrap();
731      
732      writeln!(writer, "<div class=\"time-ns-lab\">Naive time (ns): <div class=\"time-ns\">{:?}</div></div>", 
733               sample.naive_sample.compute_output.time_ns).unwrap();    
734      
735      writeln!(writer, "<div class=\"time-ms-lab\">Naive time (ms): <div class=\"time-ms\">{:.*}</div></div>", 
736               2, (sample.naive_sample.compute_output.time_ns as f64) / (1000_000 as f64)).unwrap();
737      writeln!(writer, "</div>").unwrap();
738      
739      writeln!(writer, "<div class=\"row\">").unwrap();
740      writeln!(writer, "<div class=\"time-ns-lab\">DCG time (ns): <div class=\"time-ns\">{:?}</div></div>", 
741               sample.dcg_sample.compute_output.time_ns).unwrap();    
742      
743      writeln!(writer, "<div class=\"time-ms-lab\">DCG time (ms): <div class=\"time-ms\">{:.*}</div></div>", 
744               2, (sample.dcg_sample.compute_output.time_ns as f64) / (1000_000 as f64)).unwrap();
745      writeln!(writer, "</div>").unwrap();
746      
747      if sample.naive_sample.compute_output.time_ns <
748        sample.dcg_sample.compute_output.time_ns {
749          writeln!(writer, "<div class=\"overhead-lab\">DCG Overhead: <div class=\"overhead\">{:.*}</div></div>", 
750                   2, ( (sample.dcg_sample.compute_output.time_ns  as f64) / 
751                         (sample.naive_sample.compute_output.time_ns as f64) )).unwrap();      
752        } else {      
753          writeln!(writer, "<div class=\"speedup-lab\">DCG Speedup: <div class=\"speedup\">{:.*}</div></div>", 
754                   2, ( (sample.naive_sample.compute_output.time_ns  as f64) / 
755                         (sample.dcg_sample.compute_output.time_ns as f64) )).unwrap();
756        }    
757      
758      writeln!(writer, "</div>").unwrap();
759      write_cr(&mut writer);    
760    }
761
762    // 1. Write input,
763    // 2. Write output,
764    // 3. Write last DCG, after edit but before update.
765    // 4. Write DCG of the update.
766    write_sample_dcg(&mut writer, lab, prev_sample, sample);      
767    
768    if sample.dcg_sample.compute_output.reflect_traces.len() == 0 {
769      // 5 & 6. No traces to write.
770    } else {
771      // - - - - - - - 
772      // 5. Write traces of editor
773      
774      writeln!(writer, "<div class=\"traces-box\">").unwrap();
775      // writeln!(writer, "<div class=\"time-ns-lab\">time (ns): <div class=\"time-ns\">{:?}</div></div>", 
776      //          sample.dcg_sample.process_input.time_ns).unwrap();    
777      // writeln!(writer, "<div class=\"traces-lab\">Traces (<a href={:?}>doc</a>)</div>", trace_url).unwrap();    
778      writeln!(writer, "<div class=\"label\">{}</div>", "Editor trace:").unwrap();
779      writeln!(writer, "<div class=\"traces\">").unwrap();
780      for tr in sample.dcg_sample.process_input.reflect_traces.iter() {
781        div_of_trace(tr).write_html(&mut writer)
782      }
783      writeln!(writer, "</div>").unwrap();   
784      writeln!(writer, "</div>").unwrap();
785      
786      // - - - - - - - 
787      // 6. Write traces of archivist
788
789      //writeln!(writer, "<div class=\"traces-lab\">Traces (<a href={:?}>doc</a>):</div>", trace_url).unwrap();
790      writeln!(writer, "<div class=\"traces-box\">").unwrap();
791      writeln!(writer, "<div class=\"label\">{}</div>", "Archivist trace:").unwrap();
792      writeln!(writer, "<div class=\"traces\">").unwrap();
793      for tr in sample.dcg_sample.compute_output.reflect_traces.iter() {
794        div_of_trace(tr).write_html(&mut writer)
795      }
796      writeln!(writer, "</div>").unwrap();    
797      writeln!(writer, "</div>").unwrap();
798      write_cr(&mut writer);
799    }    
800    
801    // - - - - - - - - - - - - - - -       
802    prev_sample = Some(sample) ; // Must be last!
803  }
804  writer.flush().unwrap();  
805}
806
807pub fn style_string() -> &'static str {
808"
809<html>
810<head>
811<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js\"></script>
812
813<style>
814div { 
815  display: inline
816}
817body {
818  display: inline;
819  color: #aa88cc;
820  background: #552266;
821  font-family: sans-serif;
822  text-decoration: none;
823  padding: 0px;
824  margin: 0px;
825}
826body:visited {
827  color: #aa88cc;
828}
829a {
830  text-decoration: none;
831}
832a:hover {
833  text-decoration: underline;
834}
835hr {
836  display: block;
837  float: left;
838  clear: both;
839  width: 0px;
840  border: none;
841}
842
843.lab-name {
844  color: #ccaadd;
845  margin: 1px;
846  padding: 1px;
847}
848.lab-name:visited {  
849  color: #ccaadd;
850}
851.lab-name:hover {
852  color: white;
853}
854
855.row {
856  display: block;
857}
858
859.labsum-title {
860  display: block;
861  margin: 8px;
862  font-size: 20px;
863}
864.labsum-row {
865  display: block;
866}
867.labsum-name {
868  display: table-cell;
869  margin: 8px;
870  padding: 2px;
871  width: 70%;
872}
873.lab-details {
874  display: table-cell;
875  font-size: 14px;
876  color: #ddbbee;
877  border: solid white 1px;
878  padding: 2px;
879  background-color: #441155;
880  margin: 3px;
881}
882
883.batch-name-lab {
884  font-size: 0px;
885  color: black;
886}
887.batch-name {
888  color: black;
889  font-size: 16px;
890  border: solid;
891  display: inline;
892  padding: 3px;
893  margin: 3px;
894  float: left;
895  background: #aa88aa;
896  width: 32px;
897}
898.time-ns {
899  font-size: 12px;
900  display: inline;
901}
902.time-ms {
903  font-size: 20px;
904  display: inline;
905}
906.overhead {
907  font-size: 30px;
908  display: inline;
909  color: #880000;
910  background: #ffcccc;
911  border: solid 1px red;
912}
913.speedup {
914  font-size: 30px;
915  display: inline;
916  color: #008800;
917  background: #ccffcc;
918  border: solid 1px green;
919}
920.time-ms {
921  font-size: 20px;
922  display: inline;
923}
924.editor {
925  color: black;
926  font-size: 14px;
927  border: solid;
928  display: block;
929  padding: 1px;
930  margin: 1px;
931  float: left;
932  width: 10%;
933  background: #aaaaaa;
934}
935.archivist {
936  color: black;
937  font-size: 14px;
938  border: solid;
939  display: block;
940  padding: 1px;
941  margin: 1px;
942  float: left;
943  width: 85%;
944  background: #dddddd;
945}
946.traces {
947  color: black;
948  font-size: 8px;
949  border: solid 0px;
950  display: block;
951  margin: 0px;
952  float: left;
953  width: 100%;
954}
955.traces:visited {
956  color: black;
957}
958
959.input-value, 
960.output-value,
961.archivist-dcg-tree-post-edit,
962.archivist-dcg-tree-post-update,
963.traces-box,
964.archivist-alloc-tree-post-edit,
965.archivist-force-tree-post-edit, 
966.archivist-alloc-tree-post-update, 
967.archivist-force-tree-post-update  
968{
969  display: inline;
970  float: left;
971
972  padding: 0px;
973  margin: 2px;
974
975  color: #dd88ff;
976  background: #331144;
977  border-radius: 5px;
978  border-style: solid;
979  border-width: 0px;
980  border-color: #dd88ff;
981}
982
983.input-value, 
984.output-value {
985  width: 49%;
986}
987.traces-box {
988  width: 99%;
989}
990.archivist-dcg-tree-post-edit,
991.archivist-dcg-tree-post-update {
992  width: 49%;
993}
994.archivist-alloc-tree-post-edit,
995.archivist-force-tree-post-edit, 
996.archivist-alloc-tree-post-update, 
997.archivist-force-tree-post-update {
998  width: 24%;
999}
1000.tool-label-toggles {
1001  display: block;
1002  float: right;
1003}
1004
1005
1006.trace, .force-tree, .alloc-tree, .dcg-alloc-edge, .dcg-force-edge {
1007  color: black;
1008  display: inline-block;
1009  border-style: solid;
1010  border-color: red;
1011  border-width: 1px;
1012  font-size: 0px;
1013  padding: 0px;
1014  margin: 1px;
1015  border-radius: 5px;
1016}
1017
1018.dcg-force-edge {
1019  border-color: blue;
1020  border-width: 1px;
1021}
1022.dcg-node-comp {
1023  background: #ccccff;
1024  padding: 0px;
1025}
1026.dcg-node-ref {
1027  border-radius: 0px;
1028}
1029.dcg-alloc-edge {
1030  border-color: green;
1031  background: #ccffcc;
1032  border-width: 1px;
1033  padding: 3px;
1034}
1035.dirty {
1036  border-width: 2px;
1037  border-color: red;
1038  background: #ffcccc;
1039}
1040.editor-edge {
1041  border-width: 2px;
1042  border-color: black;
1043}
1044
1045.tr-effect { 
1046  display: inline;
1047  display: none;
1048  font-size: 10px;
1049  background-color: white;
1050  border-radius: 2px;
1051}
1052.tr-symbols {  
1053  font-size: 10px;
1054  display: inline;
1055  display: none;
1056}
1057
1058.path {  
1059  display: inline-block;
1060  display: none;
1061
1062  margin: 0px;
1063  padding: 1px;
1064  border-radius: 1px;
1065  border-style: solid;
1066  border-width: 1px;
1067  border-color: #664466;
1068  background-color: #664466; 
1069}
1070
1071.alloc-kind-thunk {
1072  border-color: green;
1073  border-radius:20px;
1074}
1075.alloc-kind-refcell {
1076  border-color: green;
1077  border-radius:0;
1078}
1079.tr-force-compcache-miss {  
1080  background: #ccccff;
1081  border-color: blue;
1082  padding: 0px;
1083}
1084.tr-force-compcache-hit {  
1085  background: #ccccff;
1086  border-color: blue;
1087  border-width: 4px;
1088  padding: 3px;
1089}
1090.tr-force-refget {  
1091  border-radius: 0;
1092  border-color: blue;
1093}
1094.tr-force-dup {  
1095  border-width: 0px;
1096  padding: 0px;
1097  background: #666666;
1098  display: none;
1099}
1100.tr-clean-rec {  
1101  background: #222244;
1102  border-color: #aaaaff;
1103  border-width: 1px; 
1104}
1105.tr-clean-eval {  
1106  background: #8888ff;
1107  border-color: white;
1108  border-width: 4px; 
1109}
1110.tr-clean-edge {  
1111  background: white;
1112  border-color: #aaaaff;
1113  border-width: 2px; 
1114  padding: 3px;
1115}
1116.tr-alloc-loc-fresh {  
1117  padding: 3px;
1118  background: #ccffcc;
1119}
1120.tr-alloc-loc-exists-same {  
1121  padding: 3px;
1122  background: #ccffcc;
1123  border-width: 4px;
1124  border-color: green;
1125}
1126.tr-alloc-loc-exists-diff {  
1127  padding: 3px;
1128  background: #ffcccc;
1129  border-width: 4px;
1130  border-color: red;
1131}
1132.tr-dirty {  
1133  background: #550000;
1134  border-color: #ffaaaa;
1135  border-width: 1px;
1136}
1137.tr-remove {  
1138  background: red;
1139  border-color: black;
1140  border-width: 2px;
1141  padding: 2px;
1142}
1143
1144.force-tree {
1145  background: #ccccff;
1146  border-color: blue;
1147}
1148.alloc-tree {
1149  background: #ccffcc;
1150  border-color: green;
1151}
1152
1153.no-extent {
1154  padding: 3px;
1155}
1156.page-title {
1157  display: block;
1158  font-size: 32px;
1159  color: #ccaadd;
1160  margin: 8px;
1161}
1162
1163.val-name,
1164.val-constr,
1165.val-struct,
1166.val-tuple,
1167.val-vec,
1168.val-art
1169{
1170  display: inline-block;
1171  border-style: solid;
1172  border-width: 1px;
1173  border-color: #dd88ff;
1174  background-color: #220033;
1175  padding: 1px;
1176  margin: 1px;
1177  border-radius 2px;  
1178  font-size: 0px;  
1179}
1180.val-constr,
1181.val-struct,
1182.val-tuple,
1183.val-vec {
1184  border-color: #dd88ff;
1185  background-color: #773388;
1186}
1187.val-art
1188{
1189  padding: 2px;
1190}
1191
1192.name {
1193  display: inline;
1194  display: none;
1195
1196  font-size: 9px;
1197  color: black;
1198  background: white;
1199  border-style: solid;
1200  border-width: 1px;
1201  border-color: #664466; 
1202  border-radius: 2px;
1203  padding: 1px;
1204  margin: 1px;
1205}
1206
1207.val-const
1208{
1209  display: inline-block;
1210  border-style: solid;
1211  border-color: black;
1212  border-width: 1px;
1213  color: black;
1214  background-color: grey;
1215  padding: 2px;
1216  margin: 1px;
1217  border-radius 5px;  
1218  font-size: 8px;  
1219}
1220
1221</style>
1222
1223<script>
1224function togglePaths() {
1225 var selection = document.getElementById(\"checkbox-1\");
1226 if (selection.checked) {
1227   $('.path').css('display', 'inline-block')
1228 } else {
1229   $('.path').css('display', 'none')
1230 }
1231}
1232
1233function toggleNames() {
1234 var selection = document.getElementById(\"checkbox-2\");
1235 if (selection.checked) {
1236   $('.name').css('display', 'inline')
1237 } else {
1238   $('.name').css('display', 'none')
1239 }
1240}
1241
1242function toggleEffects() {
1243 var selection = document.getElementById(\"checkbox-3\");
1244 if (selection.checked) {
1245   $('.tr-effect').css('display', 'inline')
1246 } else {
1247   $('.tr-effect').css('display', 'none')
1248 }
1249}
1250
1251function toggleDupForces() {
1252 var selection = document.getElementById(\"checkbox-4\");
1253 if (selection.checked) {
1254   $('.tr-force-dup').css('display', 'inline')
1255 } else {
1256   $('.tr-force-dup').css('display', 'none')
1257 }
1258}
1259</script>
1260</head>
1261
1262<body>
1263
1264<fieldset class=\"tool-label-toggles\">
1265 <legend>Toggle labels: </legend>
1266 <label for=\"show-paths-checkbox\">paths</label>
1267 <input type=\"checkbox\" name=\"show-paths-checkbox\" id=\"checkbox-1\" onchange=\"togglePaths()\">
1268 <label for=\"show-names-checkbox\">names</label>
1269 <input type=\"checkbox\" name=\"show-names-checkbox\" id=\"checkbox-2\" onchange=\"toggleNames()\">
1270 <label for=\"show-effects-checkbox\">effects</label>
1271 <input type=\"checkbox\" name=\"show-effects-checkbox\" id=\"checkbox-3\" onchange=\"toggleEffects()\">
1272 <label for=\"show-effects-checkbox\">duplicate forces</label>
1273 <input type=\"checkbox\" name=\"show-effects-checkbox\" id=\"checkbox-4\" onchange=\"toggleDupForces()\">
1274</fieldset>
1275"
1276}