crepl

An intuitive calculator REPL.
git clone git://git.knutsen.co/crepl
Log | Files | Refs | README | LICENSE

commit 4455d5b57facdc9e0d359e064db22923d683f7f7
parent 2371441cbf6ff1ee4ebbbcece350721798bb8b57
Author: knutsen <samuel@knutsen.co>
Date:   Fri, 12 Mar 2021 15:02:06 +0000

Begin work for basic (gtk) GUI.

Diffstat:
MMakefile | 41++++++++++++++---------------------------
MREADME.md | 1+
Msrc/defaults.c | 16++++++++--------
Msrc/defaults.h | 25++++++++++++++++++++-----
Msrc/displays.c | 11++++++++++-
Msrc/execute.c | 38+++++++++++++++++++++++++++++++++-----
Asrc/gui.c | 314+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/gui.h | 13+++++++++++++
Msrc/main.c | 30+++++++++++++++++++++++++++---
Msrc/parse.c | 32++++++++++++++++++++++++++------
Msrc/parse.h | 8++++++++
Msrc/prelude.c | 3++-
12 files changed, 476 insertions(+), 56 deletions(-)

diff --git a/Makefile b/Makefile @@ -2,10 +2,10 @@ CC := gcc OPT := -O3 WARN := -Wall -Wpedantic -Wextra -Wshadow -Wno-psabi LINKS := -lm -lreadline -lpthread -CFLAGS := $(WARN) $(OPT) +CFLAGS = $(WARN) $(OPT) -funsigned-char TARGET := crepl -OBJS := main.o defaults.o error.o parse.o displays.o builtin.o execute.o prelude.o - +CDIR := ./src +OBJS := $(patsubst $(CDIR)/%.c,%.o,$(wildcard $(CDIR)/*.c)) ifeq ($(PREFIX),) PREFIX := /usr/local @@ -14,10 +14,15 @@ endif all: clean $(TARGET) @printf "\033[1mBuilt \`$(TARGET)' successfully.\033[0m\n" -debug: CFLAGS := $(WARN) -Og +debug: OPT := -Og debug: $(OBJS) $(CC) -Og -o $(TARGET) $(OBJS) $(LINKS) +gui: CFLAGS := $(CFLAGS) -DGUI $(shell pkg-config --cflags gtk+-3.0) +gui: LINKS := $(LINKS) $(shell pkg-config --libs gtk+-3.0) +gui: clean gui.o $(TARGET) + @printf "Built with GUI available, use -g/--gui flag.\n" + $(TARGET): $(OBJS) $(CC) $(OPT) -o $(TARGET) $(OBJS) $(LINKS) @@ -27,30 +32,12 @@ install: $(TARGET) install -d $(PREFIX)/bin install -m 755 $(TARGET) $(PREFIX)/bin -main.o: defaults.o parse.o error.o - $(CC) -c $(CFLAGS) src/main.c $(LINKS) - -defaults.o: error.o - $(CC) -c $(CFLAGS) src/defaults.c $(LINKS) - -prelude.o: - $(CC) -c $(CFLAGS) src/prelude.c $(LINKS) - -parse.o: error.o - $(CC) -c $(CFLAGS) src/parse.c $(LINKS) - -displays.o: parse.o - $(CC) -c $(CFLAGS) src/displays.c $(LINKS) - -builtin.o: - $(CC) -c $(CFLAGS) src/builtin.c $(LINKS) - -execute.o: parse.o error.o prelude.o - $(CC) -c $(CFLAGS) src/execute.c $(LINKS) - -error.o: - $(CC) -c $(CFLAGS) src/error.c $(LINKS) +%.o: $(CDIR)/%.c + $(CC) -c $(CFLAGS) -c $< -o $@ $(LINKS) clean: @echo "Cleaning previous build." rm -f $(TARGET) $(OBJS) + + +.PHONY: all clean test debug gui diff --git a/README.md b/README.md @@ -27,6 +27,7 @@ sudo make install # Installs the program system wide. ``` ## TODO + - [ ] A `ref(.)` function, for referencing/aliasing other variables. - [ ] Throw errors on overflows until we implement bignums. - [ ] Imaginary numbers (using `complex.h`). - [ ] User defined functions. diff --git a/src/defaults.c b/src/defaults.c @@ -15,9 +15,9 @@ ssize ipow(ssize base, usize exp) return result; } -char *remove_all_char(const char *str, char chr) +byte *remove_all_bytes(const byte *str, byte chr) { - char *new = strdup(str); + byte *new = strdup(str); size_t str_len = strlen(str); size_t new_len = 0; @@ -28,13 +28,13 @@ char *remove_all_char(const char *str, char chr) return new; } -char *trim(const char *str) +byte *trim(const byte *str) { - char *p = strdup(str); + byte *p = strdup(str); while (isspace(*p)) ++p; - char *end = p + strlen(p) - 1; + byte *end = p + strlen(p) - 1; while (end > p && isspace(*end)) --end; @@ -43,10 +43,10 @@ char *trim(const char *str) } -char *downcase(const char *str) +byte *downcase(const byte *str) { - char *p = strdup(str); - char *start = p; + byte *p = strdup(str); + byte *start = p; for (; *p; ++p) *p = tolower(*p); return start; } diff --git a/src/defaults.h b/src/defaults.h @@ -7,24 +7,29 @@ #include <stdbool.h> #include <string.h> #include <ctype.h> +#include <limits.h> #include <math.h> #include "error.h" +#define VERSION "0.1.0" + #define len(array) (sizeof(array) / sizeof((array)[0])) +#define UNUSED(var) (void)(var) + #ifdef INFINITY #define INF INFINITY #else #define INF (1.0 / 0.0) #endif -typedef uint8_t u8 ; +typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32; typedef uint64_t u64; -typedef int8_t s8 ; +typedef int8_t s8; typedef int16_t s16; typedef int32_t s32; typedef int64_t s64; @@ -32,15 +37,25 @@ typedef int64_t s64; typedef size_t usize; typedef ptrdiff_t ssize; +#if (CHAR_BIT == 8) + #if (CHAR_MIN < 0) + typedef unsigned char byte; + #else + typedef char byte; + #endif +#else + typedef u8 byte; +#endif + typedef float f32; typedef double f64; typedef long double fsize; ssize ipow(ssize, usize); -char *remove_all_char(const char *, char); -char *trim(const char *); -char *downcase(const char *); +byte *remove_all_bytes(const byte *, byte); +byte *trim(const byte *); +byte *downcase(const byte *); #define STR_HELPER(x) #x #define STR(x) STR_HELPER(x) diff --git a/src/displays.c b/src/displays.c @@ -60,6 +60,15 @@ char *display_parsetree(const ParseNode *tree) case NUMBER_NODE: { return display_numbernode(tree->node.number); } + case STRING_NODE: { // TODO: Escape the string. + usize l = strlen(tree->node.str.value); + byte *str = malloc(l + 2); + str[0] = '"'; + strcpy(str + 1, tree->node.str.value); + str[l + 1] = '"'; + str[l + 2] = '\0'; + return str; + } case UNARY_NODE: { UnaryNode unary = tree->node.unary; char *operand_str = display_parsetree(unary.operand); @@ -120,7 +129,7 @@ char *display_datavalue(const DataValue *data) } default: string = malloc(sizeof(char) * 128); // Safe bet. - sprintf(string, "<%s at 0x%p>", + sprintf(string, "<%s at %p>", display_datatype(data->type), data->value); } diff --git a/src/execute.c b/src/execute.c @@ -8,6 +8,7 @@ #include <math.h> static const f32 LOCALS_REALLOC_GROWTH_FACTOR = 1.5; +static const fsize ZERO = 0.0; #define NUMERICAL_BINARY_OPERATION(OPERATION) do { \ NumberNode *l_num = type_check(op, LHS, T_NUMBER, lhs); \ @@ -24,11 +25,28 @@ void free_datavalue(DataValue *data) free(data); } +static DataValue *recursive_execute(Context *ctx, const ParseNode *stmt); + /// Takes in an execution context (ctx) and a /// statement as produced by the parser (stmt). /// Returns what it evaluates to. DataValue *execute(Context *ctx, const ParseNode *stmt) { + // Recurse dowm parse tree, execute each node, bottom up. + DataValue *data = recursive_execute(ctx, stmt); + + // When line/statement is finished evaluating, bind `Ans'. + if (data != NULL && ERROR_TYPE == NO_ERROR) { + bind_local(ctx, "Ans", data->type, data->value); + bind_local(ctx, "ans", data->type, data->value); + bind_local(ctx, "_", data->type, data->value); + } + + return data; +} + +static DataValue *recursive_execute(Context *ctx, const ParseNode *stmt) +{ DataValue *data = malloc(sizeof(DataValue)); switch (stmt->type) { case IDENT_NODE: { @@ -66,9 +84,15 @@ finished_search: memcpy(data->value, &stmt->node.number, sizeof(NumberNode)); break; } + case STRING_NODE: { + data->type = T_STRING; + data->value = malloc(stmt->node.str.len + 1); + memcpy(data->value, stmt->node.str.value, stmt->node.str.len + 1); + break; + } case UNARY_NODE: { // Functions, essentially. - DataValue *callee = execute(ctx, stmt->node.unary.callee); - DataValue *operand = execute(ctx, stmt->node.unary.operand); + DataValue *callee = recursive_execute(ctx, stmt->node.unary.callee); + DataValue *operand = recursive_execute(ctx, stmt->node.unary.operand); if (callee == NULL || operand == NULL) return NULL; @@ -112,16 +136,16 @@ finished_search: } char *lvalue = stmt->node.binary.left->node.ident.value; free(data); - data = execute(ctx, stmt->node.binary.right); + data = recursive_execute(ctx, stmt->node.binary.right); bind_local(ctx, lvalue, data->type, data->value); break; } // How to evaluate specific operators. - DataValue *lhs = execute(ctx, stmt->node.binary.left); + DataValue *lhs = recursive_execute(ctx, stmt->node.binary.left); if (lhs == NULL) return NULL; - DataValue *rhs = execute(ctx, stmt->node.binary.right); + DataValue *rhs = recursive_execute(ctx, stmt->node.binary.right); if (rhs == NULL) return NULL; @@ -151,6 +175,7 @@ finished_search: return NULL; } } + return data; } @@ -263,7 +288,10 @@ Context *init_context() // name of the function/scope. Local *scope_name = make_local( "__this_scope", T_STRING, (void *)ctx->function); + Local *ans = make_local( + "Ans", T_NUMBER, (void *)make_number(FLOAT, (fsize *)&ZERO)); ctx->locals[0] = *scope_name; + ctx->locals[0] = *ans; // ^ Sets the first variable, default in every scope // (good for debuggin purposes). diff --git a/src/gui.c b/src/gui.c @@ -0,0 +1,314 @@ +#include "gui.h" + +#ifdef GUI + +void grid_fit(GtkWidget *grid) +{ + g_object_set(G_OBJECT(grid), + "column-homogeneous", TRUE, + "row-homogeneous", TRUE, + "column-spacing", 10, + "row-spacing", 10, + NULL); +} + +typedef struct { + GtkWidget *layout; + GtkWidget *display; + GtkWidget *output; + Context *exe_ctx; +} Calculator; + +typedef enum { + // Num keys + ACT_NUMBER_0 = 0, + ACT_NUMBER_1, + ACT_NUMBER_2, + ACT_NUMBER_3, + ACT_NUMBER_4, + ACT_NUMBER_5, + ACT_NUMBER_6, + ACT_NUMBER_7, + ACT_NUMBER_8, + ACT_NUMBER_9, + // Operators + ACT_MUL, ACT_ADD, + ACT_DIV, ACT_SUB, + // Actions + ACT_M_PLUS, ACT_M_MINUS, + ACT_EXEC, + ACT_DEL, + + ACTIONS_COUNT //< Always last! +} Action; + +typedef struct { + Action action; + GtkWidget *button; + Calculator *calculator; +} ButtonPress; + +static void write_to_editable(GtkEditable *editable, byte *input, gint len, gint *pos) +{ + // Wipe selection if inserting text. + gtk_editable_delete_selection(editable); + // Insert the string. + gtk_editable_insert_text(editable, input, len, pos); + // Move cursor forwards. + *pos += 1; + gtk_editable_set_position(editable, *pos); +} + +static void button_press(GtkWidget *widget, gpointer data) +{ + UNUSED(widget); + + ButtonPress *press = (ButtonPress *)data; + Action action = press->action; + Calculator *ctx = press->calculator; + + printf("Action Number: %d.\n", action); + + GtkEntry *entry = GTK_ENTRY(ctx->display); + GtkEntryBuffer *entry_buf = gtk_entry_get_buffer(entry); + UNUSED(entry_buf); + + gint curs_pos = gtk_editable_get_position(GTK_EDITABLE(ctx->display)); + + if (action <= 9 && action >= 0) { + byte digit = '0' + action; + write_to_editable(GTK_EDITABLE(ctx->display), &digit, 1, &curs_pos); + return; + } + + switch (action) { + case ACT_EXEC: { + const byte *source = gtk_entry_get_text(entry); + printf("Executing input: %s\n", source); + + ParseNode *tree = NULL; + DataValue *result = NULL; + + tree = parse(source); + if (tree == NULL || ERROR_TYPE != NO_ERROR) { + handle_error(); + return; + } + result = execute(ctx->exe_ctx, tree); + if (result == NULL || ERROR_TYPE != NO_ERROR) { + handle_error(); + return; + } + + if (result != NULL) + gtk_entry_set_text(GTK_ENTRY(ctx->output), display_datavalue(result)); + if (tree != NULL) + free_parsenode(tree); + } break; + case ACT_DEL: { + gboolean has_selection = gtk_editable_get_selection_bounds( + GTK_EDITABLE(ctx->display), NULL, NULL); + + if (has_selection) { + gtk_editable_delete_selection(GTK_EDITABLE(ctx->display)); + } else { + printf("Delete char at %d -- %d.\n", curs_pos - 1, curs_pos); + gtk_editable_delete_text(GTK_EDITABLE(ctx->display), + curs_pos - 1, curs_pos); + } + } break; + default: + printf("Unhandled action: %d.\n", action); + } +} + +static Calculator ctx + = { .layout = NULL + , .display = NULL + , .output = NULL + , .exe_ctx = NULL + }; + +static ButtonPress keys[ACTIONS_COUNT] = { 0 }; + +static void on_activate(GtkApplication *app) +{ + GtkWidget *window = gtk_application_window_new(app); + + GtkWidget *header = gtk_header_bar_new(); + gtk_header_bar_set_title((GtkHeaderBar *)header, "CREPL — Calculator"); + gtk_header_bar_set_show_close_button((GtkHeaderBar *)header, TRUE); + + // Init calculator keys. + for (Action act = ACT_NUMBER_0; act < ACTIONS_COUNT; ++act) { + keys[act].action = act; + keys[act].calculator = &ctx; + } + // Init execution context. + ctx.exe_ctx = base_context(); + + // Calculator main layout. + GtkWidget *layout = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10); + gtk_widget_set_margin_top (layout, 20); + gtk_widget_set_margin_start (layout, 20); + gtk_widget_set_margin_bottom(layout, 20); + gtk_widget_set_margin_end (layout, 20); + ctx.layout = layout; + + // Calculator input display. + GtkWidget *input_display = gtk_entry_new(); + gtk_entry_set_text(GTK_ENTRY(input_display), "crepl"); + g_signal_connect(input_display, "activate", + G_CALLBACK(button_press), (gpointer)(&keys[ACT_EXEC])); + ctx.display = input_display; + + // Output/result display. + GtkWidget *output_display = gtk_entry_new(); + gtk_entry_set_text(GTK_ENTRY(output_display), "..."); + gtk_editable_set_editable(GTK_EDITABLE(output_display), FALSE); + gtk_entry_set_alignment(GTK_ENTRY(output_display), 1); + gtk_entry_set_has_frame(GTK_ENTRY(output_display), FALSE); + gtk_widget_set_can_focus(output_display, FALSE); + ctx.output = output_display; + + gtk_widget_set_margin_top(output_display, 0); + gtk_box_pack_start(GTK_BOX(layout), input_display, TRUE, TRUE, 5); + gtk_box_pack_start(GTK_BOX(layout), output_display, TRUE, TRUE, 2); + + // Calculator mode keys. + GtkWidget *settings_grid = gtk_grid_new(); + grid_fit(settings_grid); + + GtkWidget *M_plus = gtk_button_new_with_label("M+"); // ACT_M_PLUS. + GtkWidget *M_minus = gtk_button_new_with_label("M-"); // ACT_M_MINUS. + GtkWidget *execute = gtk_button_new_with_label("EXE"); // ACT_EXEC. + GtkWidget *delete = gtk_button_new_with_label("DEL"); // ACT_DEL. + + keys[ACT_EXEC].button = execute; + g_signal_connect(execute, "clicked", + G_CALLBACK(button_press), (gpointer)(&keys[ACT_EXEC])); + keys[ACT_DEL].button = delete; + g_signal_connect(delete, "clicked", + G_CALLBACK(button_press), (gpointer)(&keys[ACT_DEL])); + + gtk_grid_attach(GTK_GRID(settings_grid), M_plus, 1, 1, 1, 1); + gtk_grid_attach(GTK_GRID(settings_grid), M_minus, 2, 1, 1, 1); + gtk_grid_attach(GTK_GRID(settings_grid), execute, 3, 1, 1, 1); + gtk_grid_attach(GTK_GRID(settings_grid), delete, 4, 1, 1, 1); + + gtk_box_pack_start(GTK_BOX(layout), settings_grid, TRUE, TRUE, 10); + + /* Main Keys */ + GtkWidget *main_keys = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 15); + + // Calculator numpad. + GtkWidget *numbers = gtk_grid_new(); + grid_fit(numbers); + + for (u8 n = 0; n < 9; ++n) { + byte digit[2] = { '1' + n, '\0' }; + GtkWidget *button = gtk_button_new_with_label(digit); + + keys[n + 1].button = button; + g_signal_connect(button, "clicked", + G_CALLBACK(button_press), (gpointer)(&keys[n + 1])); + + gtk_grid_attach(GTK_GRID(numbers), button, + n % 3 + 1, n / 3 + 1, 1, 1); + } + GtkWidget *number_zero = gtk_button_new_with_label("0"); + keys[ACT_NUMBER_0].button = number_zero; + g_signal_connect(number_zero, "clicked", + G_CALLBACK(button_press), (gpointer)(&keys[ACT_NUMBER_0])); + gtk_grid_attach(GTK_GRID(numbers), number_zero, 2, 4, 1, 1); + + gtk_box_pack_start(GTK_BOX(main_keys), numbers, TRUE, TRUE, 10); + + // Calculator function keys. + GtkWidget *operators_grid = gtk_grid_new(); + grid_fit(operators_grid); + GtkWidget *functions_grid = gtk_grid_new(); + grid_fit(functions_grid); + + // Operators, column #1. + GtkWidget *b_mul = gtk_button_new_with_label("*"); + GtkWidget *b_add = gtk_button_new_with_label("+"); + GtkWidget *b_div = gtk_button_new_with_label("/"); + GtkWidget *b_sub = gtk_button_new_with_label("-"); + + // Operators column #2. + GtkWidget *b_pow = gtk_button_new_with_label("^"); + GtkWidget *b_fac = gtk_button_new_with_label("!"); + GtkWidget *b_sqr = gtk_button_new_with_label("√"); + GtkWidget *b_equ = gtk_button_new_with_label("="); + + // Functions, column #3. + GtkWidget *b_log = gtk_button_new_with_label("log"); + GtkWidget *b_sin = gtk_button_new_with_label("sin"); + GtkWidget *b_cos = gtk_button_new_with_label("cos"); + GtkWidget *b_abs = gtk_button_new_with_label("abs"); + + // Constants, column #4. + GtkWidget *b_pi = gtk_button_new_with_label("π"); + GtkWidget *b_e = gtk_button_new_with_label("e"); + GtkWidget *b_phi = gtk_button_new_with_label("φ"); + GtkWidget *b_ans = gtk_button_new_with_label("Ans"); + + // Column #1. + gtk_grid_attach(GTK_GRID(operators_grid), b_mul, 1, 1, 1, 1); + gtk_grid_attach(GTK_GRID(operators_grid), b_add, 1, 2, 1, 1); + gtk_grid_attach(GTK_GRID(operators_grid), b_div, 1, 3, 1, 1); + gtk_grid_attach(GTK_GRID(operators_grid), b_sub, 1, 4, 1, 1); + // Column #2. + gtk_grid_attach(GTK_GRID(operators_grid), b_pow, 2, 1, 1, 1); + gtk_grid_attach(GTK_GRID(operators_grid), b_fac, 2, 2, 1, 1); + gtk_grid_attach(GTK_GRID(operators_grid), b_sqr, 2, 3, 1, 1); + gtk_grid_attach(GTK_GRID(operators_grid), b_equ, 2, 4, 1, 1); + // Column #3. + gtk_grid_attach(GTK_GRID(functions_grid), b_log, 1, 1, 1, 1); + gtk_grid_attach(GTK_GRID(functions_grid), b_sin, 1, 2, 1, 1); + gtk_grid_attach(GTK_GRID(functions_grid), b_cos, 1, 3, 1, 1); + gtk_grid_attach(GTK_GRID(functions_grid), b_abs, 1, 4, 1, 1); + // Column #4. + gtk_grid_attach(GTK_GRID(functions_grid), b_pi, 2, 1, 1, 1); + gtk_grid_attach(GTK_GRID(functions_grid), b_e, 2, 2, 1, 1); + gtk_grid_attach(GTK_GRID(functions_grid), b_phi, 2, 3, 1, 1); + gtk_grid_attach(GTK_GRID(functions_grid), b_ans, 2, 4, 1, 1); + + gtk_box_pack_end(GTK_BOX(main_keys), functions_grid, TRUE, TRUE, 10); + gtk_box_pack_end(GTK_BOX(main_keys), operators_grid, TRUE, TRUE, 10); + + // Set all keys/buttons to be unfocusable. + for (Action act = ACT_NUMBER_0; act < ACTIONS_COUNT; ++act) + if (keys[act].button != NULL) + gtk_widget_set_can_focus(keys[act].button, FALSE); + + gtk_window_set_titlebar(GTK_WINDOW(window), header); + gtk_box_pack_end(GTK_BOX(layout), main_keys, TRUE, TRUE, 10); + gtk_container_add(GTK_CONTAINER(window), layout); + + gtk_widget_show_all(window); +} + +int start_gui(void) +{ + printf("Start GUI.\n"); + + GtkApplication *app = gtk_application_new("com.crepl.GtkApplication", + G_APPLICATION_FLAGS_NONE); + + g_signal_connect(app, "activate", G_CALLBACK(on_activate), NULL); + printf("Started application.\n"); + return g_application_run(G_APPLICATION(app), 0, NULL); +} + +#else + +#include <stdio.h> +int start_gui(void) +{ + fputs("Not compiled with GUI support.", stderr); + return -1; +} + +#endif diff --git a/src/gui.h b/src/gui.h @@ -0,0 +1,13 @@ +#ifdef GUI +#include "defaults.h" +#include "error.h" +#include "parse.h" +#include "execute.h" +#include "displays.h" + +#include <gtk/gtk.h> +#endif + +int start_gui(void); + + diff --git a/src/main.c b/src/main.c @@ -15,6 +15,7 @@ #include "parse.h" #include "execute.h" #include "displays.h" +#include "gui.h" static const char *PROMPT = "::> "; @@ -81,15 +82,38 @@ int main(int argc, char **argv) printf("\033[1m"); printf("CREPL — Calculator Read Eval Print Loop"); printf("\033[0m"); - puts(" (" COMPILER ") (" __DATE__ ")"); - puts("Type \"exit\" or [Ctrl-D] (i.e. EOF) to quit."); + puts(" (v" VERSION ") (" COMPILER ") (" __DATE__ ")"); bool verbose = false; + bool gui_mode = false; // Parse command line arguments. for (int i = 0; i < argc; ++i) { - if (strcmp(argv[i], "-v") == 0) + if (strcmp(argv[i], "-v") == 0 + || strcmp(argv[i], "--verbose") == 0) verbose = true; + else if (strcmp(argv[i], "-V") == 0 + || strcmp(argv[i], "--version") == 0) + { puts("Version " VERSION "."); return EXIT_SUCCESS; } + else if (strcmp(argv[i], "-g") == 0 + || strcmp(argv[i], "--gui") == 0) + gui_mode = true; + } + +#ifndef GUI + if (gui_mode) { + puts("This CREPL executable was not compiled with GUI support."); + return EXIT_FAILURE; } +#else + if (gui_mode) { + int success = start_gui(); + if (success == 0) + return EXIT_SUCCESS; + return EXIT_FAILURE; + } +#endif + + puts("Type \"exit\" or [Ctrl-D] (i.e. EOF) to quit."); // Configure readline. rl_clear_signals(); diff --git a/src/parse.c b/src/parse.c @@ -19,6 +19,9 @@ void free_parsenode(ParseNode *node) case IDENT_NODE: free((char *)node->node.ident.value); break; + case STRING_NODE: + free((char *)node->node.str.value); + break; case NUMBER_NODE: break; case UNARY_NODE: @@ -72,6 +75,8 @@ TokenType char_token_type(char c, char last_char, TokenType last_token_type) return TT_RPAREN; if (c == '\0' || c == ' ' || c == '\t' || c == '\n' || c == '\r') return TT_NONE; + if (c == '"') + return TT_STRING; // All possible operator/special-symbol characters: if (((c >= '!' && c <= '/') @@ -138,6 +143,14 @@ Token *lex(char **source) // Do not coalesce parentheses. if (tt == TT_RPAREN || tt == TT_LPAREN) { span++; + } else if (tt == TT_STRING) { // String literals are not like others. + ++span; // Skip opening quote. + while ((*source)[span] != '"') { + if ((*source)[span] == '\\' && (*source)[span + 1] == '"') + ++span; // Don't stop at escaped quote. + ++span; + } + ++span; // Skip ending quote. } else { while (tt == previous_tt) { span++; @@ -154,6 +167,8 @@ Token *lex(char **source) *source += span; Token *token = new_token(tt, sub_str); + + free(sub_str); return token; } @@ -204,7 +219,7 @@ NumberNode *parse_number(const char *str) { NumberNode *number = malloc(sizeof(NumberNode)); - str = remove_all_char(str, '_'); + str = remove_all_bytes(str, '_'); char *exponent_ptr = strstr(str, "E"); char *neg_exponent_ptr = strstr(str, "E-"); @@ -282,6 +297,13 @@ ParseNode *parse_prefix(const Token *token, char **rest) node_into_ident(token->value, node); break; } + case TT_STRING: { + node->type = STRING_NODE; // TODO: Parse string escapes etc. + node->node.str.value = strdup(token->value + 1); + node->node.str.len = strlen(token->value) - 2; + node->node.str.value[node->node.str.len] = '\0'; + break; + } case TT_OPERATOR: { // Verify this is a prefix operator. bool is_prefix = false; @@ -329,7 +351,6 @@ ParseNode *parse_prefix(const Token *token, char **rest) ERROR_TYPE = PARSE_ERROR; sprintf(ERROR_MSG, "Unclosed paranthetical expression.\n" " Missing `)' closing parenthesis."); - free_token((Token *)token); return NULL; } free_token((Token *)token); @@ -490,10 +511,9 @@ ParseNode *parse_expr(char **slice, u16 precedence) strcpy(ERROR_MSG, "Could not finish parsing expression."); break; } - if (current_precedence != FUNCTION_PRECEDENCE) - token = lex(slice); - else - token = peek(slice); + token = current_precedence == FUNCTION_PRECEDENCE + ? peek(slice) + : lex(slice); if (token == NULL) break; diff --git a/src/parse.h b/src/parse.h @@ -8,6 +8,7 @@ typedef enum { TT_IDENTIFIER, TT_NUMERIC, TT_OPERATOR, + TT_STRING, TT_NONE, } TokenType; @@ -69,6 +70,7 @@ static const Operator KNOWN_OPERATORS[] = { typedef enum { IDENT_NODE, NUMBER_NODE, + STRING_NODE, UNARY_NODE, BINARY_NODE, } NodeType; @@ -79,6 +81,11 @@ typedef struct { char *value; } IdentNode; +typedef struct { + usize len; + byte *value; +} StringNode; + typedef enum { FLOAT, INT, @@ -111,6 +118,7 @@ typedef struct _parse_node { NodeType type; union { IdentNode ident; + StringNode str; NumberNode number; UnaryNode unary; BinaryNode binary; diff --git a/src/prelude.c b/src/prelude.c @@ -3,6 +3,7 @@ char *PRELUDE_STATEMENTS[] = { "tau = 2pi", "phi = 1.61803398875", + "crepl = \"Calculator REPL, v" VERSION ".\"" }; void execute_prelude(Context *ctx) @@ -25,6 +26,6 @@ void execute_prelude(Context *ctx) fatality: handle_error(); fprintf(stderr, "\nFATAL: Prelude failed to run without error.\n"); - fprintf(stderr, "ABORTING\n!"); + fprintf(stderr, "ABORTING!\n"); exit(1); }