/* pk-cmd-help.pk - .help dot-command.  */

/* Copyright (C) 2020, 2021, 2022, 2023, 2024, 2025, 2026 Jose E.
 * Marchesi */

/* This program 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 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/* The pk_help_topics global variable contains an array of entries
   with the form:

   ["TOPIC" "... description of topic ..."]

   The .help TOPIC command searches in this table and displays the
   corresponding description.  */

type Poke_HelpEntry =
  struct
  {
    fun is_line_p = (string s) int<32>:
      { return strchr (s, '\n') == s'length; }

    string category;
    string topic;
    string summary : is_line_p (summary);
    string description;
    ()int<32> active_p = lambda int<32>: { return 1; };
    string synopsis : is_line_p (synopsis)
      if category in ["commands", "dot-commands", "settings"];
  };

var pk_help_topics = Poke_HelpEntry[] ();

/* Add a new entry to pk_help_topics.  */

fun pk_help_add_topic = (Poke_HelpEntry entry) void:
{
  /* If the topic already exists in the table, replace its description
     with the new one.  */
  for (var i = 0; i < pk_help_topics'length; i++)
   if (pk_help_topics[i].category == entry.category
       && pk_help_topics[i].topic == entry.topic)
     {
       pk_help_topics[i] = entry;
       return;
     }

  pk_help_topics += [entry];
}

/* Print some help about a given topic.  */

fun pk_help = (string topic) void:
{
  fun print_summaries_for_category = (string category) void:
  {
    for (entry in pk_help_topics
         where entry.active_p && entry.category == category)
    {
      print ("\t");
      hserver_print_hl ('e', entry.topic, ".help " + entry.topic);
      printf (" - %s\n", entry.summary);
    }
  }

  if (topic == "")
  {
    print "Usage: .help TOPIC\n";
    print "Available help topics:\n\n";
    print "Global settings:\n";
    print_summaries_for_category ("settings");
    print "\n";
    print "Dot-Commands:\n";
    print_summaries_for_category ("dot-commands");
    print "\n";
    print "Commands:\n";
    print_summaries_for_category ("commands");
    print "\n";
    print "To search for some topic in the poke manual, type \".doc TOPIC\".\n";
    print "If you couldn't read the whole output, try \"";
    hserver_print_hl ('e', ".set pager yes", ".set pager yes");
    print "\"\n";
    return;
  }

  for (entry in pk_help_topics)
    if (entry.topic == topic)
    {
      if (entry.category == "settings")
        print ".set ";
      printf ("%s - %s\n", entry.topic, entry.summary);
      if (entry.description != "")
       {
         try {
           if (entry.synopsis != "")
             printf "\nSynopsis:\n\n  %s\n\n", entry.synopsis;
         } catch if E_elem {}
        printf ("%s\n", entry.description);
       }
      return;
    }

  print "Sorry no clue about that.
Type .help with no topic for a list of topics.\n";
}

/* Help entries for the dot-commands.  */

pk_help_add_topic
  :entry Poke_HelpEntry {
      category = "dot-commands",
      topic = ".help",
      synopsis = ".help [TOPIC]",
      summary = "display help on some given topic",
      description = "\
Where TOPIC identifies the topic for which to get help.
To get a list of available help topics, type `.help' without arguments."
  };

pk_help_add_topic
  :entry Poke_HelpEntry {
      category = "dot-commands",
      topic = ".set",
      synopsis = ".set [SETTING [,VALUE]]",
      summary = "show or set the value of a global setting",
      description = "\
Set the global SETTING to VALUE, or print its current value.
If VALUE is not specified, print the current value of SETTING.

If no arguments are specified, print out a list of all the
settings along with their current values."
  };

pk_help_add_topic
  :entry Poke_HelpEntry {
      category = "dot-commands",
      topic = ".info",
      synopsis = ".info {ios,variables,functions,types}",
      summary = "list IO spaces and other entities",
      description = "\
Show information about some given entities.

.info ios
  List the currently opened IO spaces.
.info variables [REGEXP]
  List information about defined variables.
.info functions [REGEXP]
  List information about defined functions.
.info types [REGEXP]
  List information about defined types.
.info type EXPR
  Print a description of the type of the given expression, which
  an be a type specifier."
  };

pk_help_add_topic
  :entry Poke_HelpEntry {
      category = "dot-commands",
      topic = ".ios",
      synopsis = ".ios #N",
      summary = "switch to a different IO space",
      description = "Where #N is the tag of some given IO space."
  };

pk_help_add_topic
  :entry Poke_HelpEntry {
      category = "dot-commands",
      topic = ".file",
      synopsis = ".file[/cr] FILE",
      summary = "open a file",
      description = "\
Open the given file as a new IO space and switch to it.  The
open flags will be inferred from the permissions of the file.

If the /r flag is used then the file is opened read-only.  Opening
files that won't be modified in read-only mode is recommended because
they don't require remaps.

If the /c flag is used and no file with the given name exists,
a new empty file will be created using the current UMASK and
opened."
    };

pk_help_add_topic
  :entry Poke_HelpEntry {
      category = "dot-commands",
      topic = ".proc",
      synopsis = ".proc[/mM] PID",
      summary = "open a process' memory",
      description = "\
Open a proc IO space to edit the memory of the process with
process id PID.

If the /m flag is used, sub-IOS are opened showing the several
maps in the process virtual address space.

If the /M flag is used, even more maps are shown, like the ones
corresponding to mapped executables and DSOs."
    };

pk_help_add_topic
  :entry Poke_HelpEntry {
      category = "dot-commands",
      topic = ".sub",
      synopsis = ".sub #N, BASE, SIZE, [NAME]",
      summary = "create a sub IO space",
      description = "\
Create a sub IO space showing an area into some other given IO space.

#N is the tag of the parent IO space.

BASE is the offset in bytes from the beginning of the parent IO space
from which to create the sub IO space.

SIZE is the size, in bytes."
    };

pk_help_add_topic
  :entry Poke_HelpEntry {
      category = "dot-commands",
      topic = ".mem",
      synopsis = ".mem NAME",
      summary = "create a memory IO space",
      description = "\
Create a memory backed IO space named *NAME*.

The created memory IOS contains zero bytes and has a default size of
4096 bytes.  The buffer will grow automatically when a value is mapped
beyond its current size."
    };

pk_help_add_topic
  :entry Poke_HelpEntry {
      category = "dot-commands",
      topic = ".nbd",
      synopsis = ".nbd URI",
      summary = "open a NBD IO space",
      active_p = lambda int<32>: { return pk_have_libnbd_p; },
      description = "\
Create a IO space providing access to a Network Block Device
identified by the given URI."
    };

pk_help_add_topic
  :entry Poke_HelpEntry {
      category = "dot-commands",
      topic = ".mmap",
      synopsis = ".mmap FILENAME, BASE, SIZE",
      summary = "open a device driver or file with memory mapping",
      description = "\
Create a IO space providing access to a device driver with mmap function
or also a file where the mmap is provided by the file system driver.

.mmap /dev/mem, BASE, SIZE
Mapps physical memory area at BASE address which have to be
a multiple of the page size.

.mmap /dev/mem, 0x4804c000, 0x1000
int @ 0x13C#B
int @ 0x134#B
Example on a Beaglebone black for watching GPIO registers and possibly further
changing them.
"
    };

pk_help_add_topic
  :entry Poke_HelpEntry {
      category = "dot-commands",
      topic = ".close",
      synopsis = ".close [#N]",
      summary = "close an IO space",
      description = "\
Close the current IO space, or #N if it is specified."
  };

pk_help_add_topic
  :entry Poke_HelpEntry {
      category = "dot-commands",
      topic = ".load",
      synopsis = ".load FILE",
      summary = "execute a file of Poke code",
      description = "Execute a file of Poke code named FILE."
  };

pk_help_add_topic
  :entry Poke_HelpEntry {
      category = "dot-commands",
      topic = ".source",
      synopsis = ".source FILE",
      summary = "source a commands file",
      description = "Execute a commands file named FILE."
  };

pk_help_add_topic
  :entry Poke_HelpEntry {
      category = "dot-commands",
      topic = ".doc",
      synopsis = ".doc TOPIC",
      summary = "access the poke manual",
      description = "\
Given a TOPIC, search for it in the poke manual and show the relevant
part.  The setting `doc-viewer' determines the program used to visualize
the manual."
  };

pk_help_add_topic
  :entry Poke_HelpEntry {
      category = "dot-commands",
      topic = ".editor",
      synopsis = ".editor",
      summary = "compose input for poke in $EDITOR",
      description = "\
Open your $EDITOR and use whatever you write in it as input for a poke
command.  Note that whatever text you compose is converted to a single
line by removing newlines."
  };

pk_help_add_topic
  :entry Poke_HelpEntry {
      category = "dot-commands",
      topic = ".jmd",
      synopsis = ".jmd",
      summary = "do NOT use this command",
      description = "\
Seriously, DO NOT USE THIS COMMAND.
You have been warned."
  };

pk_help_add_topic
  :entry Poke_HelpEntry {
      category = "dot-commands",
      topic = ".exit",
      synopsis = ".exit",
      summary = "exit poke :(",
  };

pk_help_add_topic
  :entry Poke_HelpEntry {
      category = "dot-commands",
      topic = ".vm",
      synopsis = ".vm {disassemble,profile,dispatch}",
      summary = "PVM related commands",
      description = "\
Perform an operation related to the Poke Virtual Machine.

.vm disassemble expression EXPR
  Show the PVM code for which EXPR is compiled.
.vm disassemble function EXPR
  Show the PVM code for the body of EXPR, which should evaluate
  to a function.

.vm profile reset
  Reset the profiling counters.
.vm profile show
  Print a report with collected profiling information.

.vm dispatch
  Print current dispatching strategy."
  };

pk_help_add_topic
  :entry Poke_HelpEntry {
      category = "dot-commands",
      topic = ".compiler",
      synopsis = ".compiler {ast}",
      summary = "Poke compiler related commands",
      description = "\
Perform an operation related to the Poke incremental compiler.

.compiler ast EXPR
  Show a printable representation of the abstract syntax tree (AST)
  corresponding to the given EXPR, which is compiled."
    };

pk_help_add_topic
  :entry Poke_HelpEntry {
      category = "dot-commands",
      topic = ".version",
      synopsis = ".version",
      summary = "display the poke version",
      description = "Print the version of poke and copyright information."
  };

pk_help_add_topic
  :entry Poke_HelpEntry {
      category = "dot-commands",
      topic = ".bases",
      synopsis = ".bases EXPR",
      summary = "print the given integral expression in base 16, 10, 8 and 2",
      description = "Print the given integer (EXPR) in base 16, 10, 8 and 2"
  };
