| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511 |
- /**
- * This header was automatically built using
- * embedded_cli.h and embedded_cli.c
- * @date 2022-11-03
- *
- * MIT License
- *
- * Copyright (c) 2021 Sviatoslav Kokurin (funbiscuit)
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
- #ifndef EMBEDDED_CLI_H
- #define EMBEDDED_CLI_H
- #ifdef __cplusplus
- extern "C" {
- #else
- #include <stdbool.h>
- #endif
- // cstdint is available only since C++11, so use C header
- #include <stdint.h>
- // used for proper alignment of cli buffer
- #if UINTPTR_MAX == 0xFFFF
- #define CLI_UINT uint16_t
- #elif UINTPTR_MAX == 0xFFFFFFFF
- #define CLI_UINT uint32_t
- #elif UINTPTR_MAX == 0xFFFFFFFFFFFFFFFFu
- #define CLI_UINT uint64_t
- #else
- #error unsupported pointer size
- #endif
- #define CLI_UINT_SIZE (sizeof(CLI_UINT))
- // convert size in bytes to size in terms of CLI_UINTs (rounded up
- // if bytes is not divisible by size of single CLI_UINT)
- #define BYTES_TO_CLI_UINTS(bytes) \
- (((bytes) + CLI_UINT_SIZE - 1)/CLI_UINT_SIZE)
- typedef struct CliCommand CliCommand;
- typedef struct CliCommandBinding CliCommandBinding;
- typedef struct EmbeddedCli EmbeddedCli;
- typedef struct EmbeddedCliConfig EmbeddedCliConfig;
- struct CliCommand {
- /**
- * Name of the command.
- * In command "set led 1 1" "set" is name
- */
- const char *name;
- /**
- * String of arguments of the command.
- * In command "set led 1 1" "led 1 1" is string of arguments
- * Is ended with double 0x00 char
- * Use tokenize functions to easily get individual tokens
- */
- char *args;
- };
- /**
- * Struct to describe binding of command to function and
- */
- struct CliCommandBinding {
- /**
- * Name of command to bind. Should not be NULL.
- */
- const char *name;
- /**
- * Help string that will be displayed when "help <cmd>" is executed.
- * Can have multiple lines separated with "\r\n"
- * Can be NULL if no help is provided.
- */
- const char *help;
- /**
- * Flag to perform tokenization before calling binding function.
- */
- bool tokenizeArgs;
- /**
- * Pointer to any specific app context that is required for this binding.
- * It will be provided in binding callback.
- */
- void *context;
- /**
- * Binding function for when command is received.
- * If null, default callback (onCommand) will be called.
- * @param cli - pointer to cli that is calling this binding
- * @param args - string of args (if tokenizeArgs is false) or tokens otherwise
- * @param context
- */
- void (*binding)(EmbeddedCli *cli, char *args, void *context);
- };
- struct EmbeddedCli {
- /**
- * Should write char to connection
- * @param cli - pointer to cli that executed this function
- * @param c - actual character to write
- */
- void (*writeChar)(EmbeddedCli *cli, char c);
- /**
- * Called when command is received and command not found in list of
- * command bindings (or binding function is null).
- * @param cli - pointer to cli that executed this function
- * @param command - pointer to received command
- */
- void (*onCommand)(EmbeddedCli *cli, CliCommand *command);
- /**
- * Can be used by for any application context
- */
- void *appContext;
- /**
- * Pointer to actual implementation, do not use.
- */
- void *_impl;
- };
- /**
- * Configuration to create CLI
- */
- struct EmbeddedCliConfig {
- /**
- * Size of buffer that is used to store characters until they're processed
- */
- uint16_t rxBufferSize;
- /**
- * Size of buffer that is used to store current input that is not yet
- * sended as command (return not pressed yet)
- */
- uint16_t cmdBufferSize;
- /**
- * Size of buffer that is used to store previously entered commands
- * Only unique commands are stored in buffer. If buffer is smaller than
- * entered command (including arguments), command is discarded from history
- */
- uint16_t historyBufferSize;
- /**
- * Maximum amount of bindings that can be added via addBinding function.
- * Cli increases takes extra bindings for internal commands:
- * - help
- */
- uint16_t maxBindingCount;
- /**
- * Buffer to use for cli and all internal structures. If NULL, memory will
- * be allocated dynamically. Otherwise this buffer is used and no
- * allocations are made
- */
- CLI_UINT *cliBuffer;
- /**
- * Size of buffer for cli and internal structures (in bytes).
- */
- uint16_t cliBufferSize;
- /**
- * Whether autocompletion should be enabled.
- * If false, autocompletion is disabled but you still can use 'tab' to
- * complete current command manually.
- */
- bool enableAutoComplete;
- };
- /**
- * Returns pointer to default configuration for cli creation. It is safe to
- * modify it and then send to embeddedCliNew().
- * Returned structure is always the same so do not free and try to use it
- * immediately.
- * Default values:
- * <ul>
- * <li>rxBufferSize = 64</li>
- * <li>cmdBufferSize = 64</li>
- * <li>historyBufferSize = 128</li>
- * <li>cliBuffer = NULL (use dynamic allocation)</li>
- * <li>cliBufferSize = 0</li>
- * <li>maxBindingCount = 8</li>
- * <li>enableAutoComplete = true</li>
- * </ul>
- * @return configuration for cli creation
- */
- EmbeddedCliConfig *embeddedCliDefaultConfig(void);
- /**
- * Returns how many space in config buffer is required for cli creation
- * If you provide buffer with less space, embeddedCliNew will return NULL
- * This amount will always be divisible by CLI_UINT_SIZE so allocated buffer
- * and internal structures can be properly aligned
- * @param config
- * @return
- */
- uint16_t embeddedCliRequiredSize(EmbeddedCliConfig *config);
- /**
- * Create new CLI.
- * Memory is allocated dynamically if cliBuffer in config is NULL.
- * After CLI is created, override function pointers to start using it
- * @param config - config for cli creation
- * @return pointer to created CLI
- */
- EmbeddedCli *embeddedCliNew(EmbeddedCliConfig *config);
- /**
- * Same as calling embeddedCliNew with default config.
- * @return
- */
- EmbeddedCli *embeddedCliNewDefault(void);
- /**
- * Receive character and put it to internal buffer
- * Actual processing is done inside embeddedCliProcess
- * You can call this function from something like interrupt service routine,
- * just make sure that you call it only from single place. Otherwise input
- * might get corrupted
- * @param cli
- * @param c - received char
- */
- void embeddedCliReceiveChar(EmbeddedCli *cli, char c);
- /**
- * Process rx/tx buffers. Command callbacks are called from here
- * @param cli
- */
- void embeddedCliProcess(EmbeddedCli *cli);
- /**
- * Add specified binding to list of bindings. If list is already full, binding
- * is not added and false is returned
- * @param cli
- * @param binding
- * @return true if binding was added, false otherwise
- */
- bool embeddedCliAddBinding(EmbeddedCli *cli, CliCommandBinding binding);
- /**
- * Print specified string and account for currently entered but not submitted
- * command.
- * Current command is deleted, provided string is printed (with new line) after
- * that current command is printed again, so user can continue typing it.
- * @param cli
- * @param string
- */
- void embeddedCliPrint(EmbeddedCli *cli, const char *string);
- /**
- * Free allocated for cli memory
- * @param cli
- */
- void embeddedCliFree(EmbeddedCli *cli);
- /**
- * Perform tokenization of arguments string. Original string is modified and
- * should not be used directly (only inside other token functions).
- * Individual tokens are separated by single 0x00 char, double 0x00 is put at
- * the end of token list. After calling this function, you can use other
- * token functions to get individual tokens and token count.
- *
- * Important: Call this function only once. Otherwise information will be lost if
- * more than one token existed
- * @param args - string to tokenize (must have extra writable char after 0x00)
- * @return
- */
- void embeddedCliTokenizeArgs(char *args);
- /**
- * Return specific token from tokenized string
- * @param tokenizedStr
- * @param pos (counted from 1)
- * @return token
- */
- const char *embeddedCliGetToken(const char *tokenizedStr, uint16_t pos);
- /**
- * Same as embeddedCliGetToken but works on non-const buffer
- * @param tokenizedStr
- * @param pos (counted from 1)
- * @return token
- */
- char *embeddedCliGetTokenVariable(char *tokenizedStr, uint16_t pos);
- /**
- * Find token in provided tokens string and return its position (counted from 1)
- * If no such token is found - 0 is returned.
- * @param tokenizedStr
- * @param token - token to find
- * @return position (increased by 1) or zero if no such token found
- */
- uint16_t embeddedCliFindToken(const char *tokenizedStr, const char *token);
- /**
- * Return number of tokens in tokenized string
- * @param tokenizedStr
- * @return number of tokens
- */
- uint16_t embeddedCliGetTokenCount(const char *tokenizedStr);
- #ifdef __cplusplus
- }
- #endif
- #endif //EMBEDDED_CLI_H
- #ifdef EMBEDDED_CLI_IMPL
- #ifndef EMBEDDED_CLI_IMPL_GUARD
- #define EMBEDDED_CLI_IMPL_GUARD
- #ifdef __cplusplus
- extern "C" {
- #endif
- #include <stdlib.h>
- #include <string.h>
- #define CLI_TOKEN_NPOS 0xffff
- #define UNUSED(x) (void)x
- #define PREPARE_IMPL(t) \
- EmbeddedCliImpl* impl = (EmbeddedCliImpl*)t->_impl
- #define IS_FLAG_SET(flags, flag) (((flags) & (flag)) != 0)
- #define SET_FLAG(flags, flag) ((flags) |= (flag))
- #define UNSET_U8FLAG(flags, flag) ((flags) &= (uint8_t) ~(flag))
- /**
- * Marks binding as candidate for autocompletion
- * This flag is updated each time getAutocompletedCommand is called
- */
- #define BINDING_FLAG_AUTOCOMPLETE 1u
- /**
- * Indicates that rx buffer overflow happened. In such case last command
- * that wasn't finished (no \r or \n were received) will be discarded
- */
- #define CLI_FLAG_OVERFLOW 0x01u
- /**
- * Indicates that initialization is completed. Initialization is completed in
- * first call to process and needed, for example, to print invitation message.
- */
- #define CLI_FLAG_INIT_COMPLETE 0x02u
- /**
- * Indicates that CLI structure and internal structures were allocated with
- * malloc and should bre freed
- */
- #define CLI_FLAG_ALLOCATED 0x04u
- /**
- * Indicates that CLI structure and internal structures were allocated with
- * malloc and should bre freed
- */
- #define CLI_FLAG_ESCAPE_MODE 0x08u
- /**
- * Indicates that CLI in mode when it will print directly to output without
- * clear of current command and printing it back
- */
- #define CLI_FLAG_DIRECT_PRINT 0x10u
- /**
- * Indicates that live autocompletion is enabled
- */
- #define CLI_FLAG_AUTOCOMPLETE_ENABLED 0x20u
- typedef struct EmbeddedCliImpl EmbeddedCliImpl;
- typedef struct AutocompletedCommand AutocompletedCommand;
- typedef struct FifoBuf FifoBuf;
- typedef struct CliHistory CliHistory;
- struct FifoBuf {
- char *buf;
- /**
- * Position of first element in buffer. From this position elements are taken
- */
- uint16_t front;
- /**
- * Position after last element. At this position new elements are inserted
- */
- uint16_t back;
- /**
- * Size of buffer
- */
- uint16_t size;
- };
- struct CliHistory {
- /**
- * Items in buffer are separated by null-chars
- */
- char *buf;
- /**
- * Total size of buffer
- */
- uint16_t bufferSize;
- /**
- * Index of currently selected element. This allows to navigate history
- * After command is sent, current element is reset to 0 (no element)
- */
- uint16_t current;
- /**
- * Number of items in buffer
- * Items are counted from top to bottom (and are 1 based).
- * So the most recent item is 1 and the oldest is itemCount.
- */
- uint16_t itemsCount;
- };
- struct EmbeddedCliImpl {
- /**
- * Invitation string. Is printed at the beginning of each line with user
- * input
- */
- const char *invitation;
- CliHistory history;
- /**
- * Buffer for storing received chars.
- * Chars are stored in FIFO mode.
- */
- FifoBuf rxBuffer;
- /**
- * Buffer for current command
- */
- char *cmdBuffer;
- /**
- * Size of current command
- */
- uint16_t cmdSize;
- /**
- * Total size of command buffer
- */
- uint16_t cmdMaxSize;
- CliCommandBinding *bindings;
- /**
- * Flags for each binding. Sizes are the same as for bindings array
- */
- uint8_t *bindingsFlags;
- uint16_t bindingsCount;
- uint16_t maxBindingsCount;
- /**
- * Total length of input line. This doesn't include invitation but
- * includes current command and its live autocompletion
- */
- uint16_t inputLineLength;
- /**
- * Stores last character that was processed.
- */
- char lastChar;
- /**
- * Flags are defined as CLI_FLAG_*
- */
- uint8_t flags;
- };
- struct AutocompletedCommand {
- /**
- * Name of autocompleted command (or first candidate for autocompletion if
- * there are multiple candidates).
- * NULL if autocomplete not possible.
- */
- const char *firstCandidate;
- /**
- * Number of characters that can be completed safely. For example, if there
- * are two possible commands "get-led" and "get-adc", then for prefix "g"
- * autocompletedLen will be 4. If there are only one candidate, this number
- * is always equal to length of the command.
- */
- uint16_t autocompletedLen;
- /**
- * Total number of candidates for autocompletion
- */
- uint16_t candidateCount;
- };
- static EmbeddedCliConfig defaultConfig;
- /**
- * Number of commands that cli adds. Commands:
- * - help
- */
- static const uint16_t cliInternalBindingCount = 1;
- static const char *lineBreak = "\r\n";
- /**
- * Navigate through command history back and forth. If navigateUp is true,
- * navigate to older commands, otherwise navigate to newer.
- * When history end is reached, nothing happens.
- * @param cli
- * @param navigateUp
- */
- static void navigateHistory(EmbeddedCli *cli, bool navigateUp);
- /**
- * Process escaped character. After receiving ESC+[ sequence, all chars up to
- * ending character are sent to this function
- * @param cli
- * @param c
- */
- static void onEscapedInput(EmbeddedCli *cli, char c);
- /**
- * Process input character. Character is valid displayable char and should be
- * added to current command string and displayed to client.
- * @param cli
- * @param c
- */
- static void onCharInput(EmbeddedCli *cli, char c);
- /**
- * Process control character (like \r or \n) possibly altering state of current
- * command or executing onCommand callback.
- * @param cli
- * @param c
- */
- static void onControlInput(EmbeddedCli *cli, char c);
- /**
- * Parse command in buffer and execute callback
- * @param cli
- */
- static void parseCommand(EmbeddedCli *cli);
- /**
- * Setup bindings for internal commands, like help
- * @param cli
- */
- static void initInternalBindings(EmbeddedCli *cli);
- /**
- * Show help for given tokens (or default help if no tokens)
- * @param cli
- * @param tokens
- * @param context - not used
- */
- static void onHelp(EmbeddedCli *cli, char *tokens, void *context);
- /**
- * Show error about unknown command
- * @param cli
- * @param name
- */
- static void onUnknownCommand(EmbeddedCli *cli, const char *name);
- /**
- * Return autocompleted command for given prefix.
- * Prefix is compared to all known command bindings and autocompleted result
- * is returned
- * @param cli
- * @param prefix
- * @return
- */
- static AutocompletedCommand getAutocompletedCommand(EmbeddedCli *cli, const char *prefix);
- /**
- * Prints autocompletion result while keeping current command unchanged
- * Prints only if autocompletion is present and only one candidate exists.
- * @param cli
- */
- static void printLiveAutocompletion(EmbeddedCli *cli);
- /**
- * Handles autocomplete request. If autocomplete possible - fills current
- * command with autocompleted command. When multiple commands satisfy entered
- * prefix, they are printed to output.
- * @param cli
- */
- static void onAutocompleteRequest(EmbeddedCli *cli);
- /**
- * Removes all input from current line (replaces it with whitespaces)
- * And places cursor at the beginning of the line
- * @param cli
- */
- static void clearCurrentLine(EmbeddedCli *cli);
- /**
- * Write given string to cli output
- * @param cli
- * @param str
- */
- static void writeToOutput(EmbeddedCli *cli, const char *str);
- /**
- * Returns true if provided char is a supported control char:
- * \r, \n, \b or 0x7F (treated as \b)
- * @param c
- * @return
- */
- static bool isControlChar(char c);
- /**
- * Returns true if provided char is a valid displayable character:
- * a-z, A-Z, 0-9, whitespace, punctuation, etc.
- * Currently only ASCII is supported
- * @param c
- * @return
- */
- static bool isDisplayableChar(char c);
- /**
- * How many elements are currently available in buffer
- * @param buffer
- * @return number of elements
- */
- static uint16_t fifoBufAvailable(FifoBuf *buffer);
- /**
- * Return first character from buffer and remove it from buffer
- * Buffer must be non-empty, otherwise 0 is returned
- * @param buffer
- * @return
- */
- static char fifoBufPop(FifoBuf *buffer);
- /**
- * Push character into fifo buffer. If there is no space left, character is
- * discarded and false is returned
- * @param buffer
- * @param a - character to add
- * @return true if char was added to buffer, false otherwise
- */
- static bool fifoBufPush(FifoBuf *buffer, char a);
- /**
- * Copy provided string to the history buffer.
- * If it is already inside history, it will be removed from it and added again.
- * So after addition, it will always be on top
- * If available size is not enough (and total size is enough) old elements will
- * be removed from history so this item can be put to it
- * @param history
- * @param str
- * @return true if string was put in history
- */
- static bool historyPut(CliHistory *history, const char *str);
- /**
- * Get item from history. Items are counted from 1 so if item is 0 or greater
- * than itemCount, NULL is returned
- * @param history
- * @param item
- * @return true if string was put in history
- */
- static const char *historyGet(CliHistory *history, uint16_t item);
- /**
- * Remove specific item from history
- * @param history
- * @param str - string to remove
- * @return
- */
- static void historyRemove(CliHistory *history, const char *str);
- /**
- * Return position (index of first char) of specified token
- * @param tokenizedStr - tokenized string (separated by \0 with
- * \0\0 at the end)
- * @param pos - token position (counted from 1)
- * @return index of first char of specified token
- */
- static uint16_t getTokenPosition(const char *tokenizedStr, uint16_t pos);
- EmbeddedCliConfig *embeddedCliDefaultConfig(void) {
- defaultConfig.rxBufferSize = 64;
- defaultConfig.cmdBufferSize = 64;
- defaultConfig.historyBufferSize = 128;
- defaultConfig.cliBuffer = NULL;
- defaultConfig.cliBufferSize = 0;
- defaultConfig.maxBindingCount = 8;
- defaultConfig.enableAutoComplete = true;
- return &defaultConfig;
- }
- uint16_t embeddedCliRequiredSize(EmbeddedCliConfig *config) {
- uint16_t bindingCount = (uint16_t) (config->maxBindingCount + cliInternalBindingCount);
- return (uint16_t) (CLI_UINT_SIZE * (
- BYTES_TO_CLI_UINTS(sizeof(EmbeddedCli)) +
- BYTES_TO_CLI_UINTS(sizeof(EmbeddedCliImpl)) +
- BYTES_TO_CLI_UINTS(config->rxBufferSize * sizeof(char)) +
- BYTES_TO_CLI_UINTS(config->cmdBufferSize * sizeof(char)) +
- BYTES_TO_CLI_UINTS(config->historyBufferSize * sizeof(char)) +
- BYTES_TO_CLI_UINTS(bindingCount * sizeof(CliCommandBinding)) +
- BYTES_TO_CLI_UINTS(bindingCount * sizeof(uint8_t))));
- }
- EmbeddedCli *embeddedCliNew(EmbeddedCliConfig *config) {
- EmbeddedCli *cli = NULL;
- uint16_t bindingCount = (uint16_t) (config->maxBindingCount + cliInternalBindingCount);
- size_t totalSize = embeddedCliRequiredSize(config);
- bool allocated = false;
- if (config->cliBuffer == NULL) {
- config->cliBuffer = (CLI_UINT *) malloc(totalSize); // malloc guarantees alignment.
- if (config->cliBuffer == NULL)
- return NULL;
- allocated = true;
- } else if (config->cliBufferSize < totalSize) {
- return NULL;
- }
- CLI_UINT *buf = config->cliBuffer;
- memset(buf, 0, totalSize);
- cli = (EmbeddedCli *) buf;
- buf += BYTES_TO_CLI_UINTS(sizeof(EmbeddedCli));
- cli->_impl = (EmbeddedCliImpl *) buf;
- buf += BYTES_TO_CLI_UINTS(sizeof(EmbeddedCliImpl));
- PREPARE_IMPL(cli);
- impl->rxBuffer.buf = (char *) buf;
- buf += BYTES_TO_CLI_UINTS(config->rxBufferSize * sizeof(char));
- impl->cmdBuffer = (char *) buf;
- buf += BYTES_TO_CLI_UINTS(config->cmdBufferSize * sizeof(char));
- impl->bindings = (CliCommandBinding *) buf;
- buf += BYTES_TO_CLI_UINTS(bindingCount * sizeof(CliCommandBinding));
- impl->bindingsFlags = (uint8_t *) buf;
- buf += BYTES_TO_CLI_UINTS(bindingCount);
- impl->history.buf = (char *) buf;
- impl->history.bufferSize = config->historyBufferSize;
- if (allocated)
- SET_FLAG(impl->flags, CLI_FLAG_ALLOCATED);
- if (config->enableAutoComplete)
- SET_FLAG(impl->flags, CLI_FLAG_AUTOCOMPLETE_ENABLED);
- impl->rxBuffer.size = config->rxBufferSize;
- impl->rxBuffer.front = 0;
- impl->rxBuffer.back = 0;
- impl->cmdMaxSize = config->cmdBufferSize;
- impl->bindingsCount = 0;
- impl->maxBindingsCount = (uint16_t) (config->maxBindingCount + cliInternalBindingCount);
- impl->lastChar = '\0';
- impl->invitation = "> ";
- initInternalBindings(cli);
- return cli;
- }
- EmbeddedCli *embeddedCliNewDefault(void) {
- return embeddedCliNew(embeddedCliDefaultConfig());
- }
- void embeddedCliReceiveChar(EmbeddedCli *cli, char c) {
- PREPARE_IMPL(cli);
- if (!fifoBufPush(&impl->rxBuffer, c)) {
- SET_FLAG(impl->flags, CLI_FLAG_OVERFLOW);
- }
- }
- void embeddedCliProcess(EmbeddedCli *cli) {
- if (cli->writeChar == NULL)
- return;
- PREPARE_IMPL(cli);
- if (!IS_FLAG_SET(impl->flags, CLI_FLAG_INIT_COMPLETE)) {
- SET_FLAG(impl->flags, CLI_FLAG_INIT_COMPLETE);
- writeToOutput(cli, impl->invitation);
- }
- while (fifoBufAvailable(&impl->rxBuffer)) {
- char c = fifoBufPop(&impl->rxBuffer);
- if (IS_FLAG_SET(impl->flags, CLI_FLAG_ESCAPE_MODE)) {
- onEscapedInput(cli, c);
- } else if (impl->lastChar == 0x1B && c == '[') {
- //enter escape mode
- SET_FLAG(impl->flags, CLI_FLAG_ESCAPE_MODE);
- } else if (isControlChar(c)) {
- onControlInput(cli, c);
- } else if (isDisplayableChar(c)) {
- onCharInput(cli, c);
- }
- printLiveAutocompletion(cli);
- impl->lastChar = c;
- }
- // discard unfinished command if overflow happened
- if (IS_FLAG_SET(impl->flags, CLI_FLAG_OVERFLOW)) {
- impl->cmdSize = 0;
- impl->cmdBuffer[impl->cmdSize] = '\0';
- UNSET_U8FLAG(impl->flags, CLI_FLAG_OVERFLOW);
- }
- }
- bool embeddedCliAddBinding(EmbeddedCli *cli, CliCommandBinding binding) {
- PREPARE_IMPL(cli);
- if (impl->bindingsCount == impl->maxBindingsCount)
- return false;
- impl->bindings[impl->bindingsCount] = binding;
- ++impl->bindingsCount;
- return true;
- }
- void embeddedCliPrint(EmbeddedCli *cli, const char *string) {
- if (cli->writeChar == NULL)
- return;
- PREPARE_IMPL(cli);
- // remove chars for autocompletion and live command
- if (!IS_FLAG_SET(impl->flags, CLI_FLAG_DIRECT_PRINT))
- clearCurrentLine(cli);
- // print provided string
- writeToOutput(cli, string);
- writeToOutput(cli, lineBreak);
- // print current command back to screen
- if (!IS_FLAG_SET(impl->flags, CLI_FLAG_DIRECT_PRINT)) {
- writeToOutput(cli, impl->invitation);
- writeToOutput(cli, impl->cmdBuffer);
- impl->inputLineLength = impl->cmdSize;
- printLiveAutocompletion(cli);
- }
- }
- void embeddedCliFree(EmbeddedCli *cli) {
- PREPARE_IMPL(cli);
- if (IS_FLAG_SET(impl->flags, CLI_FLAG_ALLOCATED)) {
- // allocation is done in single call to malloc, so need only single free
- free(cli);
- }
- }
- void embeddedCliTokenizeArgs(char *args) {
- if (args == NULL)
- return;
- // for now only space, but can add more later
- const char *separators = " ";
- // indicates that arg is quoted so separators are copied as is
- bool quotesEnabled = false;
- // indicates that previous char was a slash, so next char is copied as is
- bool escapeActivated = false;
- int insertPos = 0;
- int i = 0;
- char currentChar;
- while ((currentChar = args[i]) != '\0') {
- ++i;
- if (escapeActivated) {
- escapeActivated = false;
- } else if (currentChar == '\\') {
- escapeActivated = true;
- continue;
- } else if (currentChar == '"') {
- quotesEnabled = !quotesEnabled;
- currentChar = '\0';
- } else if (!quotesEnabled && strchr(separators, currentChar) != NULL) {
- currentChar = '\0';
- }
- // null chars are only copied once and not copied to the beginning
- if (currentChar != '\0' || (insertPos > 0 && args[insertPos - 1] != '\0')) {
- args[insertPos] = currentChar;
- ++insertPos;
- }
- }
- // make args double null-terminated source buffer must be big enough to contain extra spaces
- args[insertPos] = '\0';
- args[insertPos + 1] = '\0';
- }
- const char *embeddedCliGetToken(const char *tokenizedStr, uint16_t pos) {
- uint16_t i = getTokenPosition(tokenizedStr, pos);
- if (i != CLI_TOKEN_NPOS)
- return &tokenizedStr[i];
- else
- return NULL;
- }
- char *embeddedCliGetTokenVariable(char *tokenizedStr, uint16_t pos) {
- uint16_t i = getTokenPosition(tokenizedStr, pos);
- if (i != CLI_TOKEN_NPOS)
- return &tokenizedStr[i];
- else
- return NULL;
- }
- uint16_t embeddedCliFindToken(const char *tokenizedStr, const char *token) {
- if (tokenizedStr == NULL || token == NULL)
- return 0;
- uint16_t size = embeddedCliGetTokenCount(tokenizedStr);
- for (uint16_t i = 1; i <= size; ++i) {
- if (strcmp(embeddedCliGetToken(tokenizedStr, i), token) == 0)
- return i;
- }
- return 0;
- }
- uint16_t embeddedCliGetTokenCount(const char *tokenizedStr) {
- if (tokenizedStr == NULL || tokenizedStr[0] == '\0')
- return 0;
- int i = 0;
- uint16_t tokenCount = 1;
- while (true) {
- if (tokenizedStr[i] == '\0') {
- if (tokenizedStr[i + 1] == '\0')
- break;
- ++tokenCount;
- }
- ++i;
- }
- return tokenCount;
- }
- static void navigateHistory(EmbeddedCli *cli, bool navigateUp) {
- PREPARE_IMPL(cli);
- if (impl->history.itemsCount == 0 ||
- (navigateUp && impl->history.current == impl->history.itemsCount) ||
- (!navigateUp && impl->history.current == 0))
- return;
- clearCurrentLine(cli);
- writeToOutput(cli, impl->invitation);
- if (navigateUp)
- ++impl->history.current;
- else
- --impl->history.current;
- const char *item = historyGet(&impl->history, impl->history.current);
- // simple way to handle empty command the same way as others
- if (item == NULL)
- item = "";
- uint16_t len = (uint16_t) strlen(item);
- memcpy(impl->cmdBuffer, item, len);
- impl->cmdBuffer[len] = '\0';
- impl->cmdSize = len;
- writeToOutput(cli, impl->cmdBuffer);
- impl->inputLineLength = impl->cmdSize;
- printLiveAutocompletion(cli);
- }
- static void onEscapedInput(EmbeddedCli *cli, char c) {
- PREPARE_IMPL(cli);
- if (c >= 64 && c <= 126) {
- // handle escape sequence
- UNSET_U8FLAG(impl->flags, CLI_FLAG_ESCAPE_MODE);
- if (c == 'A' || c == 'B') {
- // treat \e[..A as cursor up and \e[..B as cursor down
- // there might be extra chars between [ and A/B, just ignore them
- navigateHistory(cli, c == 'A');
- }
- }
- }
- static void onCharInput(EmbeddedCli *cli, char c) {
- PREPARE_IMPL(cli);
- // have to reserve two extra chars for command ending (used in tokenization)
- if (impl->cmdSize + 2 >= impl->cmdMaxSize)
- return;
- impl->cmdBuffer[impl->cmdSize] = c;
- ++impl->cmdSize;
- impl->cmdBuffer[impl->cmdSize] = '\0';
- cli->writeChar(cli, c);
- }
- static void onControlInput(EmbeddedCli *cli, char c) {
- PREPARE_IMPL(cli);
- // process \r\n and \n\r as single \r\n command
- if ((impl->lastChar == '\r' && c == '\n') ||
- (impl->lastChar == '\n' && c == '\r'))
- return;
- if (c == '\r' || c == '\n') {
- // try to autocomplete command and then process it
- onAutocompleteRequest(cli);
- writeToOutput(cli, lineBreak);
- if (impl->cmdSize > 0)
- parseCommand(cli);
- impl->cmdSize = 0;
- impl->cmdBuffer[impl->cmdSize] = '\0';
- impl->inputLineLength = 0;
- impl->history.current = 0;
- writeToOutput(cli, impl->invitation);
- } else if ((c == '\b' || c == 0x7F) && impl->cmdSize > 0) {
- // remove char from screen
- cli->writeChar(cli, '\b');
- cli->writeChar(cli, ' ');
- cli->writeChar(cli, '\b');
- // and from buffer
- --impl->cmdSize;
- impl->cmdBuffer[impl->cmdSize] = '\0';
- } else if (c == '\t') {
- onAutocompleteRequest(cli);
- }
- }
- static void parseCommand(EmbeddedCli *cli) {
- PREPARE_IMPL(cli);
- bool isEmpty = true;
- for (int i = 0; i < impl->cmdSize; ++i) {
- if (impl->cmdBuffer[i] != ' ') {
- isEmpty = false;
- break;
- }
- }
- // do not process empty commands
- if (isEmpty)
- return;
- // push command to history before buffer is modified
- historyPut(&impl->history, impl->cmdBuffer);
- char *cmdName = NULL;
- char *cmdArgs = NULL;
- bool nameFinished = false;
- // find command name and command args inside command buffer
- for (int i = 0; i < impl->cmdSize; ++i) {
- char c = impl->cmdBuffer[i];
- if (c == ' ') {
- // all spaces between name and args are filled with zeros
- // so name is a correct null-terminated string
- if (cmdArgs == NULL)
- impl->cmdBuffer[i] = '\0';
- if (cmdName != NULL)
- nameFinished = true;
- } else if (cmdName == NULL) {
- cmdName = &impl->cmdBuffer[i];
- } else if (cmdArgs == NULL && nameFinished) {
- cmdArgs = &impl->cmdBuffer[i];
- }
- }
- // we keep two last bytes in cmd buffer reserved so cmdSize is always by 2
- // less than cmdMaxSize
- impl->cmdBuffer[impl->cmdSize + 1] = '\0';
- if (cmdName == NULL)
- return;
- // try to find command in bindings
- for (int i = 0; i < impl->bindingsCount; ++i) {
- if (strcmp(cmdName, impl->bindings[i].name) == 0) {
- if (impl->bindings[i].binding == NULL)
- break;
- if (impl->bindings[i].tokenizeArgs)
- embeddedCliTokenizeArgs(cmdArgs);
- // currently, output is blank line, so we can just print directly
- SET_FLAG(impl->flags, CLI_FLAG_DIRECT_PRINT);
- impl->bindings[i].binding(cli, cmdArgs, impl->bindings[i].context);
- UNSET_U8FLAG(impl->flags, CLI_FLAG_DIRECT_PRINT);
- return;
- }
- }
- // command not found in bindings or binding was null
- // try to call default callback
- if (cli->onCommand != NULL) {
- CliCommand command;
- command.name = cmdName;
- command.args = cmdArgs;
- // currently, output is blank line, so we can just print directly
- SET_FLAG(impl->flags, CLI_FLAG_DIRECT_PRINT);
- cli->onCommand(cli, &command);
- UNSET_U8FLAG(impl->flags, CLI_FLAG_DIRECT_PRINT);
- } else {
- onUnknownCommand(cli, cmdName);
- }
- }
- static void initInternalBindings(EmbeddedCli *cli) {
- CliCommandBinding b = {
- "help",
- "Print list of commands",
- true,
- NULL,
- onHelp
- };
- embeddedCliAddBinding(cli, b);
- }
- static void onHelp(EmbeddedCli *cli, char *tokens, void *context) {
- UNUSED(context);
- PREPARE_IMPL(cli);
- if (impl->bindingsCount == 0) {
- writeToOutput(cli, "Help is not available");
- writeToOutput(cli, lineBreak);
- return;
- }
- uint16_t tokenCount = embeddedCliGetTokenCount(tokens);
- if (tokenCount == 0) {
- for (int i = 0; i < impl->bindingsCount; ++i) {
- writeToOutput(cli, " * ");
- writeToOutput(cli, impl->bindings[i].name);
- writeToOutput(cli, lineBreak);
- if (impl->bindings[i].help != NULL) {
- cli->writeChar(cli, '\t');
- writeToOutput(cli, impl->bindings[i].help);
- writeToOutput(cli, lineBreak);
- }
- }
- } else if (tokenCount == 1) {
- // try find command
- const char *helpStr = NULL;
- const char *cmdName = embeddedCliGetToken(tokens, 1);
- bool found = false;
- for (int i = 0; i < impl->bindingsCount; ++i) {
- if (strcmp(impl->bindings[i].name, cmdName) == 0) {
- helpStr = impl->bindings[i].help;
- found = true;
- break;
- }
- }
- if (found && helpStr != NULL) {
- writeToOutput(cli, " * ");
- writeToOutput(cli, cmdName);
- writeToOutput(cli, lineBreak);
- cli->writeChar(cli, '\t');
- writeToOutput(cli, helpStr);
- writeToOutput(cli, lineBreak);
- } else if (found) {
- writeToOutput(cli, "Help is not available");
- writeToOutput(cli, lineBreak);
- } else {
- onUnknownCommand(cli, cmdName);
- }
- } else {
- writeToOutput(cli, "Command \"help\" receives one or zero arguments");
- writeToOutput(cli, lineBreak);
- }
- }
- static void onUnknownCommand(EmbeddedCli *cli, const char *name) {
- writeToOutput(cli, "Unknown command: \"");
- writeToOutput(cli, name);
- writeToOutput(cli, "\". Write \"help\" for a list of available commands");
- writeToOutput(cli, lineBreak);
- }
- static AutocompletedCommand getAutocompletedCommand(EmbeddedCli *cli, const char *prefix) {
- AutocompletedCommand cmd = {NULL, 0, 0};
- size_t prefixLen = strlen(prefix);
- PREPARE_IMPL(cli);
- if (impl->bindingsCount == 0 || prefixLen == 0)
- return cmd;
- for (int i = 0; i < impl->bindingsCount; ++i) {
- const char *name = impl->bindings[i].name;
- size_t len = strlen(name);
- // unset autocomplete flag
- UNSET_U8FLAG(impl->bindingsFlags[i], BINDING_FLAG_AUTOCOMPLETE);
- if (len < prefixLen)
- continue;
- // check if this command is candidate for autocomplete
- bool isCandidate = true;
- for (size_t j = 0; j < prefixLen; ++j) {
- if (prefix[j] != name[j]) {
- isCandidate = false;
- break;
- }
- }
- if (!isCandidate)
- continue;
- impl->bindingsFlags[i] |= BINDING_FLAG_AUTOCOMPLETE;
- if (cmd.candidateCount == 0 || len < cmd.autocompletedLen)
- cmd.autocompletedLen = (uint16_t) len;
- ++cmd.candidateCount;
- if (cmd.candidateCount == 1) {
- cmd.firstCandidate = name;
- continue;
- }
- for (size_t j = impl->cmdSize; j < cmd.autocompletedLen; ++j) {
- if (cmd.firstCandidate[j] != name[j]) {
- cmd.autocompletedLen = (uint16_t) j;
- break;
- }
- }
- }
- return cmd;
- }
- static void printLiveAutocompletion(EmbeddedCli *cli) {
- PREPARE_IMPL(cli);
- if (!IS_FLAG_SET(impl->flags, CLI_FLAG_AUTOCOMPLETE_ENABLED))
- return;
- AutocompletedCommand cmd = getAutocompletedCommand(cli, impl->cmdBuffer);
- if (cmd.candidateCount == 0) {
- cmd.autocompletedLen = impl->cmdSize;
- }
- // print live autocompletion (or nothing, if it doesn't exist)
- for (size_t i = impl->cmdSize; i < cmd.autocompletedLen; ++i) {
- cli->writeChar(cli, cmd.firstCandidate[i]);
- }
- // replace with spaces previous autocompletion
- for (size_t i = cmd.autocompletedLen; i < impl->inputLineLength; ++i) {
- cli->writeChar(cli, ' ');
- }
- impl->inputLineLength = cmd.autocompletedLen;
- cli->writeChar(cli, '\r');
- // print current command again so cursor is moved to initial place
- writeToOutput(cli, impl->invitation);
- writeToOutput(cli, impl->cmdBuffer);
- }
- static void onAutocompleteRequest(EmbeddedCli *cli) {
- PREPARE_IMPL(cli);
- AutocompletedCommand cmd = getAutocompletedCommand(cli, impl->cmdBuffer);
- if (cmd.candidateCount == 0)
- return;
- if (cmd.candidateCount == 1 || cmd.autocompletedLen > impl->cmdSize) {
- // can copy from index cmdSize, but prefix is the same, so copy everything
- memcpy(impl->cmdBuffer, cmd.firstCandidate, cmd.autocompletedLen);
- if (cmd.candidateCount == 1) {
- impl->cmdBuffer[cmd.autocompletedLen] = ' ';
- ++cmd.autocompletedLen;
- }
- impl->cmdBuffer[cmd.autocompletedLen] = '\0';
- writeToOutput(cli, &impl->cmdBuffer[impl->cmdSize]);
- impl->cmdSize = cmd.autocompletedLen;
- impl->inputLineLength = impl->cmdSize;
- return;
- }
- // with multiple candidates when we already completed to common prefix
- // we show all candidates and print input again
- // we need to completely clear current line since it begins with invitation
- clearCurrentLine(cli);
- for (int i = 0; i < impl->bindingsCount; ++i) {
- // autocomplete flag is set for all candidates by last call to
- // getAutocompletedCommand
- if (!(impl->bindingsFlags[i] & BINDING_FLAG_AUTOCOMPLETE))
- continue;
- const char *name = impl->bindings[i].name;
- writeToOutput(cli, name);
- writeToOutput(cli, lineBreak);
- }
- writeToOutput(cli, impl->invitation);
- writeToOutput(cli, impl->cmdBuffer);
- impl->inputLineLength = impl->cmdSize;
- }
- static void clearCurrentLine(EmbeddedCli *cli) {
- PREPARE_IMPL(cli);
- size_t len = impl->inputLineLength + strlen(impl->invitation);
- cli->writeChar(cli, '\r');
- for (size_t i = 0; i < len; ++i) {
- cli->writeChar(cli, ' ');
- }
- cli->writeChar(cli, '\r');
- impl->inputLineLength = 0;
- }
- static void writeToOutput(EmbeddedCli *cli, const char *str) {
- size_t len = strlen(str);
- for (size_t i = 0; i < len; ++i) {
- cli->writeChar(cli, str[i]);
- }
- }
- static bool isControlChar(char c) {
- return c == '\r' || c == '\n' || c == '\b' || c == '\t' || c == 0x7F;
- }
- static bool isDisplayableChar(char c) {
- return (c >= 32 && c <= 126);
- }
- static uint16_t fifoBufAvailable(FifoBuf *buffer) {
- if (buffer->back >= buffer->front)
- return (uint16_t) (buffer->back - buffer->front);
- else
- return (uint16_t) (buffer->size - buffer->front + buffer->back);
- }
- static char fifoBufPop(FifoBuf *buffer) {
- char a = '\0';
- if (buffer->front != buffer->back) {
- a = buffer->buf[buffer->front];
- buffer->front = (uint16_t) (buffer->front + 1) % buffer->size;
- }
- return a;
- }
- static bool fifoBufPush(FifoBuf *buffer, char a) {
- uint16_t newBack = (uint16_t) (buffer->back + 1) % buffer->size;
- if (newBack != buffer->front) {
- buffer->buf[buffer->back] = a;
- buffer->back = newBack;
- return true;
- }
- return false;
- }
- static bool historyPut(CliHistory *history, const char *str) {
- size_t len = strlen(str);
- // each item is ended with \0 so, need to have that much space at least
- if (history->bufferSize < len + 1)
- return false;
- // remove str from history (if it's present) so we don't get duplicates
- historyRemove(history, str);
- size_t usedSize;
- // remove old items if new one can't fit into buffer
- while (history->itemsCount > 0) {
- const char *item = historyGet(history, history->itemsCount);
- size_t itemLen = strlen(item);
- usedSize = ((size_t) (item - history->buf)) + itemLen + 1;
- size_t freeSpace = history->bufferSize - usedSize;
- if (freeSpace >= len + 1)
- break;
- // space not enough, remove last element
- --history->itemsCount;
- }
- if (history->itemsCount > 0) {
- // when history not empty, shift elements so new item is first
- memmove(&history->buf[len + 1], history->buf, usedSize);
- }
- memcpy(history->buf, str, len + 1);
- ++history->itemsCount;
- return true;
- }
- static const char *historyGet(CliHistory *history, uint16_t item) {
- if (item == 0 || item > history->itemsCount)
- return NULL;
- // items are stored in the same way (separated by \0 and counted from 1),
- // so can use this call
- return embeddedCliGetToken(history->buf, item);
- }
- static void historyRemove(CliHistory *history, const char *str) {
- if (str == NULL || history->itemsCount == 0)
- return;
- char *item = NULL;
- uint16_t itemPosition;
- for (itemPosition = 1; itemPosition <= history->itemsCount; ++itemPosition) {
- // items are stored in the same way (separated by \0 and counted from 1),
- // so can use this call
- item = embeddedCliGetTokenVariable(history->buf, itemPosition);
- if (strcmp(item, str) == 0) {
- break;
- }
- item = NULL;
- }
- if (item == NULL)
- return;
- --history->itemsCount;
- if (itemPosition == (history->itemsCount + 1)) {
- // if this is a last element, nothing is remaining to move
- return;
- }
- size_t len = strlen(item);
- size_t remaining = (size_t) (history->bufferSize - (item + len + 1 - history->buf));
- // move everything to the right of found item
- memmove(item, &item[len + 1], remaining);
- }
- static uint16_t getTokenPosition(const char *tokenizedStr, uint16_t pos) {
- if (tokenizedStr == NULL || pos == 0)
- return CLI_TOKEN_NPOS;
- uint16_t i = 0;
- uint16_t tokenCount = 1;
- while (true) {
- if (tokenCount == pos)
- break;
- if (tokenizedStr[i] == '\0') {
- ++tokenCount;
- if (tokenizedStr[i + 1] == '\0')
- break;
- }
- ++i;
- }
- if (tokenizedStr[i] != '\0')
- return i;
- else
- return CLI_TOKEN_NPOS;
- }
- #ifdef __cplusplus
- }
- #endif
- #endif // EMBEDDED_CLI_IMPL_GUARD
- #endif // EMBEDDED_CLI_IMPL
|