#line 2809 "/home/travis/build/felix-lang/felix/src/packages/flx_web.fdoc"
  
  open Regdef; // required
  
  include "./plugin_common";
  
  include "./slideshow-interface";
  var slideshow-maker  : (string->0) -> slideshow_t;
  
  include "./paragraph-interface";
  var paragraph-maker : (string->0) -> paragraph-control_t;
  
  include "./heading-interface";
  var heading-maker : paragraph-control_t * (string->0) -> heading-control_t;
  
  include "./fileseq-interface";
  var fileseq-maker : string -> fileseq-control_t;
  
  include "./scanner-interface";
  var fdocscanner-maker : unit -> fdocscanner-control_t;
  
  include "./button-interface";
  var button-factory-maker : unit -> button-factory_t;
  
  include "./fdoc-frame-interface";
  var fdoc_frame_maker : fdoc_frame_data_t -> fdoc_frame_t;
  
  include "./fdoc-interface";
  
  var xlat_cpp: string * string -> bool * string;
  var xlat_felix: string * string -> bool * string;
  var xlat_ocaml: string * string -> bool * string;
  var xlat_python: string * string -> bool * string;
  
  // felix document
  var INSTALL_ROOT="";
  var FLX_PATH = Empty[string];
  var FDOC_PATH = Empty[string];
  var FLX_PKGCONFIG_PATH = Empty[string];
  var FLX_WEBSERVER_PLUGIN_PATH = Empty[string];
  var PLUGIN_MAP = Empty[string^3];
  
  
  fun get_flx (fname:string) = {
    var flx =
      match get_file(fname,INSTALL_ROOT,FLX_PATH) with
      | Some name => load(name)
      | #None => f"NO FILE %S FOUND IN %S" (fname, str FLX_PATH)
      endmatch
    ;
    //println$ "Loaded felix file " + fname+", len="+str (flx.len.int);
    return flx;
  }
  
  // fixup text by replacing < > and & characters
  fun txt2html (x:string) =
  {
    var out2 = "";
    for var i in 0 upto x.len.int - 1 do
      var ch = x.[i];
      if ch == char "<" do out2+="&lt;";
      elif ch == char ">" do out2+="&gt;";
      elif ch == char "&" do out2+="&amp;";
      else out2+=ch;
      done
    done
  
    return out2;
  }
  
  
  proc boreq(l:&bool, r:bool) { l <- *l or r; }
  
  val markdown_code1 = RE2 ("(@{([^}]*)})");
  val markdown_code2 = RE2 (r"(@glossary\(([^)]*)\))");
  fun markdown (s:string):string= {
    var x = s;
    C_hack::ignore(GlobalReplace(&x, markdown_code1, StringPiece ("<code>\\2</code>")));
    C_hack::ignore(GlobalReplace(&x, markdown_code2, StringPiece ("<a href='/share/src/web/ref/glossary.fdoc#\\2'>\\2</a>")));
    return x;
  }
  
  val timeout = Filename::join (#Config::std_config.FLX_TARGET_DIR, "bin", "flx_timeout"+#(Filename::executable_extension));
  
  gen safer_popen(cmd:string)=>
    Process::popen_in(timeout+" -t 15 " + cmd + " 2>&1")
  ;
  
  // helper definitions
  regdef optwhite = ' '*;
  regdef white = ' '+;
  regdef felt= perl ("\\$?[A-Za-z._][-A-Za-z0-9_.]*");
  regdef fname = (felt "/")* felt;
  
  // A tangler definition looks like:
  // @tangler name = filename
  regdef tangler_def_regdef =
    "tangler" white group (felt) optwhite "="
    optwhite group (fname) optwhite
  ;
  
  // To set the output we just use
  // @tangle name
  regdef tangler_use_regdef =
    "tangle" white group (felt) optwhite
  ;
  var tangler_def_re2 = RE2 (Regdef::render tangler_def_regdef);
  var tangler_use_re2 = RE2 (Regdef::render tangler_use_regdef);
  
  object xlat_fdoc (t:string, filename:string) implements fdoc_t = {
  
    method fun whatami () => "Translator for " + filename;
    method fun mathjax_required () => needs_mathjax;
    method fun html_raw () => out;
    method fun html_page () => page;
    method fun html_title () => title;
    var title = filename;
    var slideshow = slideshow-maker write_string of (string);
    //eprintln$ "FDOC make slidehow .. " + #(slideshow.whatami);
  
    var paragraph = paragraph-maker write_string of (string);
    //eprintln$ "FDOC make paragraph .. " + #(paragraph.whatami);
  
    var heading = heading-maker (paragraph, write_string of (string));
    //eprintln$ "FDOC make heading .. " + #(heading.whatami);
  
    var fileseq = fileseq-maker (filename);
    //eprintln$ "FDOC make fileseq .. " + #(fileseq.whatami);
  
    var fdocscanner = fdocscanner-maker ();
    //eprintln$ "FDOC make scanner .. " + #(fdocscanner.whatami);
  
    var fdoc_frame_data :fdoc_frame_data_t = (heading=heading, button-factory=#button-factory-maker,fileseq=fileseq);
    var fdoc_frame = fdoc_frame_maker fdoc_frame_data;
  
  
    var needs_mathjax = false;
    var out = "";
    proc write_string(t:string)
    {
      out += t;
    }
  
    fun split_first (x:string, c:string):string*string =>
      match find_first_of (x, c) with
        | Some n => (strip(x.[to n]),strip(x.[n+1 to]))
        | _ => (x,"")
      endmatch
    ;
  
    var tanglers = strdict[string] ();
  
    proc def_tangler (id:string, filename:string)
    {
      match get tanglers id with
      | Some _ =>
        println$ "Duplicate definition of tangler " + id;
      | #None =>
        println$ "Add tangler id=" + id + " filename=" + filename;
        add tanglers id filename;
      endmatch;
    }
  
    // paragraphs
    proc sp () { paragraph.sp (); }
    proc sp (cls:string) { paragraph.sp-clas cls; }
    proc ep () { paragraph.ep (); }
    proc bp () { paragraph.bp (); }
  
    // headings
    proc h(n:int, txt:string) {
      heading.head (#(fileseq.docnum), n, markdown txt);
    }
  
  //---------------------------------------------------
    // main loop
    var inp = fdocscanner.fdoc_scan t;
    gen get_text () =>
      match #inp with
      | Some (Text x) => x
      | _ => ""
      endmatch
    ;
  next:>
    var entry = #inp;
    match entry with
    | Some (Cmd cmdline) => handle_cmd cmdline; goto next;
    | Some (Text x) =>
      for para in fdocscanner.psplit x do
        bp;
        write_string(markdown para);
      done
      ep;
      goto next;
  
    | #None =>
      ep;
      heading.finalise();
  
      slideshow.finalise();
      if #(slideshow.active) do
        eprintln$ "Slideshow Active";
      else
        //eprintln$ "Slideshow NOT active";
      done
    endmatch;
  
    var page =
     if #(slideshow.active)  then out
     else fdoc_frame.make_frame out
     endif
    ;
  
  //---------------------------------------------------
  
    // preformat
    proc inline_pre(b:string)
    {
      sp;
      write_string('<pre class="prefmtbg">');
      write_string(txt2html b);
      write_string("</pre>");
      ep;
    }
  
    proc inline_expect(b:string)
    {
      sp;
      write_string('<pre class="expected">');
      write_string(txt2html b);
      write_string("</pre>");
      ep;
    }
  
    proc inline_input(b:string)
    {
      sp;
      write_string('<pre class="input">');
      write_string(txt2html b);
      write_string("</pre>");
      ep;
    }
  
  
  
    proc inline_cpp (b:string)
    {
      sp;
      write_string("<pre class='cppbg'>");
      write_string((xlat_cpp(b,"")).1); // no parent!
      write_string("</pre>");
      ep;
    }
  
    proc inline_felix (b:string)
    {
      sp;
      write_string("<pre class='flxbg'>");
      needs_mathjax', txt := xlat_felix (b,"");
      needs_mathjax |= needs_mathjax';
      write_string(txt); // no parent!
      write_string("</pre>");
      ep;
    }
  
    proc inline_felix_unchecked (b:string)
    {
      sp;
      write_string("<pre class='uncheckedflxbg'>");
      needs_mathjax', txt := xlat_felix (b,"");
      needs_mathjax |= needs_mathjax';
      write_string(txt); // no parent!
      write_string("</pre>");
      ep;
    }
  
  
    proc inline_ocaml(b:string)
    {
      sp;
      write_string("<pre class='flxbg'>");
      needs_mathjax', txt := xlat_ocaml(b,"");
      needs_mathjax |= needs_mathjax';
      write_string(txt); // no parent!
      write_string("</pre>");
      ep;
    }
  
    proc inline_python(b:string)
    {
      sp;
      write_string("<pre class='flxbg'>");
      needs_mathjax', txt := xlat_python(b,"");
      needs_mathjax |= needs_mathjax';
      write_string(txt); // no parent!
      write_string("</pre>");
      ep;
    }
  
  
  
    proc felix_file (rest:string)
    {
        var re1 = RE2('(.*) "(.*)" "(.*)"');
        var re2 = RE2('(.*) "(.*)"');
        var v1 = varray(4uz, StringPiece "");
        var v2 = varray(4uz, StringPiece "");
        var v3 = varray(4uz, StringPiece "");
        var matched1 = Match(re1, StringPiece(rest),0,ANCHOR_BOTH,v1.stl_begin, v1.len.int);
        var matched2 = Match(re2, StringPiece(rest),0,ANCHOR_BOTH,v2.stl_begin, v2.len.int);
        if matched1 do
          var fname = v1.1.string.strip;
        elif matched2 do
          fname = v2.1.string.strip;
        else
          fname = rest;
        done
        var flx = get_flx(fname);
        if matched1 do
          var p1 = match find(flx,v1.2.string) with
          | Some i => i.int
          | #None => 0
          endmatch;
          flx = flx.[p1 to];
          var p2 = match find(flx,v1.3.string) with
          | Some i => i.int
          | #None => flx.len.int - 1
          endmatch;
          flx = flx.[to p2];
        elif matched2 do
          var re3 = RE2(v2.2.string);
          var matched3 = Match(re3,StringPiece(flx),0,UNANCHORED,v3.stl_begin, v3.len.int);
          if matched3 do
            flx = v3.1.string;
          done
        done
        needs_mathjax', html := xlat_felix (flx,"");
        needs_mathjax |= needs_mathjax';
        write_string("<pre class='inclusion'>\n"+fname+"</pre>\n");
        write_string("<pre class='flxbg'>");
        write_string(html);
        write_string("</pre>");
    }
  
    proc flx_and_expect (fname:string)
    {
      var flx = get_flx(fname+".flx");
      needs_mathjax', html := xlat_felix (flx,"");
      needs_mathjax |= needs_mathjax';
      write_string("<pre class='inclusion'>"+fname+".flx</pre>\n");
      write_string("<pre class='flxbg'>");
      write_string(html);
      write_string("</pre>\n");
      heading.add_button fname;
      write_string(heading.tree_button(fname,fname+"_d"));
      write_string("<code class='inclusion'>  "+fname+".expect</code>\n");
      var xpected = get_flx(fname+".expect");
      write_string("<pre id='"+fname+"_d' class='expected' style='display:none'>");
      write_string(xpected);
      write_string("</pre>");
    }
  
    proc extern_cpp (fname:string)
    {
      var flx = get_flx(fname);
      write_string("<pre class='inclusion'>\n"+fname+"</pre>\n");
      write_string("<pre class='cppbg'>");
      write_string((xlat_cpp (flx,"")).1);
      write_string("</pre>");
    }
  
    proc extern_ocaml (fname:string)
    {
      var flx = get_flx(fname);
      write_string("<pre class='inclusion'>\n"+fname+"</pre>\n");
      write_string("<pre class='cppbg'>");
      write_string((xlat_ocaml(flx,"")).1);
      write_string("</pre>");
    }
  
    proc extern_python(fname:string)
    {
      var flx = get_flx(fname);
      write_string("<pre class='inclusion'>\n"+fname+"</pre>\n");
      write_string("<pre class='cppbg'>");
      write_string((xlat_python(flx,"")).1);
      write_string("</pre>");
    }
  
    proc handle_cmd (b:string)
    {
  //println$ "CMD=@"+b;
      match Match (tangler_def_re2, b) with
      | Some v => def_tangler (v.1, v.2);
      | #None =>
        match Match (tangler_use_re2, b) with
        | Some s =>
          println$ "Tangle id=" + s.1;
          match get tanglers s.1 with
          | Some x =>
            println$ "Tangler filename=" + x;
            var xtn = Filename::get_extension x;
            println$ "Extension=" + xtn;
            if xtn in (".flx",".flxh") do
              write_string("<pre class='inclusion'>\n"+x+"</pre>\n");
              println$ "flx ....";
              inline_felix (#get_text);
            elif xtn in (".cxx",".cpp",".hpp",".c",".cc",".h") do
              write_string("<pre class='inclusion'>\n"+x+"</pre>\n");
              println$ "cpp ....";
              inline_cpp (#get_text);
            else
              write_string("<pre class='inclusion'>\n"+x+"</pre>\n");
              println$ "pre ....";
              inline_pre (#get_text);
            done
          | #None =>
            println$ "Can't find tangler '" + s.1+"'";
            inline_pre (#get_text);
          endmatch;
        | #None =>
          if b == "felix" do inline_felix (#get_text);
          elif b == "felix-unchecked" do inline_felix_unchecked (#get_text);
          elif prefix (b,"felix ") do felix_file (strip (b.[6 to]));
          elif prefix (b,"flx-and-expect ") do flx_and_expect (strip(b.[15 to]));
  
          elif b == "c++" do inline_cpp (#get_text);
          elif prefix (b,"c++") do extern_cpp ( strip(b.[4 to]));
  
          elif b == "ocaml" do inline_ocaml (#get_text);
          elif prefix (b,"ocaml") do extern_ocaml( strip(b.[6 to]));
  
          elif b == "python" do inline_python(#get_text);
          elif prefix (b,"python") do extern_python( strip(b.[7 to]));
  
  
          elif b=="p" do bp;
          elif b=="pre" do inline_pre (#get_text);
          elif b=="expect" do inline_expect (#get_text);
          elif b=="input" do inline_input(#get_text);
          elif b=="obsolete" do ep; sp 'obsolete'; write_string("<em>Obsolete</em> ");
          elif b=="caveat" do ep; sp 'caveat'; write_string("<em>Caveat: </em> ");
          elif b=="impl" do ep; sp 'implementation_detail'; write_string("<em>Implementation Detail: </em>");
          elif b=="future" do ep; sp 'future'; write_string("<em>In future: </em>");
          elif b=="note" do ep; sp 'bug'; write_string("<em>Note: </em>");
          elif b=="bug" do ep; sp 'bug'; write_string("<em>Bug: </em>");
          elif b=="fixed" do ep; sp 'fixed'; write_string("<em>Fixed: </em>");
          elif b=="done" do ep; sp 'done'; write_string("<em>Done: </em>");
          elif b=="mathjax" do needs_mathjax = true;
  
          elif prefix (b,"title") do title = strip(b.[5 to]);
  
          elif prefix(b,"h1") do h(1,b.[3 to]);
          elif prefix(b,"h2") do h(2,b.[3 to]);
          elif prefix(b,"h3") do h(3,b.[3 to]);
          elif prefix(b,"h4") do h(4,b.[3 to]);
          elif prefix(b,"h5") do h(5,b.[3 to]);
  
          // external image
          elif prefix(b,"image") do
            var img = split_first(b.[6 to],"|");
            write_string("<img src='"+img.(0)+"' style='"+img.(1)+"'></img>");
  
          // arbitrary shell command
          elif prefix(b,"sh") do
            var cmd = b.[3 to];
            var fout = safer_popen(cmd);
            if valid fout do
              var output = load fout;
              var result = Process::pclose fout;
              println$ "Ran cmd=" + cmd;
              //println$ "Output = " + output;
              write_string("<pre>");
              write_string output;
              write_string("</pre>");
            else
              println$ "Unable to run shell command '" + cmd "'";
              write_string("Failed cmd: " + b);
            done
  
          // slideshow
          elif slideshow.check-slide-commands b do ;
          elif b == "" do ;
          else
            println$ "Unable to understand @command '"+b+"'";
          done
        endmatch;
      endmatch;
    }
  }
  
  eprintln$ Version::felix_version +  " fdoc2html initialisation";
  
  fun setup(config_data:string) = {
    var config_lines = split(config_data, "\n");
    config_lines = map (strip of (string)) config_lines;
    var pathext = RE2("(.*)\\+=(.*)");
    var varset = RE2("(.*)=(.*)");
    var plugin_spec = RE2 " *extension (.*)->(.*)::(.*)";
  
    var result = varray[StringPiece] (4.size,StringPiece(""));
    for line in config_lines do
      var match_result = Match(pathext, StringPiece(line),0,ANCHOR_BOTH, result.stl_begin,3);
      if match_result do
        var lhs = result.1.str.strip;
        var rhs = result.2.str.strip;
        match lhs with
        | "FLX_PATH" => FLX_PATH += rhs;
        | "FDOC_PATH" => FDOC_PATH += rhs;
        | "FLX_PKGCONFIG_PATH" => FLX_PKGCONFIG_PATH += rhs;
        | "FLX_WEBSERVER_PLUGIN_PATH" => FLX_WEBSERVER_PLUGIN_PATH += rhs;
        | _ => ;
        endmatch;
      else
      match_result = Match(varset, StringPiece(line),0,ANCHOR_BOTH, result.stl_begin,3);
      if match_result do
        lhs = result.1.str.strip;
        rhs = result.2.str.strip;
        match lhs with
        | "INSTALL_ROOT" => INSTALL_ROOT = rhs;
        | _ => ;
        endmatch;
      else
      match_result = Match(plugin_spec, StringPiece(line),0,ANCHOR_BOTH, result.stl_begin,4);
      if match_result do
        var extn = result.1.str.strip;
        var lib = result.2.str.strip;
        var entry = result.3.str.strip;
        PLUGIN_MAP = Cons ((extn, lib, entry), PLUGIN_MAP);
      done done done
    done
  
    xlat_felix = Dynlink::load-plugin-func2 [bool * string, string, string] (
      dll-name="flx2html", setup-str=config_data, entry-point="flx2html"
    );
  
    xlat_cpp = Dynlink::load-plugin-func2 [bool * string, string, string] (
      dll-name="cpp2html", setup-str=config_data, entry-point="cpp2html"
    );
  
    xlat_ocaml = Dynlink::load-plugin-func2 [bool * string, string, string] (
      dll-name="ocaml2html", setup-str=config_data, entry-point="ocaml2html"
    );
  
    xlat_python = Dynlink::load-plugin-func2 [bool * string, string, string] (
      dll-name="py2html", setup-str=config_data, entry-point="py2html"
    );
  
  
    slideshow-maker  = Dynlink::load-plugin-func1 [slideshow_t, (string->0)] (dll-name="fdoc_slideshow");
  
    paragraph-maker = Dynlink::load-plugin-func1 [paragraph-control_t, (string->0)] (dll-name="fdoc_paragraph");
  
    heading-maker = Dynlink::load-plugin-func2 [heading-control_t, paragraph-control_t , (string->0)] (dll-name="fdoc_heading");
  
    fileseq-maker = Dynlink::load-plugin-func1 [fileseq-control_t,string] (dll-name="fdoc_fileseq");
  
    fdocscanner-maker = Dynlink::load-plugin-func0 [fdocscanner-control_t] (dll-name="fdoc_scanner");
  
    button-factory-maker = Dynlink::load-plugin-func0 [button-factory_t] (dll-name="fdoc_button");
  
    fdoc_frame_maker = Dynlink::load-plugin-func1 [fdoc_frame_t,fdoc_frame_data_t] (dll-name="fdoc_frame");
  
    return 0;
  }
  
  export fun setup of (string) as "fdoc2html_setup";
  export fun xlat_fdoc of (string * string) as "fdoc2html";