/*
 *  helpinit.sli
 *
 *  This file is part of NEST.
 *
 *  Copyright (C) 2004 The NEST Initiative
 *
 *  NEST is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  NEST is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with NEST.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

/* 
    Sli-commands initializing Online-help.

*/

% Define the root of the help files
/HelpRoot    statusdict /prgdocdir get (/help)       join def
/HelpdeskURL statusdict /prgdocdir get (/index.html) join def

/HelpExtension (.hlp)  def
/HtmlExtension (.html) def

/TextIndexFilename (helpindex.hlp)  def
/HtmlIndexFilename (helpindex.html) def

% This is where the generated help will go:
/HelpSubdirs    [(cc) (sli)]                 def
/CPPHelpRoot    HelpRoot (/) join (cc)  join def
/SLIHelpRoot    HelpRoot (/) join (sli) join def
/HelpSearchPath [CPPHelpRoot SLIHelpRoot]    def



% regular expressions for help system

/SimpleKeyword
  ((Name:|Synopsis:|Description:|Parameters:|Options:|Examples:|Bugs:|Diagnostics:|Author:|FirstVersion:|Remarks:|Availability:|References:|Source:|Sends:|Receives:|Transmits:)) regcomp def

/ReferencingKeyword 
 ((SeeAlso:|Variants:)) regcomp 
def

/InvariantsKeyword 
 ((Examples:)) regcomp 
def



/* BeginDocumentation 
Name: makehelp - Extracts documentation from a source file
Synopsis: string makehelp -> -
Description:
The function scans the file specified by the argument and generates
plain text online help information for all functions documented in
this file. A separate file with the extension specified by variable
HelpExtension is generated for each function. The help information can
be viewed in a terminal by function help. The generated files are the
source from which the hypertext online help is generated by an
undocumented function in a next step.

The start of documentation for a function in the source file is 
indicated by the string
  "slash-asterisk [white]BeginDocumentation" 
(without the quotation marks), where slash-star is the character "/"
immediately followed by "*" (with the quotation marks in both cases).
The end of the documentation is indicated by the same two characters
in reverse order. Thus, the documentation of a function is enclosed in
a C-style comment.

The help files are created in the directory specified by variable
CPPHelpRoot or by variable SLIHelpRoot depending on the type of the
source file.  The makehelp function recognizes the keyword "name:" in
uppercase letters. The rest of the line containing this keyword
defines the name of the function the documentation refers to followed
by a short characterization of its purpose. The two components are
separated by a the three character string " - ". More formally, 
makehelp extracts help information if the source file matches 
  [any][slash-star[[white]BeginDocumentation[any]\n
  [white] name: [any][space]-[space][any]\n[any]star-slash]]

Function makehelp appends a line to the help file starting with the
keyword "source:" in uppercase letters followed by the absolute path
to the source file.  This backward reference enables the help system
of NEST to direct an editor and validation tools to the implementation
of the documented function.

The generator of hypertext online help operating on the help file 
recognizes many more keywords for structuring and cross linking
the documentation. 

Parameters: 
The argument specifies the name of the source file by an absolute path. 
If no absolute path is provided all directories listed in the variable 
MakeHelpSearchPath are searched for the file.
Examples: 
 (sli-init.sli) makehelp 
 (/home/user/slifunctions/MySli.sli) makehelp
Author: Hehl, docu edited by Sirko Straube
FirstVersion: 14.4.1999
SeeAlso: help, helpindex, :makeallhelp, :makehelpindex
*/ 

/makehelp_s
{ << >> begin
  /TheFile Set                        % save FileName in /TheFile
    mark                              % scan for valid extension
      TheFile (.sli) :searchifend_s {
        /DestPath SLIHelpRoot def 
        exit}
      case
      TheFile (.cpp) :searchifend_s {
        /DestPath CPPHelpRoot def 
        exit}
      case
      TheFile (.cc) :searchifend_s {
        /DestPath CPPHelpRoot def 
        exit}
      case
      TheFile (.h) :searchifend_s {
         /DestPath CPPHelpRoot def 
         exit} 
      case
      { TheFile end
        /makehelp /InvalidSourceExtensionError raiseerror
      }
    switchdefault
    TheFile (/) searchif              % Do we have an explicit path?  
    {
      /SourceLine TheFile def         % Yes, remember 
      TheFile ifstream                % and try to open
    }
    {
      MakeHelpSearchPath TheFile 
      LocateFileNames                 % Try all source directories..
      dup length 0 eq{
        pop false                     % no file found     
      }
      { 
        0 get dup                     % remember for last .hlp line, e.g. 
        /SourceLine Set               % prepare SourceLine
        ifstream                      % open the first match
      } ifelse  % filename found 
    } ifelse  % direct path or SearchPath
    {
      /SourceFile Set                 % store Source  
      ( Source: ) SourceLine join /SourceLine Set
      SourceFile
      { 
        ws                            % eat white
        igood not exch ieof 3 -1 roll or 
        {exit} if                     % file still valid?
        getc (/) 0 get eq             % look for /
        { 
          igood not exch ieof 3 -1 roll or
          {exit} if                   % file still valid?
          getc (*) 0 get eq           % look for *
          {                         
            ws
            igood not exch ieof 3 -1 roll or
            {exit} if                 % file still valid?
            oldgetline                   % /* detected 
            (BeginDocumentation) searchif
            { 
              ws
              igood not exch ieof 3 -1 roll or
              {
                pop TheFile end
                /makehelp /BeginDocumentationKeyNotContinuedError
                raiseerror
              } if
              oldgetline dup (Name:) search
              {
                pop pop
                ( - ) search
                {
                  ( ) 2 trim          % remove spaces around keyword
                  HelpExtension join         % make filename
                  DestPath exch joinpath
                  /HelpFileName Set                 
                  M_STATUS (makehelp) (creating ) HelpFileName join message
                  HelpFileName (w) file /HelpFile Set
                  pop pop             % remaining from search...
                  HelpFile exch <- endl pop
                  SourceFile
                  {
                    igood not exch ieof 3 -1 roll or
                    { HelpFile closeostream pop pop % Source popped twice
                      TheFile end /makehelp
                      /IncompleteDocumentationError raiseerror
                    } if
                    oldgetline dup (*/) searchif
                      {
                        pop % doubled Line popped!
                        HelpFile SourceLine <- endl % add Source to .hlp
                        closeostream exit
                      } if
                    HelpFile exch <- endl pop
                  } loop  
                  pop  % SourceFile on stack twice here !!!
                }
                { 
                  3 npop TheFile end % pop Searchedline, original line, File
                  /makehelp 
                  /MissingObligatoryMinusAfterCommandNameError
                  raiseerror
                }ifelse
              }
              { 3 npop TheFile end  % pop Searchedline, original line, File
                /makehelp /KeyName:ExpectedError
                raiseerror 
              } ifelse
            } if
          }
          {
            oldgetline pop             % no DocText (*) 
          } ifelse
        }
        {
          oldgetline pop               % no DocText (/)
        } ifelse % / found
      } loop % read source  
      closeistream
    }
    {  
      TheFile end
      /makehelp /FileNotFoundError raiseerror
    } ifelse % stream open for reading?
  end
} bind def

