From d0697c6a67db669ed026851827e9ee992bca92ad Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Sun, 25 Jul 2021 20:03:24 -0600 Subject: [PATCH] make (load) act like inserting the loaded code at that position. Close #24 --- src/kiss/Kiss.hx | 72 ++++++++++++++++++++++++++++++++++------------ src/kiss/Macros.hx | 3 +- 2 files changed, 55 insertions(+), 20 deletions(-) diff --git a/src/kiss/Kiss.hx b/src/kiss/Kiss.hx index 8b6f455..17014ad 100644 --- a/src/kiss/Kiss.hx +++ b/src/kiss/Kiss.hx @@ -29,10 +29,14 @@ typedef KissState = { specialForms:Map, macros:Map, wrapListExps:Bool, - loadedFiles:Map, + loadedFiles:Map>, callAliases:Map, identAliases:Map, - fields:Array, + fieldList:Array, + // TODO This map was originally created to track whether the programmer wrote their own main function, but could also + // be used to allow macros to edit fields that were already defined (for instance, to decorate a function or add something + // to the constructor body) + fieldDict:Map, loadingDirectory:String, hscript:Bool }; @@ -49,7 +53,7 @@ class Kiss { specialForms: SpecialForms.builtins(), macros: Macros.builtins(), wrapListExps: true, - loadedFiles: new Map(), + loadedFiles: new Map(), // Helpful built-in aliases // These ones might conflict with a programmer's variable names, so they only apply in call expressions: callAliases: [ @@ -93,7 +97,8 @@ class Kiss { "zipThrow" => Symbol("Prelude.zipThrow"), "joinPath" => Symbol("Prelude.joinPath"), ], - fields: [], + fieldList: [], + fieldDict: new Map(), loadingDirectory: "", hscript: false }; @@ -144,37 +149,62 @@ class Kiss { if (k == null) k = defaultKissState(); - if (useClassFields) - k.fields = Context.getBuildFields(); + if (useClassFields) { + k.fieldList = Context.getBuildFields(); + for (field in k.fieldList) { + k.fieldDict[field.name] = field; + } + } k.loadingDirectory = loadingDirectory; - load(kissFile, k); + var topLevelBegin = load(kissFile, k); - k.fields; + if (topLevelBegin != null) { + + // TODO this is where an error would be thrown, or main() would be generated from top-level expressions + // TODO There are two ideas for how to handle expressions at the top level of a Kiss file: + // 1. Throw an error, because the top level should only contain field definitions + // 2. Append the expression to the body of an automatically generated main() function + // if that result is non-null, try to make main (but this may cause a duplicate declaration) + } + + k.fieldList; }); } - public static function load(kissFile:String, k:KissState, ?loadingDirectory:String) { + public static function load(kissFile:String, k:KissState, ?loadingDirectory:String):Null { if (loadingDirectory == null) loadingDirectory = k.loadingDirectory; var fullPath = Path.join([loadingDirectory, kissFile]); if (k.loadedFiles.exists(fullPath)) { - return; + return k.loadedFiles[fullPath]; } - k.loadedFiles[fullPath] = true; var stream = Stream.fromFile(fullPath); + var startPosition = stream.position(); + var loadedExps = []; Reader.readAndProcess(stream, k, (nextExp) -> { #if test Sys.println(nextExp.def.toString()); #end + // readerExpToHaxeExpr must be called to process readermacro, alias, and macro definitions var expr = readerExpToHaxeExpr(nextExp, k); - // TODO There are two ideas for how to handle expressions at the top level of a Kiss file: - // 1. Throw an error, because the top level should only contain field definitions - // 2. Append the expression to the body of an automatically generated main() function + // exps in the loaded file that actually become haxe expressions can be inserted into the file that loaded them at the position + // (load) was called + if (expr != null) { + loadedExps.push(nextExp); + } }); + + var exp = if (loadedExps.length > 0) { + CallExp(Symbol("begin").withPos(startPosition), loadedExps).withPos(startPosition); + } else { + null; + } + k.loadedFiles[fullPath] = exp; + return exp; } /** @@ -184,14 +214,18 @@ class Kiss { if (k == null) k = defaultKissState(); - if (useClassFields) - k.fields = Context.getBuildFields(); + if (useClassFields) { + k.fieldList = Context.getBuildFields(); + for (field in k.fieldList) { + k.fieldDict[field.name] = field; + } + } for (file in kissFiles) { build(file, k, false); } - return k.fields; + return k.fieldList; } public static function readerExpToHaxeExpr(exp:ReaderExp, k:KissState):Null { @@ -216,7 +250,9 @@ class Kiss { case StrExp(s): EConst(CString(s)).withMacroPosOf(exp); case CallExp({pos: _, def: Symbol(ff)}, args) if (fieldForms.exists(ff)): - k.fields.push(fieldForms[ff](exp, args, k)); + var field = fieldForms[ff](exp, args, k); + k.fieldList.push(field); + k.fieldDict[field.name] = field; null; // Field forms are no-ops case CallExp({pos: _, def: Symbol(mac)}, args) if (macros.exists(mac)): var expanded = macros[mac](exp, args, k); diff --git a/src/kiss/Macros.hx b/src/kiss/Macros.hx index 71c92b5..32fabf6 100644 --- a/src/kiss/Macros.hx +++ b/src/kiss/Macros.hx @@ -35,11 +35,10 @@ class Macros { wholeExp.checkNumArgs(1, 1, '(load "[file]")'); switch (args[0].def) { case StrExp(otherKissFile): - Kiss.load(otherKissFile, k); + return Kiss.load(otherKissFile, k); default: throw CompileError.fromExp(args[0], "only argument to load should be a string literal of a .kiss file path"); } - null; }; macros["loadFrom"] = (wholeExp:ReaderExp, args:Array, k:KissState) -> {