Tutorial

A step-by-step introduction to xelp. By the end you will have a working CLI with custom commands, KEY mode shortcuts, scripting, and token parsing.

1. Hello World — minimal CLI

The smallest useful xelp program: one command, one output function.

#include "xelp.h"

/* Platform: write one char. Replace with your UART TX. */
void my_putc(char c) { putchar(c); }
void my_bksp(void)   { my_putc('\b'); my_putc(' '); my_putc('\b'); }

/* Your first command */
XELPRESULT cmd_hello(XELP *ths, const char *args, int len) {
    XelpOut(ths, "Hello, world!\n", 0);
    return XELP_S_OK;
}

/* Command table */
XELPCLIFuncMapEntry commands[] = {
    { &cmd_hello, "hello", "say hello" },
    XELP_FUNC_ENTRY_LAST
};

XELP cli;

int main(void) {
    XelpInit(&cli, "Tutorial v1.0");
    XELP_SET_FN_OUT(cli, &my_putc);
    XELP_SET_FN_BKSP(cli, &my_bksp);
    XELP_SET_FN_CLI(cli, commands);

    int c;
    while ((c = getchar()) != EOF)
        XelpParseKey(&cli, (char)c);
    return 0;
}

Compile: gcc -Isrc src/xelp.c tutorial.c -o tutorial

What just happened

  1. XelpInit zeroes all internal state and stores the about message.
  2. XELP_SET_FN_OUT tells xelp how to emit characters on this platform.
  3. XELP_SET_FN_BKSP tells xelp how to handle destructive backspace.
  4. XELP_SET_FN_CLI registers your command table.
  5. XelpParseKey feeds one character at a time. When ENTER is received, xelp tokenizes the line and dispatches to the matching command.

2. Commands with arguments

Command functions receive the raw argument string and its length. Use the tokenizer to extract individual arguments:

XELPRESULT cmd_add(XELP *ths, const char *args, int len) {
    XelpBuf b, tok;
    int a, result;

    XELP_XB_INIT(b, args, len);

    /* Token 0 = command name ("add"), 1 = first arg, 2 = second arg */
    XELP_XB_TOP(b);
    XelpTokN(&b, 1, &tok);
    a = XelpStr2Int(tok.s, tok.p - tok.s);

    XELP_XB_TOP(b);
    XelpTokN(&b, 2, &tok);
    result = a + XelpStr2Int(tok.s, tok.p - tok.s);

    /* result now holds the sum */
    return XELP_S_OK;
}

Register it: { &cmd_add, "add", "add <a> <b>" }

Tokenizer rules

SyntaxMeaning
spaces / tabsToken separators
"hello world"Quoted string = single token
#Comment (rest of line ignored)
;Statement separator: hello; add 1 2
` (backtick)Escape next character at CLI
\ (backslash)Escape inside quoted strings

3. Counting and iterating tokens

XELPRESULT cmd_args(XELP *ths, const char *args, int len) {
    XelpBuf b, tok;
    int n, i;

    XELP_XB_INIT(b, args, len);
    XelpNumToks(&b, &n);

    for (i = 0; i < n; i++) {
        XELP_XB_TOP(b);
        XelpTokN(&b, i, &tok);
        XelpOut(ths, tok.s, tok.p - tok.s);
        XelpOut(ths, "\n", 0);
    }
    return XELP_S_OK;
}

4. KEY mode — single keypress actions

KEY mode fires a function on every keypress, no ENTER needed. Great for menus and quick toggles:

XELPRESULT key_toggle_led(XELP *ths, XELPKEYCODE c) {
    (void)c;
    /* toggle your LED here */
    XelpOut(ths, "LED toggled\n", 0);
    return XELP_S_OK;
}

XELPKeyFuncMapEntry key_commands[] = {
    { &key_help,       '?', "show help"  },
    { &key_toggle_led, 'l', "toggle LED" },
    XELP_FUNC_ENTRY_LAST
};

XELP_SET_FN_KEY(cli, key_commands);

Mode switching

KeyModeBehavior
ESCKEYEach keypress = immediate action
CTRL-PCLILine-buffered with prompt, ENTER to execute
CTRL-TTHRAll keys forwarded to pass-through function

These keys are configurable in xelpcfg.h.

5. Scripting

Any command sequence can be run as a script. Scripts are const strings — they can live in ROM:

const char *startup = "hello; add 10 20; echo done";
XelpParse(&cli, startup, XelpStrLen(startup));

Multi-line scripts:

const char *script =
    "# configuration script\n"
    "set mode 1\n"
    "set gain 50\n"
    "echo config complete\n";
XelpParse(&cli, script, XelpStrLen(script));

6. Help system

With XELP_ENABLE_HELP defined, XelpHelp(&cli) prints all registered KEY and CLI commands. The third field in every command table entry is the help text:

{ &cmd_hello, "hello", "say hello" },
/*                      ^^^^^^^^^^^ shown in help output */

7. Multiple instances

xelp uses no globals. Run independent CLIs on different ports:

XELP debug_cli, field_cli;

XelpInit(&debug_cli, "Debug Console");
XelpInit(&field_cli, "Field Service");

XELP_SET_FN_OUT(debug_cli, &uart0_putc);
XELP_SET_FN_OUT(field_cli, &uart1_putc);

XELP_SET_VAL_CLI_PROMPT(debug_cli, "dbg>");
XELP_SET_VAL_CLI_PROMPT(field_cli, "svc>");

See Multi-instance example.

8. Numeric parsing

Decimal, hex with 0x prefix, and hex with h suffix:

int val;
val = XelpStr2Int("255", 3);     /* 255 decimal       */
val = XelpStr2Int("FFh", 3);     /* 255 hex suffix    */
val = XelpStr2Int("0xFF", 4);    /* 255 hex prefix    */
val = XelpStr2Int("0x1A", 4);    /* 26  uppercase hex */

/* Safer variant with error checking */
XELPRESULT r = XelpParseNum("0xFF", 4, &val);
if (XELP_T_OK(r)) { /* val == 255 */ }

9. THR (pass-through) mode

Redirect all keystrokes to another peripheral — useful for talking to a modem or second MCU through the same terminal:

void modem_send(char c) { /* forward to modem UART */ }

XELP_SET_FN_THR(cli, &modem_send);

CTRL-T enters THR mode, CTRL-P returns to CLI mode.

10. Mode change callback

void on_mode_change(int mode) {
    if      (mode == XELP_MODE_CLI) XelpOut(&cli, "[CLI]\n", 0);
    else if (mode == XELP_MODE_KEY) XelpOut(&cli, "[KEY]\n", 0);
    else if (mode == XELP_MODE_THR) XelpOut(&cli, "[THR]\n", 0);
}

XELP_SET_FN_EMCHG(cli, &on_mode_change);

Next steps