/makehelp trie
  [/stringtype] /makehelp_s load addtotrie
def


/* Documentation (not included in helpdesk)

   Name: :searchifend_s - check wether a substring is the end of another  
   Synopsis: string1 string2 searchif -> bool
   Description: check wether a substring is the end of another
   Parameters: string1 : string to search into
               string2 : endstring to search for
   Examples: (hallo.h) (.h) :searchifend --> true
   Bugs: :Function because may be of little use ...
         Does not work if there's a multiple occurence!
   Author: Hehl
   FirstVersion: April 20, 1999
   Remarks: if command used by any other person,
            should be moved to misc_helpers.sli !
   SeeAlso: search, searchif
*/

/:searchifend_s
% string string --> bool
{
  search_s
  {
    pop pop () eq
  }
  {
    pop false
  } ifelse
} bind def




/* BeginDocumentation
Name: :makehelpindex - create a list of all commands
Synopsis: :makehelpindex -> -
Description:
:makehelpindex compiles the short descriptions of all commands to
a list which is stored in the file index.hlp.
This command is executed as part of NEST's installation procedure. 
You should not need to call this command. Perhaps you were looking
for the "helpindex" command?
Author: Hehl
FirstVersion: April 20, 1999
Remarks: Improved documentation R Kupper, 17-jul-2008
SeeAlso: helpindex, help, makehelp, :makeallhelp
*/ 

