diff --git a/src/kiss/EmbeddedScript.hx b/src/kiss/EmbeddedScript.hx index f35f038..7e459d9 100644 --- a/src/kiss/EmbeddedScript.hx +++ b/src/kiss/EmbeddedScript.hx @@ -56,20 +56,20 @@ class EmbeddedScript { // This brings in the DSL's functions and global variables. // As a side-effect, it also fills the KissState with the macros and reader macros that make the DSL syntax - for (field in Kiss.build(dslFile, k)) { - classFields.push(field); - } + classFields = classFields.concat(Kiss.build(dslFile, k)); Reader.readAndProcess(Stream.fromFile(scriptFile), k, (nextExp) -> { - var field = Kiss.readerExpToField(nextExp, k, false); - if (field != null) { - classFields.push(field); + var fields = Kiss.readerExpToFields(nextExp, k, false); + if (fields.length > 0) { + classFields = classFields.concat(fields); } else { // In a DSL script, anything that's not a field definition is a command line commandList.push(macro function(self) { ${Kiss.readerExpToHaxeExpr(nextExp, k)}; }); } + // This return is essential for type unification of concat() and push() above... ugh. + return; // TODO also allow label setting and multiple commands coming from the same expr? // Multiple things could come from the same expr by returning begin, or a call to a function that does more stuff // i.e. knot declarations need to end the previous knot, and BELOW that set a label for the new one, then increment the read count diff --git a/src/kiss/Kiss.hx b/src/kiss/Kiss.hx index 852411b..7148a06 100644 --- a/src/kiss/Kiss.hx +++ b/src/kiss/Kiss.hx @@ -124,18 +124,18 @@ class Kiss { throw CompileError.fromExp(loadArgs[0], "only argument to load should be a string literal"); } default: - var field = readerExpToField(nextExp, k); - if (field != null) { - #if test + var fields = readerExpToFields(nextExp, k); + #if test + for (field in fields) { switch (field.kind) { case FVar(_, expr) | FFun({ret: _, args: _, expr: expr}): Sys.println(expr.toString()); default: throw CompileError.fromExp(nextExp, 'cannot print the expression of generated field $field'); } - #end - classFields.push(field); } + #end + classFields = classFields.concat(fields); } }); @@ -159,7 +159,7 @@ class Kiss { return fields; } - public static function readerExpToField(exp:ReaderExp, k:KissState, errorIfNot = true):Null { + public static function readerExpToFields(exp:ReaderExp, k:KissState, errorIfNot = true):Array { var fieldForms = k.fieldForms; // Macros at top-level are allowed if they expand into a fieldform, or null like defreadermacro @@ -168,19 +168,27 @@ class Kiss { var identAliases = k.identAliases; return switch (exp.def) { + // Multiple field/macro definitions wrapped in `begin` are acceptable + case CallExp({pos: _, def: Symbol(mac)}, args) if (mac == "begin"): + var fields = []; + for (arg in args) { + fields = fields.concat(readerExpToFields(arg, k, errorIfNot)); + } + fields; case CallExp({pos: _, def: Symbol(mac)}, args) if (macros.exists(mac)): + trace(mac); var expandedExp = macros[mac](exp, args, k); - if (expandedExp != null) readerExpToField(expandedExp, k, errorIfNot) else null; + if (expandedExp != null) readerExpToFields(expandedExp, k, errorIfNot) else []; case CallExp({pos: _, def: Symbol(alias)}, args) if (callAliases.exists(alias)): var aliasedExp = CallExp(callAliases[alias].withPosOf(exp), args).withPosOf(exp); - readerExpToField(aliasedExp, k, errorIfNot); + readerExpToFields(aliasedExp, k, errorIfNot); case CallExp({pos: _, def: Symbol(alias)}, args) if (identAliases.exists(alias)): var aliasedExp = CallExp(identAliases[alias].withPosOf(exp), args).withPosOf(exp); - readerExpToField(aliasedExp, k, errorIfNot); + readerExpToFields(aliasedExp, k, errorIfNot); case CallExp({pos: _, def: Symbol(formName)}, args) if (fieldForms.exists(formName)): - fieldForms[formName](exp, args, k); + [fieldForms[formName](exp, args, k)]; default: - if (errorIfNot) throw CompileError.fromExp(exp, 'top-level expressions must be (or expand into) field definitions'); else return null; + if (errorIfNot) throw CompileError.fromExp(exp, 'top-level expressions must be (or expand into) field or macro definitions'); else []; }; } diff --git a/src/kiss/SpecialForms.hx b/src/kiss/SpecialForms.hx index cff35f3..1b9a330 100644 --- a/src/kiss/SpecialForms.hx +++ b/src/kiss/SpecialForms.hx @@ -187,8 +187,8 @@ class SpecialForms { map["lambda"] = (wholeExp:ReaderExp, args:Array, k:KissState) -> { wholeExp.checkNumArgs(2, null, "(lambda [[argsNames...]] [body...])"); var returnsValue = switch (args[0].def) { - case TypedExp("Void", argNames): - args[0] = argNames; + case TypedExp("Void", argNames): + args[0] = argNames; false; default: true; diff --git a/src/test/cases/MacroTestCase.hx b/src/test/cases/MacroTestCase.hx new file mode 100644 index 0000000..ac51632 --- /dev/null +++ b/src/test/cases/MacroTestCase.hx @@ -0,0 +1,17 @@ +package test.cases; + +import utest.Test; +import utest.Assert; +import kiss.Prelude; +import kiss.List; +import haxe.ds.Option; + +using StringTools; + +@:build(kiss.Kiss.build("kiss/src/test/cases/MacroTestCase.kiss")) +class MacroTestCase extends Test { + function testMultipleFieldForms() { + Assert.equals(5, myVar); + Assert.equals(6, myFunc()); + } +} diff --git a/src/test/cases/MacroTestCase.kiss b/src/test/cases/MacroTestCase.kiss new file mode 100644 index 0000000..7d0dcf7 --- /dev/null +++ b/src/test/cases/MacroTestCase.kiss @@ -0,0 +1,6 @@ +(defmacro defMultiple [varName funcName] + `{ + (defvar ,varName 5) + (defun ,funcName [] 6)}) + +(defMultiple myVar myFunc) \ No newline at end of file