From fb3bae2f3c41aec1bf2b4dbf926d4b034f2b339e Mon Sep 17 00:00:00 2001
From: deftio
Date: Mon, 27 Apr 2026 00:25:56 -0700
Subject: [PATCH 1/3] updated arg parse and also enabled optional command
history
---
AGENTS.md | 29 +-
CHANGELOG.md | 25 +
CMakeLists.txt | 2 +-
README.md | 47 +-
dev/arg_parse_updates.md | 335 ++++++++
dev/size_profiles.sh | 1 +
dev/xelp-todo.md | 38 +-
docs/api-reference.md | 58 +-
docs/build-profiles.md | 2 +
docs/configuration.md | 2 +
docs/examples.md | 1 +
docs/porting.md | 15 +
docs/tutorial.md | 13 +
examples/README.md | 4 +-
examples/posix-simple/README.md | 2 +
examples/posix-simple/xelp-example.c | 1 +
idf_component.yml | 6 +-
library.json | 2 +-
library.properties | 2 +-
llms.txt | 2 +-
pages/api-reference.html | 54 +-
pages/configuration.html | 5 +
pages/index.html | 46 +-
pages/releases.html | 15 +
release_management.md | 4 +-
src/xelp.c | 156 +++-
src/xelp.h | 26 +-
src/xelpcfg.h | 23 +-
tests/xelp_unit_tests.c | 1070 ++++++++++++++++++++++++--
29 files changed, 1845 insertions(+), 141 deletions(-)
create mode 100644 dev/arg_parse_updates.md
diff --git a/AGENTS.md b/AGENTS.md
index fa2dace..28de466 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -28,7 +28,7 @@ compiler.
for the life of the instance (string literals, static buffers, or
globals -- never stack-local buffers that go out of scope).
-## Function signatures (v0.3.1+)
+## Function signatures (v0.3.2+)
### CLI command functions
@@ -149,6 +149,27 @@ XELPRESULT cmd_divmod(XELP *ths, const char *args, int len) {
Tokens are NOT null-terminated. Use `tok.s`..`tok.p` or `XELP_XB_LEN(tok)`.
+## Direct argument access (XelpArgInt / XelpArgStr)
+
+For one-shot access to a specific argument by index:
+
+```c
+XELPRESULT cmd_set(XELP *ths, const char *args, int len) {
+ const char *key;
+ int klen, value;
+ XelpArgStr(args, len, 1, &key, &klen); /* arg 1 = key name */
+ XelpArgInt(args, len, 2, &value); /* arg 2 = int value */
+ return XELP_S_OK;
+}
+```
+
+| Function | Purpose |
+|----------|---------|
+| `XelpArgInt(args, len, n, &val)` | Get arg N as int (wraps TokN + ParseNum) |
+| `XelpArgStr(args, len, n, &s, &slen)` | Get arg N as string span (pointer + length) |
+
+Arg 0 is the command name (per argc/argv convention).
+
### Random-access alternative (XelpTokN)
For random-access by index, use `XelpTokN`. Note the `(char*)` cast
@@ -236,9 +257,11 @@ Edit `src/xelpcfg.h` to enable/disable features:
| `XELP_ENABLE_KEY` | Single keypress mode | ~200-500 bytes |
| `XELP_ENABLE_THR` | Pass-through mode | ~50-125 bytes |
| `XELP_ENABLE_HELP` | Built-in help command | ~180-350 bytes |
+| `XELP_ENABLE_HISTORY` | Command history (UP/DOWN recall) | ~420 bytes |
| `XELP_ENABLE_FULL` | All of the above | All combined |
Buffer size: `XELP_CMDBUFSZ` (default 64 bytes).
+History depth: `XELP_HIST_DEPTH` (default 4 commands). Override at compile time or via `XELP_CONFIG_OVERRIDE`.
## Three modes
@@ -258,7 +281,7 @@ Default mode switch keys: ESC (KEY), CTRL-P (CLI), CTRL-T (THR).
6. Hardcoding `&global_instance` instead of using `ths` in commands.
7. Calling `malloc` or stdlib functions in embedded contexts.
-## Registers (v0.3.1+)
+## Registers (v0.3.2+)
Each XELP instance has 4 return registers (`mR[0..3]`), accessed via
macros. Convention: **callee-clobbers-all** -- any command call may
@@ -308,7 +331,7 @@ XELP_SET_ECHO(*ths, XELP_ECHO_NORMAL); /* restore after ENTER */
## File structure
```
-src/xelp.c -- implementation (~980 lines)
+src/xelp.c -- implementation (~1130 lines)
src/xelp.h -- public API (types, macros, function declarations)
src/xelpcfg.h -- compile-time feature flags and settings
```
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 92c47cf..c588cc3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,31 @@ Versions always use three-component semver (e.g. `0.3.0`, never `0.3`).
## [Unreleased]
+## [0.3.2] - 2026-04-26
+
+### Added
+- **Command history** (`XELP_ENABLE_HISTORY`): UP/DOWN arrow recall of
+ previously entered commands using a fixed-slot ring buffer. Requires
+ `XELP_ENABLE_CLI` and `XELP_ENABLE_LINE_EDIT`. Configurable depth via
+ `XELP_HIST_DEPTH` (default 4). Consecutive duplicate suppression, empty
+ command filtering, in-progress line save/restore on browse.
+ RAM cost: `XELP_HIST_DEPTH * XELP_CMDBUFSZ + XELP_CMDBUFSZ + 4` bytes
+ per instance. Code cost: ~420 bytes on ARM Thumb (`-Os`).
+- `XelpArgInt(args, len, n, &val)` -- get argument N as an integer in one
+ call. Wraps `XelpTokN` + `XelpParseNum`. ~108 bytes ARM Thumb (combined
+ with XelpArgStr).
+- `XelpArgStr(args, len, n, &s, &slen)` -- get argument N as a string
+ span (pointer + length) in one call. Wraps `XelpTokN`.
+
+### Fixed
+- `XELPKEY_BKSP` was defined as 0x07 (BEL), not 0x08 (ASCII BS). Added
+ `XELPKEY_BS` (0x08) and both backspace paths in `XelpParseKey` now
+ accept 0x07, 0x08, and 0x7F (DEL).
+
+### Changed
+- Test suite expanded to 47 units, 598 test cases (from 39/531).
+- `dev/size_profiles.sh` updated with profile 10 (Full + history).
+
## [0.3.1] - 2026-04-26
### Changed
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0969676..0af39cc 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -62,7 +62,7 @@ endif()
# ---- Plain CMake library ----
cmake_minimum_required(VERSION 3.10)
project(xelp
- VERSION 0.3.1
+ VERSION 0.3.2
LANGUAGES C
DESCRIPTION "Embedded CLI/script interpreter -- no malloc, multi-instance"
)
diff --git a/README.md b/README.md
index bd43d19..8d3ef67 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-
+
[](https://opensource.org/licenses/BSD-2-Clause)
[](https://github.com/deftio/xelp/actions/workflows/ci.yml)

@@ -75,6 +75,9 @@ interactive configuration.
#define XELP_ENABLE_HELP 1
```
+Optional: add `XELP_ENABLE_HISTORY` for UP/DOWN arrow command recall
+(~420 bytes, requires `XELP_ENABLE_LINE_EDIT`).
+
### Full (~3-6 KB)
Adds THR pass-through mode (~50-125 bytes more) for forwarding all
@@ -209,7 +212,7 @@ make clean # remove test build artifacts
make clean-all # clean tests + all examples
```
-39 test units, 531 test cases, 100% line coverage of `xelp.c`.
+47 test units, 598 test cases, 100% line coverage of `xelp.c`.
Feature profile sizes: `dev/size_profiles.sh` (uses Docker for ARM Cortex-M0, falls back to host GCC).
@@ -245,26 +248,26 @@ features). Even the largest full build is under 7 KB.
| CPU | Width | Compiler | KEY (bytes) | CLI (bytes) | FULL (bytes) |
|-----|------:|----------|------------:|------------:|-------------:|
-| AVR (ATtiny85) | 8 | avr-gcc | 1046 | 4038 | 4096 |
-| AVR (ATmega328P) | 8 | avr-gcc | 1054 | 4132 | 4190 |
-| Z80 | 8 | SDCC | 2121 | 6966 | 7074 |
-| 6800 (HC08) | 8 | SDCC | 2471 | 8147 | 8288 |
-| MSP430 | 16 | msp430-gcc | 770 | 3260 | 3306 |
-| 68HC11 | 16 | m68hc11-gcc | 2369 | 6570 | 6641 |
-| Xtensa LX7 (ESP32-S3) | 32 | xtensa-esp-elf-gcc | 576 | 2508 | 2540 |
-| ARM Thumb | 32 | arm-none-eabi-gcc | 580 | 2482 | 2526 |
-| RISC-V (rv32) | 32 | riscv64-unknown-elf-gcc | 722 | 2970 | 3008 |
-| Xtensa LX106 (ESP8266) | 32 | xtensa-lx106-elf-gcc | 723 | 2831 | 2863 |
-| m68k | 32 | m68k-linux-gnu-gcc | 728 | 3146 | 3194 |
-| ARM32 | 32 | arm-none-eabi-gcc | 980 | 3746 | 3806 |
-| x86-32 | 32 | GCC | 1081 | 4604 | 4654 |
-| MIPS32 | 32 | mipsel-linux-gnu-gcc | 1296 | 4888 | 4936 |
-| PowerPC | 32 | powerpc-linux-gnu-gcc | 1504 | 5674 | 5738 |
-| RISC-V (rv64) | 64 | riscv64-linux-gnu-gcc | 756 | 3332 | 3366 |
-| x86-64 | 64 | Clang | 1043 | 5013 | 5055 |
-| x86-64 | 64 | GCC | 1063 | 4787 | 4836 |
-| AArch64 (ARM64) | 64 | aarch64-linux-gnu-gcc | 1324 | 5178 | 5234 |
-| MIPS64 | 64 | mips64el-linux-gnuabi64-gcc | 1360 | 5512 | 5560 |
+| AVR (ATtiny85) | 8 | avr-gcc | 1046 | 4266 | 4324 |
+| AVR (ATmega328P) | 8 | avr-gcc | 1054 | 4366 | 4424 |
+| Z80 | 8 | SDCC | 2121 | 7280 | 7388 |
+| 6800 (HC08) | 8 | SDCC | 2471 | 8614 | 8715 |
+| MSP430 | 16 | msp430-gcc | 770 | 3482 | 3528 |
+| 68HC11 | 16 | m68hc11-gcc | 2369 | 6884 | 6955 |
+| Xtensa LX7 (ESP32-S3) | 32 | xtensa-esp-elf-gcc | 576 | 2592 | 2624 |
+| ARM Thumb | 32 | arm-none-eabi-gcc | 580 | 2598 | 2642 |
+| RISC-V (rv32) | 32 | riscv64-unknown-elf-gcc | 722 | 3094 | 3132 |
+| Xtensa LX106 (ESP8266) | 32 | xtensa-lx106-elf-gcc | 723 | 2955 | 2987 |
+| m68k | 32 | m68k-linux-gnu-gcc | 728 | 3332 | 3380 |
+| ARM32 | 32 | arm-none-eabi-gcc | 980 | 3930 | 3990 |
+| x86-32 | 32 | GCC | 1081 | 4916 | 4966 |
+| MIPS32 | 32 | mipsel-linux-gnu-gcc | 1296 | 5224 | 5272 |
+| PowerPC | 32 | powerpc-linux-gnu-gcc | 1504 | 6058 | 6122 |
+| RISC-V (rv64) | 64 | riscv64-linux-gnu-gcc | 756 | 3548 | 3582 |
+| x86-64 | 64 | Clang | 1043 | 5268 | 5310 |
+| x86-64 | 64 | GCC | 1063 | 5136 | 5185 |
+| AArch64 (ARM64) | 64 | aarch64-linux-gnu-gcc | 1324 | 5566 | 5622 |
+| MIPS64 | 64 | mips64el-linux-gnuabi64-gcc | 1360 | 5864 | 5928 |
x86-64 GCC row is measured directly; others from cross-compilation via
diff --git a/dev/arg_parse_updates.md b/dev/arg_parse_updates.md
new file mode 100644
index 0000000..01e14ee
--- /dev/null
+++ b/dev/arg_parse_updates.md
@@ -0,0 +1,335 @@
+# Argument Parsing Ergonomics for xelp
+
+Design notes for improving how command handlers access their arguments.
+Goal: reduce per-command boilerplate while keeping zero-malloc, C89, and
+small code size.
+
+## The Problem
+
+Every command handler that takes arguments currently looks like this:
+
+```c
+XELPRESULT cmd_led(XELP *ths, const char *args, int len) {
+ XelpBuf b, tok;
+ int val;
+ XELP_XB_INIT(b, (char*)args, len);
+ if (XelpTokN(&b, 1, &tok) == XELP_S_OK) {
+ XelpParseNum(tok.s, (int)(tok.p - tok.s), &val);
+ /* finally do something with val */
+ }
+ return XELP_S_OK;
+}
+```
+
+That's 4 lines of parsing machinery for one integer argument. The XelpArgs
+iterator (added in 0.3.1) is better but still verbose:
+
+```c
+XELPRESULT cmd_led(XELP *ths, const char *args, int len) {
+ XelpArgs a;
+ int val;
+ XelpArgsInit(&a, args, len);
+ XelpNextTok(&a, NULL); /* skip command name */
+ XelpNextInt(&a, &val);
+ /* finally do something with val */
+ return XELP_S_OK;
+}
+```
+
+Compare with what the JS/Python world expects:
+
+```js
+cli.addCommand("led", (args) => { led(parseInt(args[1])); });
+```
+
+We can't match that in C89, but we can get closer.
+
+## What Other Embedded CLI Libraries Do
+
+### funbiscuit/embedded-cli (~2KB code, 1.5KB RAM)
+
+Handler receives `(EmbeddedCli *cli, char *args, void *context)`.
+Arguments pre-tokenized if flag set during registration. Access via:
+
+```c
+const char *arg = embeddedCliGetToken(args, 1); /* 1-indexed */
+uint8_t count = embeddedCliGetTokenCount(args);
+```
+
+**Trade-off**: Modifies the input buffer in-place (inserts nulls between
+tokens). Tokens are null-terminated C strings. Simple to use, but
+destructive -- can't re-parse or use const input. xelp deliberately
+avoids this (scripts are const/ROM-able).
+
+### Helius/microrl
+
+Handler receives `(int argc, char **argv)` -- classic main() style.
+Library tokenizes into a pre-allocated argv array.
+
+**Trade-off**: Requires a fixed-size `char *argv[N]` array. Each token
+is null-terminated (destructive). Simple and familiar, but the argv
+array costs RAM (N * sizeof(char*)) and limits max arguments.
+
+### AndreRenaud/EmbeddedCLI (~1KB code, 200B RAM minimal)
+
+Also parses into argc/argv. Supports quoted strings and escapes.
+Suggests pairing with a separate "Simple Options" library for
+`-flag value` style parsing.
+
+### MicroShell (marcinbor85)
+
+Filesystem-like command tree (ls, cat, pwd). Not really comparable --
+different problem domain. No dynamic allocation, callback-based.
+
+### Summary: Industry Patterns
+
+| Library | Arg Interface | Destructive? | Null-terminated? | Extra RAM |
+|---------|-------------|-------------|-----------------|-----------|
+| embedded-cli | getToken(args, N) | Yes | Yes | N/A (in-place) |
+| microrl | argc/argv | Yes | Yes | argv array |
+| EmbeddedCLI | argc/argv | Yes | Yes | argv array |
+| xelp (current) | XelpTokN / XelpArgs | No | No | XelpBuf on stack |
+
+**Key observation**: Every other library modifies the input buffer to
+null-terminate tokens. xelp is the only one that preserves const input
+(needed for ROM-able scripts). This is a genuine differentiator but
+it costs ergonomics -- tokens come as (pointer, length) pairs instead
+of C strings.
+
+## Options Evaluated
+
+### Option A: Direct-access convenience functions (CHOSEN)
+
+Add `XelpArgInt` and `XelpArgStr` as functions (not macros) that
+combine "get Nth argument" into one call. No new types, no new
+concepts -- just fewer lines per command.
+
+Functions are the right choice over macros: the linker includes each
+function body once regardless of how many commands call it. A macro
+would expand the full XelpTokN + XelpParseNum sequence at every call
+site -- ~50 bytes per invocation instead of once. Five commands using
+XelpArgInt as a function: ~50 bytes total. As a macro: ~250 bytes.
+
+```c
+/* Get argument N as an integer. Arg 0 is the command name. */
+XELPRESULT XelpArgInt(const char *args, int len, int n, int *val);
+
+/* Get argument N as a string span. Sets *s and *slen. */
+XELPRESULT XelpArgStr(const char *args, int len, int n,
+ const char **s, int *slen);
+```
+
+**Pros**:
+- Dead simple, self-documenting
+- No new types or state
+- Works with existing const/non-destructive parsing
+- Function body included once by linker, called from many sites
+
+**Cons**:
+- O(N) per call (re-scans from start each time). Fine for commands
+ with 1-3 arguments. Bad if someone calls it in a loop for 20 args.
+- Doesn't cover the "iterate all args" case (use existing XelpArgs)
+
+### Option B: Enhanced XelpArgs iterator (NOT CHOSEN)
+
+Would have added `XelpArgsBegin` (auto-skip command name) and typed
+accessors (`XelpArgsInt`, `XelpArgsStr`).
+
+**Rejected** because:
+- Auto-skipping arg 0 violates the argc/argv convention. Arg 0 is the
+ command name in every C program and every other CLI library. A command
+ registered under two names (`"help"` and `"?"`) may need to know which
+ name invoked it. Silently skipping it would be surprising.
+- The existing XelpArgs iterator (XelpArgsInit + XelpNextTok +
+ XelpNextInt) already covers the stateful iteration case adequately.
+- Adding more functions to the iterator increases API surface for
+ marginal benefit.
+
+### Option C: argc/argv with pre-allocated array (NOT CHOSEN)
+
+Tokenize into a fixed-size XelpBuf array.
+
+**Rejected** because:
+- Stack cost per call (N * 12 bytes on 32-bit)
+- Tokens are still (ptr, len), not null-terminated C strings --
+ so you can't pass them to printf("%s") or strcmp() directly.
+ This reduces the ergonomic win vs. other libraries.
+- XELP_MAX_ARGS is a footgun (silent truncation if exceeded)
+
+### Option D: Destructive argc/argv (NOT CHOSEN)
+
+Insert null bytes into the args buffer to give real C strings.
+
+**Rejected** because:
+- **Breaks xelp's const-input guarantee.** Scripts can't live in ROM.
+ This is a fundamental design principle of xelp.
+- Requires user to copy input to a mutable buffer first
+
+## Final Proposal
+
+Two new functions added to the base CLI API. No new compile flag --
+compiled whenever `XELP_ENABLE_CLI` is on (these are useless without
+the CLI tokenizer anyway). Implemented as functions, not macros.
+
+```c
+XELPRESULT XelpArgInt(const char *args, int len, int n, int *val);
+XELPRESULT XelpArgStr(const char *args, int len, int n,
+ const char **s, int *slen);
+```
+
+Arg 0 is the command name, arg 1 is the first real argument. Follows
+the argc/argv convention exactly.
+
+Internally: thin wrappers over existing `XelpTokN` + `XelpParseNum`.
+No new types, no new state, no behavior changes to existing functions.
+
+### Code Size
+
+| Function | Est. ARM Thumb | Notes |
+|----------|---------------|-------|
+| `XelpArgInt` | ~50 bytes | XelpTokN + XelpParseNum wrapper |
+| `XelpArgStr` | ~40 bytes | XelpTokN wrapper, returns span |
+| **Total** | **~90 bytes** | Part of base CLI, no extra flag |
+
+For context, the full CLI build is ~2500 bytes on ARM Thumb. This adds
+~3.5%. Two functions that improve every command handler in the project.
+
+## Before/After Comparison
+
+### Simple command (1 int arg)
+
+**Before** (4 lines of boilerplate):
+```c
+XELPRESULT cmd_led(XELP *ths, const char *args, int len) {
+ XelpBuf b, tok;
+ int val;
+ XELP_XB_INIT(b, (char*)args, len);
+ if (XelpTokN(&b, 1, &tok) == XELP_S_OK) {
+ XelpParseNum(tok.s, (int)(tok.p - tok.s), &val);
+ set_led(val);
+ }
+ return XELP_S_OK;
+}
+```
+
+**After** (1 line):
+```c
+XELPRESULT cmd_led(XELP *ths, const char *args, int len) {
+ int val;
+ if (XelpArgInt(args, len, 1, &val) == XELP_S_OK)
+ set_led(val);
+ return XELP_S_OK;
+}
+```
+
+### Multi-arg command (2 ints)
+
+**Before**:
+```c
+XELPRESULT cmd_divmod(XELP *ths, const char *args, int len) {
+ XelpBuf b, tok;
+ int a, d;
+ XELP_XB_INIT(b, (char*)args, len);
+ if (XelpTokN(&b, 1, &tok) != XELP_S_OK) goto usage;
+ a = XelpStr2Int(tok.s, (int)(tok.p - tok.s));
+ XELP_XB_TOP(b);
+ if (XelpTokN(&b, 2, &tok) != XELP_S_OK) goto usage;
+ d = XelpStr2Int(tok.s, (int)(tok.p - tok.s));
+ /* ... */
+}
+```
+
+**After**:
+```c
+XELPRESULT cmd_divmod(XELP *ths, const char *args, int len) {
+ int a, d;
+ if (XelpArgInt(args, len, 1, &a) != XELP_S_OK) goto usage;
+ if (XelpArgInt(args, len, 2, &d) != XELP_S_OK) goto usage;
+ /* ... */
+}
+```
+
+### String argument
+
+**Before**:
+```c
+XELPRESULT cmd_ssid(XELP *ths, const char *args, int len) {
+ XelpBuf b, tok;
+ XELP_XB_INIT(b, (char*)args, len);
+ if (XelpTokN(&b, 1, &tok) == XELP_S_OK) {
+ int slen = (int)(tok.p - tok.s);
+ memcpy(gSsid, tok.s, slen);
+ gSsid[slen] = '\0';
+ }
+ return XELP_S_OK;
+}
+```
+
+**After**:
+```c
+XELPRESULT cmd_ssid(XELP *ths, const char *args, int len) {
+ const char *s; int slen;
+ if (XelpArgStr(args, len, 1, &s, &slen) == XELP_S_OK) {
+ memcpy(gSsid, s, slen);
+ gSsid[slen] = '\0';
+ }
+ return XELP_S_OK;
+}
+```
+
+## Design Decisions
+
+1. **Arg 0 is the command name.** Follows argc/argv convention. The
+ handler picks which index it wants. No implicit skipping.
+
+2. **Functions, not macros.** Linker includes the body once. Macros
+ would duplicate ~50 bytes of XelpTokN + XelpParseNum at every call
+ site. For something called from every command handler, this matters.
+
+3. **No new compile flag.** These live in the base CLI API, gated only
+ by `XELP_ENABLE_CLI`. Users who enable CLI want arg parsing. Users
+ on KEY-only don't have arguments to parse.
+
+4. **No changes to existing API.** XelpArgs, XelpTokN, XelpNextTok,
+ XelpNextInt all stay as-is. The new functions are additive.
+
+5. **Hex auto-detection.** XelpArgInt calls XelpParseNum internally,
+ which already handles `0x1A` and `1Ah` formats. No decision needed.
+
+6. **Error semantics.** If arg N doesn't exist, return XELP_E_ERR and
+ leave *val unchanged. Caller can set a default before calling.
+ Matches existing XelpParseNum behavior.
+
+7. **O(N) is acceptable.** XelpArgInt re-scans from the start each
+ call. For commands with 1-3 args (the vast majority), this is
+ negligible. Commands with many args should use the XelpArgs
+ iterator, which is O(1) per token.
+
+## Existing API (unchanged)
+
+For reference, the existing argument APIs remain available:
+
+```c
+/* Random access (O(N) per call) */
+XELPRESULT XelpTokN(XelpBuf *buf, int n, XelpBuf *tok);
+XELPRESULT XelpNumToks(XelpBuf *b, int *n);
+
+/* Sequential iterator (O(1) per call) */
+XELPRESULT XelpArgsInit(XelpArgs *a, const char *args, int len);
+XELPRESULT XelpNextTok(XelpArgs *a, XelpBuf *tok);
+XELPRESULT XelpNextInt(XelpArgs *a, int *val);
+XELPRESULT XelpArgCount(XelpArgs *a, int *n);
+
+/* Low-level */
+int XelpStr2Int(const char *s, int maxlen);
+XELPRESULT XelpParseNum(const char *s, int maxlen, int *n);
+```
+
+The new `XelpArgInt` and `XelpArgStr` are sugar over these for the
+common case. Power users retain full access to the tokenizer.
+
+## Implementation Priority
+
+Developer-ergonomics improvement, not a functional change. Ship when
+convenient -- fully backward-compatible, no API breaks. Good candidate
+for the next release (0.3.2 or 0.4.0).
diff --git a/dev/size_profiles.sh b/dev/size_profiles.sh
index c2fe77f..c62e368 100755
--- a/dev/size_profiles.sh
+++ b/dev/size_profiles.sh
@@ -116,5 +116,6 @@ build "6. CLI + line edit" XELP_ENABLE_CLI XELP_ENABLE_LINE_EDIT
build "7. CLI + line edit + help" XELP_ENABLE_CLI XELP_ENABLE_LINE_EDIT XELP_ENABLE_HELP
build "8. CLI + LE + help + key" XELP_ENABLE_CLI XELP_ENABLE_LINE_EDIT XELP_ENABLE_HELP XELP_ENABLE_KEY
build "9. Full (all features)" XELP_ENABLE_CLI XELP_ENABLE_LINE_EDIT XELP_ENABLE_HELP XELP_ENABLE_KEY XELP_ENABLE_THR
+build "10. Full + history" XELP_ENABLE_CLI XELP_ENABLE_LINE_EDIT XELP_ENABLE_HELP XELP_ENABLE_KEY XELP_ENABLE_THR XELP_ENABLE_HISTORY
echo ""
diff --git a/dev/xelp-todo.md b/dev/xelp-todo.md
index 1a17e14..bdd3b37 100644
--- a/dev/xelp-todo.md
+++ b/dev/xelp-todo.md
@@ -3,7 +3,7 @@
Future work for xelp, organized by area. Items marked with a design doc
link have detailed specifications in `dev/`.
-## Recently Completed (0.3.0 – 0.3.1)
+## Recently Completed (0.3.0 – 0.3.2)
- [x] Breaking API change: `XELP *ths` on all command/key signatures
- [x] XelpBuf macro normalization (SCREAMING_CASE)
@@ -27,6 +27,12 @@ link have detailed specifications in `dev/`.
- [x] Cross-build multi-config (KEY/CLI/FULL, `extract_size.py`, 18 targets)
- [x] README rewrite (3-column size table, grouped by word size)
- [x] CI aligned with local validation (`make validate` in all workflows)
+- [x] Command history (`XELP_ENABLE_HISTORY`): ring-buffer recall with
+ UP/DOWN arrows, consecutive dup suppression, in-progress save/restore.
+ ~420 bytes ARM Thumb.
+- [x] `XelpArgInt` / `XelpArgStr` convenience functions for direct argument
+ access by index.
+- [x] Test suite: 47 units, 598 cases, 100% line coverage
## Scripting Engine (deferred -- clean up core first)
@@ -51,13 +57,41 @@ include xelp as the text front-end. Parked here for reference.
Design doc: [dev/xelp_vm.md](xelp_vm.md)
+## Argument Parsing Ergonomics
+
+Two-phase plan to reduce per-command boilerplate. Design doc:
+[dev/arg_parse_updates.md](arg_parse_updates.md).
+
+### 0.3.2: Non-breaking convenience functions
+
+- [x] **`XelpArgInt(args, len, n, &val)`** -- get arg N as int, one call.
+ Wrapper over XelpTokN + XelpParseNum. ~50 bytes ARM Thumb.
+- [x] **`XelpArgStr(args, len, n, &s, &slen)`** -- get arg N as string
+ span. ~40 bytes ARM Thumb.
+
+Functions (not macros) in the base CLI API. No new flag. Arg 0 is the
+command name per argc/argv convention.
+
+### 0.4.0: Breaking handler signature change (argc/argv)
+
+- [ ] **Change CLI handler signature** from
+ `fn(XELP *ths, const char *args, int len)` to
+ `fn(XELP *ths, int argc, XelpBuf *argv)`.
+ Dispatcher pre-tokenizes into stack-allocated `XelpBuf argv[]`.
+ `XelpArgInt`/`XelpArgStr` simplify to take `XelpBuf *` directly.
+
+Last planned breaking change before the scripting engine. Stack cost
+(~12 bytes per arg on 32-bit) is acceptable -- the primary audience
+is 32-bit targets (ARM, ESP32, RISC-V). 8/16-bit builds remain
+supported but are not the optimization target.
+
## CLI Ergonomics
Quality-of-life improvements for interactive use. Each is compile-time
optional. Design constraint: must not bloat the core or break existing
bare-metal use cases.
-- [ ] **Command history** -- circular buffer of last N command lines,
+- [x] **Command history** -- ring buffer of last N command lines,
up/down arrow recall. User-supplied buffer (e.g. `char hist[4][64]`).
Multi-byte key detection is already in place (`XELPKEYCODE` handles
ESC sequences). Up/Down are currently silently dropped -- reserved
diff --git a/docs/api-reference.md b/docs/api-reference.md
index b28d866..693e78f 100644
--- a/docs/api-reference.md
+++ b/docs/api-reference.md
@@ -1,6 +1,6 @@
# API Reference
-All public types, functions, and macros defined in `xelp.h`. Version 0.3.1.
+All public types, functions, and macros defined in `xelp.h`. Version 0.3.2.
## Types
@@ -208,6 +208,41 @@ XELPRESULT cmd_divmod(XELP *ths, const char *args, int len) {
}
```
+### `XelpArgInt`
+
+```c
+XELPRESULT XelpArgInt(const char *args, int len, int n, int *val);
+```
+
+Get argument `n` (0-indexed) and parse it as an integer in a single call.
+Wraps `XelpTokN` + `XelpParseNum`. Returns `XELP_S_OK` on success,
+`XELP_E_ERR` if the token does not exist or is not a valid number.
+
+### `XelpArgStr`
+
+```c
+XELPRESULT XelpArgStr(const char *args, int len, int n,
+ const char **s, int *slen);
+```
+
+Get argument `n` (0-indexed) as a string span. On success, `*s` points to
+the first character of the token and `*slen` is its length. The token is
+NOT null-terminated. Returns `XELP_S_OK` on success, `XELP_E_ERR` if the
+token does not exist.
+
+### Example
+
+```c
+XELPRESULT cmd_set(XELP *ths, const char *args, int len) {
+ const char *key;
+ int klen, value;
+ XelpArgStr(args, len, 1, &key, &klen); /* arg 1 = key name */
+ XelpArgInt(args, len, 2, &value); /* arg 2 = int value */
+ /* ... use key/klen and value ... */
+ return XELP_S_OK;
+}
+```
+
## String Utilities
### `XelpStrLen`
@@ -341,7 +376,7 @@ natural value (e.g. `'a'` == 0x61). Multi-byte keys are >= 0x100.
| Constant | Value | Description |
|----------|-------|-------------|
-| `XELP_VERSION` | 0x00000301 | Library version (32-bit hex: `0x00MMmmpp`) |
+| `XELP_VERSION` | 0x00000302 | Library version (32-bit hex: `0x00MMmmpp`) |
| `XELP_VER_MAJOR(v)` | | Extract major version byte |
| `XELP_VER_MINOR(v)` | | Extract minor version byte |
| `XELP_VER_PATCH(v)` | | Extract patch version byte |
@@ -359,15 +394,16 @@ Cortex-M0 (Thumb, `-Os`):
| Profile | .text (bytes) | Flags |
|---------|------------:|-------|
-| CLI only | 1396 | `XELP_ENABLE_CLI` |
-| CLI + help | 1496 | + `XELP_ENABLE_HELP` |
-| CLI + key | 1500 | + `XELP_ENABLE_KEY` |
-| CLI + help + key | 1874 | + both |
-| CLI + help + key + thru | 1910 | + `XELP_ENABLE_THR` |
-| CLI + line edit | 1840 | + `XELP_ENABLE_LINE_EDIT` |
-| CLI + line edit + help | 1936 | + both |
-| CLI + LE + help + key | 2358 | + all three |
-| Full (all features) | 2394 | all flags |
+| CLI only | 1508 | `XELP_ENABLE_CLI` |
+| CLI + help | 1608 | + `XELP_ENABLE_HELP` |
+| CLI + key | 1612 | + `XELP_ENABLE_KEY` |
+| CLI + help + key | 1986 | + both |
+| CLI + help + key + thru | 2026 | + `XELP_ENABLE_THR` |
+| CLI + line edit | 1952 | + `XELP_ENABLE_LINE_EDIT` |
+| CLI + line edit + help | 2048 | + both |
+| CLI + LE + help + key | 2466 | + all three |
+| Full (all features) | 2506 | all flags |
+| Full + history | 2922 | all flags + `XELP_ENABLE_HISTORY` |
Use `dev/size_profiles.sh` to regenerate this table for your toolchain.
diff --git a/docs/build-profiles.md b/docs/build-profiles.md
index 95b3c30..0140a94 100644
--- a/docs/build-profiles.md
+++ b/docs/build-profiles.md
@@ -71,6 +71,7 @@ without KEY, or KEY without HELP.
| `XELP_ENABLE_KEY` | Single key press mode (menus, immediate actions) | -- | ~200-500 bytes |
| `XELP_ENABLE_CLI` | Command line prompt, backspace, command dispatch, scripting, tokenizer | -- | Core (~2 KB) |
| `XELP_ENABLE_LINE_EDIT` | Cursor movement (left/right, Home/End), insert-at-cursor, Delete, multi-byte ANSI key recognition | `XELP_ENABLE_CLI` | ~800-1000 bytes |
+| `XELP_ENABLE_HISTORY` | Command history (UP/DOWN arrow recall of previous commands) | `XELP_ENABLE_CLI` + `XELP_ENABLE_LINE_EDIT` | ~420 bytes |
| `XELP_ENABLE_THR` | Pass-through mode -- redirect all keys to another peripheral | -- | ~50-125 bytes |
| `XELP_ENABLE_HELP` | Built-in help command listing all registered commands | -- | ~180-350 bytes |
| `XELP_ENABLE_FULL` | Shorthand: enables KEY, CLI, THR, and HELP | -- | All combined |
@@ -182,6 +183,7 @@ XELP_SET_VAL_CLI_PROMPT(myXelp, "ser1>");
#define XELP_ENABLE_LINE_EDIT 1
#define XELP_ENABLE_KEY 1
#define XELP_ENABLE_HELP 1
+#define XELP_ENABLE_HISTORY 1 /* optional: UP/DOWN arrow command recall */
#define XELP_CLI_PROMPT "mydev>"
diff --git a/docs/configuration.md b/docs/configuration.md
index ae26606..4f98d4d 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -13,6 +13,7 @@ compiled out saves code space.
| `XELP_ENABLE_KEY` | Single key press mode (menus, immediate actions) | ~200--500 bytes |
| `XELP_ENABLE_THR` | Pass-through mode (redirect keys to another peripheral) | ~50--125 bytes |
| `XELP_ENABLE_HELP` | Built-in help function listing all commands | ~180--350 bytes |
+| `XELP_ENABLE_HISTORY` | Command history (UP/DOWN arrow recall). Requires `XELP_ENABLE_CLI` + `XELP_ENABLE_LINE_EDIT`. | ~420 bytes |
| `XELP_ENABLE_FULL` | Enable all of the above | All combined |
## Key Mappings
@@ -39,6 +40,7 @@ Override by redefining in `xelpcfg.h`, e.g. `#define XELPKEY_CLI ('c')`
| Define | Default | Purpose |
|--------|---------|---------|
| `XELP_CMDBUFSZ` | 64 | Command line buffer size in bytes |
+| `XELP_HIST_DEPTH` | 4 | Number of commands stored in history ring (requires `XELP_ENABLE_HISTORY`) |
| `XELP_REGS_SZ` | 4 | Number of callee-clobbers-all return registers (minimum 4). R0 is command status, R1-R3 are command-specific. |
| `XELPREG` | `int` | Register type (change for platforms where `int` is not ideal) |
diff --git a/docs/examples.md b/docs/examples.md
index 17b2844..f173a72 100644
--- a/docs/examples.md
+++ b/docs/examples.md
@@ -143,6 +143,7 @@ Requires ncurses (`sudo apt-get install libncurses5-dev` on Debian/Ubuntu).
- Backspace handling with ncurses `delch()`
- Mode change callback showing mode transitions
- Token parsing and numeric argument handling
+- Command history: UP/DOWN arrow recall of previous commands
### Architecture
diff --git a/docs/porting.md b/docs/porting.md
index 8df1684..c49c794 100644
--- a/docs/porting.md
+++ b/docs/porting.md
@@ -52,6 +52,21 @@ void handle_bksp(void) {
XELP_SET_FN_BKSP(myXelp, &handle_bksp);
```
+## Optional: Command History
+
+If you enable `XELP_ENABLE_HISTORY` (requires `XELP_ENABLE_CLI` + `XELP_ENABLE_LINE_EDIT`),
+users can recall previous commands with UP/DOWN arrows. No extra setup needed --
+it works automatically. Be aware of the RAM cost:
+
+```
+RAM = XELP_HIST_DEPTH * XELP_CMDBUFSZ + XELP_CMDBUFSZ + 4 bytes
+ = 4 * 64 + 64 + 4 = 324 bytes (default settings)
+```
+
+Reduce `XELP_HIST_DEPTH` (default 4) or `XELP_CMDBUFSZ` (default 64) if RAM is tight.
+Override via compiler flag (`-DXELP_HIST_DEPTH=2`) or `xelp_ovr.h` when using
+`XELP_CONFIG_OVERRIDE`.
+
## Optional: Other Callbacks
| Callback | Signature | Purpose |
diff --git a/docs/tutorial.md b/docs/tutorial.md
index 5310e8a..ab01b7f 100644
--- a/docs/tutorial.md
+++ b/docs/tutorial.md
@@ -292,6 +292,19 @@ beyond standard VT100/ANSI support.
Without `XELP_ENABLE_LINE_EDIT`, CLI uses append-only input with
backspace via the `mpfBksp` callback.
+## 10a. Command history
+
+When `XELP_ENABLE_HISTORY` is defined (requires `XELP_ENABLE_LINE_EDIT`),
+the CLI remembers previously entered commands. Press UP to recall older
+commands, DOWN to return to newer ones.
+
+- Empty commands are never stored
+- Consecutive duplicates are suppressed (typing "help" three times stores one entry)
+- In-progress text is saved when you first press UP and restored when you press DOWN past the newest entry
+- The number of stored commands is configurable via `XELP_HIST_DEPTH` (default 4)
+
+No API calls are needed -- history works automatically once enabled.
+
## 11. Mode change callback
Get notified when the user switches modes:
diff --git a/examples/README.md b/examples/README.md
index 0d05521..54a75a4 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -3,8 +3,8 @@
## posix-simple
Interactive CLI demo using ncurses for terminal handling on Linux/macOS.
-Demonstrates CLI commands, KEY mode, THR mode, math operations, and
-token parsing.
+Demonstrates CLI commands, KEY mode, THR mode, command history (UP/DOWN
+arrow recall), line editing, math operations, and token parsing.
```bash
cd posix-simple && make
diff --git a/examples/posix-simple/README.md b/examples/posix-simple/README.md
index 629662c..8f21ad9 100644
--- a/examples/posix-simple/README.md
+++ b/examples/posix-simple/README.md
@@ -70,3 +70,5 @@ make clean # remove build artifacts
- Token parsing and numeric arguments
- Math operations dispatched to the same handler function
- Register file (R0-R3) for command return values
+- Line editing (left/right, Home/End, Delete) and multi-byte key recognition
+- Command history: UP/DOWN arrow recall of previous commands
diff --git a/examples/posix-simple/xelp-example.c b/examples/posix-simple/xelp-example.c
index 5dc61f8..d0d3b43 100644
--- a/examples/posix-simple/xelp-example.c
+++ b/examples/posix-simple/xelp-example.c
@@ -351,6 +351,7 @@ int main (int argc, char *argv[])
"ESC : single-key mode (x = exit, h = help)\n"
"CTRL-P : CLI mode (type command + ENTER)\n"
"CTRL-T : pass-through mode\n"
+ "UP/DOWN : recall previous commands\n"
"\n";
XelpInit(&example, pAboutStr);
diff --git a/idf_component.yml b/idf_component.yml
index 5b088ff..efabd36 100644
--- a/idf_component.yml
+++ b/idf_component.yml
@@ -3,13 +3,13 @@
# This file allows xelp to be published to the ESP Component Registry
# (components.espressif.com) and installed in any ESP-IDF project with:
#
-# idf.py add-dependency "deftio/xelp==0.3.1"
+# idf.py add-dependency "deftio/xelp==0.3.2"
#
# Or by adding to your project's main/idf_component.yml:
#
# dependencies:
# deftio/xelp:
-# version: "~0.3.1"
+# version: "~0.3.2"
#
# After installation, use in your C or C++ source:
#
@@ -21,7 +21,7 @@
#
# See: https://docs.espressif.com/projects/idf-component-manager/en/latest/reference/manifest_file.html
-version: "0.3.1"
+version: "0.3.2"
description: >-
Tiny CLI and script interpreter for embedded systems.
2KB flash, zero malloc, multi-instance.
diff --git a/library.json b/library.json
index 38ff7dd..72a22e9 100644
--- a/library.json
+++ b/library.json
@@ -1,6 +1,6 @@
{
"name": "xelp",
- "version": "0.3.1",
+ "version": "0.3.2",
"description": "Tiny CLI and script interpreter for embedded systems. 2KB flash, zero malloc, multi-instance. Interactive serial command line with scriptable dispatch, single-key menus, and pass-through mode.",
"keywords": [
"cli",
diff --git a/library.properties b/library.properties
index d5536de..1a24ace 100644
--- a/library.properties
+++ b/library.properties
@@ -1,5 +1,5 @@
name=xelp
-version=0.3.1
+version=0.3.2
author=M. A. Chatterjee
maintainer=M. A. Chatterjee
sentence=Tiny CLI and script interpreter for embedded systems. 2KB, zero malloc, multi-instance.
diff --git a/llms.txt b/llms.txt
index 3b32da6..af490e9 100644
--- a/llms.txt
+++ b/llms.txt
@@ -22,7 +22,7 @@ commands, single-key menus, and pass-through mode. Three source files
## Code
- [xelp.h](https://github.com/deftio/xelp/blob/master/src/xelp.h): Public API header -- types, macros, function declarations
-- [xelp.c](https://github.com/deftio/xelp/blob/master/src/xelp.c): Implementation (~980 lines)
+- [xelp.c](https://github.com/deftio/xelp/blob/master/src/xelp.c): Implementation (~1100 lines)
- [xelpcfg.h](https://github.com/deftio/xelp/blob/master/src/xelpcfg.h): Compile-time configuration
## Examples
diff --git a/pages/api-reference.html b/pages/api-reference.html
index 9829cf3..5a0b5fe 100644
--- a/pages/api-reference.html
+++ b/pages/api-reference.html
@@ -27,7 +27,7 @@
API Reference
All public types, functions, and macros defined in xelp.h.
-Version 0.3.1.
+Version 0.3.2.
Types
@@ -106,7 +106,7 @@ XelpParseXB
Same as XelpParse but takes a XelpBuf.
XelpExecKC
-XELPRESULT XelpExecKC(XELP *ths, char key);
+XELPRESULT XelpExecKC(XELP *ths, XELPKEYCODE key);
Execute a single-key command directly (bypasses mode checking).
@@ -114,7 +114,7 @@ XelpOut
XELPRESULT XelpOut(XELP *ths, const char *msg, int maxlen);
Output a string through the instance’s output function. If
-maxlen > 0, prints exactly that many characters. If
+maxlen > 0, prints at most that many characters. If
maxlen == 0, prints until null terminator.
XelpHelp
@@ -141,6 +141,52 @@ XelpNumToks
Count the number of tokens in a buffer.
+XelpArgs — Sequential Argument Iterator
+
+A left-to-right token iterator for CLI command handlers. O(1) per token.
+
+XelpArgsInit
+XELPRESULT XelpArgsInit(XelpArgs *a, const char *args, int len);
+
+Initialize an argument iterator from the callback’s args and len.
+
+XelpNextTok
+XELPRESULT XelpNextTok(XelpArgs *a, XelpBuf *tok);
+
+Get the next token. Pass NULL for tok to skip.
+
+XelpNextInt
+XELPRESULT XelpNextInt(XelpArgs *a, int *val);
+
+Get the next token and parse it as an integer (decimal or hex).
+
+XelpArgCount
+XELPRESULT XelpArgCount(XelpArgs *a, int *n);
+
+Count total tokens without consuming them.
+
+Direct Argument Access
+
+XelpArgInt
+XELPRESULT XelpArgInt(const char *args, int len, int n, int *val);
+
+Get argument n (0-indexed) as an integer in one call.
+Wraps XelpTokN + XelpParseNum.
+
+XelpArgStr
+XELPRESULT XelpArgStr(const char *args, int len, int n,
+ const char **s, int *slen);
+
+Get argument n (0-indexed) as a string span. On success,
+*s points to the token and *slen is its length.
+Not null-terminated.
+
+XelpPutc
+XELPRESULT XelpPutc(XELP *ths, char c);
+
+Output a single character through the instance’s output function.
+Respects mOutEnable.
+
String utilities
XelpStrLen
@@ -218,7 +264,7 @@ Constants
| Constant | Value | Description |
-XELP_VERSION | 0x00000300 | Library version (32-bit hex: 0x00MMmmpp) |
+XELP_VERSION | 0x00000302 | Library version (32-bit hex: 0x00MMmmpp) |
XELP_VER_MAJOR(v) | | Extract major version byte |
XELP_VER_MINOR(v) | | Extract minor version byte |
XELP_VER_PATCH(v) | | Extract patch version byte |
diff --git a/pages/configuration.html b/pages/configuration.html
index 85f8d19..5e0d702 100644
--- a/pages/configuration.html
+++ b/pages/configuration.html
@@ -58,6 +58,9 @@ CLI (~3–5 KB)
#define XELP_ENABLE_HELP 1
+Optional: add XELP_ENABLE_HISTORY for UP/DOWN arrow command
+recall (~420 bytes, requires XELP_ENABLE_LINE_EDIT).
+
Representative size: ~2600 bytes on ARM Thumb,
~4200 bytes on AVR.
@@ -84,6 +87,7 @@ Feature flags
XELP_ENABLE_KEY | Single key press mode (menus, immediate actions) | ~200–500 bytes |
XELP_ENABLE_THR | Pass-through mode (redirect keys to another peripheral) | ~50–125 bytes |
XELP_ENABLE_HELP | Built-in help function listing all commands | ~180–350 bytes |
+XELP_ENABLE_HISTORY | Command history (UP/DOWN arrow recall). Requires XELP_ENABLE_CLI + XELP_ENABLE_LINE_EDIT. | ~420 bytes |
XELP_ENABLE_FULL | Enable all of the above | All combined |
@@ -120,6 +124,7 @@ Buffer and register sizes
| Define | Default | Purpose |
XELP_CMDBUFSZ | 64 | Command line buffer size in bytes |
+XELP_HIST_DEPTH | 4 | Number of commands stored in history ring (requires XELP_ENABLE_HISTORY) |
XELP_REGS_SZ | 4 | Number of callee-clobbers-all return registers (minimum 4). R0 is command status, R1–R3 are command-specific. |
XELPREG | int | Register type (change for platforms where int is not ideal) |
diff --git a/pages/index.html b/pages/index.html
index 9035e73..1d96c20 100644
--- a/pages/index.html
+++ b/pages/index.html
@@ -29,7 +29,7 @@
A command line interpreter and script engine for embedded systems.
-

+

@@ -57,6 +57,8 @@
Features
with a one-line table entry, callable interactively or from scripts
Multiple independent instances on different serial ports, BLE, USB CDC
Char-at-a-time parsing — feed from UART, ISR, or any byte stream
+
Command history — UP/DOWN arrow recall of previous commands
+ (optional, XELP_ENABLE_HISTORY)
Built-in tokenizer with quoted strings, escape sequences, # comments
Scriptable — run multi-command const strings from ROM or RAM
Zero dependencies (no stdio.h, string.h, etc.)
@@ -169,26 +171,26 @@
Compiled sizes
| CPU | Width | Compiler | KEY (bytes) | CLI (bytes) | FULL (bytes) |
-| AVR (ATtiny85) | 8 | avr-gcc | 1046 | 4038 | 4096 |
-| AVR (ATmega328P) | 8 | avr-gcc | 1054 | 4132 | 4190 |
-| Z80 | 8 | SDCC | 2121 | 6966 | 7074 |
-| 6800 (HC08) | 8 | SDCC | 2471 | 8147 | 8288 |
-| MSP430 | 16 | msp430-gcc | 770 | 3260 | 3306 |
-| 68HC11 | 16 | m68hc11-gcc | 2369 | 6570 | 6641 |
-| Xtensa LX7 (ESP32-S3) | 32 | xtensa-esp-elf-gcc | 576 | 2508 | 2540 |
-| ARM Thumb | 32 | arm-none-eabi-gcc | 580 | 2482 | 2526 |
-| RISC-V (rv32) | 32 | riscv64-unknown-elf-gcc | 722 | 2970 | 3008 |
-| Xtensa LX106 (ESP8266) | 32 | xtensa-lx106-elf-gcc | 723 | 2831 | 2863 |
-| m68k | 32 | m68k-linux-gnu-gcc | 728 | 3146 | 3194 |
-| ARM32 | 32 | arm-none-eabi-gcc | 980 | 3746 | 3806 |
-| x86-32 | 32 | GCC | 1081 | 4604 | 4654 |
-| MIPS32 | 32 | mipsel-linux-gnu-gcc | 1296 | 4888 | 4936 |
-| PowerPC | 32 | powerpc-linux-gnu-gcc | 1504 | 5674 | 5738 |
-| RISC-V (rv64) | 64 | riscv64-linux-gnu-gcc | 756 | 3332 | 3366 |
-| x86-64 | 64 | Clang | 1043 | 5013 | 5055 |
-| x86-64 | 64 | GCC | 1063 | 4787 | 4836 |
-| AArch64 (ARM64) | 64 | aarch64-linux-gnu-gcc | 1324 | 5178 | 5234 |
-| MIPS64 | 64 | mips64el-linux-gnuabi64-gcc | 1360 | 5512 | 5560 |
+| AVR (ATtiny85) | 8 | avr-gcc | 1046 | 4266 | 4324 |
+| AVR (ATmega328P) | 8 | avr-gcc | 1054 | 4366 | 4424 |
+| Z80 | 8 | SDCC | 2121 | 7280 | 7388 |
+| 6800 (HC08) | 8 | SDCC | 2471 | 8614 | 8715 |
+| MSP430 | 16 | msp430-gcc | 770 | 3482 | 3528 |
+| 68HC11 | 16 | m68hc11-gcc | 2369 | 6884 | 6955 |
+| Xtensa LX7 (ESP32-S3) | 32 | xtensa-esp-elf-gcc | 576 | 2592 | 2624 |
+| ARM Thumb | 32 | arm-none-eabi-gcc | 580 | 2598 | 2642 |
+| RISC-V (rv32) | 32 | riscv64-unknown-elf-gcc | 722 | 3094 | 3132 |
+| Xtensa LX106 (ESP8266) | 32 | xtensa-lx106-elf-gcc | 723 | 2955 | 2987 |
+| m68k | 32 | m68k-linux-gnu-gcc | 728 | 3332 | 3380 |
+| ARM32 | 32 | arm-none-eabi-gcc | 980 | 3930 | 3990 |
+| x86-32 | 32 | GCC | 1081 | 4916 | 4966 |
+| MIPS32 | 32 | mipsel-linux-gnu-gcc | 1296 | 5224 | 5272 |
+| PowerPC | 32 | powerpc-linux-gnu-gcc | 1504 | 6058 | 6122 |
+| RISC-V (rv64) | 64 | riscv64-linux-gnu-gcc | 756 | 3548 | 3582 |
+| x86-64 | 64 | Clang | 1043 | 5268 | 5310 |
+| x86-64 | 64 | GCC | 1063 | 5136 | 5185 |
+| AArch64 (ARM64) | 64 | aarch64-linux-gnu-gcc | 1324 | 5566 | 5622 |
+| MIPS64 | 64 | mips64el-linux-gnuabi64-gcc | 1360 | 5864 | 5928 |
@@ -207,7 +209,7 @@
Building and testing
make clean-all # remove all build artifacts
-
39 test units, 531 test cases, 100% line coverage of xelp.c.
+
47 test units, 598 test cases, 100% line coverage of xelp.c.
Documentation
diff --git a/pages/releases.html b/pages/releases.html
index bf8cf2c..1377e23 100644
--- a/pages/releases.html
+++ b/pages/releases.html
@@ -29,6 +29,21 @@
Release History
See also: CHANGELOG.md
on GitHub for the full changelog.
+
v0.3.2 (2026-04-26)
+
+ - Command history (
XELP_ENABLE_HISTORY):
+ UP/DOWN arrow recall of previously entered commands. Fixed-slot ring
+ buffer, configurable depth (XELP_HIST_DEPTH, default 4).
+ Consecutive duplicate suppression, in-progress line save/restore.
+ ~420 bytes ARM Thumb.
+ XelpArgInt / XelpArgStr: direct-access
+ convenience functions for getting the Nth argument as an integer or
+ string span in a single call.
+ - Fixed
XELPKEY_BKSP (0x07 BEL): added XELPKEY_BS
+ (0x08) so both backspace codes are accepted.
+ - Test suite expanded to 47 units, 598 test cases.
+
+
v0.3.1 (2026-04-26)
- Function rename: all public functions renamed from
diff --git a/release_management.md b/release_management.md
index 6e87fc6..f49a22f 100644
--- a/release_management.md
+++ b/release_management.md
@@ -7,14 +7,14 @@ Reference for building, testing, and releasing xelp.
The `XELP_VERSION` macro in `src/xelp.h` is the single source of truth:
```c
-#define XELP_VERSION (0x00000301UL) /* 32-bit: 0x00MMmmpp */
+#define XELP_VERSION (0x00000302UL) /* 32-bit: 0x00MMmmpp */
#define XELP_VER_MAJOR(v) (((v) >> 16) & 0xFF)
#define XELP_VER_MINOR(v) (((v) >> 8) & 0xFF)
#define XELP_VER_PATCH(v) ( (v) & 0xFF)
```
The 32-bit hex format encodes `0x00MMmmpp` (major.minor.patch), one byte
-each. Example: `0x00010000` = version 1.0.0, `0x00000301` = version 0.3.1.
+each. Example: `0x00010000` = version 1.0.0, `0x00000302` = version 0.3.2.
Accessor macros resolve to constants at compile time on all targets.
The build tool `tools/extract_version.c` reads the version via the C
diff --git a/src/xelp.c b/src/xelp.c
index 149f7a1..df3ed7a 100755
--- a/src/xelp.c
+++ b/src/xelp.c
@@ -162,6 +162,112 @@ static void _xelpRedrawFromCursor(XELP *ths) {
}
#endif
+#if defined(XELP_ENABLE_CLI) && defined(XELP_ENABLE_LINE_EDIT) && defined(XELP_ENABLE_HISTORY)
+
+/*****************************************
+ _xelpHistReplaceLine() - clear displayed line and load new content.
+ */
+static void _xelpHistReplaceLine(XELP *ths, const char *src, int slen) {
+ int oldlen = (int)(ths->mCmdXB.p - ths->mCmdXB.s);
+ int i;
+
+ /* move cursor to beginning of line (visual) */
+ while (ths->mCur > ths->mCmdXB.s) {
+ ths->mCur--;
+ _CURSOR('\b');
+ }
+ /* overwrite old content with spaces */
+ for (i = 0; i < oldlen; i++) _CURSOR(' ');
+ /* backspace back to start */
+ for (i = 0; i < oldlen; i++) _CURSOR('\b');
+
+ /* copy new content into command buffer */
+ for (i = 0; i < slen && i < XELP_CMDBUFSZ - 1; i++)
+ ths->mCmdMsgBuf[i] = src[i];
+ ths->mCmdXB.p = ths->mCmdXB.s + slen;
+ ths->mCur = ths->mCmdXB.p;
+
+ /* echo new content */
+ for (i = 0; i < slen; i++) _ECHO(src[i]);
+}
+
+/*****************************************
+ _xelpHistSave() - save command to history ring.
+ Called from _xelpHandleEnter before buffer reset.
+ Skips empty commands and consecutive duplicates.
+ */
+static void _xelpHistSave(XELP *ths) {
+ int len = (int)(ths->mCmdXB.p - ths->mCmdXB.s);
+ int prev;
+ ths->mHistBrowse = -1; /* ENTER always ends browse session */
+ if (len <= 0) return;
+
+ /* skip consecutive duplicate */
+ if (ths->mHistCount > 0) {
+ prev = ((int)ths->mHistWrite - 1 + XELP_HIST_DEPTH) % XELP_HIST_DEPTH;
+ if (XelpStrEq(ths->mCmdXB.s, len, ths->mHistBuf[prev]) == XELP_S_OK)
+ return;
+ }
+
+ /* copy command into ring slot */
+ {
+ char *dst = ths->mHistBuf[(int)ths->mHistWrite];
+ int i;
+ for (i = 0; i < len && i < XELP_CMDBUFSZ - 1; i++)
+ dst[i] = ths->mCmdXB.s[i];
+ dst[i] = 0;
+ }
+ ths->mHistWrite = (char)(((int)ths->mHistWrite + 1) % XELP_HIST_DEPTH);
+ if (ths->mHistCount < XELP_HIST_DEPTH)
+ ths->mHistCount++;
+}
+
+/*****************************************
+ _xelpHistRecall() - handle UP/DOWN arrow for history browsing.
+ dir: -1 = UP (older), +1 = DOWN (newer)
+ */
+static void _xelpHistRecall(XELP *ths, int dir) {
+ if (dir < 0) {
+ /* UP: go to older entry */
+ if (ths->mHistCount == 0) return;
+
+ if (ths->mHistBrowse == -1) {
+ /* first UP: save in-progress line */
+ int len = (int)(ths->mCmdXB.p - ths->mCmdXB.s);
+ int i;
+ for (i = 0; i < len; i++)
+ ths->mHistSaved[i] = ths->mCmdXB.s[i];
+ ths->mHistSaved[len] = 0;
+ ths->mHistSavedLen = (char)len;
+ /* start at most recent entry */
+ ths->mHistBrowse = ths->mHistCount - 1;
+ } else if (ths->mHistBrowse > 0) {
+ ths->mHistBrowse--;
+ } else {
+ return; /* already at oldest */
+ }
+ } else {
+ /* DOWN: go to newer entry */
+ if (ths->mHistBrowse == -1) return; /* not browsing */
+
+ if (ths->mHistBrowse < ths->mHistCount - 1) {
+ ths->mHistBrowse++;
+ } else {
+ /* past newest: restore in-progress line */
+ ths->mHistBrowse = -1;
+ _xelpHistReplaceLine(ths, ths->mHistSaved, (int)ths->mHistSavedLen);
+ return;
+ }
+ }
+
+ /* load the entry at mHistBrowse (0=oldest, count-1=newest) */
+ {
+ int slot = ((int)ths->mHistWrite - (int)ths->mHistCount + (int)ths->mHistBrowse + XELP_HIST_DEPTH) % XELP_HIST_DEPTH;
+ _xelpHistReplaceLine(ths, ths->mHistBuf[slot], XelpStrLen(ths->mHistBuf[slot]));
+ }
+}
+#endif /* XELP_ENABLE_CLI && XELP_ENABLE_LINE_EDIT && XELP_ENABLE_HISTORY */
+
#ifdef XELP_ENABLE_HELP
/*****************************************
_xelpPrintKeyName() - print human-readable name for a keycode in help output
@@ -286,6 +392,9 @@ XELPRESULT XelpInit (
#endif
#if defined(XELP_ENABLE_CLI) && defined(XELP_ENABLE_LINE_EDIT)
ths->mCur = ths->mCmdXB.s;
+#endif
+#if defined(XELP_ENABLE_CLI) && defined(XELP_ENABLE_HISTORY)
+ ths->mHistBrowse = -1;
#endif
/* comand mode mssage index
ths->mCmdMsgIndex = 0; //set to 0 by ptr loop at top
@@ -711,6 +820,36 @@ XELPRESULT XelpArgCount (XelpArgs *a, int *n)
XELP_XB_COPY(save, a->buf);
return XELP_S_OK;
}
+
+/********************************************************
+ XelpArgInt() - get the Nth argument as an integer (random access).
+ Arg 0 is the command name. Returns XELP_E_ERR if arg N doesn't exist
+ or is not a valid number.
+ */
+XELPRESULT XelpArgInt (const char *args, int len, int n, int *val)
+{
+ XelpBuf b, tok;
+ XELP_XB_INIT(b, (char*)args, len);
+ if (XelpTokN(&b, n, &tok) != XELP_S_OK) return XELP_E_ERR;
+ return XelpParseNum(tok.s, (int)(tok.p - tok.s), val);
+}
+
+/********************************************************
+ XelpArgStr() - get the Nth argument as a string span (random access).
+ Sets *s to start of token and *slen to its length.
+ Token is NOT null-terminated (buffer is not modified).
+ Returns XELP_E_ERR if arg N doesn't exist.
+ */
+XELPRESULT XelpArgStr (const char *args, int len, int n,
+ const char **s, int *slen)
+{
+ XelpBuf b, tok;
+ XELP_XB_INIT(b, (char*)args, len);
+ if (XelpTokN(&b, n, &tok) != XELP_S_OK) return XELP_E_ERR;
+ *s = tok.s;
+ *slen = (int)(tok.p - tok.s);
+ return XELP_S_OK;
+}
#endif /* XELP_ENABLE_CLI */
/********************************************************
@@ -739,9 +878,12 @@ static void _xelpCursorMove(XELP *ths, int dir, int all) {
#endif
#ifdef XELP_ENABLE_CLI
-/* handle ENTER: echo newline, execute buffer, reset, show prompt */
+/* handle ENTER: echo newline, save to history, execute buffer, reset, show prompt */
static void _xelpHandleEnter(XELP *ths) {
XelpBuf line;
+#if defined(XELP_ENABLE_LINE_EDIT) && defined(XELP_ENABLE_HISTORY)
+ _xelpHistSave(ths);
+#endif
_PUTC(XELPKEY_ENTER);
XELP_XB_INIT_PTRS(line, ths->mCmdXB.s, ths->mCmdXB.s, ths->mCmdXB.p);
XelpParseXB(ths, &line);
@@ -839,8 +981,14 @@ XELPRESULT XelpParseKey (XELP *ths, char key)
break;
}
case XELP_KEYCODE_UP:
+#ifdef XELP_ENABLE_HISTORY
+ _xelpHistRecall(ths, -1);
+#endif
+ break;
case XELP_KEYCODE_DOWN:
- /* silently drop (reserved for future history) */
+#ifdef XELP_ENABLE_HISTORY
+ _xelpHistRecall(ths, +1);
+#endif
break;
default:
/* silently drop other multi-byte keys */
@@ -849,7 +997,7 @@ XELPRESULT XelpParseKey (XELP *ths, char key)
} else {
/* single-char key in CLI mode with line editing */
char ch = (char)keycode;
- if (ch == XELPKEY_BKSP || ch == XELPKEY_DEL) {
+ if (ch == XELPKEY_BKSP || ch == XELPKEY_BS || ch == XELPKEY_DEL) {
/* delete char before cursor */
if (ths->mCur > ths->mCmdXB.s) {
int tail = (int)(ths->mCmdXB.p - ths->mCur);
@@ -891,7 +1039,7 @@ XELPRESULT XelpParseKey (XELP *ths, char key)
/* silently drop multi-byte keys */
} else {
char ch = (char)keycode;
- if (ch == XELPKEY_BKSP) {
+ if (ch == XELPKEY_BKSP || ch == XELPKEY_BS) {
if (ths->mCmdXB.p > ths->mCmdXB.s) {
(ths->mCmdXB.p)--;
if (ths->mpfBksp)
diff --git a/src/xelp.h b/src/xelp.h
index 7bd6f47..a14b161 100755
--- a/src/xelp.h
+++ b/src/xelp.h
@@ -44,7 +44,7 @@ extern "C"
{
#endif
-#define XELP_VERSION (0x00000301UL) /* 32-bit version: 0x00MMmmpp (major.minor.patch) */
+#define XELP_VERSION (0x00000302UL) /* 32-bit version: 0x00MMmmpp (major.minor.patch) */
#define XELP_VER_MAJOR(v) (((v) >> 16) & 0xFF)
#define XELP_VER_MINOR(v) (((v) >> 8) & 0xFF)
#define XELP_VER_PATCH(v) ( (v) & 0xFF)
@@ -140,7 +140,11 @@ typedef unsigned long XELPKEYCODE;
#define XELP_T_OK(r) ((r)>=0) /* simple macro for testing OK or warning (e.g. not a failure) */
-#define XELP_CMDBUFSZ (64)
+#ifndef XELP_HIST_DEPTH
+#define XELP_HIST_DEPTH (4) /* history ring depth (overridable) */
+#endif
+
+#define XELP_CMDBUFSZ (64)
/**
used by tokenizer funciton
@@ -238,7 +242,8 @@ typedef struct
#define XELPKEY_ENTER ('\n') /* Enter Key for Cmd Mode */
#define XELPKEY_SPC (0x20) /* space char */
-#define XELPKEY_BKSP (0x7) /* back space */
+#define XELPKEY_BKSP (0x7) /* back space (legacy) */
+#define XELPKEY_BS (0x08) /* ASCII BS */
#define XELPKEY_DEL (0x7f) /* DEL */
#define XELPKEY_ESC (0x1b) /* Escape */
@@ -302,6 +307,15 @@ typedef struct XELP_tag
char* mCur; /* cursor position in [mCmdXB.s .. mCmdXB.p] */
#endif
+#if defined(XELP_ENABLE_CLI) && defined(XELP_ENABLE_HISTORY)
+ char mHistBuf[XELP_HIST_DEPTH][XELP_CMDBUFSZ]; /* history ring */
+ char mHistWrite; /* next write slot (ring index) */
+ char mHistCount; /* entries stored (0..DEPTH) */
+ char mHistBrowse; /* browse position (-1 = not browsing) */
+ char mHistSaved[XELP_CMDBUFSZ]; /* stash of in-progress line on first UP */
+ char mHistSavedLen; /* length of saved in-progress line */
+#endif
+
/****
platform dependant dispatch functions (light-weight hardware abstraction layer)
note that if any are left unset (zero) this is OK as system will not call null ptrs.
@@ -406,6 +420,12 @@ XELPRESULT XelpNextTok (XelpArgs *a, XelpBuf *tok);
XELPRESULT XelpNextInt (XelpArgs *a, int *val);
XELPRESULT XelpArgCount (XelpArgs *a, int *n);
+/* Direct-access argument helpers (random access, O(N) per call).
+ Arg 0 is the command name, arg 1 is the first real argument. */
+XELPRESULT XelpArgInt (const char *args, int len, int n, int *val);
+XELPRESULT XelpArgStr (const char *args, int len, int n,
+ const char **s, int *slen);
+
/* XELPNEXTTOK get next token in a string buffer. This is just a macro call to XelpTokLine */
/* #define XELPNEXTTOK(buf,blen,tok_s,tok_e) (XelpTokLine(buf, buf+blen, tok_s, tok_e, 0, XELP_TOK_ONLY)) */
int XelpStrLen(const char* c); /* compute length of null terminated string. */
diff --git a/src/xelpcfg.h b/src/xelpcfg.h
index 4411cad..87f975e 100755
--- a/src/xelpcfg.h
+++ b/src/xelpcfg.h
@@ -15,8 +15,16 @@
#define __XELP_CONFIG_H__
#ifdef XELP_CONFIG_OVERRIDE
-#include "xelp_ovr.h" /* 8.3 filenaming convention used due to old-school compilers and filesystem support */
-#else /* use the rest of this file's conventions */
+/*
+ To use your own configuration without modifying this file:
+ 1. Define XELP_CONFIG_OVERRIDE in your compiler flags (-DXELP_CONFIG_OVERRIDE)
+ 2. Create xelp_ovr.h in your include path with the defines you need
+ 3. Any XELP_XXX define not set in xelp_ovr.h will use the default from xelp.h
+ This keeps the library source untouched across updates.
+ See docs/build-profiles.md for details and examples.
+*/
+#include "xelp_ovr.h"
+#else /* use the defaults below */
/****************************************************************************************************
@@ -53,6 +61,17 @@
*/
#define XELP_ENABLE_LINE_EDIT 1
+/****************************************************************************************************
+ Enable Command History (UP/DOWN arrow recall).
+ When defined, provides a ring buffer of previously entered commands that can
+ be browsed with UP/DOWN arrows. Requires XELP_ENABLE_CLI and XELP_ENABLE_LINE_EDIT.
+ XELP_HIST_DEPTH sets the number of commands stored (default 4, overridable via
+ compiler flag or xelp_ovr.h when XELP_CONFIG_OVERRIDE is defined).
+ RAM cost: XELP_HIST_DEPTH * XELP_CMDBUFSZ + XELP_CMDBUFSZ + 4 bytes per instance.
+ Code cost: ~420 bytes on ARM Thumb (-Os).
+ */
+#define XELP_ENABLE_HISTORY 1
+
/****************************************************************************************************
Enable KEY Mode.
diff --git a/tests/xelp_unit_tests.c b/tests/xelp_unit_tests.c
index 82c6cea..435fb21 100755
--- a/tests/xelp_unit_tests.c
+++ b/tests/xelp_unit_tests.c
@@ -2506,10 +2506,17 @@ XELPRESULT test_KeyAccumulator() {
if (JB_ASSERT(XELP_XB_POS(x.mCmdXB) != 0, "accum CSI stalls"))
return XELP_E_ERR;
- /* ESC + '[' + 'A' = UP arrow: silently dropped in CLI (no change to buf) */
+ /* ESC + '[' + 'A' = UP arrow in CLI */
XelpParseKey(&x, 'A');
+#ifdef XELP_ENABLE_HISTORY
+ /* history recalls "a" (typed above): buf should have 1 char */
+ if (JB_ASSERT(XELP_XB_POS(x.mCmdXB) != 1, "accum UP arrow recalls from history"))
+ return XELP_E_ERR;
+ XelpParseKey(&x, XELPKEY_ENTER); /* reset for next test */
+#else
if (JB_ASSERT(XELP_XB_POS(x.mCmdXB) != 0, "accum UP arrow dropped in CLI"))
return XELP_E_ERR;
+#endif
/* 4-byte sequence: ESC [ 3 ~ (KDEL) at empty buf: no effect */
XelpParseKey(&x, 0x1B);
@@ -2711,6 +2718,200 @@ XELPRESULT test_CLILineEdit_Backspace() {
return XELP_S_OK;
}
+/* ====================================================================
+ test_CLIBackspaceBS() -- comprehensive tests for 0x08 (ASCII BS)
+ Verifies that 0x08 works identically to XELPKEY_BKSP (0x07) in all contexts.
+ */
+XELPRESULT test_CLIBackspaceBS() {
+ XELP x;
+ char buf[64];
+
+ /* --- 1. 0x08 deletes char at end of line --- */
+ {
+ XelpInit(&x,"TestBS1");
+ XELP_SET_FN_CLI(x,gMyCLICommands);
+ XELP_SET_FN_OUT(x,dummyOut);
+
+ feedString(&x, "abc");
+ XelpParseKey(&x, XELPKEY_BS);
+
+ getCmdBuf(&x, buf, sizeof(buf));
+ if (JB_ASSERT(XELP_S_OK != XelpStrEq(buf, XelpStrLen(buf), "ab"),
+ "BS 0x08 delete at end"))
+ return XELP_E_ERR;
+ }
+
+ /* --- 2. 0x08 deletes char mid-line (with line editing) --- */
+ {
+ XelpInit(&x,"TestBS2");
+ XELP_SET_FN_CLI(x,gMyCLICommands);
+ XELP_SET_FN_OUT(x,dummyOut);
+
+ feedString(&x, "hello");
+ feedKeycode(&x, XELP_KEYCODE_LEFT);
+ feedKeycode(&x, XELP_KEYCODE_LEFT);
+ XelpParseKey(&x, XELPKEY_BS);
+
+ getCmdBuf(&x, buf, sizeof(buf));
+ if (JB_ASSERT(XELP_S_OK != XelpStrEq(buf, XelpStrLen(buf), "helo"),
+ "BS 0x08 mid-line"))
+ return XELP_E_ERR;
+ }
+
+ /* --- 3. 0x08 at start of line: no-op (no crash, no change) --- */
+ {
+ XelpInit(&x,"TestBS3");
+ XELP_SET_FN_CLI(x,gMyCLICommands);
+ XELP_SET_FN_OUT(x,dummyOut);
+
+ feedString(&x, "ab");
+ feedKeycode(&x, XELP_KEYCODE_HOME);
+ XelpParseKey(&x, XELPKEY_BS);
+
+ getCmdBuf(&x, buf, sizeof(buf));
+ if (JB_ASSERT(XELP_S_OK != XelpStrEq(buf, XelpStrLen(buf), "ab"),
+ "BS 0x08 at start no-op"))
+ return XELP_E_ERR;
+ }
+
+ /* --- 4. 0x08 on empty buffer: no crash --- */
+ {
+ XelpInit(&x,"TestBS4");
+ XELP_SET_FN_CLI(x,gMyCLICommands);
+ XELP_SET_FN_OUT(x,dummyOut);
+
+ XelpParseKey(&x, XELPKEY_BS);
+
+ getCmdBuf(&x, buf, sizeof(buf));
+ if (JB_ASSERT(XelpStrLen(buf) != 0,
+ "BS 0x08 empty buf no crash"))
+ return XELP_E_ERR;
+ }
+
+ /* --- 5. 0x08 and 0x07 produce identical results --- */
+ {
+ XELP x1, x2;
+ char buf1[64], buf2[64];
+
+ XelpInit(&x1,"TestBS5a");
+ XELP_SET_FN_CLI(x1,gMyCLICommands);
+ XELP_SET_FN_OUT(x1,dummyOut);
+ XelpInit(&x2,"TestBS5b");
+ XELP_SET_FN_CLI(x2,gMyCLICommands);
+ XELP_SET_FN_OUT(x2,dummyOut);
+
+ feedString(&x1, "test");
+ feedKeycode(&x1, XELP_KEYCODE_LEFT);
+ XelpParseKey(&x1, XELPKEY_BKSP);
+
+ feedString(&x2, "test");
+ feedKeycode(&x2, XELP_KEYCODE_LEFT);
+ XelpParseKey(&x2, XELPKEY_BS);
+
+ getCmdBuf(&x1, buf1, sizeof(buf1));
+ getCmdBuf(&x2, buf2, sizeof(buf2));
+
+ if (JB_ASSERT(XELP_S_OK != XelpStrEq(buf1, XelpStrLen(buf1), buf2),
+ "BS 0x08 == BKSP 0x07"))
+ return XELP_E_ERR;
+ }
+
+ /* --- 6. Multiple 0x08 deletions --- */
+ {
+ XelpInit(&x,"TestBS6");
+ XELP_SET_FN_CLI(x,gMyCLICommands);
+ XELP_SET_FN_OUT(x,dummyOut);
+
+ feedString(&x, "abcde");
+ XelpParseKey(&x, XELPKEY_BS);
+ XelpParseKey(&x, XELPKEY_BS);
+ XelpParseKey(&x, XELPKEY_BS);
+
+ getCmdBuf(&x, buf, sizeof(buf));
+ if (JB_ASSERT(XELP_S_OK != XelpStrEq(buf, XelpStrLen(buf), "ab"),
+ "BS 0x08 triple delete"))
+ return XELP_E_ERR;
+ }
+
+ /* --- 7. 0x08 after insert mid-line --- */
+ {
+ XelpInit(&x,"TestBS7");
+ XELP_SET_FN_CLI(x,gMyCLICommands);
+ XELP_SET_FN_OUT(x,dummyOut);
+
+ feedString(&x, "abcd");
+ feedKeycode(&x, XELP_KEYCODE_LEFT);
+ feedKeycode(&x, XELP_KEYCODE_LEFT);
+ feedString(&x, "XY");
+ XelpParseKey(&x, XELPKEY_BS);
+
+ getCmdBuf(&x, buf, sizeof(buf));
+ if (JB_ASSERT(XELP_S_OK != XelpStrEq(buf, XelpStrLen(buf), "abXcd"),
+ "BS 0x08 after insert"))
+ return XELP_E_ERR;
+ }
+
+ /* --- 8. 0x08 delete all chars one by one --- */
+ {
+ int i;
+ XelpInit(&x,"TestBS8");
+ XELP_SET_FN_CLI(x,gMyCLICommands);
+ XELP_SET_FN_OUT(x,dummyOut);
+
+ feedString(&x, "abcd");
+ for (i = 0; i < 4; i++)
+ XelpParseKey(&x, XELPKEY_BS);
+
+ getCmdBuf(&x, buf, sizeof(buf));
+ if (JB_ASSERT(XelpStrLen(buf) != 0,
+ "BS 0x08 delete all"))
+ return XELP_E_ERR;
+
+ /* one more should be harmless */
+ XelpParseKey(&x, XELPKEY_BS);
+ getCmdBuf(&x, buf, sizeof(buf));
+ if (JB_ASSERT(XelpStrLen(buf) != 0,
+ "BS 0x08 past empty"))
+ return XELP_E_ERR;
+ }
+
+ /* --- 9. 0x08 with echo mask --- */
+ {
+ XelpInit(&x,"TestBS9");
+ XELP_SET_FN_CLI(x,gMyCLICommands);
+ XELP_SET_FN_OUT(x,gDummyBufOut);
+ XELP_SET_ECHO(x, '*');
+
+ resetDummyBuf();
+ feedString(&x, "abc");
+ XelpParseKey(&x, XELPKEY_BS);
+
+ getCmdBuf(&x, buf, sizeof(buf));
+ if (JB_ASSERT(XELP_S_OK != XelpStrEq(buf, XelpStrLen(buf), "ab"),
+ "BS 0x08 with mask buffer"))
+ return XELP_E_ERR;
+ }
+
+ /* --- 10. Mixed 0x07 and 0x08 in same session --- */
+ {
+ XelpInit(&x,"TestBS10");
+ XELP_SET_FN_CLI(x,gMyCLICommands);
+ XELP_SET_FN_OUT(x,dummyOut);
+
+ feedString(&x, "abcdef");
+ XelpParseKey(&x, XELPKEY_BKSP); /* 0x07: remove 'f' */
+ XelpParseKey(&x, XELPKEY_BS); /* 0x08: remove 'e' */
+ XelpParseKey(&x, XELPKEY_BKSP); /* 0x07: remove 'd' */
+
+ getCmdBuf(&x, buf, sizeof(buf));
+ if (JB_ASSERT(XELP_S_OK != XelpStrEq(buf, XelpStrLen(buf), "abc"),
+ "BS mixed 0x07 0x08"))
+ return XELP_E_ERR;
+ }
+
+ return XELP_S_OK;
+}
+
/* ====================================================================
test_CLIArrowsDrop() - UP/DOWN in CLI: no corruption
*/
@@ -3250,6 +3451,109 @@ XELPRESULT test_XelpArgs() {
return XELP_S_OK;
}
+/* ====================================================================
+ test_XelpArgIntStr() - direct-access argument helpers
+ */
+XELPRESULT test_XelpArgIntStr() {
+ XELPRESULT r;
+ int val;
+ const char *s;
+ int slen;
+
+ /* 1. XelpArgInt: get arg 1 as int */
+ {
+ char buf[] = "led 42";
+ r = XelpArgInt(buf, XelpStrLen(buf), 1, &val);
+ if (JB_ASSERT(r != XELP_S_OK || val != 42, "ArgInt basic"))
+ return XELP_E_ERR;
+ }
+
+ /* 2. XelpArgInt: arg 0 is the command name */
+ {
+ char buf[] = "divmod 10 3";
+ r = XelpArgInt(buf, XelpStrLen(buf), 0, &val);
+ /* "divmod" is not a number */
+ if (JB_ASSERT(r != XELP_E_ERR, "ArgInt arg0 not a number"))
+ return XELP_E_ERR;
+ }
+
+ /* 3. XelpArgInt: multi args */
+ {
+ char buf[] = "divmod 10 3";
+ r = XelpArgInt(buf, XelpStrLen(buf), 1, &val);
+ if (JB_ASSERT(r != XELP_S_OK || val != 10, "ArgInt arg1=10"))
+ return XELP_E_ERR;
+ r = XelpArgInt(buf, XelpStrLen(buf), 2, &val);
+ if (JB_ASSERT(r != XELP_S_OK || val != 3, "ArgInt arg2=3"))
+ return XELP_E_ERR;
+ }
+
+ /* 4. XelpArgInt: hex */
+ {
+ char buf[] = "cmd 0xFF";
+ r = XelpArgInt(buf, XelpStrLen(buf), 1, &val);
+ if (JB_ASSERT(r != XELP_S_OK || val != 255, "ArgInt hex 0xFF"))
+ return XELP_E_ERR;
+ }
+
+ /* 5. XelpArgInt: arg past end */
+ {
+ char buf[] = "cmd 1";
+ val = -1;
+ r = XelpArgInt(buf, XelpStrLen(buf), 5, &val);
+ if (JB_ASSERT(r != XELP_E_ERR, "ArgInt past end"))
+ return XELP_E_ERR;
+ if (JB_ASSERT(val != -1, "ArgInt past end val unchanged"))
+ return XELP_E_ERR;
+ }
+
+ /* 6. XelpArgInt: negative */
+ {
+ char buf[] = "adj -7";
+ r = XelpArgInt(buf, XelpStrLen(buf), 1, &val);
+ if (JB_ASSERT(r != XELP_S_OK || val != -7, "ArgInt negative"))
+ return XELP_E_ERR;
+ }
+
+ /* 7. XelpArgStr: basic */
+ {
+ char buf[] = "ssid MyNetwork";
+ r = XelpArgStr(buf, XelpStrLen(buf), 1, &s, &slen);
+ if (JB_ASSERT(r != XELP_S_OK || slen != 9, "ArgStr basic len"))
+ return XELP_E_ERR;
+ if (JB_ASSERT(s[0] != 'M' || s[8] != 'k', "ArgStr basic content"))
+ return XELP_E_ERR;
+ }
+
+ /* 8. XelpArgStr: arg 0 = command name */
+ {
+ char buf[] = "echo hello";
+ r = XelpArgStr(buf, XelpStrLen(buf), 0, &s, &slen);
+ if (JB_ASSERT(r != XELP_S_OK || slen != 4, "ArgStr arg0 len"))
+ return XELP_E_ERR;
+ if (JB_ASSERT(XELP_S_OK != XelpStrEq(s, slen, "echo"), "ArgStr arg0 echo"))
+ return XELP_E_ERR;
+ }
+
+ /* 9. XelpArgStr: past end */
+ {
+ char buf[] = "help";
+ r = XelpArgStr(buf, XelpStrLen(buf), 1, &s, &slen);
+ if (JB_ASSERT(r != XELP_E_ERR, "ArgStr past end"))
+ return XELP_E_ERR;
+ }
+
+ /* 10. XelpArgStr: empty buffer */
+ {
+ char buf[] = "";
+ r = XelpArgStr(buf, 0, 0, &s, &slen);
+ if (JB_ASSERT(r != XELP_E_ERR, "ArgStr empty buf"))
+ return XELP_E_ERR;
+ }
+
+ return XELP_S_OK;
+}
+
/* ====================================================================
test_CursorWithEcho()
Verify arrow keys, HOME/END, insert, delete, backspace with echo
@@ -3858,69 +4162,721 @@ XELPRESULT test_EchoControl() {
return XELP_S_OK;
}
-/* ************************************************
- Xelp Simple Unit Test suite.
-*/
-FILE *logfile;
-int flogout (char x) {
- if (logfile) {
- fputc(x,logfile);
- fflush(logfile);
+/* ====================================================================
+ Command History tests -- guarded by both XELP_ENABLE_LINE_EDIT and
+ XELP_ENABLE_HISTORY so they compile out when history is disabled.
+ */
+#if defined(XELP_ENABLE_LINE_EDIT) && defined(XELP_ENABLE_HISTORY)
+
+/* ====================================================================
+ test_HistoryBasic() -- ~8 cases covering fundamental history recall
+ */
+XELPRESULT test_HistoryBasic() {
+ char buf[64];
+
+ /* 1. Fresh init: UP does nothing, buffer stays empty */
+ {
+ XELP x;
+ XelpInit(&x,"HB1");
+ XELP_SET_FN_CLI(x,gMyCLICommands);
+ XELP_SET_FN_OUT(x,dummyOut);
+
+ feedKeycode(&x, XELP_KEYCODE_UP);
+ getCmdBuf(&x, buf, sizeof(buf));
+ if (JB_ASSERT(XelpStrLen(buf) != 0, "Fresh UP does nothing"))
+ return XELP_E_ERR;
}
- return 0;
-}
-int putcharc (char x) {
- return putchar(x);
-}
-int run_tests() {
- JumpBug_InitGlobal("Xelp", putcharc,flogout);
+ /* 2. Type "hello" + ENTER, UP recalls "hello" */
+ {
+ XELP x;
+ XelpInit(&x,"HB2");
+ XELP_SET_FN_CLI(x,gMyCLICommands);
+ XELP_SET_FN_OUT(x,dummyOut);
- JumpBug_RunUnit(test_XelpStrLen,"XelpStrLen");
- JumpBug_RunUnit(test_XelpStr2Int,"XelpStr2Int");
- JumpBug_RunUnit(test_XelpStrEq, "StrEq");
- JumpBug_RunUnit(test_XelpStrEq2, "StrEq2");
- JumpBug_RunUnit(test_XelpBufCmp,"XelpBufCmp");
- JumpBug_RunUnit(test_XelpFindTok,"XelpFindTok");
- JumpBug_RunUnit(test_XelpTokLineXB,"XelpTokLineXB");
+ feedString(&x, "hello");
+ XelpParseKey(&x, XELPKEY_ENTER);
+ feedKeycode(&x, XELP_KEYCODE_UP);
- JumpBug_RunUnit(test_XelpTokN,"XelpTokN");
- JumpBug_RunUnit(test_XelpNumToks,"XelpNumToks");
- JumpBug_RunUnit(test_XelpInit,"XelpInit");
- JumpBug_RunUnit(test_XelpOut_comprehensive,"XelpOut");
- JumpBug_RunUnit(test_XelpExecKC,"XelpExecKC");
+ getCmdBuf(&x, buf, sizeof(buf));
+ if (JB_ASSERT(XELP_S_OK != XelpStrEq(buf, XelpStrLen(buf), "hello"),
+ "UP recalls hello"))
+ return XELP_E_ERR;
+ }
- JumpBug_RunUnit(test_XelpParseKey,"XelpParseKey");
- JumpBug_RunUnit(test_XelpParse,"XelpParse");
- JumpBug_RunUnit(test_XelpParseXB,"XelpParseXB");
- JumpBug_RunUnit(test_XelpHelp,"XelpHelp");
- JumpBug_RunUnit(test_XelpParseNum,"XelpParseNum");
- JumpBug_RunUnit(test_XelpBufMacros,"XelpBufMacros");
- JumpBug_RunUnit(test_default_handlers,"DefaultHandlers");
- JumpBug_RunUnit(test_buffer_boundaries,"BufferBoundaries");
- JumpBug_RunUnit(test_stress_malformed,"StressMalformed");
- JumpBug_RunUnit(test_XelpRegisters,"XelpRegisters");
- JumpBug_RunUnit(test_KeyAccumulator,"KeyAccumulator");
- JumpBug_RunUnit(test_MultiByteKeyDispatch,"MultiByteKeyDispatch");
-#ifdef XELP_ENABLE_LINE_EDIT
- JumpBug_RunUnit(test_CLILineEdit_Insert,"LineEditInsert");
- JumpBug_RunUnit(test_CLILineEdit_Delete,"LineEditDelete");
- JumpBug_RunUnit(test_CLILineEdit_HomeEnd,"LineEditHomeEnd");
- JumpBug_RunUnit(test_CLILineEdit_Backspace,"LineEditBackspace");
- JumpBug_RunUnit(test_CLIArrowsDrop,"CLIArrowsDrop");
- JumpBug_RunUnit(test_CLILineEdit_BufferFull,"LineEditBufferFull");
- JumpBug_RunUnit(test_CLILineEdit_Right,"LineEditRight");
-#endif
- JumpBug_RunUnit(test_HelpMultiByteKeys,"HelpMultiByteKeys");
- JumpBug_RunUnit(test_AccumOverflow,"AccumOverflow");
- JumpBug_RunUnit(test_CLIMalformedKeys,"CLIMalformedKeys");
- JumpBug_RunUnit(test_MultiInstance,"MultiInstance");
- JumpBug_RunUnit(test_XelpArgs,"XelpArgs");
-#ifdef XELP_ENABLE_LINE_EDIT
- JumpBug_RunUnit(test_CursorWithEcho,"CursorWithEcho");
-#endif
+ /* 3. "aaa" ENTER, "bbb" ENTER, UP=bbb, UP=aaa */
+ {
+ XELP x;
+ XelpInit(&x,"HB3");
+ XELP_SET_FN_CLI(x,gMyCLICommands);
+ XELP_SET_FN_OUT(x,dummyOut);
+
+ feedString(&x, "aaa"); XelpParseKey(&x, XELPKEY_ENTER);
+ feedString(&x, "bbb"); XelpParseKey(&x, XELPKEY_ENTER);
+
+ feedKeycode(&x, XELP_KEYCODE_UP);
+ getCmdBuf(&x, buf, sizeof(buf));
+ if (JB_ASSERT(XELP_S_OK != XelpStrEq(buf, XelpStrLen(buf), "bbb"),
+ "UP1 = bbb"))
+ return XELP_E_ERR;
+
+ feedKeycode(&x, XELP_KEYCODE_UP);
+ getCmdBuf(&x, buf, sizeof(buf));
+ if (JB_ASSERT(XELP_S_OK != XelpStrEq(buf, XelpStrLen(buf), "aaa"),
+ "UP2 = aaa"))
+ return XELP_E_ERR;
+ }
+
+ /* 4. DOWN after UP: returns to more recent entry */
+ {
+ XELP x;
+ XelpInit(&x,"HB4");
+ XELP_SET_FN_CLI(x,gMyCLICommands);
+ XELP_SET_FN_OUT(x,dummyOut);
+
+ feedString(&x, "aaa"); XelpParseKey(&x, XELPKEY_ENTER);
+ feedString(&x, "bbb"); XelpParseKey(&x, XELPKEY_ENTER);
+
+ feedKeycode(&x, XELP_KEYCODE_UP); /* bbb */
+ feedKeycode(&x, XELP_KEYCODE_UP); /* aaa */
+ feedKeycode(&x, XELP_KEYCODE_DOWN); /* bbb */
+
+ getCmdBuf(&x, buf, sizeof(buf));
+ if (JB_ASSERT(XELP_S_OK != XelpStrEq(buf, XelpStrLen(buf), "bbb"),
+ "DOWN returns to bbb"))
+ return XELP_E_ERR;
+ }
+
+ /* 5. DOWN past newest: restores empty line */
+ {
+ XELP x;
+ XelpInit(&x,"HB5");
+ XELP_SET_FN_CLI(x,gMyCLICommands);
+ XELP_SET_FN_OUT(x,dummyOut);
+
+ feedString(&x, "aaa"); XelpParseKey(&x, XELPKEY_ENTER);
+ feedKeycode(&x, XELP_KEYCODE_UP); /* aaa */
+ feedKeycode(&x, XELP_KEYCODE_DOWN); /* past newest → empty */
+
+ getCmdBuf(&x, buf, sizeof(buf));
+ if (JB_ASSERT(XelpStrLen(buf) != 0, "DOWN past newest = empty"))
+ return XELP_E_ERR;
+ }
+
+ /* 6. UP past oldest: stays on oldest, no crash */
+ {
+ XELP x;
+ XelpInit(&x,"HB6");
+ XELP_SET_FN_CLI(x,gMyCLICommands);
+ XELP_SET_FN_OUT(x,dummyOut);
+
+ feedString(&x, "aaa"); XelpParseKey(&x, XELPKEY_ENTER);
+ feedString(&x, "bbb"); XelpParseKey(&x, XELPKEY_ENTER);
+
+ feedKeycode(&x, XELP_KEYCODE_UP); /* bbb */
+ feedKeycode(&x, XELP_KEYCODE_UP); /* aaa */
+ feedKeycode(&x, XELP_KEYCODE_UP); /* clamped at aaa */
+ feedKeycode(&x, XELP_KEYCODE_UP); /* still aaa */
+
+ getCmdBuf(&x, buf, sizeof(buf));
+ if (JB_ASSERT(XELP_S_OK != XelpStrEq(buf, XelpStrLen(buf), "aaa"),
+ "UP past oldest stays on aaa"))
+ return XELP_E_ERR;
+ }
+
+ /* 7. ENTER on recalled line dispatches it */
+ {
+ XELP x;
+ XelpInit(&x,"HB7");
+ XELP_SET_FN_CLI(x,gMyCLICommands);
+ XELP_SET_FN_OUT(x,dummyOut);
+
+ gGlobalCallbackData.c1 = 0;
+ feedString(&x, "foo"); XelpParseKey(&x, XELPKEY_ENTER);
+ if (JB_ASSERT(gGlobalCallbackData.c1 != 1, "foo executed first time"))
+ return XELP_E_ERR;
+
+ /* reset and recall */
+ gGlobalCallbackData.c1 = 0;
+ feedKeycode(&x, XELP_KEYCODE_UP);
+ XelpParseKey(&x, XELPKEY_ENTER);
+ if (JB_ASSERT(gGlobalCallbackData.c1 != 1, "Recalled foo executes"))
+ return XELP_E_ERR;
+ }
+
+ /* 8. Empty ENTER does NOT store in history */
+ {
+ XELP x;
+ XelpInit(&x,"HB8");
+ XELP_SET_FN_CLI(x,gMyCLICommands);
+ XELP_SET_FN_OUT(x,dummyOut);
+
+ XelpParseKey(&x, XELPKEY_ENTER); /* empty */
+ XelpParseKey(&x, XELPKEY_ENTER); /* empty */
+ feedKeycode(&x, XELP_KEYCODE_UP);
+
+ getCmdBuf(&x, buf, sizeof(buf));
+ if (JB_ASSERT(XelpStrLen(buf) != 0, "Empty ENTER not stored"))
+ return XELP_E_ERR;
+ }
+
+ return XELP_S_OK;
+}
+
+/* ====================================================================
+ test_HistoryInProgressSave() -- ~4 cases for in-progress line stashing
+ */
+XELPRESULT test_HistoryInProgressSave() {
+ char buf[64];
+
+ /* 1. Type "partial", UP, DOWN: "partial" restored */
+ {
+ XELP x;
+ XelpInit(&x,"HIP1");
+ XELP_SET_FN_CLI(x,gMyCLICommands);
+ XELP_SET_FN_OUT(x,dummyOut);
+
+ feedString(&x, "aaa"); XelpParseKey(&x, XELPKEY_ENTER);
+ feedString(&x, "partial");
+ feedKeycode(&x, XELP_KEYCODE_UP); /* saves "partial", shows "aaa" */
+ feedKeycode(&x, XELP_KEYCODE_DOWN); /* restores "partial" */
+
+ getCmdBuf(&x, buf, sizeof(buf));
+ if (JB_ASSERT(XELP_S_OK != XelpStrEq(buf, XelpStrLen(buf), "partial"),
+ "In-progress restored after UP DOWN"))
+ return XELP_E_ERR;
+ }
+
+ /* 2. Type "partial", UP, UP, DOWN, DOWN: "partial" restored exactly */
+ {
+ XELP x;
+ XelpInit(&x,"HIP2");
+ XELP_SET_FN_CLI(x,gMyCLICommands);
+ XELP_SET_FN_OUT(x,dummyOut);
+
+ feedString(&x, "aaa"); XelpParseKey(&x, XELPKEY_ENTER);
+ feedString(&x, "bbb"); XelpParseKey(&x, XELPKEY_ENTER);
+ feedString(&x, "partial");
+
+ feedKeycode(&x, XELP_KEYCODE_UP); /* bbb */
+ feedKeycode(&x, XELP_KEYCODE_UP); /* aaa */
+ feedKeycode(&x, XELP_KEYCODE_DOWN); /* bbb */
+ feedKeycode(&x, XELP_KEYCODE_DOWN); /* partial */
+
+ getCmdBuf(&x, buf, sizeof(buf));
+ if (JB_ASSERT(XELP_S_OK != XelpStrEq(buf, XelpStrLen(buf), "partial"),
+ "In-progress restored multi bounce"))
+ return XELP_E_ERR;
+ }
+
+ /* 3. Type "partial", UP, type over, ENTER: new text executes + saves */
+ {
+ XELP x;
+ XelpInit(&x,"HIP3");
+ XELP_SET_FN_CLI(x,gMyCLICommands);
+ XELP_SET_FN_OUT(x,dummyOut);
+
+ feedString(&x, "aaa"); XelpParseKey(&x, XELPKEY_ENTER);
+ feedString(&x, "partial");
+ feedKeycode(&x, XELP_KEYCODE_UP); /* "aaa" recalled */
+
+ /* clear and type "bar" */
+ feedKeycode(&x, XELP_KEYCODE_HOME);
+ {
+ int j;
+ for (j = 0; j < 3; j++) feedKeycode(&x, XELP_KEYCODE_KDEL);
+ }
+ gGlobalCallbackData.c2 = -1;
+ feedString(&x, "bar");
+ XelpParseKey(&x, XELPKEY_ENTER);
+ if (JB_ASSERT(gGlobalCallbackData.c2 != 2, "Overtyped recalled cmd executes bar"))
+ return XELP_E_ERR;
+
+ /* "bar" should be in history now */
+ feedKeycode(&x, XELP_KEYCODE_UP);
+ getCmdBuf(&x, buf, sizeof(buf));
+ if (JB_ASSERT(XELP_S_OK != XelpStrEq(buf, XelpStrLen(buf), "bar"),
+ "Overtyped cmd saved in history"))
+ return XELP_E_ERR;
+ }
+
+ /* 4. Type "partial", UP, ENTER: partial is lost */
+ {
+ XELP x;
+ XelpInit(&x,"HIP4");
+ XELP_SET_FN_CLI(x,gMyCLICommands);
+ XELP_SET_FN_OUT(x,dummyOut);
+
+ feedString(&x, "aaa"); XelpParseKey(&x, XELPKEY_ENTER);
+ feedString(&x, "partial");
+ feedKeycode(&x, XELP_KEYCODE_UP); /* "aaa" recalled */
+ XelpParseKey(&x, XELPKEY_ENTER); /* executes "aaa" */
+
+ /* history should now have "aaa" at top (re-executed), not "partial" */
+ feedKeycode(&x, XELP_KEYCODE_UP);
+ getCmdBuf(&x, buf, sizeof(buf));
+ if (JB_ASSERT(XELP_S_OK != XelpStrEq(buf, XelpStrLen(buf), "aaa"),
+ "Partial lost after recall+enter"))
+ return XELP_E_ERR;
+ }
+
+ return XELP_S_OK;
+}
+
+/* ====================================================================
+ test_HistoryFull() -- ~4 cases for ring buffer capacity/eviction
+ */
+XELPRESULT test_HistoryFull() {
+ char buf[64];
+
+ /* 1. Fill to capacity, verify all recallable */
+ {
+ XELP x;
+ int i;
+ char cmd[8];
+
+ XelpInit(&x,"HF1");
+ XELP_SET_FN_CLI(x,gMyCLICommands);
+ XELP_SET_FN_OUT(x,dummyOut);
+
+ for (i = 0; i < XELP_HIST_DEPTH; i++) {
+ cmd[0] = 'a' + (char)i; cmd[1] = 0;
+ feedString(&x, cmd);
+ XelpParseKey(&x, XELPKEY_ENTER);
+ }
+ /* UP should recall in reverse: last entered first */
+ for (i = XELP_HIST_DEPTH - 1; i >= 0; i--) {
+ feedKeycode(&x, XELP_KEYCODE_UP);
+ getCmdBuf(&x, buf, sizeof(buf));
+ cmd[0] = 'a' + (char)i; cmd[1] = 0;
+ if (JB_ASSERT(XELP_S_OK != XelpStrEq(buf, XelpStrLen(buf), cmd),
+ "Full ring recall"))
+ return XELP_E_ERR;
+ }
+ }
+
+ /* 2. Overfill: oldest evicted, newest stored */
+ {
+ XELP x;
+ int i;
+ char cmd[8];
+
+ XelpInit(&x,"HF2");
+ XELP_SET_FN_CLI(x,gMyCLICommands);
+ XELP_SET_FN_OUT(x,dummyOut);
+
+ /* store DEPTH+1 entries: "a","b","c","d","e" (for DEPTH=4) */
+ for (i = 0; i <= XELP_HIST_DEPTH; i++) {
+ cmd[0] = 'a' + (char)i; cmd[1] = 0;
+ feedString(&x, cmd);
+ XelpParseKey(&x, XELPKEY_ENTER);
+ }
+ /* "a" should be evicted; first UP gives last entered */
+ feedKeycode(&x, XELP_KEYCODE_UP);
+ getCmdBuf(&x, buf, sizeof(buf));
+ cmd[0] = 'a' + (char)XELP_HIST_DEPTH; cmd[1] = 0;
+ if (JB_ASSERT(XELP_S_OK != XelpStrEq(buf, XelpStrLen(buf), cmd),
+ "Overfill newest at top"))
+ return XELP_E_ERR;
+
+ /* go to oldest -- should NOT be "a" */
+ for (i = 1; i < XELP_HIST_DEPTH; i++)
+ feedKeycode(&x, XELP_KEYCODE_UP);
+ getCmdBuf(&x, buf, sizeof(buf));
+ if (JB_ASSERT(XELP_S_OK == XelpStrEq(buf, XelpStrLen(buf), "a"),
+ "Overfill oldest evicted"))
+ return XELP_E_ERR;
+ }
+
+ /* 3. Fill + evict several times, verify ring integrity */
+ {
+ XELP x;
+ int i;
+ char cmd[8];
+
+ XelpInit(&x,"HF3");
+ XELP_SET_FN_CLI(x,gMyCLICommands);
+ XELP_SET_FN_OUT(x,dummyOut);
+
+ /* push 3 * DEPTH entries */
+ for (i = 0; i < 3 * XELP_HIST_DEPTH; i++) {
+ cmd[0] = 'A' + (char)(i % 26); cmd[1] = 0;
+ feedString(&x, cmd);
+ XelpParseKey(&x, XELPKEY_ENTER);
+ }
+ /* last DEPTH entries should be recallable */
+ for (i = 3 * XELP_HIST_DEPTH - 1; i >= 3 * XELP_HIST_DEPTH - XELP_HIST_DEPTH; i--) {
+ feedKeycode(&x, XELP_KEYCODE_UP);
+ getCmdBuf(&x, buf, sizeof(buf));
+ cmd[0] = 'A' + (char)(i % 26); cmd[1] = 0;
+ if (JB_ASSERT(XELP_S_OK != XelpStrEq(buf, XelpStrLen(buf), cmd),
+ "Ring integrity after many evictions"))
+ return XELP_E_ERR;
+ }
+ }
+
+ /* 4. Very long command near XELP_CMDBUFSZ: stored and recalled */
+ {
+ XELP x;
+ int i, len;
+
+ XelpInit(&x,"HF4");
+ XELP_SET_FN_CLI(x,gMyCLICommands);
+ XELP_SET_FN_OUT(x,dummyOut);
+
+ len = XELP_CMDBUFSZ - 2; /* max usable (CMDBUFSZ-1 is buf limit, -1 for safety) */
+ for (i = 0; i < len; i++)
+ XelpParseKey(&x, 'Z');
+ XelpParseKey(&x, XELPKEY_ENTER);
+
+ feedKeycode(&x, XELP_KEYCODE_UP);
+ getCmdBuf(&x, buf, sizeof(buf));
+ if (JB_ASSERT(XelpStrLen(buf) != len, "Long cmd recalled correct len"))
+ return XELP_E_ERR;
+ if (JB_ASSERT(buf[0] != 'Z' || buf[len-1] != 'Z', "Long cmd content correct"))
+ return XELP_E_ERR;
+ }
+
+ return XELP_S_OK;
+}
+
+/* ====================================================================
+ test_HistoryWithEditing() -- ~5 cases for cursor editing of recalled cmds
+ */
+XELPRESULT test_HistoryWithEditing() {
+ char buf[64];
+
+ /* 1. Recall with UP, LEFT/RIGHT works on recalled text */
+ {
+ XELP x;
+ XelpInit(&x,"HE1");
+ XELP_SET_FN_CLI(x,gMyCLICommands);
+ XELP_SET_FN_OUT(x,dummyOut);
+
+ feedString(&x, "abcde"); XelpParseKey(&x, XELPKEY_ENTER);
+ feedKeycode(&x, XELP_KEYCODE_UP);
+ feedKeycode(&x, XELP_KEYCODE_LEFT);
+ feedKeycode(&x, XELP_KEYCODE_LEFT);
+ XelpParseKey(&x, 'X');
+
+ getCmdBuf(&x, buf, sizeof(buf));
+ if (JB_ASSERT(XELP_S_OK != XelpStrEq(buf, XelpStrLen(buf), "abcXde"),
+ "Edit recalled: insert X"))
+ return XELP_E_ERR;
+ }
+
+ /* 2. Recall, HOME, type prefix, ENTER: modified version executes */
+ {
+ XELP x;
+ XelpInit(&x,"HE2");
+ XELP_SET_FN_CLI(x,gMyCLICommands);
+ XELP_SET_FN_OUT(x,dummyOut);
+
+ feedString(&x, "oo"); XelpParseKey(&x, XELPKEY_ENTER);
+ feedKeycode(&x, XELP_KEYCODE_UP);
+ feedKeycode(&x, XELP_KEYCODE_HOME);
+ XelpParseKey(&x, 'f');
+
+ gGlobalCallbackData.c1 = 0;
+ XelpParseKey(&x, XELPKEY_ENTER);
+ if (JB_ASSERT(gGlobalCallbackData.c1 != 1, "Modified recalled cmd foo executes"))
+ return XELP_E_ERR;
+ }
+
+ /* 3. Recall, KDEL at cursor */
+ {
+ XELP x;
+ XelpInit(&x,"HE3");
+ XELP_SET_FN_CLI(x,gMyCLICommands);
+ XELP_SET_FN_OUT(x,dummyOut);
+
+ feedString(&x, "abcd"); XelpParseKey(&x, XELPKEY_ENTER);
+ feedKeycode(&x, XELP_KEYCODE_UP);
+ feedKeycode(&x, XELP_KEYCODE_HOME);
+ feedKeycode(&x, XELP_KEYCODE_KDEL); /* delete 'a' */
+
+ getCmdBuf(&x, buf, sizeof(buf));
+ if (JB_ASSERT(XELP_S_OK != XelpStrEq(buf, XelpStrLen(buf), "bcd"),
+ "Recalled KDEL deletes char"))
+ return XELP_E_ERR;
+ }
+
+ /* 4. Recall, backspace */
+ {
+ XELP x;
+ XelpInit(&x,"HE4");
+ XELP_SET_FN_CLI(x,gMyCLICommands);
+ XELP_SET_FN_OUT(x,dummyOut);
+
+ feedString(&x, "abcd"); XelpParseKey(&x, XELPKEY_ENTER);
+ feedKeycode(&x, XELP_KEYCODE_UP);
+ XelpParseKey(&x, XELPKEY_BKSP); /* delete last char 'd' */
+
+ getCmdBuf(&x, buf, sizeof(buf));
+ if (JB_ASSERT(XELP_S_OK != XelpStrEq(buf, XelpStrLen(buf), "abc"),
+ "Recalled bksp works"))
+ return XELP_E_ERR;
+ }
+
+ /* 5. Recall, HOME, END: cursor at correct positions */
+ {
+ XELP x;
+ XelpInit(&x,"HE5");
+ XELP_SET_FN_CLI(x,gMyCLICommands);
+ XELP_SET_FN_OUT(x,dummyOut);
+
+ feedString(&x, "hello"); XelpParseKey(&x, XELPKEY_ENTER);
+ feedKeycode(&x, XELP_KEYCODE_UP);
+ feedKeycode(&x, XELP_KEYCODE_HOME);
+ XelpParseKey(&x, 'A'); /* insert at start */
+ feedKeycode(&x, XELP_KEYCODE_END);
+ XelpParseKey(&x, 'Z'); /* append at end */
+
+ getCmdBuf(&x, buf, sizeof(buf));
+ if (JB_ASSERT(XELP_S_OK != XelpStrEq(buf, XelpStrLen(buf), "AhelloZ"),
+ "HOME/END on recalled line"))
+ return XELP_E_ERR;
+ }
+
+ return XELP_S_OK;
+}
+
+/* ====================================================================
+ test_HistoryDuplicates() -- ~3 cases for consecutive duplicate suppression
+ */
+XELPRESULT test_HistoryDuplicates() {
+ char buf[64];
+
+ /* 1. "aaa" three times: only one entry (skip consecutive dups) */
+ {
+ XELP x;
+ XelpInit(&x,"HD1");
+ XELP_SET_FN_CLI(x,gMyCLICommands);
+ XELP_SET_FN_OUT(x,dummyOut);
+
+ feedString(&x, "aaa"); XelpParseKey(&x, XELPKEY_ENTER);
+ feedString(&x, "aaa"); XelpParseKey(&x, XELPKEY_ENTER);
+ feedString(&x, "aaa"); XelpParseKey(&x, XELPKEY_ENTER);
+
+ feedKeycode(&x, XELP_KEYCODE_UP); /* aaa */
+ getCmdBuf(&x, buf, sizeof(buf));
+ if (JB_ASSERT(XELP_S_OK != XelpStrEq(buf, XelpStrLen(buf), "aaa"),
+ "Dup: first UP = aaa"))
+ return XELP_E_ERR;
+
+ feedKeycode(&x, XELP_KEYCODE_UP); /* should still be aaa (no more entries) */
+ getCmdBuf(&x, buf, sizeof(buf));
+ if (JB_ASSERT(XELP_S_OK != XelpStrEq(buf, XelpStrLen(buf), "aaa"),
+ "Dup: second UP still aaa (only 1 entry)"))
+ return XELP_E_ERR;
+ }
+
+ /* 2. "aaa", "bbb", "aaa": all three stored (non-consecutive) */
+ {
+ XELP x;
+ XelpInit(&x,"HD2");
+ XELP_SET_FN_CLI(x,gMyCLICommands);
+ XELP_SET_FN_OUT(x,dummyOut);
+
+ feedString(&x, "aaa"); XelpParseKey(&x, XELPKEY_ENTER);
+ feedString(&x, "bbb"); XelpParseKey(&x, XELPKEY_ENTER);
+ feedString(&x, "aaa"); XelpParseKey(&x, XELPKEY_ENTER);
+
+ feedKeycode(&x, XELP_KEYCODE_UP); /* aaa (newest) */
+ getCmdBuf(&x, buf, sizeof(buf));
+ if (JB_ASSERT(XELP_S_OK != XelpStrEq(buf, XelpStrLen(buf), "aaa"),
+ "Non-consec: UP1 = aaa"))
+ return XELP_E_ERR;
+
+ feedKeycode(&x, XELP_KEYCODE_UP); /* bbb */
+ getCmdBuf(&x, buf, sizeof(buf));
+ if (JB_ASSERT(XELP_S_OK != XelpStrEq(buf, XelpStrLen(buf), "bbb"),
+ "Non-consec: UP2 = bbb"))
+ return XELP_E_ERR;
+
+ feedKeycode(&x, XELP_KEYCODE_UP); /* aaa (oldest) */
+ getCmdBuf(&x, buf, sizeof(buf));
+ if (JB_ASSERT(XELP_S_OK != XelpStrEq(buf, XelpStrLen(buf), "aaa"),
+ "Non-consec: UP3 = aaa"))
+ return XELP_E_ERR;
+ }
+
+ /* 3. Empty string never stored regardless */
+ {
+ XELP x;
+ XelpInit(&x,"HD3");
+ XELP_SET_FN_CLI(x,gMyCLICommands);
+ XELP_SET_FN_OUT(x,dummyOut);
+
+ feedString(&x, "aaa"); XelpParseKey(&x, XELPKEY_ENTER);
+ XelpParseKey(&x, XELPKEY_ENTER); /* empty */
+ XelpParseKey(&x, XELPKEY_ENTER); /* empty */
+
+ feedKeycode(&x, XELP_KEYCODE_UP);
+ getCmdBuf(&x, buf, sizeof(buf));
+ if (JB_ASSERT(XELP_S_OK != XelpStrEq(buf, XelpStrLen(buf), "aaa"),
+ "Empty not stored, UP = aaa"))
+ return XELP_E_ERR;
+ }
+
+ return XELP_S_OK;
+}
+
+/* ====================================================================
+ test_HistoryAndEcho() -- ~3 cases for history interaction with echo/output
+ */
+XELPRESULT test_HistoryAndEcho() {
+ char buf[64];
+
+ /* 1. Echo mask '*': recall works, output shows masked chars */
+ {
+ XELP x;
+ XelpInit(&x,"HEC1");
+ XELP_SET_FN_CLI(x,gMyCLICommands);
+ XELP_SET_FN_OUT(x,gDummyBufOut);
+ XELP_SET_ECHO(x, '*');
+
+ feedString(&x, "secret");
+ XelpParseKey(&x, XELPKEY_ENTER);
+
+ resetDummyBuf();
+ feedKeycode(&x, XELP_KEYCODE_UP);
+ gDummyBufOut(0);
+
+ getCmdBuf(&x, buf, sizeof(buf));
+ if (JB_ASSERT(XELP_S_OK != XelpStrEq(buf, XelpStrLen(buf), "secret"),
+ "Echo mask: buffer has real text"))
+ return XELP_E_ERR;
+ /* output should contain '*' chars, not real text */
+ {
+ int i, stars = 0;
+ for (i = 0; i < XelpStrLen(gDummyBuf); i++)
+ if (gDummyBuf[i] == '*') stars++;
+ if (JB_ASSERT(stars < 6, "Echo mask: output has stars"))
+ return XELP_E_ERR;
+ }
+ }
+
+ /* 2. Output disabled: history still saves and recalls */
+ {
+ XELP x;
+ XelpInit(&x,"HEC2");
+ XELP_SET_FN_CLI(x,gMyCLICommands);
+ XELP_SET_FN_OUT(x,gDummyBufOut);
+ XELP_SET_OUT_ENABLE(x, 0);
+
+ feedString(&x, "silent");
+ XelpParseKey(&x, XELPKEY_ENTER);
+ feedKeycode(&x, XELP_KEYCODE_UP);
+
+ getCmdBuf(&x, buf, sizeof(buf));
+ if (JB_ASSERT(XELP_S_OK != XelpStrEq(buf, XelpStrLen(buf), "silent"),
+ "Output disabled: recall works"))
+ return XELP_E_ERR;
+
+ XELP_SET_OUT_ENABLE(x, 1);
+ }
+
+ /* 3. After recall with echo off, re-enable, type: echo resumes */
+ {
+ XELP x;
+ XelpInit(&x,"HEC3");
+ XELP_SET_FN_CLI(x,gMyCLICommands);
+ XELP_SET_FN_OUT(x,gDummyBufOut);
+
+ feedString(&x, "cmd1");
+ XelpParseKey(&x, XELPKEY_ENTER);
+
+ XELP_SET_ECHO(x, XELP_ECHO_OFF);
+ feedKeycode(&x, XELP_KEYCODE_UP);
+ XelpParseKey(&x, XELPKEY_ENTER);
+
+ XELP_SET_ECHO(x, XELP_ECHO_NORMAL);
+ resetDummyBuf();
+ XelpParseKey(&x, 'Q');
+ gDummyBufOut(0);
+ if (JB_ASSERT(gDummyBuf[0] != 'Q', "Echo resumes after recall"))
+ return XELP_E_ERR;
+ }
+
+ return XELP_S_OK;
+}
+
+#endif /* XELP_ENABLE_LINE_EDIT && XELP_ENABLE_HISTORY */
+
+/* ************************************************
+ Xelp Simple Unit Test suite.
+*/
+FILE *logfile;
+int flogout (char x) {
+ if (logfile) {
+ fputc(x,logfile);
+ fflush(logfile);
+ }
+ return 0;
+}
+int putcharc (char x) {
+ return putchar(x);
+}
+int run_tests() {
+
+ JumpBug_InitGlobal("Xelp", putcharc,flogout);
+
+ JumpBug_RunUnit(test_XelpStrLen,"XelpStrLen");
+ JumpBug_RunUnit(test_XelpStr2Int,"XelpStr2Int");
+ JumpBug_RunUnit(test_XelpStrEq, "StrEq");
+ JumpBug_RunUnit(test_XelpStrEq2, "StrEq2");
+ JumpBug_RunUnit(test_XelpBufCmp,"XelpBufCmp");
+ JumpBug_RunUnit(test_XelpFindTok,"XelpFindTok");
+ JumpBug_RunUnit(test_XelpTokLineXB,"XelpTokLineXB");
+
+ JumpBug_RunUnit(test_XelpTokN,"XelpTokN");
+ JumpBug_RunUnit(test_XelpNumToks,"XelpNumToks");
+ JumpBug_RunUnit(test_XelpInit,"XelpInit");
+ JumpBug_RunUnit(test_XelpOut_comprehensive,"XelpOut");
+ JumpBug_RunUnit(test_XelpExecKC,"XelpExecKC");
+
+ JumpBug_RunUnit(test_XelpParseKey,"XelpParseKey");
+ JumpBug_RunUnit(test_XelpParse,"XelpParse");
+ JumpBug_RunUnit(test_XelpParseXB,"XelpParseXB");
+ JumpBug_RunUnit(test_XelpHelp,"XelpHelp");
+ JumpBug_RunUnit(test_XelpParseNum,"XelpParseNum");
+ JumpBug_RunUnit(test_XelpBufMacros,"XelpBufMacros");
+ JumpBug_RunUnit(test_default_handlers,"DefaultHandlers");
+ JumpBug_RunUnit(test_buffer_boundaries,"BufferBoundaries");
+ JumpBug_RunUnit(test_stress_malformed,"StressMalformed");
+ JumpBug_RunUnit(test_XelpRegisters,"XelpRegisters");
+ JumpBug_RunUnit(test_KeyAccumulator,"KeyAccumulator");
+ JumpBug_RunUnit(test_MultiByteKeyDispatch,"MultiByteKeyDispatch");
+#ifdef XELP_ENABLE_LINE_EDIT
+ JumpBug_RunUnit(test_CLILineEdit_Insert,"LineEditInsert");
+ JumpBug_RunUnit(test_CLILineEdit_Delete,"LineEditDelete");
+ JumpBug_RunUnit(test_CLILineEdit_HomeEnd,"LineEditHomeEnd");
+ JumpBug_RunUnit(test_CLILineEdit_Backspace,"LineEditBackspace");
+ JumpBug_RunUnit(test_CLIBackspaceBS,"BackspaceBS");
+ JumpBug_RunUnit(test_CLIArrowsDrop,"CLIArrowsDrop");
+ JumpBug_RunUnit(test_CLILineEdit_BufferFull,"LineEditBufferFull");
+ JumpBug_RunUnit(test_CLILineEdit_Right,"LineEditRight");
+#endif
+ JumpBug_RunUnit(test_HelpMultiByteKeys,"HelpMultiByteKeys");
+ JumpBug_RunUnit(test_AccumOverflow,"AccumOverflow");
+ JumpBug_RunUnit(test_CLIMalformedKeys,"CLIMalformedKeys");
+ JumpBug_RunUnit(test_MultiInstance,"MultiInstance");
+ JumpBug_RunUnit(test_XelpArgs,"XelpArgs");
+ JumpBug_RunUnit(test_XelpArgIntStr,"XelpArgIntStr");
+#ifdef XELP_ENABLE_LINE_EDIT
+ JumpBug_RunUnit(test_CursorWithEcho,"CursorWithEcho");
+#endif
JumpBug_RunUnit(test_OutputEnable,"OutputEnable");
JumpBug_RunUnit(test_EchoControl,"EchoControl");
+#if defined(XELP_ENABLE_LINE_EDIT) && defined(XELP_ENABLE_HISTORY)
+ JumpBug_RunUnit(test_HistoryBasic,"HistoryBasic");
+ JumpBug_RunUnit(test_HistoryInProgressSave,"HistInProgress");
+ JumpBug_RunUnit(test_HistoryFull,"HistoryFull");
+ JumpBug_RunUnit(test_HistoryWithEditing,"HistoryEditing");
+ JumpBug_RunUnit(test_HistoryDuplicates,"HistoryDups");
+ JumpBug_RunUnit(test_HistoryAndEcho,"HistoryEcho");
+#endif
JumpBug_PrintResults();
From e1a367a3d522d2b03f197c8be8da8f34ad87ce2a Mon Sep 17 00:00:00 2001
From: deftio
Date: Mon, 27 Apr 2026 00:31:28 -0700
Subject: [PATCH 2/3] sync manifests, badges, and sizes for 0.3.2
---
README.md | 34 +++++++++++++++++-----------------
pages/index.html | 34 +++++++++++++++++-----------------
2 files changed, 34 insertions(+), 34 deletions(-)
diff --git a/README.md b/README.md
index 8d3ef67..3fdb9f1 100644
--- a/README.md
+++ b/README.md
@@ -248,25 +248,25 @@ features). Even the largest full build is under 7 KB.
| CPU | Width | Compiler | KEY (bytes) | CLI (bytes) | FULL (bytes) |
|-----|------:|----------|------------:|------------:|-------------:|
-| AVR (ATtiny85) | 8 | avr-gcc | 1046 | 4266 | 4324 |
-| AVR (ATmega328P) | 8 | avr-gcc | 1054 | 4366 | 4424 |
-| Z80 | 8 | SDCC | 2121 | 7280 | 7388 |
-| 6800 (HC08) | 8 | SDCC | 2471 | 8614 | 8715 |
-| MSP430 | 16 | msp430-gcc | 770 | 3482 | 3528 |
-| 68HC11 | 16 | m68hc11-gcc | 2369 | 6884 | 6955 |
-| Xtensa LX7 (ESP32-S3) | 32 | xtensa-esp-elf-gcc | 576 | 2592 | 2624 |
+| AVR (ATtiny85) | 8 | avr-gcc | 1046 | 4270 | 4328 |
+| AVR (ATmega328P) | 8 | avr-gcc | 1054 | 4370 | 4428 |
+| Z80 | 8 | SDCC | 2121 | 7287 | 7395 |
+| 6800 (HC08) | 8 | SDCC | 2471 | 8616 | 8718 |
+| MSP430 | 16 | msp430-gcc | 770 | 3486 | 3532 |
+| 68HC11 | 16 | m68hc11-gcc | 2369 | 6895 | 6966 |
+| Xtensa LX7 (ESP32-S3) | 32 | xtensa-esp-elf-gcc | 576 | 2600 | 2632 |
| ARM Thumb | 32 | arm-none-eabi-gcc | 580 | 2598 | 2642 |
-| RISC-V (rv32) | 32 | riscv64-unknown-elf-gcc | 722 | 3094 | 3132 |
-| Xtensa LX106 (ESP8266) | 32 | xtensa-lx106-elf-gcc | 723 | 2955 | 2987 |
-| m68k | 32 | m68k-linux-gnu-gcc | 728 | 3332 | 3380 |
-| ARM32 | 32 | arm-none-eabi-gcc | 980 | 3930 | 3990 |
-| x86-32 | 32 | GCC | 1081 | 4916 | 4966 |
+| RISC-V (rv32) | 32 | riscv64-unknown-elf-gcc | 722 | 3100 | 3138 |
+| Xtensa LX106 (ESP8266) | 32 | xtensa-lx106-elf-gcc | 723 | 2947 | 2979 |
+| m68k | 32 | m68k-linux-gnu-gcc | 728 | 3336 | 3384 |
+| ARM32 | 32 | arm-none-eabi-gcc | 980 | 3934 | 3994 |
+| x86-32 | 32 | GCC | 1081 | 4919 | 4969 |
| MIPS32 | 32 | mipsel-linux-gnu-gcc | 1296 | 5224 | 5272 |
-| PowerPC | 32 | powerpc-linux-gnu-gcc | 1504 | 6058 | 6122 |
-| RISC-V (rv64) | 64 | riscv64-linux-gnu-gcc | 756 | 3548 | 3582 |
-| x86-64 | 64 | Clang | 1043 | 5268 | 5310 |
-| x86-64 | 64 | GCC | 1063 | 5136 | 5185 |
-| AArch64 (ARM64) | 64 | aarch64-linux-gnu-gcc | 1324 | 5566 | 5622 |
+| PowerPC | 32 | powerpc-linux-gnu-gcc | 1504 | 6066 | 6130 |
+| RISC-V (rv64) | 64 | riscv64-linux-gnu-gcc | 756 | 3554 | 3588 |
+| x86-64 | 64 | Clang | 1043 | 5269 | 5311 |
+| x86-64 | 64 | GCC | 1063 | 5138 | 5187 |
+| AArch64 (ARM64) | 64 | aarch64-linux-gnu-gcc | 1324 | 5574 | 5630 |
| MIPS64 | 64 | mips64el-linux-gnuabi64-gcc | 1360 | 5864 | 5928 |
diff --git a/pages/index.html b/pages/index.html
index 1d96c20..5f4c168 100644
--- a/pages/index.html
+++ b/pages/index.html
@@ -171,25 +171,25 @@
Compiled sizes
| CPU | Width | Compiler | KEY (bytes) | CLI (bytes) | FULL (bytes) |
-| AVR (ATtiny85) | 8 | avr-gcc | 1046 | 4266 | 4324 |
-| AVR (ATmega328P) | 8 | avr-gcc | 1054 | 4366 | 4424 |
-| Z80 | 8 | SDCC | 2121 | 7280 | 7388 |
-| 6800 (HC08) | 8 | SDCC | 2471 | 8614 | 8715 |
-| MSP430 | 16 | msp430-gcc | 770 | 3482 | 3528 |
-| 68HC11 | 16 | m68hc11-gcc | 2369 | 6884 | 6955 |
-| Xtensa LX7 (ESP32-S3) | 32 | xtensa-esp-elf-gcc | 576 | 2592 | 2624 |
+| AVR (ATtiny85) | 8 | avr-gcc | 1046 | 4270 | 4328 |
+| AVR (ATmega328P) | 8 | avr-gcc | 1054 | 4370 | 4428 |
+| Z80 | 8 | SDCC | 2121 | 7287 | 7395 |
+| 6800 (HC08) | 8 | SDCC | 2471 | 8616 | 8718 |
+| MSP430 | 16 | msp430-gcc | 770 | 3486 | 3532 |
+| 68HC11 | 16 | m68hc11-gcc | 2369 | 6895 | 6966 |
+| Xtensa LX7 (ESP32-S3) | 32 | xtensa-esp-elf-gcc | 576 | 2600 | 2632 |
| ARM Thumb | 32 | arm-none-eabi-gcc | 580 | 2598 | 2642 |
-| RISC-V (rv32) | 32 | riscv64-unknown-elf-gcc | 722 | 3094 | 3132 |
-| Xtensa LX106 (ESP8266) | 32 | xtensa-lx106-elf-gcc | 723 | 2955 | 2987 |
-| m68k | 32 | m68k-linux-gnu-gcc | 728 | 3332 | 3380 |
-| ARM32 | 32 | arm-none-eabi-gcc | 980 | 3930 | 3990 |
-| x86-32 | 32 | GCC | 1081 | 4916 | 4966 |
+| RISC-V (rv32) | 32 | riscv64-unknown-elf-gcc | 722 | 3100 | 3138 |
+| Xtensa LX106 (ESP8266) | 32 | xtensa-lx106-elf-gcc | 723 | 2947 | 2979 |
+| m68k | 32 | m68k-linux-gnu-gcc | 728 | 3336 | 3384 |
+| ARM32 | 32 | arm-none-eabi-gcc | 980 | 3934 | 3994 |
+| x86-32 | 32 | GCC | 1081 | 4919 | 4969 |
| MIPS32 | 32 | mipsel-linux-gnu-gcc | 1296 | 5224 | 5272 |
-| PowerPC | 32 | powerpc-linux-gnu-gcc | 1504 | 6058 | 6122 |
-| RISC-V (rv64) | 64 | riscv64-linux-gnu-gcc | 756 | 3548 | 3582 |
-| x86-64 | 64 | Clang | 1043 | 5268 | 5310 |
-| x86-64 | 64 | GCC | 1063 | 5136 | 5185 |
-| AArch64 (ARM64) | 64 | aarch64-linux-gnu-gcc | 1324 | 5566 | 5622 |
+| PowerPC | 32 | powerpc-linux-gnu-gcc | 1504 | 6066 | 6130 |
+| RISC-V (rv64) | 64 | riscv64-linux-gnu-gcc | 756 | 3554 | 3588 |
+| x86-64 | 64 | Clang | 1043 | 5269 | 5311 |
+| x86-64 | 64 | GCC | 1063 | 5138 | 5187 |
+| AArch64 (ARM64) | 64 | aarch64-linux-gnu-gcc | 1324 | 5574 | 5630 |
| MIPS64 | 64 | mips64el-linux-gnuabi64-gcc | 1360 | 5864 | 5928 |
From e38b326a9721d37e686c149204630f811df27c03 Mon Sep 17 00:00:00 2001
From: deftio
Date: Mon, 27 Apr 2026 00:33:55 -0700
Subject: [PATCH 3/3] updated library.properties
---
library.properties | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/library.properties b/library.properties
index 1a24ace..dbdfb23 100644
--- a/library.properties
+++ b/library.properties
@@ -1,5 +1,5 @@
name=xelp
-version=0.3.2
+version=0.3.2
author=M. A. Chatterjee
maintainer=M. A. Chatterjee
sentence=Tiny CLI and script interpreter for embedded systems. 2KB, zero malloc, multi-instance.