/:makehelpindex
{ << >> begin
  /WorkingDir Directory def
  /TheIndex [] def
  HelpSearchPath
  {
    dup /ThePath Set
    SetDirectory pop
 
   (.*) FileNames
    {
      /currentfile Set      

      currentfile      
      dup HelpExtension search
      {
        4 1 roll                   % filename without .hlp stored
        pop pop                    % other substrings popped
        ThePath exch joinpath
        ifstream                   % was given us by ls, exists!
        {
          oldgetline                  % read first line
          exch closeistream        % and forget about the rest
          ( - ) search
          {
            pop pop 
            ( ) 2 trim exch
            {                      % normalize command to 20 characters,
              ( ) join             % but make sure that the command is terminated
              % by a white-space.
              dup length 20 gt {exit} if
            } loop
            exch join
            TheIndex exch append
            /TheIndex Set
          }
          {
            pop pop            
            M_WARNING (:makehelpindex) (Illformed "Name" line in file ) WorkingDir currentfile joinpath join (:) join message
            M_WARNING (:makehelpindex) (  missing "-" after command name.) message
          } ifelse
        }
        {
          pop                        % commandname   
        } ifelse 
      } 
      {                                    % no .hlp filename
        pop pop                            % (shouldn't occur, but who knows?)
      } ifelse
    } forall
  } forall
  HelpRoot TextIndexFilename joinpath
  (w) {file} stopped
  {
    errordict /newerror false put
    3 npop end                            % file with its 2 args popped

    M_ERROR (:makehelpindex) (Could not write to index file: )
    HelpRoot TextIndexFilename joinpath
    join message    

    /:makehelpindex /IOError raiseerror
  }
  {
    /IndexFile Set
    TheIndex :sort
    {
      IndexFile exch
      <- endl ;
    } forall
    IndexFile closeostream
  } ifelse
  WorkingDir SetDirectory pop
  end
} bind def


/* BeginDocumentation
 Name: :hlp2html - Generate HTML-help pages for all existing help files.
 Synopsis: :hlp2html -> -
 Description: :hlp2html operator translates .hlp files generated by 
   "makehelp" to HTML. There are two types of keywords: normal
   and referencing.
   Referencing Keywords are SeeAlso and Variants, each followed by 
   a colon. The operator will try to set up hyperlinks for every
   name found after a Referencing Keyword, seperated by commas.
   A normal Keyword will simply be used for formatting.
   :hlp2html generates several warnings if it stumbles accross 
   syntactical inccorect documentation files.
   Normally called by :makeallhelp.
 Parameters: none
 Examples: :hlp2html. Normal usage is to call :makeallhelp instead!
 Bugs: 
 Author: Hehl
 FirstVersion: Oct 13th, 1999
 Remarks: This is not implemented for performance reasons. The regex
    search probably is not the fastes solution.
 SeeAlso: :makeallhelp, help
*/ 

/:hlp2html
{ << >> begin

%%=====================================================
%% Valid documentation keywords.
%%
%% IF YOU ADD, REMOVE, OR MODIFY ANY OF THESE KEYWORDS,
%% YOU MUST ALSO UPDATE THE FILES
%% synod2/doc/quickref.html
%% AND
%% synod2/lib/help/doc_header.txt!
%%=====================================================

  /WorkingDir Directory def
  HelpSearchPath
  {
    dup /CurrentDir Set SetDirectory pop
    (.*\\) HelpExtension join  FileNames
    % dup ==
    {
      dup (Translating File ) CurrentDir join 
      exch joinpath M_DEBUG message
      /in Set
      /out in dup length 4 sub 4 erase dup /TheCommand Set (.html) join
      ofstream not {end /:hlp2html /IOError raiseerror} if def
      out  (<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
<head>
  <title>NEST Command Index: ) <- TheCommand <- (</title>
  <style type="text/css">
    body {
      padding: 0;
      margin: 0;
    }
    a {
      color: #339;
      text-decoration: none;
    }
    a:visited {
      color: #339;
      text-decoration: none;
    }
    a:hover {
      text-decoration: underline;
    }
    h1 {
      padding: 15px 0 0 15px;
    }
    p {
      padding-left: 15px;
    }
    table.headerfooter {
      margin: 20px 0 20px 0;
      background-color: #eee;
      width: 100%;
      height: 30px;
      border-top: 2px solid #ccc;
      border-bottom: 2px solid #ccc;
      text-align: center;
    }
  </style>
</head>
<body bgcolor="white" fgcolor="black">
<h1>Command: ) <- TheCommand <- (</h1>
 
<table class="headerfooter">
  <tr>
    <td width="30%" align=center><a href="../../index.html">NEST HelpDesk</a></td>
    <td width="30%" align=center><a href="../helpindex.html">Command Index</a></td>
    <td width="30%" align=center><a href="../../quickref.html">NEST Quick Reference</a></td>
  </tr>
</table>

<p>
<table>)  <- endl ;
      /TableIsOpen false def % This flag is used to monitor the table.
      /in in ifstream not {end /HTML /IOError raiseerror} if def
      {
	% now we read the file line by line
        in getline not {pop exit} if
        /Line Set pop
        SimpleKeyword Line 1 0 regexec 0 eq
        {
	  TableIsOpen 
	  { 
	    out (</pre></td></tr>) <- ; % close the open table row
	    /TableIsOpen false def      % and note it.
	  } if
          0 get /where Set
          Line where 0 get dup where 1 get exch sub getinterval dup
          (<tr><td valign="top"><b>) exch join (</b></td><td valign="top"><pre>) join Line regex_replace
           out exch <- endl ;
	  /TableIsOpen true def % Make a note that we have opened a table row. It must be closed
	                        % when the next keyword has been found, or the file is closed.
        } 
        {
          pop  %array from regexec doesn't mean anything right here
          ReferencingKeyword Line 1 0 regexec 0 eq
          {
            0 get /where Set 
            Line where 0 get dup where 1 get exch sub getinterval
            /ThisKeyword Set
            out (<tr><td valign="top"><b>) ThisKeyword join (</b></td><td valign="top">) join <- ;
            Line length where 1 get eq
            {()}
            {Line dup length where 1 get sub 
            where 1 get exch getinterval}ifelse
            ( +) () 3 -1 roll  regex_replace
            dup length 0 eq
            {
              pop
              (Removed empty Reference Keyword ") ThisKeyword join 
              (" in ) join TheCommand join
              M_DEBUG message
            }
            {
             (,) join /Rest Set
             {
              Rest length 0 eq {exit} if
              Rest (,) search not
              { % this case should never happen at all!
                pop TheCommand (: Syntax error while hyperlinking ) join
                Line join M_WARNING message
                out (<tr><td></td><td>) Line join 
                (</td></tr>) join <- endl ;
                exit
              }
              {
                /TheLink Set pop /Rest Set
                /LinkTo () def
                HelpSubdirs
                {
                  (../) exch join
                  TheLink HelpExtension join joinpath dup
                  ifstream
                  {pop /LinkTo Set}
                  {pop} ifelse
                } forall
                LinkTo length 0 eq
                {
                  (Dead Link to ) TheLink join ( in ) join TheCommand join
                  M_WARNING message
                  out TheLink ( ) join <- ;
                }
                {
                  out LinkTo dup length 3 sub 3 erase (html) join 
                  (<a href=") exch join ("><tt>) join 
                  TheLink join (</tt></a>  ) join <- ; 
                } ifelse
              } ifelse
             } loop
            } ifelse
	    % Here, the referencing-keyword table row is closed,
	    % so we don't have to use the flag TableIsOpen.
            out (</td></tr>) <- endl ;
	    
          } 
          {
	    % Here, we havent't got a keyword. Thus, we just have to
	    % print the line.
	    % At this position, TableIsOpen should be true!
            pop %array from regexec
            out Line <- endl ;
          } ifelse
        } ifelse
      } loop
      out
      TableIsOpen
      {
	(</pre></td></tr>) <-
        /TableIsOpen false def
      } if
      (</table>
</p>

<table class="headerfooter">
  <tr>
    <td width="30%" align=center><a href="../../index.html">NEST HelpDesk</a></td>
    <td width="30%" align=center><a href="../helpindex.html">Command Index</a></td>
    <td width="30%" align=center><a href="../../quickref.html">NEST Quick Reference</a></td>
  </tr>
</table>
<p style="text-align:center">
  &copy; 2000-2010 <a href="http://www.nest-initiative.org">The NEST Initiative</a>
</p>
</body>\n</html>) <- endl closeostream
    } forall
  } forall
  WorkingDir SetDirectory pop
  HelpRoot TextIndexFilename joinpath ifstream
  not {/HMTL /IOError raiseerror} if /in Set
  HelpRoot HtmlIndexFilename joinpath ofstream
  not {/HMTL /IOError raiseerror} if /out Set
  out (<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
<head>
  <title>NEST Command Index</title>
  <style type="text/css">
    body {
      padding: 0;
      margin: 0;
    }
    h1 {
      padding: 15px 0 0 15px;
    }
    p {
      padding-left: 15px;
    }
    a {
      color: #339;
      text-decoration: none;
    }
    a:visited {
      color: #339;
      text-decoration: none;
    }
    a:hover {
      text-decoration: underline;
    }
    h1 a {
      color: #000;
      text-decoration: none;
    }
    table.headerfooter {
      margin: 20px 0 20px 0;
      background-color: #eee;
      width: 100%;
      height: 30px;
      border-top: 2px solid #ccc;
      border-bottom: 2px solid #ccc;
      text-align: center;
    }
    table.commands {
      margin: 15px 0 15px 0;
      background-color: #eee;
      width: 90%;
      border: 2px solid #ccc;
      border-spacing: 0px;
      border-collapse: collapse;
    }
    table.commands td {
      border-bottom: 1px solid #ccc;
      border-right: 1px dotted #ccc;
      padding: 5px 0 5px 10px;
      text-align: left;
    }
    table.letteridx {
      margin: 0;
      background-color: #eee;
      width: 90%;
      border: 2px solid #ccc;
      border-spacing: 0px;
      border-collapse: collapse;
    }
    table.letteridx td {
      border-right: 1px solid #ccc;
      padding: 5px;
      text-align: center;
    }
    table.letteridx a {
      display: block;
      height: 100%;
      width: 100%;
    }
  </style>
</head>
<body bgcolor="white" fgcolor="black">
<h1><a name="top">NEST Command Index</a></h1>

<table class="headerfooter">
  <tr>
    <td width="50%" align=center><a href="../index.html">NEST HelpDesk</a></td>
    <td width="50%" align=center><a href="../quickref.html">NEST Quick Reference</a></td>
  </tr>
</table>) <- endl ;

  /Letters [] def
  /CommandsByLetter << >> def
  /ThePath HelpRoot (/) join def
  {
    in getline not {pop exit} if 
    /Line Set pop

    /HtmlLine (<tr><td width="33%">) def

    Line ( ) search pop /command Set pop /Description Set
    /LinkTo () def
    HelpSubdirs
    {
      dup HelpRoot exch joinpath 
      command joinpath (.html) join
      ifstream {exch /LinkTo Set} if 
      pop
    } forall
    LinkTo length 0 eq
    {
      (Dead Helpindex Link to ) command join M_WARNING message
      out command <- ;
    }
    {
      % We only collect data here to be printed later
      /cmdFirstLetter command 0 1 getinterval ToUppercase def
      % We add a new empty array only for the first occurence of a letter
      % and if the letter is not known yet
      CommandsByLetter cmdFirstLetter cvlit known not {
        /Letters Letters cmdFirstLetter append def
        CommandsByLetter cmdFirstLetter cvlit [] put
      } if

      /HtmlLine HtmlLine (<a href=") join LinkTo command joinpath join (.html">) join command join (</a>) join def
    } ifelse
    /HtmlLine HtmlLine (</td><td>) join Description join (</td></tr>) join def
    CommandsByLetter cmdFirstLetter cvlit get HtmlLine append
    CommandsByLetter exch cmdFirstLetter cvlit exch put
  } loop

  % We want to have the non-letters (:, <, ...) at the end
  /Letters Letters Sort def
  Letters {0 get 65 lt} Map [false] search { length /rotation Set } { 0 /rotation Set } ifelse
  /Letters Letters rotation Rotate def

  Letters {
    /LetterQ Set

    out (<center>\n<a name=") LetterQ join ("><table class="letteridx">\n<tr>) join <- endl ;
    Letters {
      /CurrentLetter Set
      CurrentLetter LetterQ eq
        { out (<td>) CurrentLetter join (</td>) join <- endl ; }
        { out (<td><a href="#) CurrentLetter join (">) join CurrentLetter join (</a></td>) join <- endl ; }
      ifelse
    } forall
    out (</tr>\n</table></a>\n</center>\n\n) <- endl ;

    out (<center><table class="commands">\n) <- endl ;
    CommandsByLetter LetterQ cvlit get {
      out exch <- endl ;
    } forall
    out (</table></center>) <- endl ;
  } forall

  out (<table class="headerfooter">
  <tr>
    <td width="30%" align=center><a href="../index.html">NEST HelpDesk</a></td>
    <td width="30%" align=center><a href="#top">Top</a></td>
    <td width="30%" align=center><a href="../quickref.html">NEST Quick Reference</a></td>
  </tr>
</table>

<p style="text-align:center">
  &copy; 2000-2010 <a href="http://www.nest-initiative.org">The NEST Initiative</a>
</p>
</body>\n</html>) <- endl ;
  end
} bind def


/* BeginDocumentation
Name: :makeallhelp - creates/updates all oneline-help texts and the index
Synopsis: :makeallhelp -> -
Description:
:makeallhelp calls makehelp for any file found in one of the
directories of MakeHelpSearchPath. Afterwards calls :makehelpindex to
update index file.
This command is executed as part of NEST's installation procedure. 
You should not need to call this command. Perhaps you were looking
for the "helpindex" command?
Author: Hehl
FirstVersion: April 20, 1999
Remarks:
Should get a 'forced' option where it even tries to continue
 parsing source files where a corrupt documentation was found.
Improved documentation R Kupper 17-jul-2008
SeeAlso: help, makehelp, helpindex, :makehelpindex
*/ 

/:makeallhelp
{ << >> begin
  /WorkingDir Directory def %save old working dir

  SLIHelpRoot SetDirectory not {SLIHelpRoot MakeDirectory} if
  CPPHelpRoot SetDirectory not {CPPHelpRoot MakeDirectory} if

  (chmod 755 ) SLIHelpRoot join system
  (chmod 755 ) CPPHelpRoot join system

  WorkingDir cd  

  MakeHelpSearchPath
  {
    /makeallhelp_ThePath Set
    M_INFO (:makeallhelp) (Processing directory ) makeallhelp_ThePath join message
    makeallhelp_ThePath SetDirectory pop (.*) FileNames
    {
      /makeallhelp_TheFile Set      
      M_STATUS (:makeallhelp) (processing file ) makeallhelp_TheFile join message
      makeallhelp_ThePath makeallhelp_TheFile joinpath {makehelp} stopped 
      {
        errordict /errorname get        
        /InvalidSourceExtensionError eq
        {
          % this one's not reported
          pop pop % pop command name and argument
          errordict /newerror false put
        }
        {
          M_ERROR (:makeallhelp) (Documentation error in file ) makeallhelp_ThePath makeallhelp_TheFile joinpath join message
          handleerror    
        } ifelse
      } if
    } forall
  } forall
  WorkingDir SetDirectory pop %restore working dir
  end

  :makehelpindex
  :hlp2html

} bind def

/*BeginDocumentation
Name: ppage - print a file to cout in chunks of 20 lines.

Description:
This is a "poor man's pager" implemented in SLI. It can be used when
no external pager program is available.

Synopsis:
      ifstream   ppage -> -
      (filename) ppage -> - 

Diagnostics:
If a filename is given and does not exist, /FileOpenError is raised.
If an ifstream is given, it will be closed by ppage.

Author: Marc-Oliver Gewaltig, R Kupper

FirstVersion: 27-sep-2007

SeeAlso: page
*/
/ppage [/istreamtype]
{
  {
    () =  
    20
    {
        getline
        { (> ) =only = } {exit} ifelse
    } repeat
    igood not {exit} if
    () =    
    (Press <RETURN> to continue or "q <RETURN>" to quit. ) readline
    {      
     0 get 113 eq {exit} if 
    } if
  } loop
  closeistream
  () =  
} def  

/ppage [/stringtype]
{
  dup
  ifstream not
  {
    /ppage /FileOpenError raiseerror    
  } if
  ppage  
  pop  
} def


/* BeginDocumentation
   
Name: page - Conveniently display contents of a long file

Synopsis: (filename) page -> - 

Description:
  The "page" command is used by NEST's online-help system to display
  the help pages. It displays the contents of a file, either through a
  built-in pager, or an external pager program. The text you are
  reading right now is displayed using "page".

  You can customize the display. Type "/page ShowOptions" to show your
  current settings. Use the "SetOptions" command to customize the
  pager program. Here are some useful suggestions. You will want to
  put your favourite into your ~/.nestrc file (see "/nestrc help"):
  
  1. /page << /command (more)             % Use more as a pager
              /wait    true >> SetOptions % and wait for termination
  
  2. /page << /command (less -S)          % Use less as a pager
              /wait    true >> SetOptions % and wait for termination
  
  3. /page << /command (xterm -e less -S)  % Run less in a separate xterm window...
              /wait    false >> SetOptions % ..so we can go on working in parallel.
  
  4. /page << /command (xless)             % Use xless as a pager
              /wait    false >> SetOptions % xless opens it's own window.
  
  5. /page << /command (xemacs)            % If you like to show helpfiles using xemacs.
              /wait    false >> SetOptions % xemacs opens it's own window.
  
  6. /page << /command false >> SetOptions % use built-in pager.
  
  7. /page ResetOptions % Return to the default configuration ($PAGER or built-in pager).

Options:
  /command - UNIX command to display a file (default: see below)
  
    /command can be either false or a string.
    If /command is set to false, "page" uses the simple built-in
    pager "ppage" for display.
  
    If it is set to the name of a UNIX program, "page" will execute
    this program as a child process, passing the name of the
    file as argument.
  
    /command defaults to the contents of the $PAGER environment
    variable. If this variable is not set, it defaults to false.
  
  /wait - whether to wait for the UNIX program to finish. (default: true)
  
    If /wait is set to true, the "page" command will not return until
    the UNIX process is terminated. Use this setting for all pager
    programs that write to stdout.
  
    If /wait is set to false, "page" will return immediately after
    creating the child process. Use this setting for all pager programs
    that open up their own window.
  
    The value of /wait is ignored, if /command is set to false.

Examples:
  The following example displays the license agreement that came with
  your copy of NEST:

  "statusdict /prgdatadir get (/LICENSE) join page"

Bugs: The string contained in /command is processed by "breakup" to separate
      the command from its parameters. Be aware that there is a bug in the
      "breakup"-routine, which makes it impossible to pass a parameter
      containing blanks by enclosing it in quotation marks.

      WARNING: Setting the /wait option to false with a pager
      program that writes to stdout will screw up your SLI prompt.

Author: R Kupper

FirstVersion: Apr 22 1999, reworked 27-sep-2007

Remarks: If /wait is set to false, the "system"-command is called in
          no-care-mode. Be aware that this may be an expensive operation
          depending on the ressources your SLI-process occupies.
          (see description of "system").
         Even if /wait is set to true, creation of a child process may
          be expensive. To avoid any overhead caused by process creation, set /command
          to false.

SeeAlso: help, helpindex, license, system
*/

/page <<
    /command  (PAGER) getenv not {false} if
    /wait     true
>> Options

/page [/stringtype]
{
  /page /command GetOption false eq not % Is there a Pager program to use?
  {%yes, there is a Pager
    /page /wait GetOption % Shall we wait for its termination?
    {%yes, we shall wait for termination of pager process
      /page /command GetOption ( ) breakup exch append 0 system % Spawn Pager Program to display text
      2 npop % Wait for Pager process to terminate and clear stack
    }
    {%no, we shall not wait for termination of pager process
      /page /command GetOption ( ) breakup exch append 1 system % Spawn orphaned Pager process to display text
    }
    ifelse
  }
  {%no, there is no Pager. So use our built-in poor man's pager:
    ppage
  }
  ifelse
} def  


/* BeginDocumentation
 Name: help - prints help text for a given command

 Synopsis: /name help -> -

 Description:
   help displays the online help file of the command /name.
   This documantation is created during installation.

   help uses the command "page" to display its information.
   Type
       /page help
   to learn how to customize the output of this command.

 Parameters: 
   /name is the command you want to read a help about.

 Examples: 
   /help help %prints this text.
   /page help %learn how to customize the output of the help command
   /ThisIsDefinitelyNoCommand help %raises a NoHelpInformationError.

 Bugs: 

 Author: Diesmann, Hehl, R Kupper

 FirstVersion: 26.3.1999

 Remarks:
  help uses the command "page" to display its information.
  Type
      /page help
  to learn how to customize the output of this command.

  Commented 13 April, 1999

  Developers are kindly requested to use our conventional header for
  commenting any new and all those older commands.

 SeeAlso: page, helpindex, helpdesk, apropos, which
*/

/help_l
{
 cvs HelpExtension join
  HelpSearchPath exch LocateFileNames  %Find *.hlp-File in HelpSearchPath
  dup [] eq not %Was a help file found?

  {%yes, a helpfile was found
    0 get page    
  }
  {%no, no helpfile was found
	/help /NoHelpInformationError raiseerror
  } ifelse
} bind def

/help_any
{
   /help help_l
  /help /LiteralExpected raiseerror
} def

/help_ trie
  [/literaltype] /help_l load addtotrie
  [/stringtype] {cvlit help_l} addtotrie
  [/anytype] /help_any load addtotrie
def

/help
{
  count 0 eq
  {
    :helptext    
  }
  {  
    help_
  } ifelse
} def


/* BeginDocumentation
 Name: helpindex - displays a list of all commands

 Synopsis: helpindex -> -

 Description:
   helpindex prints a list of all commands together with a short summary
   of their usage.

   helpindex uses the command "page" to display its information.
   Type
       /page help
   to learn how to customize the output of this command.

 Parameters: none

 Examples:
   helpindex --> lists all commands

 Bugs: Should work with a string parameter giving some letters or
 keywords, e.g. (ma) helpindex --> list of all commands beginning with ma
 or (file) helpindex --> list of all commands for filehandling!

 Author: Hehl

 FirstVersion: April 20, 1999

 Remarks: 
  helpindex uses the command "page" to display its information.
  Type
      /page help
  to learn how to customize the output of this command.

 SeeAlso: help, apropos, which
*/ 

/helpindex
{
 HelpRoot (/) join TextIndexFilename join
  page
} bind def

/* Documentation (not included in helpdesk)

 Name: :sort - dummy sorting via UNIX
 Synopsis: array :sort -> array
 Description:
   alphabetically sorts array via UNIX-sort. Don't know what happens
   to non-string arrays.
 Parameters:
 Examples: 
 Bugs: 
 Author: Hehl
 FirstVersion: May 19th, 1999
 Remarks: Waiting for our real sorting mechanism as discussed on our
 17/18 May meeting ...
 SeeAlso: 
*/ 

/:sort_a
{  << >> begin
    (sort) spawn /i Set /o Set
    {
      o exch <- endl ;
    } forall
    o closeostream
    [] i
    {
      getline not
      {exit} if
      /zeile Set
      exch zeile append exch
    } loop  
    closeistream
  end
} bind def


/:sort trie
  [/arraytype] /:sort_a load addtotrie
def


/* BeginDocumentation
Name: helpdesk - Display on-line help in external browser

Description:
  "helpdesk" opens a browser window with the SLI Online help. The
  browser executable to use must be specified by the user as a command
  option. (See "SetOptions" on how to do this.)

Options:
  /command - UNIX command to use as browser (default: false)
  
    /command is initially set to false. "helpdesk" will issue a
    message that the user needs to specify the browser.
    Ths user will usually set /command to his preferred browser in his
    .nestrc file.
    Example: /helpdesk << /command (firefox) >> SetOptions

    If /command is set to the name of a UNIX program, "helpdesk" will
    execute this program as a child process, and use it to display the
    HTML-helpdesk.
  
  /wait - whether to wait for the UNIX program to finish. (default: false)
  
    If /wait is set to true, the "helpdesk" command will not return until
    the UNIX process is terminated. Use this setting for all browsers that
    use the terminal exclusively.
  
    If /wait is set to false, "helpdesk" will return immediately after
    creating the child process. Use this setting for all browsers  that
    open up their own window.
  
    The value of /wait is ignored if /command is set to false.

Author: Gewaltig, Eppler, Kupper

SeeAlso: SetOptions, help, helpindex
*/

/helpdesk <<
    /command false
    /wait    false
>> Options

/helpdesk
{
  /helpdesk /command GetOption false eq not
  {
    /helpdesk /wait GetOption 
    {
      {
	/helpdesk /command GetOption ( ) join HelpdeskURL join 0 system
	2 npop
      } stopped
    }
    {
      /helpdesk /command GetOption ( ) join HelpdeskURL join 1 system 
    } ifelse
  }
  {
    M_ERROR (MissingOptionError) (NEST does not know how to connect to your browser. Please see the file ~/.nestrc to learn how to tell NEST about your browser.) message
    /helpdesk /MissingOptionError raiseerror    
  } ifelse
} def


/* BeginDocumentation
 Name: edit - Open a file in an external editor

 Synopsis: (filename) edit -> -

 Options:
  /command - UNIX command to edit a file (default: see below)
    /command can be either false or a string.
    If /command is set to false, "edit" will issue a message that informs
    about how to set the editor
  
    If it is set to the name of a UNIX program, "edit" will execute
    this program as a child process, passing the name of the
    file as argument.
  
    /command defaults to the contents of the $EDITOR environment
    variable. If this variable is not set, it defaults to false.
  
  /wait - whether to wait for the UNIX program to finish. (default: true)
  
    If /wait is set to true, the "edit" command will not return until
    the UNIX process is terminated. Use this setting for all editors
    that use the terminal exclusively.
  
    If /wait is set to false, "edit" will return immediately after
    creating the child process. Use this setting for all editors that
    open up their own window.
  
    The value of /wait is ignored, if /command is set to false.

 Description:
   Opens the file specified by filename.

 Parameters: 
   The name of a file to be edited (or created, if non-existent).

 Examples: (helloworld.sli) edit

 Bugs: 

 Author: Schrader

 FirstVersion:

 Remarks: The location of the file is only specified by the parameter;
   SLISearchPath is not scanned as e.g. in 'run'.

 SeeAlso: page, run
*/ 

/edit <<
    /command (EDITOR) getenv not {false} if
    /wait    true
>> Options

/edit [/stringtype]
{
 /edit /command GetOption false eq not
 { % yes, there is an Editor
  /edit /wait GetOption 
  { % yes, we shall wait for termination of editor process
   /edit /command GetOption ( ) join exch join 0 system
   2 npop
  }
  { % no, we shall not wait for termination of editor process
   /edit /command GetOption ( ) join exch join 1 system 
  }
  ifelse
 }
 {
   /edit MissingOptionError
 }ifelse
} bind def


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% experimental code for invariants and test driven 
% implementation
% 

/* BeginDocumentation
 Name: --> - longrightarrow, usually delayed comparison
 Synopsis: --> -> /-->
 Description:
  The symbol --> is used in the example sections of the NEST 
  documentation to indicate the transformation an operator 
  performs to its arguments. The SLI operator --> is defined 
  to just reproduce itself. This enables NEST to evaluate the 
  example sections as regular SLI code. As a result, the arguments
  of the documented operator and its results are separated by 
  the literaal /-->. A parser for infix notation can then apply
  the documented operator to the values on the left side of -->
  and compare the results to the values on the right side of -->
 
 Examples: 
    [8 --> 6] --> 8 /--> cvx exec 6  3 arraystore 
 Author: Diesmann
 FirstVersion: 080506
 Remarks: 
  The idea of --> is to contribute to a notation for test driven
  programming and user documentation which keeps the syntactic 
  clutter at a minimum. 

 SeeAlso: makehelp, helpdesk, EvaluateLiteralInfixes
*/ 
/--> /--> def



/* BeginDocumentation
 Name: EvaluateLiteralInfixes - Evaluates an array of infix expressions  
 Synopsis: array -> array
 Description:
  The function evaluates an array of expressions assuming that every 
  literal is an infix operator. Inside this function --> is defined as 
  eq, the comparison for equality.
 Examples: 
   [ 5 /add 6 /sub 2] EvaluateLiteralInfixes --> [9]
   [ 5 --> 6 true ] EvaluateLiteralInfixes   --> [false true]
 Author: Diesmann
 FirstVersion: 080506
 Remarks:
  With the further development of test driven programming and unit
  testing in NEST, the function may develop into a more complete 
  parser for infix notation
 SeeAlso: makehelp, helpdesk, -->
*/ 
/EvaluateLiteralInfixes
{
 << /--> /eq load >> begin

 % source array with infix expression
 container
 % source []
 {
  % loop over source array until empty
  exch empty
  {pop exit } if
  % target source
  dup First exch Rest
  % target f source
  rollu
  % source target f

  dup type
  /literaltype eq           % handle literal operators
  {  
   % source target f
   % f is a literal
   exch dup Most exch Last
   % source f target a
   4 -1 roll
   % f target a source
   dup Rest exch First
   % f target a source b
   exch
   % f target a b source 
   5 1 roll
   % source f target a b
   4 -1 roll
   % source target a b f
   cvn exec
   % source target abf
   append
   % source target
  }
  {                          % handle all operands
   % source target f
   % f is not a literal
   append
   % source target
  }
  ifelse
 } loop
 % target

 end
} def


/* BeginDocumentation
 Name: validate - Check if a function is consistent with all its examples
 Synopsis: /literal -> boolean
 Description:

The function validate checks whether the examples in the documentation
of the function specified by the literal argument work as documented.
Every example in the example section is expected to result in a boolean
value. validate only returns true if the result of all examples is true.
In order to make the documentation of examples more readable validate 
supports a special (infix) syntax for the examples:
      a1 ... an --> b1 ...bm
is translated to 
      a b eq 
assuming that the sequence ai collapses to a and the sequence bj to b.
Thus, --> has delayed execution time and is only processed after all 
expressions in the example section have been executed once. The -->
expressions can be freely mixed with regular SLI code boolean 
expressions like
      a b eq    
The documentation of function Part contains several examples.

Before a function can be validated it needs to be registered with the 
help system. This is required because in SLI several functions can be 
defined in the same source file. Thus, only after processing the source
file SLI is aware of the functions implemented by this file. The help
system of SLI stores the path to the implementing file with the 
documentation of each function. Therefore, SLI can lookup the source 
code for each function without searching.

The typical workflow in test driven programming in SLI is as follows:
 1. create a new file myfunctions.sli
 2. write documentation including examples for one or
    more functions, e.g. /myfunc1
 3. call (myfunctions.sli) makehelp
 4. implement /myfunc1
 5. call /myfunc1 validate
 6. continue with 4 (not 3) until validate is successful

The algorithm of function validate is as follows:
 1. find the help file (.hlp) for the specified function. If not 
    found ask for running makehelp for first time registration of 
    the function
 2. obtain the source file implementing the function
    from the help file
 3. update the help file of the function
 4. construct target path for test files
 5. write some header
 6. copy the invariants
 7. write some footer
 8. run the test file

 Examples: 
    /Part validate     --> true 
%   /validate validate --> true   % infinite recursion
 Author: Diesmann
 FirstVersion: 080503
 Remarks: 
 The function is experimental. It constitutes a first attempt to
 provide support for test driven programming in NEST. A variant of 
 this function could easily print a detailed report of the test results
 and the intermediate generation of a test script is not necessary.
 In the present implementation validate updates the plain text help file
 (.hlp) but not the hypertext help (.html).
 SeeAlso: EvaluateLiteralInfixes, -->, Part, makehelp, helpdesk
*/ 
/validate [/literaltype]
{

 % string name of function
 cvs /c Set
 % file name of test
 (test_) c join (.sli) join /cf Set

 % locate the help file
 c HelpExtension join
 HelpSearchPath exch LocateFileNames First

 % if not found print message that documentation
 % of function is not in data base. Please run 
 % makeallhelp for first time registration of the
 % new function in the help system
 /f Set

 % search for the source file implementing 
 % the command
 f (r) file
 {
  getline
  not {close exit} if
  (Source:) search
  {pop pop exch close exit}
  {pop}
  ifelse
 } loop 
 ( ) () ReplaceOccurrences

 % update the .hlp file
 makehelp

 % here we could also update
 % the .html file. At present all .html files 
 % are updated simultaneously by function :hlp2html
 %:makehtmlhelp

 % construct target path for test files
 %
 f (nest/help) search 
 pop /base Set pop pop
 base (nest/unittests/) join cf join /tf Set


 % write header to test file
 tf (w) file /t Set
 t 
  (true [\n\n) print ;


 % extract invariants from .hlp file and 
 % write to test file.
 false /copystate Set
 f (r) file
 { 
  getline
  not {close exit} if
  /Line Set

  SimpleKeyword Line 1 0 regexec 0 eq
  {false /copystate Set } if pop

  ReferencingKeyword Line 1 0 regexec 0 eq
  {false /copystate Set } if pop

  copystate 
  {
   t Line print (\n) print ;
  }
  if

  InvariantsKeyword Line 1 0 regexec 0 eq
  {true /copystate Set } if pop


 }
 loop

 % write footer to test file
 %
 t (] EvaluateLiteralInfixes {and} Fold\n) print ;

 t close

 % execute the new test file
 tf run

} def



/* BeginDocumentation
Name: apropos - Search the command index for a regular expression.

Synopsis: (regexp) apropos -> -
          /regexp  apropos -> -


Description:
Apropos prints out all lines of the helpindex that match the regular
expression. The regular expression can be a simple string.
The search is case invariant. Apropos uses extended regular
expressions.

Parameters:
  regexp: Regular expression, either as a string or literal.

Examples:
  SLI ] /apropos apropos
  apropos              Search the command index for a regular expression.
  
  SLI ] /Apropos apropos
  apropos              Search the command index for a regular expression.
  
  SLI ] /Express* apropos
  apropos              Search the command index for a regular expression.
  EvaluateLiteralInfixes Evaluates an array of infix expressions
  ExecMath             execute a math expression.
  grep                 extract lines matching a regular expression pattern
  regcomp              Create a regular expression
  regexec              compare string and regular expression
  testsuite::test_iaf_psp_peak test of closed form expression for peak
  ToMathematicaExpression converts SLI data to Mathematica input
  
  SLI ] (regular expression) apropos
  apropos              Search the command index for a regular expression.
  regcomp              Create a regular expression
  regexec              compare string and regular expression
  
  SLI ] /DoWhatIWant apropos
  DoWhatIWant: nothing appropriate.

Diagnostics:
Raises /CannotConvertToRegexp if the expression cannot be converted
to a regular expression (see :regerror).
Raises /FileNotFound if the help index file cannot be found.

Author: R Kupper

FirstVersion: 2008-jul-15

SeeAlso: helpindex, help, which, regcomp
*/
/apropos[/stringtype /e]
{  
  /flags
  regexdict/REG_ICASE :: 
  regexdict/REG_EXTENDED :: add 
  def    

  e flags regcomp /regexp Set

  % open the helpindex text file:
  /helpindexfile HelpRoot (/) join TextIndexFilename join def

  helpindexfile ifstream not
  {
    M_ERROR (apropos) (Cannot find the helpindex file. I thought it was here: ) helpindexfile join (. Is your installation broken?) join message
    funcname /FileNotFound raiseerror
  } if

  /found false def  

  {
    getline not {exit} if
    /line Set
    regexp line regex_find
    {
      line =
      /found true def      
    } if
  } loop
  pop % the stream  
  
  found not
  {
    e =only (: nothing appropriate.) =
  } if

} SLIFunctionWrapper

/apropos[/literaltype] {cvs apropos} def


/* BeginDocumentation
Name: which - Display the name of a command's source file.

Synopsis: /command which -> 

Description:
"which" displays the name of the file containing the documentation
header for /command. This is most likely the location, where the
command was defined.

Parameters:
/command - commandname, as listed by "helpindex", including a
           possible namespace prefix (see examples)

Examples:
 /which which
 -> Source: /home/kupper/nest2/lib/sli/helpinit.sli

 /MemoryInfo which
 -> Source: /home/kupper/nest2/nestkernel/nestmodule.cpp

 /unittest::pass_or_die which
 -> Source: /home/kupper/nest2/lib/sli/unittest.sli

Diagnostics:
Raises /NoHelpInformationError if the command is undocumented.

Author: R Kupper

FirstVersion: 23-jul-2008

Availability: Standard SLI

SeeAlso: helpindex, help, apropos
*/

/which[/literaltype /c]
{
  /helpfilename c cvs HelpExtension join def
  
  helpfilename HelpSearchPath exch LocateFileNames  %Find *.hlp-File in HelpSearchPath

  dup [] eq not %Was a help file found?
  {%yes, a helpfile was found
    0 get (^[[:space:]]*Source: ) grep
    {=} forall
  }
  {%no, no helpfile was found
    pop    
    funcname /NoHelpInformationError raiseerror
  } ifelse
} SLIFunctionWrapper