crepl

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

commit 520534baedc529ed128876adbf70be26eb0de73e
parent c12ed3e0974d52dd93162ce5ba0538a292840d1d
Author: Demonstrandum <moi@knutsen.co>
Date:   Wed, 24 Jun 2020 22:14:57 +0100

Added implicit casting, local variable searching, and more operators.

Diffstat:
Msrc/builtin.c | 56++++++++++++++++++++++++++++++++++----------------------
Msrc/builtin.h | 4++++
Msrc/displays.c | 39++++++++++++++++++++++++++++++---------
Msrc/displays.h | 1+
Msrc/execute.c | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Msrc/execute.h | 1+
Msrc/main.c | 3+--
Msrc/parse.c | 78+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Msrc/prelude.c | 15+++++++++++++++
Msrc/prelude.h | 2++
10 files changed, 216 insertions(+), 52 deletions(-)

diff --git a/src/builtin.c b/src/builtin.c @@ -75,29 +75,41 @@ NumberNode *upcast_pair(NumberNode lhs, NumberNode rhs) return pair; } -NumberNode *num_add(NumberNode lhs, NumberNode rhs) -{ - NumberNode *upcasted = upcast_pair(lhs, rhs); - if (upcasted == NULL) - return NULL; +#define BINARY_FUNCTION(NAME, OP) \ +NumberNode *num_ ## NAME (NumberNode lhs, NumberNode rhs) \ +{ \ + NumberNode *upcasted = upcast_pair(lhs, rhs); \ + if (upcasted == NULL) \ + return NULL; \ + \ + NumberNode *result = upcasted + 0; \ + \ + switch (result->type) { \ + case FLOAT: \ + result->value.f = upcasted[0].value.f OP upcasted[1].value.f; \ + break; \ + case INT: \ + result->value.i = upcasted[0].value.i OP upcasted[1].value.i; \ + break; \ + default: { \ + ERROR_TYPE = EXECUTION_ERROR; \ + strcpy(ERROR_MSG, "Unsupported number type."); \ + return NULL; \ + } \ + } \ + result = realloc(result, sizeof(NumberNode)); \ + return result; \ +} - NumberNode *result = upcasted + 0; +BINARY_FUNCTION(add, +) // `num_add` function. +BINARY_FUNCTION(sub, -) // `num_sub` function. +BINARY_FUNCTION(mul, *) // `num_mul` function. - switch (result->type) { - case FLOAT: - result->value.f = upcasted[0].value.f + upcasted[1].value.f; - break; - case INT: - result->value.i = upcasted[0].value.i + upcasted[1].value.i; - break; - default: { - ERROR_TYPE = EXECUTION_ERROR; - strcpy(ERROR_MSG, "Unsupported number type."); - return NULL; - } - } - result = realloc(result, sizeof(NumberNode)); +// `num_div` function is different, it always gives a float. +NumberNode *num_div(NumberNode lhs, NumberNode rhs) +{ + NumberNode *result = malloc(sizeof(NumberNode)); + result->type = FLOAT; + result->value.f = num_to_float(lhs).value.f / num_to_float(rhs).value.f; return result; } - - diff --git a/src/builtin.h b/src/builtin.h @@ -7,5 +7,9 @@ NumberNode num_to_float(NumberNode); NumberNode num_to_int(NumberNode); NumberNode *upcast_pair(NumberNode, NumberNode); + NumberNode *num_add(NumberNode, NumberNode); +NumberNode *num_sub(NumberNode, NumberNode); +NumberNode *num_mul(NumberNode, NumberNode); +NumberNode *num_div(NumberNode, NumberNode); diff --git a/src/displays.c b/src/displays.c @@ -8,6 +8,22 @@ #include "execute.h" #include "displays.h" +char *display_numbernode(NumberNode num) +{ + char *str = malloc(sizeof(char) * 128); // Hope that's enough. + switch (num.type) { + case INT: + sprintf(str, "%ld", num.value.i); + break; + case FLOAT: + sprintf(str, "%.15LG", num.value.f); + break; + default: + strcpy(str, "undisplayable-number-type"); + } + return str; +} + char *display_parampos(ParamPos pos) { switch (pos) { @@ -43,10 +59,7 @@ char *display_parsetree(const ParseNode *tree) return tree->node.ident.value; } case NUMBER_NODE: { - // TODO: Check the number type (int, float, etc.). - char *number = malloc(sizeof(char) * 64); // Guess? - sprintf(number, "%ld", tree->node.number.value.i); - return number; + return display_numbernode(tree->node.number); } case UNARY_NODE: { UnaryNode unary = tree->node.unary; @@ -78,14 +91,13 @@ char *display_parsetree(const ParseNode *tree) return binary_str; } default: - return "[Unknown Parse Node]"; + return "[unknown-parse-node]"; } } char *display_datavalue(const DataValue *data) { - // Safe bet. - char *string = malloc(sizeof(char) * 512); + char *string; if (data == NULL) return "internal-null-pointer"; @@ -95,11 +107,20 @@ char *display_datavalue(const DataValue *data) if (data->value == NULL) return "number-with-null-value"; NumberNode *num = data->value; - // TODO: Handle more than just INT type. - sprintf(string, "%ld", num->value.i); + return display_numbernode(*num); + } + case T_STRING: { + char *inside = data->value; + usize len = strlen(inside); + string = malloc(len + 2); + strcpy(string + 1, inside); + string[0] = '"'; + string[len + 1] = '"'; + string[len + 2] = '\0'; break; } default: + string = malloc(sizeof(char) * 128); // Safe bet. sprintf(string, "<%s at 0x%p>", display_datatype(data->type), data->value); diff --git a/src/displays.h b/src/displays.h @@ -1,6 +1,7 @@ #include "parse.h" #include "execute.h" +char *display_numbernode(NumberNode _); char *display_parampos(ParamPos _); char *display_datatype(DataType _); char *display_parsetree(const ParseNode *); diff --git a/src/execute.c b/src/execute.c @@ -4,6 +4,21 @@ #include "builtin.h" #include "displays.h" +#define BINARY_OPERATION(OPERATION) do { \ + NumberNode *l_num = type_check("+", LHS, T_NUMBER, lhs); \ + NumberNode *r_num = type_check("+", RHS, T_NUMBER, rhs); \ + if (l_num == NULL || r_num == NULL) \ + return NULL; \ + data->type = T_NUMBER; \ + data->value = num_ ##OPERATION (*l_num, *r_num); \ +} while (0); + +void free_datavalue(DataValue *data) +{ + free(data->value); + free(data); +} + /// Takes in an execution context (ctx) and a /// statement as produced by the parser (stmt). /// Returns what it evaluates to. @@ -11,6 +26,39 @@ DataValue *execute(Context *ctx, const ParseNode *stmt) { DataValue *data = malloc(sizeof(DataValue)); switch (stmt->type) { + case IDENT_NODE: { + // Resolve variables by searching through local + // variables. If that fails, search through the + // superior sopes varaibles, repeat until there + // are no more superior scopes, if the local is + // found yield its corresponding value, or else + // throw an execution error. + char *ident_name = stmt->node.ident.value; + + free(data); + data = NULL; + + Context *current_ctx = ctx; + while (current_ctx != NULL) { + for (usize i = 0; i < current_ctx->locals_count; ++i) { + Local *local = &current_ctx->locals[i]; + if (strcmp(local->name, ident_name) == 0) { + data = &local->value; + goto finished_search; + } + } + current_ctx = current_ctx->superior; + } + +finished_search: + if (data == NULL) { + ERROR_TYPE = EXECUTION_ERROR; + sprintf(ERROR_MSG, "Could not find variable `%s'\n" + " in any local or superior scope.", ident_name); + return NULL; + } + break; + } case NUMBER_NODE: { data->type = T_NUMBER; data->value = malloc(sizeof(NumberNode)); @@ -30,15 +78,13 @@ DataValue *execute(Context *ctx, const ParseNode *stmt) DataValue *rhs = execute(ctx, stmt->node.binary.right); if (strcmp(op, "+") == 0) { - NumberNode *l_num = type_check("+", LHS, T_NUMBER, lhs); - NumberNode *r_num = type_check("+", RHS, T_NUMBER, rhs); - if (l_num == NULL || r_num == NULL) - return NULL; - // Finally, the addition is performed. - data->type = T_NUMBER; - data->value = num_add(*l_num, *r_num); + BINARY_OPERATION(add); } else if (strcmp(op, "-") == 0) { - + BINARY_OPERATION(sub); + } else if (strcmp(op, "*") == 0) { + BINARY_OPERATION(mul); + } else if (strcmp(op, "/") == 0) { + BINARY_OPERATION(div); } else { ERROR_TYPE = EXECUTION_ERROR; sprintf(ERROR_MSG, "Do not know how to evaluate" @@ -75,10 +121,13 @@ void *type_check(char *function_name, ParamPos pos, ERROR_TYPE = TYPE_ERROR; sprintf(ERROR_MSG, "Wrong type for %s of `%s' operation,\n" - " needed type of `%s'.", + " expected type of `%s', got type of `%s'.", display_parampos(pos), function_name, - display_datatype(type)); + display_datatype(type), + value == NULL + ? "null-pointer" + : display_datatype(value->type)); return NULL; } diff --git a/src/execute.h b/src/execute.h @@ -34,6 +34,7 @@ typedef enum { ARG, LHS, RHS } ParamPos; +void free_datavalue(DataValue *); void *type_check(char *, ParamPos, DataType, DataValue *); DataValue *execute(Context *, const ParseNode *); Local *make_local(char *, DataType, void *); diff --git a/src/main.c b/src/main.c @@ -88,7 +88,7 @@ int main(int argc, char **argv) printf("\033[%luC\033[1A", strlen(PROMPT) + strlen(response)); - printf(" ≡ %s\n", display_parsetree(tree)); + printf("\033[2m ≡ %s\n\033[0m", display_parsetree(tree)); DataValue *result = execute(ctx, tree); @@ -99,7 +99,6 @@ int main(int argc, char **argv) printf("#=> %s\n", display_datavalue(result)); - //free_datavalue(result); free_parsenode(tree); } while (true); diff --git a/src/parse.c b/src/parse.c @@ -3,6 +3,7 @@ #include <stdlib.h> #include <stdio.h> #include <string.h> +#include <math.h> #include "error.h" #include "displays.h" @@ -45,7 +46,7 @@ Token *new_token(TokenType type, const char *value) return t; } -TokenType char_token_type(char c, TokenType last_token_type) +TokenType char_token_type(char c, char last_char, TokenType last_token_type) { if (c <= '9' && c >= '0') { if (last_token_type == TT_IDENTIFIER) @@ -61,9 +62,13 @@ TokenType char_token_type(char c, TokenType last_token_type) && (last_token_type == TT_OPERATOR || last_token_type == TT_NONE)) return TT_NUMERIC; - if ((c == '.' || c == 'E') + if ((c == '.' || c == 'E') // Scientific notation. && last_token_type == TT_NUMERIC) return TT_NUMERIC; + if ((c == '+' || c == '-') && last_char == 'E') + return TT_NUMERIC; + if ((c == 'x' || c == 'o') && last_char == '0') + return TT_NUMERIC; if (c == '(') return TT_LPAREN; if (c == ')') @@ -93,14 +98,14 @@ Token *lex(char **source) if (**source == '\0') return NULL; // No more tokens. - TokenType tt = char_token_type(**source, TT_NONE); + TokenType tt = char_token_type(**source, ' ', TT_NONE); // Skip over TT_NONE tokens (spaces, tabs, etc.). while (tt == TT_NONE) { (*source)++; if (**source == '\0') return NULL; - tt = char_token_type(**source, tt); + tt = char_token_type(**source, *(*source - 1), tt); } // First of all, check if it matches an operator. @@ -139,7 +144,10 @@ Token *lex(char **source) } else { while (tt == previous_tt) { span++; - previous_tt = char_token_type(*(*source + span), previous_tt); + previous_tt = char_token_type( + *(*source + span), + *(*source + span - (span == 0 ? 0 : 1)), + previous_tt); } } @@ -170,12 +178,64 @@ void node_into_ident(const char *str, ParseNode *node) node->node.ident = *ident; } -// TODO: Support more than just INTs. NumberNode *parse_number(const char *str) { NumberNode *number = malloc(sizeof(NumberNode)); - number->type = INT; - number->value.i = atoi(str); + + char *exponent_ptr = strstr(str, "E"); + char *neg_exponent_ptr = strstr(str, "E-"); + char *decimal_point_ptr = strstr(str, "."); + + // Sanity. + if (exponent_ptr != NULL) { + // No trailing 'E'. + if (*(exponent_ptr + 1) == '\0') + return NULL; + // No trailing 'E+' or 'E-'. + if ((*(exponent_ptr + 1) == '+' + || *(exponent_ptr + 1) == '-') + && *(exponent_ptr + 2) == '\0') + return NULL; + // No repreated 'E' and no decimal point ('.') after 'E'. + if (strstr(exponent_ptr + 1, "E") != NULL + || strstr(exponent_ptr + 1, ".") != NULL) + return NULL; + } + if (decimal_point_ptr != NULL) { + // No trailing decimal point ('.'). + if (*(decimal_point_ptr + 1) == '\0') + return NULL; + // No decimal point ('.') after first decimal point. + if (strstr(decimal_point_ptr + 1, ".") != NULL) + return NULL; + } + + // No negative exponent and no decimal point, means + // the number literal is certainly an integer. + if (neg_exponent_ptr == NULL && decimal_point_ptr == NULL) { + number->type = INT; + ssize significand = strtoll(str, NULL, 0); + if (exponent_ptr == NULL) { // No power-term. + number->value.i = significand; + return number; + } + + usize exponent = strtoull(exponent_ptr + 1, NULL, 10); + ssize power_term = ipow(10, exponent); + if (power_term >= 0 && exponent <= 18) { + // Probably didn't overflow. + number->value.i = significand * power_term; + return number; + } + // Fallback to float. + } + + number->type = FLOAT; + fsize power_term = 1; + + fsize significand = strtold(str, NULL); + number->value.f = significand * power_term; + return number; } @@ -188,7 +248,7 @@ ParseNode *parse_prefix(const Token *token, char **rest) NumberNode *num = parse_number(token->value); if (num == NULL) { ERROR_TYPE = SYNTAX_ERROR; - sprintf(ERROR_MSG, "Malformatted number literal (`%s').", + sprintf(ERROR_MSG, "Malformed number literal (`%s').", token->value); return NULL; } diff --git a/src/prelude.c b/src/prelude.c @@ -1,5 +1,20 @@ #include "prelude.h" +ssize ipow(ssize base, usize exp) +{ + ssize result = 1; + do { + if (exp & 1) + result *= base; + exp >>= 1; + if (!exp) + break; + base *= base; + } while (true); + + return result; +} + char *trim(char *str) { char *p = strdup(str); diff --git a/src/prelude.h b/src/prelude.h @@ -29,6 +29,8 @@ typedef float f32; typedef double f64; typedef long double fsize; +ssize ipow(ssize, usize); + char *trim(char *); char *downcase(char *);