diff --git a/src/kiss/EType.hx b/src/kiss/EType.hx new file mode 100644 index 0000000..de7cbcc --- /dev/null +++ b/src/kiss/EType.hx @@ -0,0 +1,10 @@ +package kiss; + +enum EType { + EStream(message:String); + EKiss(message:String); + EUnmatchedBracket(type:String); + EException(message:String); + EExpected(e:EType); + EUnexpected(e:Dynamic); +} diff --git a/src/kiss/Kiss.hx b/src/kiss/Kiss.hx index 6e3b4c5..7181138 100644 --- a/src/kiss/Kiss.hx +++ b/src/kiss/Kiss.hx @@ -17,7 +17,9 @@ import kiss.Macros; import kiss.KissError; import kiss.cloner.Cloner; import tink.syntaxhub.*; +import tink.macro.Exprs; import haxe.ds.Either; +import kiss.EType; using kiss.Kiss; using kiss.Helpers; @@ -218,29 +220,73 @@ class Kiss { return k; } - public static function _try(operation:() -> T):Null { + public static function _try(operation:() -> T, ?expectedError:EType):Null { #if !macrotest try { #end return operation(); #if !macrotest } catch (err:StreamError) { - Sys.stderr().writeString(err + "\n"); - Sys.exit(1); - return null; + function printErr() { + Sys.stderr().writeString(err + "\n"); + } + switch (expectedError) { + case EStream(message) if (message == err.message): + throw EExpected(expectedError); + case null: + printErr(); + Sys.exit(1); + return null; + default: + printErr(); + throw EUnexpected(err); + } } catch (err:KissError) { - Sys.stderr().writeString(err + "\n"); - Sys.exit(1); - return null; + function printErr() { + Sys.stderr().writeString(err + "\n"); + } + switch (expectedError) { + case EKiss(message) if (message == err.message): + throw EExpected(expectedError); + case null: + printErr(); + Sys.exit(1); + return null; + default: + printErr(); + throw EUnexpected(err); + } } catch (err:UnmatchedBracketSignal) { - Sys.stderr().writeString(Stream.toPrint(err.position) + ': Unmatched ${err.type}\n'); - Sys.exit(1); - return null; + function printErr() { + Sys.stderr().writeString(Stream.toPrint(err.position) + ': Unmatched ${err.type}\n'); + } + switch (expectedError) { + case EUnmatchedBracket(type) if (type == err.type): + throw EExpected(expectedError); + case null: + printErr(); + Sys.exit(1); + return null; + default: + printErr(); + throw EUnexpected(err); + } } catch (err:Exception) { - Sys.stderr().writeString("Error: " + err.message + "\n"); - Sys.stderr().writeString(err.stack.toString() + "\n"); - Sys.exit(1); - return null; + function printErr() { + Sys.stderr().writeString("Error: " + err.message + "\n"); + Sys.stderr().writeString(err.stack.toString() + "\n"); + } + switch (expectedError) { + case EException(message) if (message == err.message): + throw EExpected(expectedError); + case null: + printErr(); + Sys.exit(1); + return null; + default: + printErr(); + throw EUnexpected(err); + } } #end } @@ -297,10 +343,77 @@ class Kiss { } } + // This is only for testing: + public static function buildExpectingError(expectedError:ExprOf, ?kissFile:String, ?k:KissState, useClassFields = true, ?context:FrontendContext):Array { + var buildFields = Context.getBuildFields(); + var hasTestExpectedError = false; + for (field in buildFields) { + switch (field) { + case { + name: "testExpectedError", + kind: FFun({ + params: [], + expr: { + expr: EBlock([{expr: ECall({expr: EConst(CIdent("_testExpectedError"))}, [])}]) + } + }) + }: + hasTestExpectedError = true; + default: + } + } + if (!hasTestExpectedError) { + throw "When building with Kiss.buildExpectingError(), you must add this Haxe function: " + + "function testExpectedError() { _testExpectedError(); }"; + } + + var expectedError = Exprs.eval(expectedError); + var s = Std.string(expectedError); + + try { + build(kissFile, k, useClassFields, context, expectedError); + + // Build success, which is bad: + buildFields.push({ + pos: Context.currentPos(), + name: "_testExpectedError", + kind: FFun({ + args: [], + expr: macro utest.Assert.fail('Build succeeded when an error was expected: ' + $v{s}) + }) + }); + } catch (e:EType) { + switch (e) { + case EExpected(e): + buildFields.push({ + pos: Context.currentPos(), + name: "_testExpectedError", + kind: FFun({ + args: [], + expr: macro utest.Assert.pass() + }) + }); + case EUnexpected(e): + buildFields.push({ + pos: Context.currentPos(), + name: "_testExpectedError", + kind: FFun({ + args: [], + expr: macro utest.Assert.fail('Build failed in an unexpected way. Expected: ' + $v{s}) + }) + }); + default: + throw "unexpected error is neither expected nor unexpected ¯\\_(ツ)_/¯"; + } + } + + return buildFields; + } + /** Build macro: add fields to a class from a corresponding .kiss file **/ - public static function build(?kissFile:String, ?k:KissState, useClassFields = true, ?context:FrontendContext):Array { + public static function build(?kissFile:String, ?k:KissState, useClassFields = true, ?context:FrontendContext, ?expectedError:EType):Array { var classPath = Context.getPosInfos(Context.currentPos()).file; // (load... ) relative to the original file @@ -364,7 +477,7 @@ class Kiss { #end k.fieldList; - }); + }, expectedError); return result; } diff --git a/src/kiss/KissError.hx b/src/kiss/KissError.hx index e54c4f5..a906c2b 100644 --- a/src/kiss/KissError.hx +++ b/src/kiss/KissError.hx @@ -15,7 +15,7 @@ enum ErrorType { // Internal Kiss errors class KissError { var exps:List; - var message:String; + public var message(default, null):String; public function new(exps:Array, message:String) { this.exps = exps; diff --git a/src/kiss/Stream.hx b/src/kiss/Stream.hx index db796f3..60393cf 100644 --- a/src/kiss/Stream.hx +++ b/src/kiss/Stream.hx @@ -18,7 +18,7 @@ typedef Position = { class StreamError { var position:Position; - var message:String; + public var message(default,null):String; public function new(position:Position, message:String) { this.position = position; @@ -54,7 +54,7 @@ class Stream { var file = "string"; if (position != null) { file = position.file; - } + } var s = new Stream(file, content); if (position != null) { s.line = position.line; @@ -140,7 +140,7 @@ class Stream { startOfLine = false; } } - + function record() { recording += content.substr(0, count); } @@ -154,7 +154,7 @@ class Stream { record(); default: } - + content = content.substr(count); } @@ -389,7 +389,7 @@ class Stream { private var recordingType:StreamRecordType = Neither; private var recording = ""; - + public function recordTransaction(type:StreamRecordType = Both, transaction:Void->Void) { if (type == Neither) { error(this, "Tried to start recording a transaction that would always return an empty string"); diff --git a/src/test/cases/UnmatchedBracketTestCase.hx b/src/test/cases/UnmatchedBracketTestCase.hx new file mode 100644 index 0000000..a792ba1 --- /dev/null +++ b/src/test/cases/UnmatchedBracketTestCase.hx @@ -0,0 +1,12 @@ +package test.cases; + +import utest.Test; +import utest.Assert; +import kiss.Prelude; + +@:build(kiss.Kiss.buildExpectingError(kiss.EType.EUnmatchedBracket("}"))) +class UnmatchedBracketTestCase extends Test { + function testExpectedError() { + _testExpectedError(); + } +} \ No newline at end of file diff --git a/src/test/cases/UnmatchedBracketTestCase.kiss b/src/test/cases/UnmatchedBracketTestCase.kiss new file mode 100644 index 0000000..ff30235 --- /dev/null +++ b/src/test/cases/UnmatchedBracketTestCase.kiss @@ -0,0 +1 @@ +} \ No newline at end of file