diff --git a/chapter_08/Makefile b/chapter_08/Makefile new file mode 100644 index 0000000..e831f5c --- /dev/null +++ b/chapter_08/Makefile @@ -0,0 +1,13 @@ +MPC_DIR := ../mpc + + +all: lispy + + +%: %.c + cc -std=c11 -Wall $@.c ${MPC_DIR}/mpc.c -ledit -o $@ + + +.PHONY: clean +clean: + rm -rf lispy diff --git a/chapter_08/lispy.c b/chapter_08/lispy.c new file mode 100644 index 0000000..a8584ed --- /dev/null +++ b/chapter_08/lispy.c @@ -0,0 +1,226 @@ +#include +#include +#include "../mpc/mpc.h" + + +/* If we are on Windows compile these functions */ +#ifdef _WIN32 +#include +#include + +static char buffer[2048]; + +/* Fake readline function */ +char* readline(char* prompt) { + fputs(prompt, stdout); + fgets(buffer, 2048, stdin); + char* cpy = malloc(strlen(buffer)+1); + assert(cpy != NULL) + strcpy(cpy, buffer); + cpy[strlen(cpy)-1] = '\0'; + return cpy; +} + +/* Fake add_history function */ +void add_history(char* unused) {} + +/* Otherwise include the editline headers + could use __APPLE__ for detection of OSX */ +#else +#include +#endif + + +/* Declare new lval struct */ +typedef struct { + int type; + long num; + int err; +} lval; + + +/* Create enumeration of possible lval types */ +enum {LVAL_NUM, LVAL_ERR}; + + +/* Create enumeration of possible error types */ +enum {LERR_DIV_ZERO, LERR_BAD_OP, LERR_BAD_NUM}; + + +/* Create a new number of type lval */ +lval lval_num(long x) { + lval v; + v.type = LVAL_NUM; + v.num = x; + return v; +} + + +/* Create a new error type lval */ +lval lval_err(int x) { + lval v; + v.type = LVAL_ERR; + v.err = x; + return v; +} + + +/* Print an lval */ +void lval_print(lval v) { + switch (v.type) { + /* In the case the type is a number print it + Then break out of the switch */ + case LVAL_NUM: + printf("%li", v.num); + break; + /* In the case the type is an error */ + case LVAL_ERR: + /* Check what type of error it is & print it */ + if (v.err == LERR_DIV_ZERO) { + printf("Error: Division by zero!"); + } + if (v.err == LERR_BAD_OP) { + printf("Error: Invalid operator!"); + } + if (v.err == LERR_BAD_NUM) { + printf("Error: Invalid number!"); + } + break; + } +} + + +/* Print an lval followed by a newline */ +void lval_println(lval v) { + lval_print(v); + putchar('\n'); +} + + +long min(long x, long y) { + if (x <= y) { + return x; + } else { + return y; + } +} + + +long max(long x, long y) { + if (x >= y) { + return x; + } else { + return y; + } +} + + +/* Use operator string to see which operation to perform */ +lval eval_op(lval x, char* op, lval y) { + + /* If either value is an error return it */ + if (x.type == LVAL_ERR) { + return x; + } + if (y.type == LVAL_ERR) { + return y; + } + + /* Otherwise do maths on the number values */ + + if (strcmp(op, "+") == 0) {return lval_num(x.num + y.num);} + if (strcmp(op, "-") == 0) {return lval_num(x.num - y.num);} + if (strcmp(op, "*") == 0) {return lval_num(x.num * y.num);} + if (strcmp(op, "/") == 0) { + /* If second operand is zero return error */ + return y.num == 0 ? lval_err(LERR_DIV_ZERO) + : lval_num(x.num / y.num); + } + if (strcmp(op, "%") == 0) {return lval_num(x.num % y.num);} + if (strcmp(op, "^") == 0) {return lval_num(pow(x.num, y.num));} + if (strcmp(op, "min") == 0) {return lval_num(min(x.num, y.num));} + if (strcmp(op, "max") == 0) {return lval_num(max(x.num, y.num));} + + return lval_err(LERR_BAD_OP); +} + + +lval eval(mpc_ast_t* t) { + + if (strstr(t->tag, "number")) { + /* Check if there is some error in conversion */ + errno = 0; + long x = strtol(t->contents, NULL, 10); + return errno != ERANGE ? lval_num(x) : lval_err(LERR_BAD_NUM); + } + + /* The operator is always second child */ + char* op = t->children[1]->contents; + + /* We store the third children in x */ + lval x = eval(t->children[2]); + + /* Iterate the remaining children and combining */ + unsigned int i = 3; + while (strstr(t->children[i]->tag, "expr")) { + x = eval_op(x, op, eval(t->children[i])); + i++; + } + + return x; +} + + +int main(int argc, char const *argv[]) { + + /* Create some parsers */ + mpc_parser_t* Number = mpc_new("number"); + mpc_parser_t* Operator = mpc_new("operator"); + mpc_parser_t* Expr = mpc_new("expr"); + mpc_parser_t* Lispy = mpc_new("lispy"); + + /* Define them with the following language */ + mpca_lang(MPCA_LANG_DEFAULT, + " \ + number : /-?[0-9]+/ ; \ + operator : '+' | '-' | '*' | '/' | '%' | '^' | \ + \"min\" | \"max\" ; \ + expr : | '(' + ')' ; \ + lispy : /^/ + /$/ ; \ + ", + Number, Operator, Expr, Lispy); + + /* Print version and exit information */ + puts("Lispy version 0.0.0.0.4"); + puts("Press Ctrl+c to exit\n"); + + /* In a never ending loop */ + while (1) { + /* Output our prompt and get input */ + char* input = readline("lispy> "); + + /* Add input to history */ + add_history(input); + + /* Attempt to parse the user input */ + mpc_result_t r; + if (mpc_parse("", input, Lispy, &r)) { + /* On success evaluate the user input */ + lval result = eval(r.output); + lval_println(result); + mpc_ast_delete(r.output); + } else { + /* Otherwise print the error */ + mpc_err_print(r.error); + mpc_err_delete(r.error); + } + + /* Free retrieved input */ + free(input); + } + + /* Undefine and delete our parsers */ + mpc_cleanup(4, Number, Operator, Expr, Lispy); + + return 0; +}