make (load) act like inserting the loaded code at that position. Close #24

This commit is contained in:
2021-07-25 20:03:24 -06:00
parent 5d991ea295
commit 722128cb82
2 changed files with 55 additions and 20 deletions

View File

@@ -29,10 +29,14 @@ typedef KissState = {
specialForms:Map<String, SpecialFormFunction>, specialForms:Map<String, SpecialFormFunction>,
macros:Map<String, MacroFunction>, macros:Map<String, MacroFunction>,
wrapListExps:Bool, wrapListExps:Bool,
loadedFiles:Map<String, Bool>, loadedFiles:Map<String, Null<ReaderExp>>,
callAliases:Map<String, ReaderExpDef>, callAliases:Map<String, ReaderExpDef>,
identAliases:Map<String, ReaderExpDef>, identAliases:Map<String, ReaderExpDef>,
fields:Array<Field>, fieldList:Array<Field>,
// 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<String, Field>,
loadingDirectory:String, loadingDirectory:String,
hscript:Bool hscript:Bool
}; };
@@ -49,7 +53,7 @@ class Kiss {
specialForms: SpecialForms.builtins(), specialForms: SpecialForms.builtins(),
macros: Macros.builtins(), macros: Macros.builtins(),
wrapListExps: true, wrapListExps: true,
loadedFiles: new Map<String, Bool>(), loadedFiles: new Map<String, ReaderExp>(),
// Helpful built-in aliases // Helpful built-in aliases
// These ones might conflict with a programmer's variable names, so they only apply in call expressions: // These ones might conflict with a programmer's variable names, so they only apply in call expressions:
callAliases: [ callAliases: [
@@ -93,7 +97,8 @@ class Kiss {
"zipThrow" => Symbol("Prelude.zipThrow"), "zipThrow" => Symbol("Prelude.zipThrow"),
"joinPath" => Symbol("Prelude.joinPath"), "joinPath" => Symbol("Prelude.joinPath"),
], ],
fields: [], fieldList: [],
fieldDict: new Map(),
loadingDirectory: "", loadingDirectory: "",
hscript: false hscript: false
}; };
@@ -144,37 +149,62 @@ class Kiss {
if (k == null) if (k == null)
k = defaultKissState(); k = defaultKissState();
if (useClassFields) if (useClassFields) {
k.fields = Context.getBuildFields(); k.fieldList = Context.getBuildFields();
for (field in k.fieldList) {
k.fieldDict[field.name] = field;
}
}
k.loadingDirectory = loadingDirectory; 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<ReaderExp> {
if (loadingDirectory == null) if (loadingDirectory == null)
loadingDirectory = k.loadingDirectory; loadingDirectory = k.loadingDirectory;
var fullPath = Path.join([loadingDirectory, kissFile]); var fullPath = Path.join([loadingDirectory, kissFile]);
if (k.loadedFiles.exists(fullPath)) { if (k.loadedFiles.exists(fullPath)) {
return; return k.loadedFiles[fullPath];
} }
k.loadedFiles[fullPath] = true;
var stream = Stream.fromFile(fullPath); var stream = Stream.fromFile(fullPath);
var startPosition = stream.position();
var loadedExps = [];
Reader.readAndProcess(stream, k, (nextExp) -> { Reader.readAndProcess(stream, k, (nextExp) -> {
#if test #if test
Sys.println(nextExp.def.toString()); Sys.println(nextExp.def.toString());
#end #end
// readerExpToHaxeExpr must be called to process readermacro, alias, and macro definitions
var expr = readerExpToHaxeExpr(nextExp, k); var expr = readerExpToHaxeExpr(nextExp, k);
// TODO There are two ideas for how to handle expressions at the top level of a Kiss file: // exps in the loaded file that actually become haxe expressions can be inserted into the file that loaded them at the position
// 1. Throw an error, because the top level should only contain field definitions // (load) was called
// 2. Append the expression to the body of an automatically generated main() function 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) if (k == null)
k = defaultKissState(); k = defaultKissState();
if (useClassFields) if (useClassFields) {
k.fields = Context.getBuildFields(); k.fieldList = Context.getBuildFields();
for (field in k.fieldList) {
k.fieldDict[field.name] = field;
}
}
for (file in kissFiles) { for (file in kissFiles) {
build(file, k, false); build(file, k, false);
} }
return k.fields; return k.fieldList;
} }
public static function readerExpToHaxeExpr(exp:ReaderExp, k:KissState):Null<Expr> { public static function readerExpToHaxeExpr(exp:ReaderExp, k:KissState):Null<Expr> {
@@ -216,7 +250,9 @@ class Kiss {
case StrExp(s): case StrExp(s):
EConst(CString(s)).withMacroPosOf(exp); EConst(CString(s)).withMacroPosOf(exp);
case CallExp({pos: _, def: Symbol(ff)}, args) if (fieldForms.exists(ff)): 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 null; // Field forms are no-ops
case CallExp({pos: _, def: Symbol(mac)}, args) if (macros.exists(mac)): case CallExp({pos: _, def: Symbol(mac)}, args) if (macros.exists(mac)):
var expanded = macros[mac](exp, args, k); var expanded = macros[mac](exp, args, k);

View File

@@ -35,11 +35,10 @@ class Macros {
wholeExp.checkNumArgs(1, 1, '(load "[file]")'); wholeExp.checkNumArgs(1, 1, '(load "[file]")');
switch (args[0].def) { switch (args[0].def) {
case StrExp(otherKissFile): case StrExp(otherKissFile):
Kiss.load(otherKissFile, k); return Kiss.load(otherKissFile, k);
default: default:
throw CompileError.fromExp(args[0], "only argument to load should be a string literal of a .kiss file path"); 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<ReaderExp>, k:KissState) -> { macros["loadFrom"] = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {