From 9c790f8cd6051a2d9b58a63691fc022121412ba5 Mon Sep 17 00:00:00 2001 From: Kayne Ruse Date: Sat, 17 Sep 2022 13:01:09 +0100 Subject: [PATCH] Got hooks working --- README.md | 2 +- docs/TODO.txt | 2 + makefile | 13 ++-- repl/lib_standard.c | 99 +++++++++++++++++++++++++++++ repl/lib_standard.h | 6 ++ repl/makefile | 27 ++++++++ {source => repl}/repl_main.c | 16 +++-- source/{lib_builtin.c => builtin.c} | 2 +- source/{lib_builtin.h => builtin.h} | 1 - source/interpreter.c | 61 +++++++++++++++++- source/interpreter.h | 10 ++- source/makefile | 18 ++---- 12 files changed, 228 insertions(+), 29 deletions(-) create mode 100644 repl/lib_standard.c create mode 100644 repl/lib_standard.h create mode 100644 repl/makefile rename {source => repl}/repl_main.c (96%) rename source/{lib_builtin.c => builtin.c} (99%) rename source/{lib_builtin.h => builtin.h} (88%) diff --git a/README.md b/README.md index 9d89700..79cd282 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Special thanks to http://craftinginterpreters.com/ for their fantastic book that For windows, simply run `make` in the root directory. -For linux, run `make static` in the root directory (see [this issue for details](https://github.com/Ratstail91/Toy/issues/26)). +For linux, run `make repl-static` in the root directory (see [this issue for details](https://github.com/Ratstail91/Toy/issues/26)). For mac, good luck. diff --git a/docs/TODO.txt b/docs/TODO.txt index 5163c4a..139bc91 100644 --- a/docs/TODO.txt +++ b/docs/TODO.txt @@ -39,6 +39,8 @@ DONE: nested compound assignment bug TODO: hooks on the external libraries, triggered on import + + TODO: standard library TODO: external script runner library TODO: document how it all works diff --git a/makefile b/makefile index 7623851..5dd4732 100644 --- a/makefile +++ b/makefile @@ -1,12 +1,17 @@ export OUTDIR = out -all: $(OUTDIR) - $(MAKE) -C source +all: $(OUTDIR) repl -library: clean $(OUTDIR) +repl: $(OUTDIR) library + $(MAKE) -C repl + +repl-static: $(OUTDIR) static + $(MAKE) -C repl + +library: $(OUTDIR) $(MAKE) -C source library -static: clean $(OUTDIR) +static: $(OUTDIR) $(MAKE) -C source static test: clean $(OUTDIR) diff --git a/repl/lib_standard.c b/repl/lib_standard.c new file mode 100644 index 0000000..6da3166 --- /dev/null +++ b/repl/lib_standard.c @@ -0,0 +1,99 @@ +#include "lib_standard.h" + +#include "memory.h" + +#include + +static int nativeClock(Interpreter* interpreter, LiteralArray* arguments) { + //no arguments + if (arguments->count != 0) { + interpreter->errorOutput("Incorrect number of arguments to clock\n"); + return -1; + } + + //get the time from C (what a pain) + time_t rawtime = time(NULL); + struct tm* timeinfo = localtime( &rawtime ); + char* timestr = asctime(timeinfo); + + //push to the stack + int len = strlen(timestr) - 1; //-1 for the newline + Literal timeLiteral = TO_STRING_LITERAL(copyString(timestr, len), len); + + //push to the stack + pushLiteralArray(&interpreter->stack, timeLiteral); + + //cleanup + freeLiteral(timeLiteral); + + return 1; +} + +//call the hook +typedef struct Natives { + char* name; + NativeFn fn; +} Natives; + +int hookStandard(Interpreter* interpreter, Literal identifier, Literal alias) { + //build the natives list + Natives natives[] = { + {"clock", nativeClock}, + {NULL, NULL} + }; + + /* TODO: too exhuasted to fix this right now + //store the library in an aliased dictionary + if (!IS_NULL(alias)) { + //get the alias as a string + Literal dictName = TO_STRING_LITERAL( copyString(AS_IDENTIFIER(alias), alias.as.identifier.length), alias.as.identifier.length ); + + //make sure the name isn't taken + if (existsLiteralDictionary(&interpreter->scope->variables, dictName)) { + interpreter->errorOutput("Can't override an existing variable\n"); + freeLiteral(dictName); + return false; + } + + LiteralDictionary* dictionary = ALLOCATE(LiteralDictionary, 1); + initLiteralDictionary(dictionary); + + for (int i = 0; natives[i].name; i++) { + Literal name = TO_STRING_LITERAL(copyString(natives[i].name, strlen(natives[i].name)), strlen(natives[i].name)); + Literal func = TO_FUNCTION_LITERAL((void*)natives[i].fn, 0); + func.type = LITERAL_FUNCTION_NATIVE; + + setLiteralDictionary(dictionary, name, func); + + freeLiteral(name); + freeLiteral(func); + } + + //build the type + Literal type = TO_TYPE_LITERAL(LITERAL_DICTIONARY, true); + Literal strType = TO_TYPE_LITERAL(LITERAL_STRING, true); + Literal fnType = TO_TYPE_LITERAL(LITERAL_FUNCTION_NATIVE, true); + TYPE_PUSH_SUBTYPE(&type, strType); + TYPE_PUSH_SUBTYPE(&type, fnType); + + //set scope + Literal dict = TO_DICTIONARY_LITERAL(dictionary); + setLiteralDictionary(&interpreter->scope->variables, dictName, dict); + setLiteralDictionary(&interpreter->scope->types, dictName, type); + + //cleanup + freeLiteral(dict); + freeLiteral(type); + freeLiteral(dictName); + return 0; + } + */ + + //default + for (int i = 0; natives[i].name; i++) { + injectNativeFn(interpreter, natives[i].name, natives[i].fn); + } + + return 0; +} + diff --git a/repl/lib_standard.h b/repl/lib_standard.h new file mode 100644 index 0000000..46c146d --- /dev/null +++ b/repl/lib_standard.h @@ -0,0 +1,6 @@ +#pragma once + +#include "interpreter.h" + +int hookStandard(Interpreter* interpreter, Literal identifier, Literal alias); + diff --git a/repl/makefile b/repl/makefile new file mode 100644 index 0000000..e6030e8 --- /dev/null +++ b/repl/makefile @@ -0,0 +1,27 @@ +CC=gcc + +IDIR+=. ../source +CFLAGS+=$(addprefix -I,$(IDIR)) -g -Wall -W -pedantic -Wno-unused-parameter -Wno-unused-function -Wno-unused-variable +LIBS+=-ltoy + +ODIR = obj +SRC = $(wildcard *.c) +OBJ = $(addprefix $(ODIR)/,$(SRC:.c=.o)) + +OUT=../$(OUTDIR)/toyrepl + +all: $(OBJ) + $(CC) -DTOY_IMPORT $(CFLAGS) -o $(OUT) $(OBJ) -L$(realpath $(shell pwd)/../$(OUTDIR)) $(LIBS) + +$(OBJ): | $(ODIR) + +$(ODIR): + mkdir $(ODIR) + +$(ODIR)/%.o: %.c + $(CC) -c -o $@ $< $(CFLAGS) + +.PHONY: clean + +clean: + $(RM) $(ODIR) diff --git a/source/repl_main.c b/repl/repl_main.c similarity index 96% rename from source/repl_main.c rename to repl/repl_main.c index a86138d..fda22ca 100644 --- a/source/repl_main.c +++ b/repl/repl_main.c @@ -9,6 +9,9 @@ #include #include +//extra libraries +#include "lib_standard.h" + //IO functions char* readFile(char* path, size_t* fileSize) { FILE* file = fopen(path, "rb"); @@ -102,6 +105,10 @@ unsigned char* compileString(char* source, size_t* size) { void runBinary(unsigned char* tb, size_t size) { Interpreter interpreter; initInterpreter(&interpreter); + + //inject the libs + injectNativeHook(&interpreter, "standard", hookStandard); + runInterpreter(&interpreter, tb, size); freeInterpreter(&interpreter); } @@ -123,12 +130,6 @@ void runSource(char* source) { return; } - //DEBUG - // for (size_t i = 0; i < size; i++) { - // printf("%d, ", tb[i]); - // } - // printf("\n"); - runBinary(tb, size); } @@ -150,6 +151,9 @@ void repl() { Interpreter interpreter; //persist the interpreter for the scopes initInterpreter(&interpreter); + //inject the libs + injectNativeHook(&interpreter, "standard", hookStandard); + for(;;) { printf("> "); fgets(input, size, stdin); diff --git a/source/lib_builtin.c b/source/builtin.c similarity index 99% rename from source/lib_builtin.c rename to source/builtin.c index 6ae2d47..5ffbb09 100644 --- a/source/lib_builtin.c +++ b/source/builtin.c @@ -1,4 +1,4 @@ -#include "lib_builtin.h" +#include "builtin.h" #include "memory.h" #include "literal.h" diff --git a/source/lib_builtin.h b/source/builtin.h similarity index 88% rename from source/lib_builtin.h rename to source/builtin.h index a32c6ea..aa95220 100644 --- a/source/lib_builtin.h +++ b/source/builtin.h @@ -3,7 +3,6 @@ #include "interpreter.h" int _index(Interpreter* interpreter, LiteralArray* arguments); -int _dot(Interpreter* interpreter, LiteralArray* arguments); int _set(Interpreter* interpreter, LiteralArray* arguments); int _get(Interpreter* interpreter, LiteralArray* arguments); int _push(Interpreter* interpreter, LiteralArray* arguments); diff --git a/source/interpreter.c b/source/interpreter.c index 574d2a4..928c256 100644 --- a/source/interpreter.c +++ b/source/interpreter.c @@ -6,7 +6,7 @@ #include "keyword_types.h" #include "opcodes.h" -#include "lib_builtin.h" +#include "builtin.h" #include #include @@ -56,6 +56,32 @@ bool injectNativeFn(Interpreter* interpreter, char* name, NativeFn func) { return true; } +bool injectNativeHook(Interpreter* interpreter, char* name, HookFn hook) { + //reject reserved words + if (findTypeByKeyword(name) != TOKEN_EOF) { + interpreter->errorOutput("Can't inject a hook on an existing keyword\n"); + return false; + } + + int identifierLength = strlen(name); + Literal identifier = TO_IDENTIFIER_LITERAL(copyString(name, identifierLength), identifierLength); + + //make sure the name isn't taken + if (existsLiteralDictionary(interpreter->hooks, identifier)) { + interpreter->errorOutput("Can't override an existing hook\n"); + return false; + } + + Literal fn = TO_FUNCTION_LITERAL((void*)hook, 0); + fn.type = LITERAL_FUNCTION_NATIVE; + + setLiteralDictionary(interpreter->hooks, identifier, fn); + + freeLiteral(identifier); + + return true; +} + void parseCompoundToPureValues(Interpreter* interpreter, Literal* literalPtr) { if (IS_IDENTIFIER(*literalPtr)) { parseIdentifierToValue(interpreter, literalPtr); @@ -1180,6 +1206,7 @@ static bool execFnCall(Interpreter* interpreter, bool looseFirstArgument) { initLiteralArray(&inner.stack); inner.exports = interpreter->exports; inner.exportTypes = interpreter->exportTypes; + inner.hooks = interpreter->hooks; setInterpreterPrint(&inner, interpreter->printOutput); setInterpreterAssert(&inner, interpreter->assertOutput); setInterpreterError(&inner, interpreter->errorOutput); @@ -1413,6 +1440,31 @@ static bool execImport(Interpreter* interpreter) { Literal alias = popLiteralArray(&interpreter->stack); Literal identifier = popLiteralArray(&interpreter->stack); + //access the hooks + if (existsLiteralDictionary(interpreter->hooks, identifier)) { + Literal func = getLiteralDictionary(interpreter->hooks, identifier); + + if (!IS_FUNCTION_NATIVE(func)) { + interpreter->errorOutput("Expected native function for a hook: "); + printLiteralCustom(identifier, interpreter->errorOutput); + interpreter->errorOutput("\"\n"); + + freeLiteral(func); + freeLiteral(alias); + freeLiteral(identifier); + return false; + } + + HookFn fn = (HookFn)AS_FUNCTION(func).bytecode; + + fn(interpreter, identifier, alias); + + freeLiteral(func); + freeLiteral(alias); + freeLiteral(identifier); + return true; + } + Literal lit = getLiteralDictionary(interpreter->exports, identifier); Literal type = getLiteralDictionary(interpreter->exportTypes, identifier); @@ -2321,6 +2373,8 @@ void initInterpreter(Interpreter* interpreter) { initLiteralDictionary(interpreter->exports); interpreter->exportTypes = ALLOCATE(LiteralDictionary, 1); initLiteralDictionary(interpreter->exportTypes); + interpreter->hooks = ALLOCATE(LiteralDictionary, 1); + initLiteralDictionary(interpreter->hooks); //set up the output streams setInterpreterPrint(interpreter, printWrapper); @@ -2448,7 +2502,12 @@ void freeInterpreter(Interpreter* interpreter) { freeLiteralDictionary(interpreter->exports); FREE(LiteralDictionary, interpreter->exports); interpreter->exports = NULL; + freeLiteralDictionary(interpreter->exportTypes); FREE(LiteralDictionary, interpreter->exportTypes); interpreter->exportTypes = NULL; + + freeLiteralDictionary(interpreter->hooks); + FREE(LiteralDictionary, interpreter->hooks); + interpreter->hooks = NULL; } diff --git a/source/interpreter.h b/source/interpreter.h index 9fcd6ba..f01070d 100644 --- a/source/interpreter.h +++ b/source/interpreter.h @@ -21,9 +21,11 @@ typedef struct Interpreter { Scope* scope; LiteralArray stack; - //output LiteralDictionary* exports; //read-write - interface with Toy from C - this is a pointer, since it works at a script-level LiteralDictionary* exportTypes; + LiteralDictionary* hooks; + + //debug outputs PrintFn printOutput; PrintFn assertOutput; PrintFn errorOutput; @@ -32,10 +34,12 @@ typedef struct Interpreter { bool panic; } Interpreter; -//native function API +//native API typedef int (*NativeFn)(Interpreter* interpreter, LiteralArray* arguments); TOY_API bool injectNativeFn(Interpreter* interpreter, char* name, NativeFn func); -//TODO: injectNativeHook + +typedef int (*HookFn)(Interpreter* interpreter, Literal identifier, Literal alias); +TOY_API bool injectNativeHook(Interpreter* interpreter, char* name, HookFn hook); //utilities for the host program TOY_API bool parseIdentifierToValue(Interpreter* interpreter, Literal* literalPtr); diff --git a/source/makefile b/source/makefile index 255c0b1..2eec327 100644 --- a/source/makefile +++ b/source/makefile @@ -1,11 +1,11 @@ CC=gcc -IDIR +=. -CFLAGS +=$(addprefix -I,$(IDIR)) -g -Wall -W -pedantic -Wno-unused-parameter -Wno-unused-function -Wno-unused-variable -LIBS += +IDIR+=. +CFLAGS+=$(addprefix -I,$(IDIR)) -g -Wall -W -pedantic -Wno-unused-parameter -Wno-unused-function -Wno-unused-variable +LIBS+= ODIR = obj -SRC = $(filter-out $(wildcard *main.c),$(wildcard *.c)) +SRC = $(wildcard *.c) OBJ = $(addprefix $(ODIR)/,$(SRC:.c=.o)) OUTNAME=toy @@ -26,17 +26,11 @@ else exit 1 endif -REPLSRC = $(wildcard repl_main.c) -REPLOUT = $(OUTNAME)repl.exe - -all: library $(addprefix $(ODIR)/,$(REPLSRC:.c=.o)) - $(CC) -DTOY_IMPORT $(CFLAGS) -o ../$(OUTDIR)/$(REPLOUT) $(addprefix $(ODIR)/,$(REPLSRC:.c=.o)) $(LIBS) -L$(realpath $(shell pwd)/../$(OUTDIR)) -l$(OUTNAME) - library: $(OBJ) $(CC) -DTOY_EXPORT $(CFLAGS) -shared -o $(OUT) $(LIBLINE) -static: $(OBJ) $(addprefix $(ODIR)/,$(REPLSRC:.c=.o)) - $(CC) $(CFLAGS) -o ../$(OUTDIR)/$(REPLOUT) $(addprefix $(ODIR)/,$(REPLSRC:.c=.o)) $(OBJ) $(LIBS) +static: $(OBJ) + ar crs ../$(OUTDIR)/lib$(OUTNAME).a $(OBJ) $(OBJ): | $(ODIR)