From c5295004db5970ce898a91f4147eb7bc1c40a7ed Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Sat, 25 May 2024 19:38:34 +0200 Subject: finish parser scanning part --- client/parse.cpp | 47 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 15 deletions(-) (limited to 'client/parse.cpp') diff --git a/client/parse.cpp b/client/parse.cpp index 223dc5d..16a7afc 100644 --- a/client/parse.cpp +++ b/client/parse.cpp @@ -26,6 +26,8 @@ static int parse_str(const char* str, char* data, size_t* size) { for (i = 1; i < len && str[i] != '\0'; i++) { char c = str[i]; + // TODO: handle escaped characters + if (c == closing) { if (scan) printf("string%s of length %d\n", escape ? " (w/ escape)" : "", i - 1); return i + 1; // +1 for closing quote @@ -72,46 +74,61 @@ static int parse_num(const char* str, char* data, size_t* size) { if (base == 10) *size += 1; else if (base == 16) { if (!bytestring) { - *size += (len - i + 1) / 2; - } else { - for (; colon != NULL && colon < str + len; colon = strchr(str, ':')) { + size_t prefixless = len - i; + switch (prefixless) { + case 2: // 8-bit (2 hex characters) + case 4: // 16-bit + case 8: // 32-bit + case 16: // 64-bit + break; + default: + return -i; + } + *size += prefixless / 2; + } else { // if bytestring + size_t c = 0, field = strcspn(str, ifs); // length until end of field + while (c < field) { // count bytes in bytestring + if (strspn(str + c, SET_HEX) != 2) + return -i -c; + c += 2; *size += 1; + + if (str[c] == ':') { + c += 1; + continue; + } + break; } } } } - if (scan) printf("number (base %d%s) of length %lu\n", base, bytestring ? " as bytestring" : "", len - i); - return len; + i += len; + return i; } int strtodata(const char* str, char** data, size_t* size) { const char* ifs = IFS; *size = 0; - size_t i; + size_t i = 0; size_t len = strlen(str); - for (i = 0; i < len;) { + while (i < len) { // skip whitespace - int run; - run = strspn(&str[i], ifs); - if (run > 0) printf("skipping whitespace for %d bytes...\n", run); - i += run; + i += strspn(&str[i], ifs); // end of string if (str[i] == '\0') break; + int run; if ((run = parse_str(str + i, NULL, size)) > 0) { i += run; continue; } if ((run = parse_num(str + i, NULL, size)) > 0) { i += run; continue; } // no format detected return -i + run; } - printf("end of string w/o parse errors\n"); - printf("buffer size is now %lu\n", *size); - exit(0); *data = (char*) malloc(*size); - return 0; + return *size; } -- cgit v1.2.3 From 6cb0ea50e1829c0c6c2e0179d3c6b7573c4a1b24 Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Sat, 25 May 2024 20:10:45 +0200 Subject: split up hex string parser and number parser + small refactoring --- client/parse.cpp | 136 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 78 insertions(+), 58 deletions(-) (limited to 'client/parse.cpp') diff --git a/client/parse.cpp b/client/parse.cpp index 16a7afc..6eca774 100644 --- a/client/parse.cpp +++ b/client/parse.cpp @@ -5,7 +5,7 @@ #include "parse.h" -static int parse_str(const char* str, char* data, size_t* size) { +static int parse_string(const char* str, char* data, size_t* offset) { char closing = str[0]; char escape = false; bool scan = data == NULL; @@ -28,18 +28,49 @@ static int parse_str(const char* str, char* data, size_t* size) { // TODO: handle escaped characters - if (c == closing) { - if (scan) printf("string%s of length %d\n", escape ? " (w/ escape)" : "", i - 1); + if (c == closing) return i + 1; // +1 for closing quote - } - if (scan) *size += 1; + *offset += 1; } return -i; } -static int parse_num(const char* str, char* data, size_t* size) { +static int parse_hexstr(const char* str, char* data, size_t* offset) { + const char* ifs = IFS; + size_t len = strcspn(str, ifs); + bool scan = data == NULL; + int i = 0; + + // check if token contains at least one colon + const char* colon = strchr(str, ':'); + if (colon == NULL) return -i; + if (colon >= str + len) return -i; + + // check if token only contains allowed characters [0-9a-fA-F:] + size_t len_ok = strspn(str + i, SET_HEX_STR) + i; + if (len != len_ok) return -len_ok; + + size_t c = 0; + while (c < len) { // count bytes in bytestring + if (strspn(str + c, SET_HEX) != 2) + return -i -c; + c += 2; + *offset += 1; + + if (str[c] == ':') { + c += 1; + continue; + } + break; + } + + i += len; + return i; +} + +static int parse_number(const char* str, char* data, size_t* offset) { const char* ifs = IFS; size_t len = strcspn(str, ifs); bool scan = data == NULL; @@ -47,11 +78,7 @@ static int parse_num(const char* str, char* data, size_t* size) { int base = 10; bool bytestring = false; - const char* colon = strchr(str, ':'); - if (colon != NULL && colon < str + len) { // byte string - base = 16; - bytestring = true; - } else if (len > 2 && strncmp(str, "0x", 2) == 0) { // hexadecimal prefix + if (len > 2 && strncmp(str, "0x", 2) == 0) { // hexadecimal prefix base = 16; i += 2; }/* else if (len > 1 && strncmp(str, "0", 1) == 0) { // octal prefix @@ -62,73 +89,66 @@ static int parse_num(const char* str, char* data, size_t* size) { const char* set; // if (base == 8) set = SET_OCT; if (base == 10) set = SET_DEC; - if (base == 16) { - if (bytestring) set = SET_HEX_STR; - else set = SET_HEX; - } + if (base == 16) set = SET_HEX; size_t len_ok = strspn(str + i, set) + i; if (len != len_ok) return -len_ok; - if (scan) { - if (base == 10) *size += 1; - else if (base == 16) { - if (!bytestring) { - size_t prefixless = len - i; - switch (prefixless) { - case 2: // 8-bit (2 hex characters) - case 4: // 16-bit - case 8: // 32-bit - case 16: // 64-bit - break; - default: - return -i; - } - *size += prefixless / 2; - } else { // if bytestring - size_t c = 0, field = strcspn(str, ifs); // length until end of field - while (c < field) { // count bytes in bytestring - if (strspn(str + c, SET_HEX) != 2) - return -i -c; - c += 2; - *size += 1; - - if (str[c] == ':') { - c += 1; - continue; - } - break; - } - } + if (base == 10) *offset += 1; + else if (base == 16) { + size_t prefixless = len - i; + switch (prefixless) { + case 2: // 8-bit (2 hex characters) + case 4: // 16-bit + case 8: // 32-bit + case 16: // 64-bit + break; + default: + return -i; } + *offset += prefixless / 2; } i += len; return i; } -int strtodata(const char* str, char** data, size_t* size) { +static int _strtodata_main(const char* str, char* _data, size_t* offset) { const char* ifs = IFS; - *size = 0; - size_t i = 0; size_t len = strlen(str); + size_t i = 0; + while (i < len) { - // skip whitespace - i += strspn(&str[i], ifs); - // end of string - if (str[i] == '\0') break; + i += strspn(&str[i], ifs); // skip whitespace + if (str[i] == '\0') break; // end of string int run; - if ((run = parse_str(str + i, NULL, size)) > 0) { i += run; continue; } - if ((run = parse_num(str + i, NULL, size)) > 0) { i += run; continue; } + char* data = _data == NULL ? NULL : _data + *offset; + if ((run = parse_string(str + i, data, offset)) > 0) goto format_ok; + if ((run = parse_hexstr(str + i, data, offset)) > 0) goto format_ok; + if ((run = parse_number(str + i, data, offset)) > 0) goto format_ok; + + return -i + run; // no format detected - // no format detected - return -i + run; +format_ok: + i += run; + continue; } - *data = (char*) malloc(*size); + return i; +} + +int strtodata(const char* str, char** data, size_t* size) { + *size = 0; - return *size; + // 1st pass: check data format + int ret = _strtodata_main(str, NULL, size); + if (ret <= 0) return ret; // on error + + // 2nd pass: convert string literals into binary data + *data = (char*) malloc(*size); + size_t written = 0; + return _strtodata_main(str, *data, &written); } -- cgit v1.2.3 From c3491119759462aeb3eed4b39aa34f6f98ab8a4f Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Sun, 26 May 2024 11:15:56 +0200 Subject: WIP convert parse.cpp data into raw data --- client/CMakeLists.txt | 1 + client/cmd.cpp | 6 ++++- client/parse.cpp | 74 ++++++++++++++++++++++++++++++++------------------- client/parse.h | 4 +-- client/xxd.c | 44 ++++++++++++++++++++++++++++++ client/xxd.h | 17 ++++++++++++ 6 files changed, 116 insertions(+), 30 deletions(-) create mode 100644 client/xxd.c create mode 100644 client/xxd.h (limited to 'client/parse.cpp') diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 35a55b6..5da93e4 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -14,6 +14,7 @@ add_executable(pbc sock.cpp cmd.cpp parse.cpp + xxd.c ) target_link_libraries(pbc diff --git a/client/cmd.cpp b/client/cmd.cpp index 1ec2cb8..78a6c3c 100644 --- a/client/cmd.cpp +++ b/client/cmd.cpp @@ -5,6 +5,7 @@ #include "cmd.h" #include "sock.h" #include "parse.h" +#include "xxd.h" char* consume_token(char* input, const char* ifs) { strtok(input, ifs); @@ -55,8 +56,11 @@ void cmd_send(char* addr_str) { return; } + printf("char data[%lu = 0x%02lx]:\n", data_size, data_size); + xxd(data, data_size); + // printf("(0x%02x) -> \"%.*s\"\n", addr, data_size, data); - i2c_send(addr, data, data_size); + // i2c_send(addr, data, data_size); free(data); } diff --git a/client/parse.cpp b/client/parse.cpp index 6eca774..5672ff2 100644 --- a/client/parse.cpp +++ b/client/parse.cpp @@ -1,14 +1,13 @@ -#include #include #include #include +#include #include "parse.h" -static int parse_string(const char* str, char* data, size_t* offset) { +static int parse_string(const char * str, char * data, size_t * offset) { char closing = str[0]; char escape = false; - bool scan = data == NULL; int i = 0; size_t len = strlen(str); @@ -23,7 +22,7 @@ static int parse_string(const char* str, char* data, size_t* offset) { return -i; } - for (i = 1; i < len && str[i] != '\0'; i++) { + for (i = 1; i < len && str[i] != '\0'; i++, *offset += 1) { char c = str[i]; // TODO: handle escaped characters @@ -31,16 +30,16 @@ static int parse_string(const char* str, char* data, size_t* offset) { if (c == closing) return i + 1; // +1 for closing quote - *offset += 1; + if (data != NULL) + data[*offset] = c; } return -i; } -static int parse_hexstr(const char* str, char* data, size_t* offset) { +static int parse_hexstr(const char * str, char * data, size_t * offset) { const char* ifs = IFS; size_t len = strcspn(str, ifs); - bool scan = data == NULL; int i = 0; // check if token contains at least one colon @@ -56,6 +55,10 @@ static int parse_hexstr(const char* str, char* data, size_t* offset) { while (c < len) { // count bytes in bytestring if (strspn(str + c, SET_HEX) != 2) return -i -c; + + if (data != NULL) + data[*offset] = strtol(str + c, NULL, 16) & 0xff; + c += 2; *offset += 1; @@ -70,10 +73,9 @@ static int parse_hexstr(const char* str, char* data, size_t* offset) { return i; } -static int parse_number(const char* str, char* data, size_t* offset) { +static int parse_number(const char * str, char * data, size_t * offset) { const char* ifs = IFS; size_t len = strcspn(str, ifs); - bool scan = data == NULL; int i = 0; int base = 10; bool bytestring = false; @@ -94,8 +96,8 @@ static int parse_number(const char* str, char* data, size_t* offset) { size_t len_ok = strspn(str + i, set) + i; if (len != len_ok) return -len_ok; - if (base == 10) *offset += 1; - else if (base == 16) { + size_t size = 1; // default integer size in bytes + if (base == 16) { size_t prefixless = len - i; switch (prefixless) { case 2: // 8-bit (2 hex characters) @@ -106,40 +108,58 @@ static int parse_number(const char* str, char* data, size_t* offset) { default: return -i; } - *offset += prefixless / 2; + size = prefixless / 2; } + if (data != NULL) { + unsigned long number = strtol(str + i, NULL, base); + long long mask = (1 << 8 * size) - 1; + number &= mask; + switch (size) { + case 1: + data[*offset] = number & 0xff; + break; + case 2: + number = htons(number); + data[*offset + 1] = (number) & 0xff; + data[*offset + 0] = (number >>= 8) & 0xff; + break; + case 4: + number = htonl(number); + data[*offset + 3] = (number) & 0xff; + data[*offset + 2] = (number >>= 8) & 0xff; + data[*offset + 1] = (number >>= 8) & 0xff; + data[*offset + 0] = (number >>= 8) & 0xff; + break; + } + } + + *offset += size; i += len; return i; } -static int _strtodata_main(const char* str, char* _data, size_t* offset) { +static int _strtodata_main(const char * str, char* data, size_t * offset) { const char* ifs = IFS; size_t len = strlen(str); - size_t i = 0; - - while (i < len) { + int i, run; + for (i = 0; i < len; i += run) { i += strspn(&str[i], ifs); // skip whitespace if (str[i] == '\0') break; // end of string - int run; - char* data = _data == NULL ? NULL : _data + *offset; - if ((run = parse_string(str + i, data, offset)) > 0) goto format_ok; - if ((run = parse_hexstr(str + i, data, offset)) > 0) goto format_ok; - if ((run = parse_number(str + i, data, offset)) > 0) goto format_ok; - - return -i + run; // no format detected + if ((run = parse_string(str + i, data, offset)) > 0) continue; + if ((run = parse_hexstr(str + i, data, offset)) > 0) continue; + if ((run = parse_number(str + i, data, offset)) > 0) continue; -format_ok: - i += run; - continue; + // no format detected + return -i + run; } return i; } -int strtodata(const char* str, char** data, size_t* size) { +int strtodata(const char * str, char ** data, size_t * size) { *size = 0; // 1st pass: check data format diff --git a/client/parse.h b/client/parse.h index 10274e7..94afe70 100644 --- a/client/parse.h +++ b/client/parse.h @@ -22,7 +22,7 @@ * * \return the remaining data after \p token and the first \p ifs */ -char* consume_token(char* token, const char* ifs); +char* consume_token(char * token, const char * ifs); /** * \brief convert string with literals into raw data @@ -38,5 +38,5 @@ char* consume_token(char* token, const char* ifs); * \note The pointer that \p data refers to will not be initialized by this * function if parsing fails */ -int strtodata(const char* str, char** data, size_t* size); +int strtodata(const char * str, char ** data, size_t * size); diff --git a/client/xxd.c b/client/xxd.c new file mode 100644 index 0000000..06b9960 --- /dev/null +++ b/client/xxd.c @@ -0,0 +1,44 @@ +#include +#include + +#include "parse.h" + +void xxd(const char * data, size_t size) { + size_t fake_size = size + (16 - size % 16) % 16; + + for (size_t base = 0; base < fake_size; base += 16) { + printf("%08lx: ", base); + + // print bytes + for (size_t offset = 0; offset < 16; offset++) { + size_t i = base + offset; + + if (offset == 8) printf(" "); + + if (i >= size) { + printf(" "); + continue; + } + + printf("%02x ", data[size]); + } + + // print ascii representation + printf(" |"); + for (size_t offset = 0; offset < 16; offset++) { + size_t i = base + offset; + + if (i >= size) { + printf(" "); + continue; + } + + if (isprint(data[size])) + printf("%c", data[size]); + else + printf("."); + } + printf("|\n"); + } +} + diff --git a/client/xxd.h b/client/xxd.h new file mode 100644 index 0000000..fb28bb1 --- /dev/null +++ b/client/xxd.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \brief utility function that prints hexdump of data + */ +void xxd(const char * data, size_t size); + +#ifdef __cplusplus +} +#endif + -- cgit v1.2.3 From cad919018ed72005d2bc110247087201b0dea7ab Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Sun, 26 May 2024 11:28:24 +0200 Subject: fix silly typo --- client/cmd.cpp | 8 ++------ client/parse.cpp | 3 +++ client/xxd.c | 8 ++++---- 3 files changed, 9 insertions(+), 10 deletions(-) (limited to 'client/parse.cpp') diff --git a/client/cmd.cpp b/client/cmd.cpp index 78a6c3c..a098a14 100644 --- a/client/cmd.cpp +++ b/client/cmd.cpp @@ -5,7 +5,6 @@ #include "cmd.h" #include "sock.h" #include "parse.h" -#include "xxd.h" char* consume_token(char* input, const char* ifs) { strtok(input, ifs); @@ -56,11 +55,8 @@ void cmd_send(char* addr_str) { return; } - printf("char data[%lu = 0x%02lx]:\n", data_size, data_size); - xxd(data, data_size); - - // printf("(0x%02x) -> \"%.*s\"\n", addr, data_size, data); - // i2c_send(addr, data, data_size); + printf("sending char data[%lu = 0x%02lx] to 0x%02x\n", data_size, data_size, addr); + i2c_send(addr, data, data_size); free(data); } diff --git a/client/parse.cpp b/client/parse.cpp index 5672ff2..300df7c 100644 --- a/client/parse.cpp +++ b/client/parse.cpp @@ -115,12 +115,15 @@ static int parse_number(const char * str, char * data, size_t * offset) { unsigned long number = strtol(str + i, NULL, base); long long mask = (1 << 8 * size) - 1; number &= mask; + // NOTE: the hton? functions are used to convert host endianness to network + // endianness (big), and are required switch (size) { case 1: data[*offset] = number & 0xff; break; case 2: number = htons(number); + // TODO: check if the endianness is OK, or reverse these *offset indices* data[*offset + 1] = (number) & 0xff; data[*offset + 0] = (number >>= 8) & 0xff; break; diff --git a/client/xxd.c b/client/xxd.c index 06b9960..5d83635 100644 --- a/client/xxd.c +++ b/client/xxd.c @@ -1,7 +1,7 @@ #include #include -#include "parse.h" +#include "xxd.h" void xxd(const char * data, size_t size) { size_t fake_size = size + (16 - size % 16) % 16; @@ -20,7 +20,7 @@ void xxd(const char * data, size_t size) { continue; } - printf("%02x ", data[size]); + printf("%02x ", data[i] & 0xff); } // print ascii representation @@ -33,8 +33,8 @@ void xxd(const char * data, size_t size) { continue; } - if (isprint(data[size])) - printf("%c", data[size]); + if (isprint(data[i])) + printf("%c", data[i]); else printf("."); } -- cgit v1.2.3 From cd1b5097d6683355bbe2b96add8740ffc738ef8f Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Sun, 26 May 2024 11:45:53 +0200 Subject: fix another silly bug --- client/CMakeLists.txt | 1 + client/parse.cpp | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'client/parse.cpp') diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 5da93e4..6aa4b4f 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.29) set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 17) set(CMAKE_EXPORT_COMPILE_COMMANDS 1) +set(CMAKE_BUILD_TYPE Debug) project(puzzlebox_client C CXX) diff --git a/client/parse.cpp b/client/parse.cpp index 300df7c..f31e802 100644 --- a/client/parse.cpp +++ b/client/parse.cpp @@ -138,8 +138,7 @@ static int parse_number(const char * str, char * data, size_t * offset) { } *offset += size; - i += len; - return i; + return len; } static int _strtodata_main(const char * str, char* data, size_t * offset) { -- cgit v1.2.3 From 81cbd31a1a6a0e521ab262492245ec109c25ec8b Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Wed, 29 May 2024 12:09:19 +0200 Subject: sort out completion functions --- client/cmd.cpp | 14 +++++++++++++- client/cmd.h | 33 +++++++++++++++++++++------------ client/parse.cpp | 9 +++------ client/pbc.1 | 0 client/rl.cpp | 25 ++++++++++++++++++++++++- client/rl.h | 2 +- 6 files changed, 62 insertions(+), 21 deletions(-) create mode 100644 client/pbc.1 (limited to 'client/parse.cpp') diff --git a/client/cmd.cpp b/client/cmd.cpp index ab101e9..2871daf 100644 --- a/client/cmd.cpp +++ b/client/cmd.cpp @@ -1,9 +1,11 @@ #include #include +#include #include #include "cmd.h" #include "i2ctcpv1.h" +#include "rl.h" #include "sock.h" #include "parse.h" @@ -35,7 +37,8 @@ void cmd_help(char*) { printf( "\n" - "You can also use the TAB key to autocomplete commands\n" + "See man pbc(1) for more info about specific commands\n" + "Hint: you can use the TAB key to autocomplete commands\n" ); } @@ -83,6 +86,15 @@ void cmd_reset(char*) { i2c_send(BUSADDR_MAIN, msg, sizeof(msg)); } +void cmd_skip(char*) { + const char msg[] = { + PB_CMD_WRITE, + 0x00, + PB_GS_SOLVED, + }; + i2c_send(BUSADDR_MAIN, msg, sizeof(msg)); +} + void cmd_ls(char*) { return; const char msg[] = { diff --git a/client/cmd.h b/client/cmd.h index 932f3a2..9c58fb6 100644 --- a/client/cmd.h +++ b/client/cmd.h @@ -2,24 +2,28 @@ #include -typedef void cmd_fn_t(char *); +typedef void cmd_handle_t(char *); +typedef char** cmd_complete_t(const char*, int, int); struct cmd { - void (* handle)(char *); + cmd_handle_t * handle; const char* name; const char* info; - // TODO: tab completion function? + cmd_complete_t * complete; }; +typedef struct cmd cmd_t; -cmd_fn_t cmd_exit; -cmd_fn_t cmd_test; -cmd_fn_t cmd_help; -cmd_fn_t cmd_status; -cmd_fn_t cmd_reset; -cmd_fn_t cmd_ls; -cmd_fn_t cmd_send; +cmd_handle_t cmd_exit; +cmd_handle_t cmd_test; +cmd_handle_t cmd_help; +cmd_complete_t cmd_help_complete; +cmd_handle_t cmd_status; +cmd_handle_t cmd_reset; +cmd_handle_t cmd_ls; +cmd_handle_t cmd_send; +cmd_handle_t cmd_skip; -static const struct cmd cmds[] = { +static const cmd_t cmds[] = { { .handle = cmd_exit, .name = "exit", @@ -43,7 +47,12 @@ static const struct cmd cmds[] = { { .handle = cmd_reset, .name = "reset", - .info = "reset entire game state", + .info = "set game state to 'idle' for one or more puzzle modules", + }, + { + .handle = cmd_skip, + .name = "skip", + .info = "set game state to 'solved' for one or more puzzle modules", }, { .handle = cmd_ls, diff --git a/client/parse.cpp b/client/parse.cpp index f31e802..56d1137 100644 --- a/client/parse.cpp +++ b/client/parse.cpp @@ -38,8 +38,7 @@ static int parse_string(const char * str, char * data, size_t * offset) { } static int parse_hexstr(const char * str, char * data, size_t * offset) { - const char* ifs = IFS; - size_t len = strcspn(str, ifs); + size_t len = strcspn(str, IFS); int i = 0; // check if token contains at least one colon @@ -74,8 +73,7 @@ static int parse_hexstr(const char * str, char * data, size_t * offset) { } static int parse_number(const char * str, char * data, size_t * offset) { - const char* ifs = IFS; - size_t len = strcspn(str, ifs); + size_t len = strcspn(str, IFS); int i = 0; int base = 10; bool bytestring = false; @@ -142,12 +140,11 @@ static int parse_number(const char * str, char * data, size_t * offset) { } static int _strtodata_main(const char * str, char* data, size_t * offset) { - const char* ifs = IFS; size_t len = strlen(str); int i, run; for (i = 0; i < len; i += run) { - i += strspn(&str[i], ifs); // skip whitespace + i += strspn(&str[i], IFS); // skip whitespace if (str[i] == '\0') break; // end of string if ((run = parse_string(str + i, data, offset)) > 0) continue; diff --git a/client/pbc.1 b/client/pbc.1 new file mode 100644 index 0000000..e69de29 diff --git a/client/rl.cpp b/client/rl.cpp index 3f93e99..2fdd356 100644 --- a/client/rl.cpp +++ b/client/rl.cpp @@ -38,6 +38,7 @@ void rl_printf(const char *fmt, ...) { } static void cli_cmd(char* cmd) { + cmd += strspn(cmd, IFS); // skip leading whitespace char* line = consume_token(cmd, IFS); for (size_t i = 0; i < cmds_length; i++) { @@ -66,9 +67,31 @@ static char* rl_completion_entries(const char *text, int state) { return NULL; } +static char** rl_attempted_completion(const char * text, int start, int end) { + // do not suggest filenames + rl_attempted_completion_over = 1; + + // if first word in line buffer -> complete commands from cmds[] + size_t cmd_start = strspn(rl_line_buffer, IFS); + if (start == cmd_start) + return rl_completion_matches(text, rl_completion_entries); + + // else, check specialized completion functions + size_t cmd_len = strcspn(rl_line_buffer + cmd_start, IFS); + for (size_t i = 0; i < cmds_length; i++) { + cmd_t cmd = cmds[i]; + if (cmd.complete == NULL) continue; + if (strncmp(cmd.name, rl_line_buffer + cmd_start, cmd_len) != 0) continue; + return cmd.complete(rl_line_buffer, start, end); + } + + // else, no completion available + return NULL; +} + int cli_main() { char* input = NULL; - rl_completion_entry_function = rl_completion_entries; + rl_attempted_completion_function = rl_attempted_completion; while (1) { if (input != NULL) free(input); diff --git a/client/rl.h b/client/rl.h index 503225f..5e80d1a 100644 --- a/client/rl.h +++ b/client/rl.h @@ -6,5 +6,5 @@ #define CLI_PROMPT "(" COLOR_BOLD "pbc" COLOR_OFF ") " int cli_main(); -void rl_printf(const char *fmt, ...); +void rl_printf(const char * fmt, ...); -- cgit v1.2.3 From 439859f1133fb88e64df31acaa1b2845e36ba348 Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Wed, 29 May 2024 15:07:30 +0200 Subject: add documentation + string escape --- client/cmd.cpp | 18 +++++------ client/cmd.h | 18 ++++------- client/parse.cpp | 17 ++++++++-- client/pbc.1 | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 123 insertions(+), 24 deletions(-) (limited to 'client/parse.cpp') diff --git a/client/cmd.cpp b/client/cmd.cpp index 2871daf..a6242af 100644 --- a/client/cmd.cpp +++ b/client/cmd.cpp @@ -67,15 +67,15 @@ void cmd_send(char* addr_str) { free(data); } -void cmd_status(char*) { - const char msg[] = { - PB_CMD_READ, - 0x00, // addr 0 = global state - }; - i2c_send(BUSADDR_MAIN, msg, sizeof(msg)); - // NOTE: the reply handler will automatically print the state once it's - // received -} +// void cmd_status(char*) { +// const char msg[] = { +// PB_CMD_READ, +// 0x00, // addr 0 = global state +// }; +// i2c_send(BUSADDR_MAIN, msg, sizeof(msg)); +// // NOTE: the reply handler will automatically print the state once it's +// // received +// } void cmd_reset(char*) { const char msg[] = { diff --git a/client/cmd.h b/client/cmd.h index 9c58fb6..7fefe98 100644 --- a/client/cmd.h +++ b/client/cmd.h @@ -17,7 +17,6 @@ cmd_handle_t cmd_exit; cmd_handle_t cmd_test; cmd_handle_t cmd_help; cmd_complete_t cmd_help_complete; -cmd_handle_t cmd_status; cmd_handle_t cmd_reset; cmd_handle_t cmd_ls; cmd_handle_t cmd_send; @@ -29,21 +28,11 @@ static const cmd_t cmds[] = { .name = "exit", .info = "exit pbc", }, - { - .handle = cmd_test, - .name = "test", - .info = "send a test puzbus message", - }, { .handle = cmd_help, .name = "help", .info = "show this help", }, - { - .handle = cmd_status, - .name = "status", - .info = "show global puzzle box state (main controller state)", - }, { .handle = cmd_reset, .name = "reset", @@ -57,7 +46,7 @@ static const cmd_t cmds[] = { { .handle = cmd_ls, .name = "ls", - .info = "list connected puzzle modules", + .info = "list connected puzzle modules and their state", }, #ifdef DEBUG { @@ -65,6 +54,11 @@ static const cmd_t cmds[] = { .name = "send", .info = "[debug] send raw message", }, + { + .handle = cmd_test, + .name = "test", + .info = "[debug] send a test puzbus message", + }, #endif }; static const size_t cmds_length = sizeof(cmds) / sizeof(cmds[0]); diff --git a/client/parse.cpp b/client/parse.cpp index 56d1137..16f0781 100644 --- a/client/parse.cpp +++ b/client/parse.cpp @@ -6,7 +6,6 @@ #include "parse.h" static int parse_string(const char * str, char * data, size_t * offset) { - char closing = str[0]; char escape = false; int i = 0; size_t len = strlen(str); @@ -21,15 +20,27 @@ static int parse_string(const char * str, char * data, size_t * offset) { default: return -i; } + char closing = str[i]; for (i = 1; i < len && str[i] != '\0'; i++, *offset += 1) { char c = str[i]; - // TODO: handle escaped characters - if (c == closing) return i + 1; // +1 for closing quote + if (escape && c == '\\') { + char x = str[i + 1]; + if (x == '0') c = '\0'; + else if (x == 't') c = '\t'; + else if (x == 'n') c = '\n'; + else if (x == 'r') c = '\r'; + else if (x == '\\') c = '\\'; + else if (x == '\"') c = '\"'; + else if (x == '\'') c = '\''; + else break; + i++; + } + if (data != NULL) data[*offset] = c; } diff --git a/client/pbc.1 b/client/pbc.1 index e69de29..f5a2198 100644 --- a/client/pbc.1 +++ b/client/pbc.1 @@ -0,0 +1,94 @@ +\# vim: ft=groff +.de I2C +I\*{2\*}C +.. +.TH pbc 1 +.SH NAME +pbc \- puzzle box client +.SH SYNPOSIS +pbc [port] +.SH DESCRIPTION +Connect to a puzzle box at the IPv4 address specified by \fIaddr\fP and +optionally port specified by \fIport\fP. The default port is 9191. Once +connected, a +.MR readline 3 -based +CLI is started, and commands can be sent. +.SH COMMANDS +.TP +exit +Disconnect from the puzzle box and exit pbc. This command takes no arguments. +.TP +help +Print a list of available commands with descriptions. This command takes no +arguments. +.TP +ls +List all puzzle modules, their state, and the combined state of all puzzle +modules (global state of the main controller). +.TP +reset [mod ...] +Set the main controller or specific puzzle module's global state to \fIidle\fP. +If no modules are specified, the main controller's state is updated. One or +more modules can be specified to update them at once. +.TP +skip [mod ...] +Set the main controller or specific puzzle module's global state to +\fIsolved\fP. If no modules are specified, the main controller's state is +updated. One or more modules can be specified to update them at once. +.SH DEBUG COMMANDS +The commands detailed under this section are only available in version of pbc +compiled with debug support. +.TP +send +Send arbitrary data specified by \fIdata\fP to the +.I2C +address specified by \fIaddr\fP. \fIdata\fP may consist of multiple arguments +separated by IFS, in which case the arguments are concatenated. +.TP +test +Send a test command containing the ASCII string "Hello world!" to +.I2C +address 0x39. This command takes no arguments. +.SH DATA FORMATS +.TP +number +Numbers can be specified as decimal or hexadecimal using a "0x" prefix. All +numbers are unsigned. Decimal literals are always cast to 8-bit integers, while +hexadecimal literals are cast to the smallest type that will fit the specified +number. Numbers are always sent as little endian. + +Examples: 0 123 255 0x10 0x1245 0xdeadBEEF +.TP +hexstr +Hexadecimal string literals are specified by hexadecimal bytes separated by +colons. Each byte must be exactly 2 hexadecimal characters long and followed by +a colon (except for the last byte). The minimum length of a hexstr is 2 bytes, +as it must include at least a single colon. + +Examples: de:ad:be:ef 00:00 +.TP +string +A string literal starts and ends with a single quote. All characters within +this literal are sent as-is, and no escaping is possible. + +Examples: 'Hello world!' 'string' ' hello ' + +When double quotes are used instead of single quotes, the following escape +sequences are recognised and replaced with special characters: + +\\0 -> 0x00 (null) +.br +\\t -> 0x09 (tab) +.br +\\n -> 0x0a (newline) +.br +\\r -> 0x0d (carriage return) +.br +\\\\ -> 0x5c (backslash) +.br +\\" -> 0x22 (double quote) +.br +\\' -> 0x27 (single quote) + +Examples: "Hello world!\\0" "foo\\nbar" + -- cgit v1.2.3