payredu

[WIP] Cross-platform ledger GUI written in c99
git clone git@nonplanar.org:payredu.git
Log | Files | Refs | README

commit 8b5eb06ff3db5f05ca5bebc5a57da3eb298ee719
parent 2a0b60d627b59c3a4eff1f72306707e246bfafb8
Author: Bharatvaj Hemanth <bharatvaj@yahoo.com>
Date:   Wed, 26 Mar 2025 07:28:42 +0530

Amount, sign and denom parsing works!

Add support decimals with upto two decimal places

Move commented hot-reload code from payredu.c to hot.c

Fix hot-reload debugging

Diffstat:
MCHANGELOG | 9++++++++-
MMakefile | 4++--
Mbook.c | 206++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Mhot.c | 47+++++++++++++++++++++++++++++++++++++++++++----
Moctober-2023.txt | 2+-
Mpayredu.c | 42++----------------------------------------
6 files changed, 176 insertions(+), 134 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG @@ -1,6 +1,13 @@ +0.2 (2025/03/27) +--- +- Amount, sign and denom parsing works! +- Add support decimals with upto two decimal places +- Move commented hot-reload code from payredu.c to hot.c +- Fix hot-reload debugging + Dark Ages --------- -- Starting keeping CHANGELOG file for payredo +- Start keeping CHANGELOG file for payredu - Basic combined parser/lexer for the ledger format has been implemented in book.c - Fix Makefile issue with tests/tests.mk diff --git a/Makefile b/Makefile @@ -19,7 +19,7 @@ bal: balance: balance.c ledger.h -hot: hot.c libbalance.so +hot: hot.c libbook.so libbook.a: book.o account.o ar cr $@ $> @@ -36,4 +36,4 @@ format: include tests/tests.mk clean: - -rm *.so *.o hotbook libbook.so $(TESTS) + -rm *.so *.o hotbook libbook.a libbook.so $(TESTS) diff --git a/book.c b/book.c @@ -4,27 +4,13 @@ #include <inttypes.h> #include <ctype.h> #include <limits.h> +#include <errno.h> + #include "common.h" #include "strn.h" #include <unistd.h> -#ifdef ENABLE_GUI -#include <GLFW/glfw3.h> -#define NK_INCLUDE_FIXED_TYPES -#define NK_INCLUDE_STANDARD_IO -#define NK_INCLUDE_STANDARD_VARARGS -#define NK_INCLUDE_DEFAULT_ALLOCATOR -#define NK_INCLUDE_VERTEX_BUFFER_OUTPUT -#define NK_INCLUDE_FONT_BAKING -#define NK_INCLUDE_DEFAULT_FONT -#define NK_IMPLEMENTATION -#define NK_GLFW_GL2_IMPLEMENTATION -#define NK_KEYSTATE_BASED_INPUT -#include <nuklear.h> -#include <nuklear_glfw_gl2.h> -#endif - #define _XOPEN_SOURCE #include <time.h> @@ -74,7 +60,8 @@ map_tree_t *rootp = NULL; typedef struct { vstr_t *denom; - size_t amount; + size_t quantity; + int8_t decimal; } LedgerValue; typedef struct { @@ -88,7 +75,8 @@ typedef struct { LedgerRecord **records; } LedgerEntry; -time_t ledger_timestamp_from_ledger_date(char *date_str) +time_t +ledger_timestamp_from_ledger_date(char *date_str) { // converts string 'YYYY-MM-DD' to unix timestamp // date_str should be exactly 10 characters @@ -108,12 +96,8 @@ const char *states_str[] = { "ENTRY START", "ENTRY SPACE", "ENTRY WHO", - "ENTRY SIGN", - "ENTRY SIGN OR AMOUNT", "ENTRY AMOUNT", "ENTRY DENOM", - "ENTRY DENOM OR AMOUNT", - "ENTRY SIGN OR DENOM OR AMOUNT", "ENTRY END", }; @@ -123,19 +107,19 @@ typedef enum { ENTRY_START, // entry starts after a comment ENTRY_SPACE, ENTRY_WHO, - ENTRY_SIGN, - ENTRY_SIGN_AMOUNT, ENTRY_AMOUNT, ENTRY_DENOM , - ENTRY_DENOM_AMOUNT, - ENTRY_SIGN_DENOM_AMOUNT, ENTRY_END, // finish up entry if encountering any \n\n or \n text_len == i or text_len == i, otherwise set state to ENTRY_SPACE POSTING_END, } LedgerParseStates; -void ledger_parse_data(char *text, size_t text_len) +void +ledger_parse_data(char *text, size_t text_len) { + char *denom_list[256]; + memset(denom_list, 0, 256); + char* denomptr = NULL; setvbuf(stdout, NULL, _IONBF, 0); LedgerParseStates state = DATE; size_t line_no = 1; @@ -148,14 +132,17 @@ void ledger_parse_data(char *text, size_t text_len) time_t hold_date; vstr_t hold_comment = { 0 }; vstr_t hold_register = { 0 }; - long int hold_amount = LONG_MAX; - short hold_sign = -1; + long hold_amount = LONG_MAX; + char hold_sign = 0; size_t hold_denom_id = { 0 }; short n_count = 0; + char hold_fquantity = 0; + + hold_amount = LONG_MAX; while (i < text_len) { char c = text[i]; - // we use \n to identify entry done in ledger + /* \n identifies an entry done in ledger */ switch (c) { case '\n': case '\r': @@ -165,25 +152,23 @@ void ledger_parse_data(char *text, size_t text_len) // after parsing the amount seq, we set the state to ENTRY_WHO case ENTRY_WHO: case ENTRY_END: - hold_sign = -1; + hold_sign = 0; hold_amount = LONG_MAX; + hold_denom_id = 0; // if entry_count <= 1 throw error if (text[i - 1] == '\n') { state = DATE; // TODO push the entries to stack or somethin warning("\n==\n"); // state = POSTING_END; - } else { - state = ENTRY_WHO; - warning(","); + break; } + state = ENTRY_WHO; + warning(","); break; case COMMENT: state = ENTRY_START; break; - case ENTRY_SIGN_DENOM_AMOUNT: - state = ENTRY_WHO; - break; case ENTRY_DENOM: //warningf("%s", "denom not found, setting state WHO"); state = ENTRY_WHO; @@ -218,17 +203,15 @@ void ledger_parse_data(char *text, size_t text_len) case COMMENT: if (isalnum(c)) { // we hit alphanumerical after whitespace - size_t comment_len = 0; vstr_t comment = { .str = text + i, .len = 0 }; while (i < text_len && *(text + i) != '\n') { i++; - comment_len++; + comment.len++; } - comment.len = comment_len; - warningf("Comment: %.*s", comment_len, + warningf("Comment: %.*s", comment.len, comment); state = ENTRY_START; } @@ -260,63 +243,110 @@ void ledger_parse_data(char *text, size_t text_len) ledger_who_parsed: who_len = i - who_len; account_add(&rootp, who.str, who_len); - warningf("\n(%d) Who: %.*s", i, who_len, who); - state = ENTRY_SIGN_DENOM_AMOUNT; - // add to tags here + warningf("\n@%d Who=%.*s", i, who_len, who); + state = ENTRY_DENOM; + /* TODO add to tags here */ } break; - case ENTRY_SIGN_DENOM_AMOUNT: - if (*(text + i) == '-' ) { - // TODO throw already set error - if (hold_sign >= 0) goto ledger_parse_error_handle; - state = ENTRY_SIGN; - } else if (isdigit(*(text + i))) state = ENTRY_AMOUNT; - else state = ENTRY_DENOM; - continue; - case ENTRY_SIGN_AMOUNT: - if (*(text + i) == '-' ) { - // TODO throw already set error - if (hold_sign >= 0) goto ledger_parse_error_handle; - state = ENTRY_SIGN; - } else if (isdigit(*(text + i))) state = ENTRY_AMOUNT; - else goto ledger_parse_error_handle; - break; - case ENTRY_SIGN: { - if (*(text + i) == '-') { - i++; - // AMOUNT cannot be set before SIGN - if (hold_amount != LONG_MAX) goto ledger_parse_error_handle; - hold_sign = 1; - state = ENTRY_SIGN_DENOM_AMOUNT; - } - } break; case ENTRY_DENOM: { char _c; - warningf(" %d: D:", i + 1); + if (hold_denom_id) { + warning("Denom already parsed\n"); + goto ledger_parse_error_handle; + } char *denom = text + i; size_t denom_len = 0; + + if (*denom == '-') { + if (hold_sign) goto ledger_parse_error_handle; + hold_sign = 1; + i++; + state = ENTRY_DENOM; + break; + } + + warningf(" @%d D", i + 1); + while (i < text_len && ( isalpha(*(text + i)) + /* TODO Search denomlist instead of just checking '$' */ || *(text + i) == '$')) i++; denom_len = (text + i) - denom; - if (hold_amount == LONG_MAX) - state = hold_sign? ENTRY_AMOUNT: ENTRY_SIGN_AMOUNT; - else - state = ENTRY_END; - warningf(" %.*s(%d)", denom_len, denom, denom_len); + /* FIXME Use proper id */ + hold_denom_id = *denom;//get_denom_id(denom, denom_len); + // hold_denom_len = denom_len; + state = hold_amount == LONG_MAX? ENTRY_AMOUNT : ENTRY_END; + warningf("#%d(%.*s)", denom_len, denom_len, denom); break; } case ENTRY_AMOUNT: { char _c; - warningf(" %d A:", i + 1); + if (hold_amount != LONG_MAX) { + warning("Amount already parsed\n"); + goto ledger_parse_error_handle; + } + char *amount = text + i; size_t amount_len = 0; - while (i < text_len && (_c = *(text + i)) == '.' || isdigit(_c) || _c == ',') i++; - amount_len = (text + i) - amount; - // TODO convert amount to hold_amount integer - hold_amount = 0; - state = hold_denom_id == 0? ENTRY_DENOM : ENTRY_END; - warningf(" %.*s(%d)", amount_len, amount, amount_len); + + char *eptr = NULL; + char *efptr = NULL; + + if (*amount == '-') { + if (hold_sign) goto ledger_parse_error_handle; + hold_sign = 1; + i++; + state = ENTRY_AMOUNT; + break; + } + + warningf(" @%d A", i + 1); + + hold_amount = strtol(amount, &eptr, 10); + + if (errno == ERANGE) { + perror("FATAL: Big ints are not supported at the moment"); + exit(-1); + } + if (errno) { + perror("Some unknown error occured while parsing quantity"); + exit(-1); + } + + amount_len = eptr - amount; + + if (*eptr == ',') *eptr = '.'; + if (amount_len && *eptr == '.') { + float temp = strtof(eptr, &efptr); + if (efptr == eptr) { + /* + TODO Check if '.' should end a transaction + TODO Handle @ + */ + printf("Searching for decimal value got '%c'\n", *efptr); + exit(-1); + } + + if (efptr - eptr > 3) { + warning("FATAL: Only 2 decimal places are supported at the moment\n"); + exit(-1); + } + hold_fquantity = (char) (temp * 100); + eptr = efptr; + } + + amount_len = eptr - amount; + i += amount_len; + + /* number parse failed */ + if (eptr == amount) { + if (hold_denom_id) goto ledger_parse_error_handle; + state = ENTRY_DENOM; + break; + } + + state = hold_denom_id ? ENTRY_END : ENTRY_DENOM; + warningf("#%d(%.*s)", amount_len, amount_len, amount); } break; default: @@ -326,10 +356,12 @@ ledger_who_parsed: warning("read complete\n"); return; ledger_parse_error_handle: - warningf("Parse failed at %ld b:(%d), Expected %s, got '%c'", - line_no, i, states_str[state], text[i]); + warningf("\nParse failed @%d line:%ld, Expected %s, got '%c'\n", + i, line_no, states_str[state], text[i]); } -Entry** ledger_read_file(const char* filename, time_t date_start, time_t date_end) { + +Entry** +ledger_read_file(const char* filename, time_t date_start, time_t date_end) { Entity me = {"Account:Income"}; // list population, read from FILE* file = fopen(filename, "r"); @@ -348,10 +380,12 @@ Entry** ledger_read_file(const char* filename, time_t date_start, time_t date_en return new_list; } -void *module_main(char *data, size_t data_len) +void +*module_main(char *data, size_t data_len) { // printf("%s\n", data); warning("\n=======| Startality |=======\n"); ledger_parse_data(data, data_len); warning("\n========| Fatality |========\n"); } + diff --git a/hot.c b/hot.c @@ -2,7 +2,9 @@ #include <stdlib.h> #include <dlfcn.h> #include <signal.h> +#include <unistd.h> +#ifdef ENABLE_GUI #include <GLFW/glfw3.h> #define NK_INCLUDE_FIXED_TYPES #define NK_INCLUDE_STANDARD_IO @@ -16,18 +18,24 @@ #define NK_KEYSTATE_BASED_INPUT #include <nuklear.h> #include <nuklear_glfw_gl2.h> +#endif + #include "common.h" #define MAX_MEMORY 4064 #define WINDOW_WIDTH 512 #define WINDOW_HEIGHT 512 +#define BUFFER_SIZE 256 void* state = NULL; int should_exit = 0; // init gui state int width,height; + +#ifdef ENABLE_GUI struct nk_context* ctx; GLFWwindow* win; +#endif void sig_handle() { printf("Reloaded\n"); @@ -35,13 +43,15 @@ void sig_handle() { should_exit = 1; } -typedef void* (*module_main_func)(void*, int, int); +typedef void* (*module_main_func)(void*, int); static void error_callback(int e, const char *d) {printf("Error %d: %s\n", e, d);} -int main(int argc, char* argv[]) { +int +main(int argc, char* argv[]) { signal(SIGQUIT, sig_handle); +#ifdef ENABLE_GUI glfwSetErrorCallback(error_callback); if (!glfwInit()) { fprintf(stdout, "[GFLW] failed to init!\n"); @@ -64,12 +74,12 @@ int main(int argc, char* argv[]) { nk_style_set_font(ctx, &droid->handle); } while(1) { - void* module = dlopen("./libbalance.so", RTLD_NOW); + void* module = dlopen("./libbook.so", RTLD_NOW); while(module == NULL) { fprintf(stderr, "Failed to load module. (%s)\n", dlerror()); fprintf(stderr, "Press return to try again.\n"); getchar(); - module = dlopen("./libbalance.so", RTLD_NOW); + module = dlopen("./libbook.so", RTLD_NOW); } module_main_func module_main = dlsym(module, "module_main"); while (!glfwWindowShouldClose(win) && !should_exit) { @@ -90,6 +100,35 @@ int main(int argc, char* argv[]) { dlclose(module); should_exit = 0; } +#else + //while(1) { + void* module = dlopen("./libbook.so", RTLD_NOW); + while(module == NULL){ + fprintf(stderr, "Failed to load module. (%s)\n", dlerror()); + fprintf(stderr, "Press return to try again.\n"); + getchar(); + module = dlopen("./libbook.so", RTLD_NOW); + } + module_main_func module_main = dlsym(module, "module_main"); + FILE* in = fopen("october-2023.txt", "r"); + char* data = (char*)malloc(2048 * sizeof(char)); + size_t data_size = 0; + size_t c_read = 0; + while((c_read = fread(data + data_size + 0, 1, BUFFER_SIZE, in)) != 0) { + data_size += c_read; + } + if (ferror(in)) fprintf(stderr, "Error reading file\n"); + fprintf(stdout, "Startig loop\n"); + module_main(data, data_size); + while(should_exit == 0) { + sleep(1); + } + should_exit = 0; + dlclose(module); + fprintf(stderr, "Continue?\n"); + //} +#endif return 0; } + diff --git a/october-2023.txt b/october-2023.txt @@ -21,4 +21,4 @@ 2015/10/12 Zoho Income:Salary - Assets:Bank:Checking $10,000 + Assets:Bank:Checking $10,00 diff --git a/payredu.c b/payredu.c @@ -7,8 +7,6 @@ #include <book.h> #define MAX_MEMORY 4064 -#define WINDOW_WIDTH 512 -#define WINDOW_HEIGHT 512 #define BUFFER_SIZE 256 int should_exit = 0; @@ -19,9 +17,8 @@ void sig_handle() { should_exit = 1; } -typedef void* (*module_main_func)(const char*, size_t); - -int main(int argc, char* argv[]) { +int +main(int argc, char* argv[]) { FILE* in = fopen("october-2023.txt", "r"); char* data = (char*)malloc(2048 * sizeof(char)); size_t data_size = 0; @@ -35,38 +32,3 @@ int main(int argc, char* argv[]) { return 0; } - -/* -int main(int argc, char* argv[]) { - signal(SIGQUIT, sig_handle); - //while(1) { - void* module = dlopen("./libbook.so", RTLD_NOW); - while(module == NULL){ - fprintf(stderr, "Failed to load module. (%s)\n", dlerror()); - fprintf(stderr, "Press return to try again.\n"); - getchar(); - module = dlopen("./libbook.so", RTLD_NOW); - } - module_main_func module_main = dlsym(module, "module_main"); - FILE* in = fopen("october-2023.txt", "r"); - char* data = (char*)malloc(2048 * sizeof(char)); - size_t data_size = 0; - size_t c_read = 0; - while((c_read = fread(data + data_size + 0, 1, BUFFER_SIZE, in)) != 0) { - data_size += c_read; - } - if (ferror(in)) fprintf(stderr, "Error reading file\n"); - fprintf(stdout, "Startig loop\n"); - module_main(data, data_size); - - while(should_exit == 0) { - sleep(1); - } - should_exit = 0; - dlclose(module); - fprintf(stderr, "Continue?\n"); - //} - - return 0; -} -*/