diff options
-rw-r--r-- | Makefile | 10 | ||||
-rw-r--r-- | README.md | 4 | ||||
-rw-r--r-- | example/example.imp | 11 | ||||
-rw-r--r-- | include/interpreter.h | 6 | ||||
-rw-r--r-- | include/repl.h | 6 | ||||
-rw-r--r-- | src/driver.c | 49 | ||||
-rw-r--r-- | src/interpreter.c | 46 | ||||
-rw-r--r-- | src/parser.y | 4 | ||||
-rw-r--r-- | src/repl.c | 65 |
9 files changed, 165 insertions, 36 deletions
@@ -1,5 +1,6 @@ CC ?= clang CFLAGS ?= -Wall -Wextra -g +LDFLAGS ?= -lreadline BISON ?= bison FLEX ?= flex @@ -26,12 +27,12 @@ TARGET := $(BUILD_DIR)/imp CFLAGS += -I$(INC_DIR) -MMD -MP DEPS := $(OBJS:.o=.d) -.PHONY: all clean example +.PHONY: all clean example repl all: $(TARGET) $(TARGET): $(OBJS) - $(CC) $(CFLAGS) $^ -o $@ + $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) $(BUILD_DIR): @mkdir -p $@ @@ -52,7 +53,10 @@ $(LEXER_O): $(LEXER_C) $(CC) $(CFLAGS) -c $< -o $@ example: $(TARGET) - ./$(TARGET) example/example.imp + ./$(TARGET) -i example/example.imp + +repl: $(TARGET) + ./$(TARGET) clean: @rm -rf $(BUILD_DIR) @@ -5,10 +5,12 @@ A small interpreter of the IMP programming language. ## Build - `make all` to build interpreter. +- `make repl` to run repl. - `make example` to interpret "example/example.imp". - `make clean` to remove build folder. ## Dependencies - [flex](https://github.com/westes/flex) -- [bison](https://www.gnu.org/software/bison)
\ No newline at end of file +- [bison](https://www.gnu.org/software/bison) +- [readline](https://tiswww.case.edu/php/chet/readline/rltop.html)
\ No newline at end of file diff --git a/example/example.imp b/example/example.imp index e1ddf22..9db6655 100644 --- a/example/example.imp +++ b/example/example.imp @@ -1,4 +1,7 @@ -x := 1; -while x < 10 do - x := (x * 2) -end
\ No newline at end of file +(y := 0; +var x := 5 in + (while x < 10 do + x := (x + 1) + end; + y := x) +end)
\ No newline at end of file diff --git a/include/interpreter.h b/include/interpreter.h index 301a3d8..6e60113 100644 --- a/include/interpreter.h +++ b/include/interpreter.h @@ -9,5 +9,11 @@ void exec_stmt(hashmap_t context, ASTNode *node); void context_print(hashmap_t context); +void context_set(hashmap_t context, const char *name, int value); +int context_get(hashmap_t context, const char *name); + +int exec_file (hashmap_t context, const char *path); +int exec_str (hashmap_t context, const char *str); + #endif
\ No newline at end of file diff --git a/include/repl.h b/include/repl.h new file mode 100644 index 0000000..804b702 --- /dev/null +++ b/include/repl.h @@ -0,0 +1,6 @@ +#ifndef REPL_H +#define REPL_H + +void repl(void); + +#endif
\ No newline at end of file diff --git a/src/driver.c b/src/driver.c index 597d7a8..60d3ce2 100644 --- a/src/driver.c +++ b/src/driver.c @@ -1,32 +1,35 @@ #include <stdio.h> #include <stdlib.h> +#include <string.h> +#include <unistd.h> #include "interpreter.h" +#include "repl.h" -extern FILE *yyin; -extern int yyparse(void); -extern ASTNode *ast_root; +static void interpret_file(const char *path) { + hashmap_t context = hashmap_create(); + exec_file(context, path); + context_print(context); + hashmap_free(context); +} int main(int argc, char **argv) { - if (argc > 1) { - yyin = fopen(argv[1], "r"); - if (!yyin) { - perror(argv[1]); - return EXIT_FAILURE; + int opt; + const char *script = NULL; + while ((opt = getopt(argc, argv, "i:h")) != -1) { + switch (opt){ + case 'i': + interpret_file(optarg); + return EXIT_SUCCESS; + case 'h': + default: + fprintf(stderr, + "Usage: %s [-i program.imp]\n" + " -i <program.imp> interpret program and exit\n" + " (no args) start REPL\n", + argv[0]); + return (opt == 'h') ? EXIT_SUCCESS : EXIT_FAILURE; } - } else { - yyin = stdin; - } - - if (yyparse() != 0) { - fprintf(stderr, "Parsing failed.\n"); - return EXIT_FAILURE; } - - hashmap_t context = hashmap_create(); - exec_stmt(context, ast_root); - context_print(context); - hashmap_free(context); - ast_free(ast_root); - + repl(); return EXIT_SUCCESS; -} +}
\ No newline at end of file diff --git a/src/interpreter.c b/src/interpreter.c index 6b1ebe1..27626a8 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -3,11 +3,20 @@ #include <stdio.h> #include <string.h> -static void context_set(hashmap_t context, const char *name, int val) { +typedef void *YY_BUFFER_STATE; +extern FILE *yyin; +extern ASTNode *ast_root; +extern int yyparse(void); +extern void yyrestart (FILE *); +extern YY_BUFFER_STATE yy_scan_string(const char *); +extern void yy_delete_buffer(YY_BUFFER_STATE); + + +void context_set(hashmap_t context, const char *name, int val) { hashmap_insert(context, name, val); } -static int context_get(hashmap_t context, const char *name) { +int context_get(hashmap_t context, const char *name) { int *val = hashmap_get(context, name); if (val) return *val; return 0; @@ -111,3 +120,36 @@ void exec_stmt(hashmap_t context, ASTNode *node) { } } } + +int exec_file (hashmap_t context, const char *path) { + yyin = fopen(path, "r"); + if (!yyin) { + perror(path); + return -1; + } + yyrestart(yyin); + if (!yyparse()) { + exec_stmt(context, ast_root); + ast_free(ast_root); + } else { + fprintf(stderr, "Parse error in %s\n", path); + fclose(yyin); + return -1; + } + fclose(yyin); + return 0; +} + +int exec_str (hashmap_t context, const char *str) { + YY_BUFFER_STATE buf = yy_scan_string(str); + if (!yyparse()) { + exec_stmt(context, ast_root); + ast_free(ast_root); + } else { + fprintf(stderr, "Parse error\n"); + yy_delete_buffer(buf); + return -1; + } + yy_delete_buffer(buf); + return 0; +} diff --git a/src/parser.y b/src/parser.y index c1e1d0e..cb3544e 100644 --- a/src/parser.y +++ b/src/parser.y @@ -27,7 +27,7 @@ void yyerror(const char *s) { %token <num> TOKEN_NUMERAL %token TOKEN_ASSIGN %token TOKEN_LEFT_PARENTHESIS TOKEN_RIGHT_PARENTHESIS -%left TOKEN_SEMICOLON +%token TOKEN_SEMICOLON %token TOKEN_SKIP %token TOKEN_IF TOKEN_THEN TOKEN_ELSE TOKEN_END TOKEN_WHILE TOKEN_DO TOKEN_VAR TOKEN_IN %token TOKEN_PLUS TOKEN_MINUS TOKEN_MULTIPLY @@ -50,8 +50,6 @@ statement : TOKEN_SKIP { $$ = ast_assign($1, $3); } | TOKEN_LEFT_PARENTHESIS statement TOKEN_SEMICOLON statement TOKEN_RIGHT_PARENTHESIS { $$ = ast_seq($2, $4); } - | statement TOKEN_SEMICOLON statement - { $$ = ast_seq($1, $3); } | TOKEN_IF boolean_expression TOKEN_THEN statement TOKEN_ELSE statement TOKEN_END { $$ = ast_if($2, $4, $6); } | TOKEN_WHILE boolean_expression TOKEN_DO statement TOKEN_END diff --git a/src/repl.c b/src/repl.c new file mode 100644 index 0000000..9c61b98 --- /dev/null +++ b/src/repl.c @@ -0,0 +1,65 @@ +#include "repl.h" +#include "interpreter.h" +#include <readline/readline.h> +#include <readline/history.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +static void print_help(void) { + puts( + "IMP REPL (type IMP statements or commands starting with '%')\n" + "Commands:\n" + " %quit exit\n" + " %run <path/to/file.imp> interpret program\n" + " %set <var> <val> set variable\n" + " %print [<var>] print variable, or all variables\n" + " %help show this message\n"); +} + +static void repl_exec_command(hashmap_t context, const char *command) { + char *cmd = strtok(command, " \t"); + if (strcmp(cmd, "%quit") == 0) { + exit(EXIT_SUCCESS); + } else if (strcmp(cmd, "%help") == 0) { + print_help(); + } else if (strcmp(cmd, "%run") == 0) { + char *file = strtok(NULL, " \t"); + if (file) { + if (!exec_file(context, file)) context_print(context); + } else { + fprintf(stderr, "Usage: %%run <path/to/file.imp>\n"); + } + } else if (strcmp(cmd, "%set") == 0) { + char *var = strtok(NULL, " \t"); + char *val = strtok(NULL, " \t"); + if (var && val) context_set(context, var, atoi(val)); + else fprintf(stderr, "Usage: %%set <var> <val>\n"); + } else if (strcmp(cmd, "%print") == 0) { + char *var = strtok(NULL, " \t"); + if (var) printf("%s = %d\n", var, context_get(context, var)); + else context_print(context); + } else { + fprintf(stderr, "Unknown command: %s\n", cmd); + } +} + +static void repl_exec_statement(hashmap_t context, const char *statement) { + if (!exec_str(context, statement)) context_print(context); +} + +void repl(void) { + hashmap_t context = hashmap_create(); + + char *line; + print_help(); + while ((line = readline("imp> ")) != NULL) { + if (*line) add_history(line); + if (line[0] == '%') repl_exec_command(context, line); + else if (*line) repl_exec_statement(context, line); + free(line); + } + printf("\n"); + + hashmap_free(context); +}
\ No newline at end of file |