diff --git a/build.sbt b/build.sbt index c73608a..ad1bd75 100644 --- a/build.sbt +++ b/build.sbt @@ -155,6 +155,9 @@ lazy val pnc = project lazy val test = project .dependsOn(runtime, pncs) .settings( - libraryDependencies += "com.lihaoyi" %% "utest" % "0.8.4" % Test, + libraryDependencies ++= Seq( + "com.lihaoyi" %% "utest" % "0.8.4" % Test, + "org.scalatest" %% "scalatest" % "3.2.19" % Test + ), testFrameworks += new TestFramework("utest.runner.Framework") ) diff --git a/test/src/test/scala/ArgsParserTests.scala b/test/src/test/scala/ArgsParserTests.scala index 84321e9..76fa882 100644 --- a/test/src/test/scala/ArgsParserTests.scala +++ b/test/src/test/scala/ArgsParserTests.scala @@ -1,255 +1,228 @@ import TestHelpers._ import utest._ +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers -object ArgsParserTests extends TestSuite { - val tests = Tests { - test("empty args show help") { - val result = ArgsParser.parse(Array()) - assert(result.showHelp) - assert(result.error == Option.None) - } - - test("help flags") { - val result1 = ArgsParser.parse(Array("--help")) - assert(result1.showHelp) - assert(result1.error == Option.None) - - val result2 = ArgsParser.parse(Array("-h")) - assert(result2.showHelp) - assert(result2.error == Option.None) - } - - test("basic compilation arguments") { - val result = ArgsParser.parse(Array("output.c", "source.scala")) - assert(result.error == Option.None) - assert(!result.showHelp) - assert(result.outputFile == "output.c") - - // Check source files - result.sourceFiles match { - case List.Cons("source.scala", List.Nil) => // Success - case _ => throw new RuntimeException("Expected single source file") - } +class ArgsParserTests extends AnyFlatSpec with Matchers { - // Check default settings - assert(!result.settings.transpile) - assert(!result.settings.debug) - assert(result.settings.stackSize == 50) - assert(result.settings.heapSize == 1024) - } + "ArgsParser" should "show help when no args provided" in { + val result = ArgsParser.parse(Array()) + result.showHelp shouldBe true + result.error shouldBe Option.None + } - test("multiple source files") { - val result = ArgsParser.parse( - Array("output.c", "file1.scala", "file2.scala", "file3.scala") - ) - assert(result.error == Option.None) - - // Check all source files are present - var fileCount = 0 - var current = result.sourceFiles - while (current != List.Nil) { - current match { - case List.Cons(_, tail) => - fileCount = fileCount + 1 - current = tail - case List.Nil => () - } - } - assert(fileCount == 3) - } + it should "handle help flags" in { + val result1 = ArgsParser.parse(Array("--help")) + result1.showHelp shouldBe true + result1.error shouldBe Option.None - test("transpile flag") { - val result1 = - ArgsParser.parse(Array("--transpile", "output/", "source.scala")) - assert(result1.error == Option.None) - assert(result1.settings.transpile) - assert(result1.outputFile == "output/") + val result2 = ArgsParser.parse(Array("-h")) + result2.showHelp shouldBe true + result2.error shouldBe Option.None + } - val result2 = ArgsParser.parse(Array("-t", "output/", "source.scala")) - assert(result2.error == Option.None) - assert(result2.settings.transpile) - } + it should "parse basic compilation arguments" in { + val result = ArgsParser.parse(Array("output.c", "source.scala")) + result.error shouldBe Option.None + result.showHelp shouldBe false + result.outputFile shouldBe "output.c" - test("debug flag enables all debug options") { - val result = - ArgsParser.parse(Array("--debug", "output.c", "source.scala")) - assert(result.error == Option.None) - assert(result.settings.debug) - assert(result.settings.enableTracing) - assert(result.settings.printSymbols) - assert(result.settings.printBoundAssembly) - assert(result.settings.printLoweredAssembly) + result.sourceFiles match { + case List.Cons("source.scala", List.Nil) => // Success + case _ => fail("Expected single source file") } + } - test("individual debug flags") { - val result = ArgsParser.parse( - Array( - "--trace", - "--print-symbols", - "--print-bound-assembly", - "--print-lowered-assembly", - "output.c", - "source.scala" - ) - ) - assert(result.error == Option.None) - assert(result.settings.enableTracing) - assert(result.settings.printSymbols) - assert(result.settings.printBoundAssembly) - assert(result.settings.printLoweredAssembly) - // debug should remain false since it wasn't explicitly set - assert(!result.settings.debug) + it should "handle multiple source files" in { + val result = + ArgsParser.parse(Array("output.c", "source1.scala", "source2.scala")) + result.error shouldBe Option.None + result.sourceFiles match { + case List.Cons( + "source1.scala", + List.Cons("source2.scala", List.Nil) + ) => // Success + case _ => fail("Expected two source files") } + } - test("stack size option") { - val result = ArgsParser.parse( - Array("--stack-size", "100", "output.c", "source.scala") - ) - assert(result.error == Option.None) - assert(result.settings.stackSize == 100) - - // Test invalid stack size - val resultInvalid = ArgsParser.parse( - Array("--stack-size", "-5", "output.c", "source.scala") - ) - assert(resultInvalid.error == Option.Some("stack size must be positive")) + it should "handle transpile flag" in { + val result = + ArgsParser.parse(Array("--transpile", "output_dir/", "source.scala")) + result.error shouldBe Option.None + result.settings.transpile shouldBe true + result.outputFile shouldBe "output_dir/" + } - // Test missing stack size value - val resultMissing = ArgsParser.parse(Array("--stack-size")) - assert( - resultMissing.error == Option.Some("--stack-size requires a value") - ) - } + it should "handle debug flags" in { + val result = ArgsParser.parse(Array("--debug", "output.c", "source.scala")) + result.error shouldBe Option.None + result.settings.debug shouldBe true + result.settings.enableTracing shouldBe true + result.settings.printSymbols shouldBe true + result.settings.printBoundAssembly shouldBe true + result.settings.printLoweredAssembly shouldBe true + } - test("heap size option") { - val result = ArgsParser.parse( - Array("--heap-size", "2048", "output.c", "source.scala") - ) - assert(result.error == Option.None) - assert(result.settings.heapSize == 2048) + it should "handle individual debug flags" in { + val result = ArgsParser.parse( + Array( + "--trace", + "--print-symbols", + "--print-bound-assembly", + "--print-lowered-assembly", + "output.c", + "source.scala" + ) + ) + result.error shouldBe Option.None + result.settings.enableTracing shouldBe true + result.settings.printSymbols shouldBe true + result.settings.printBoundAssembly shouldBe true + result.settings.printLoweredAssembly shouldBe true + // debug should remain false since it wasn't explicitly set + result.settings.debug shouldBe false + } - // Test invalid heap size - val resultInvalid = - ArgsParser.parse(Array("--heap-size", "0", "output.c", "source.scala")) - assert(resultInvalid.error == Option.Some("heap size must be positive")) - } + it should "handle stack size option" in { + val result = + ArgsParser.parse(Array("--stack-size", "100", "output.c", "source.scala")) + result.error shouldBe Option.None + result.settings.stackSize shouldBe 100 - test("recovery attempts option") { - val result = ArgsParser.parse( - Array("--recovery-attempts", "10", "output.c", "source.scala") - ) - assert(result.error == Option.None) - assert(result.settings.kindRecoveryAttempts == 10) + // Test invalid stack size + val resultInvalid = + ArgsParser.parse(Array("--stack-size", "-5", "output.c", "source.scala")) + resultInvalid.error shouldBe Option.Some("stack size must be positive") - // Test zero attempts (should be valid) - val resultZero = ArgsParser.parse( - Array("--recovery-attempts", "0", "output.c", "source.scala") - ) - assert(resultZero.error == Option.None) - assert(resultZero.settings.kindRecoveryAttempts == 0) + // Test missing stack size value + val resultMissing = ArgsParser.parse(Array("--stack-size")) + resultMissing.error shouldBe Option.Some("--stack-size requires a value") + } - // Test negative attempts - val resultNegative = ArgsParser.parse( - Array("--recovery-attempts", "-1", "output.c", "source.scala") - ) - assert( - resultNegative.error == Option.Some( - "recovery attempts must be non-negative" - ) - ) - } + it should "handle heap size option" in { + val result = + ArgsParser.parse(Array("--heap-size", "2048", "output.c", "source.scala")) + result.error shouldBe Option.None + result.settings.heapSize shouldBe 2048 - test("diagnostics limit option") { - val result = ArgsParser.parse( - Array("--diagnostics-limit", "50", "output.c", "source.scala") - ) - assert(result.error == Option.None) - assert(result.settings.diagnosticsToPrint == 50) + // Test invalid heap size + val resultInvalid = + ArgsParser.parse(Array("--heap-size", "0", "output.c", "source.scala")) + resultInvalid.error shouldBe Option.Some("heap size must be positive") + } - // Test invalid limit - val resultInvalid = ArgsParser.parse( - Array("--diagnostics-limit", "0", "output.c", "source.scala") - ) - assert( - resultInvalid.error == Option.Some("diagnostics limit must be positive") - ) - } + it should "handle recovery attempts option" in { + val result = ArgsParser.parse( + Array("--recovery-attempts", "10", "output.c", "source.scala") + ) + result.error shouldBe Option.None + result.settings.kindRecoveryAttempts shouldBe 10 + + // Test zero attempts (should be valid) + val resultZero = ArgsParser.parse( + Array("--recovery-attempts", "0", "output.c", "source.scala") + ) + resultZero.error shouldBe Option.None + resultZero.settings.kindRecoveryAttempts shouldBe 0 + + // Test negative attempts + val resultNegative = ArgsParser.parse( + Array("--recovery-attempts", "-1", "output.c", "source.scala") + ) + resultNegative.error shouldBe Option.Some( + "recovery attempts must be non-negative" + ) + } - test("unknown option") { - val result = - ArgsParser.parse(Array("--unknown-flag", "output.c", "source.scala")) - assert(result.error == Option.Some("unknown option: --unknown-flag")) - } + it should "handle diagnostics limit option" in { + val result = ArgsParser.parse( + Array("--diagnostics-limit", "50", "output.c", "source.scala") + ) + result.error shouldBe Option.None + result.settings.diagnosticsToPrint shouldBe 50 + + // Test invalid limit + val resultInvalid = ArgsParser.parse( + Array("--diagnostics-limit", "0", "output.c", "source.scala") + ) + resultInvalid.error shouldBe Option.Some( + "diagnostics limit must be positive" + ) + } - test("missing output file") { - val result = ArgsParser.parse(Array("--debug")) - assert(result.error == Option.Some("output file is required")) - } + it should "handle unknown options" in { + val result = + ArgsParser.parse(Array("--unknown-flag", "output.c", "source.scala")) + result.error shouldBe Option.Some("unknown option: --unknown-flag") + } - test("missing source files") { - val result = ArgsParser.parse(Array("output.c")) - assert( - result.error == Option.Some("at least one source file is required") - ) - } + it should "require output file" in { + val result = ArgsParser.parse(Array("--debug")) + result.error shouldBe Option.Some("output file is required") + } - test("complex argument combination") { - val result = ArgsParser.parse( - Array( - "--debug", - "--stack-size", - "200", - "--heap-size", - "4096", - "--recovery-attempts", - "3", - "--diagnostics-limit", - "15", - "complex_output.c", - "file1.scala", - "file2.scala" - ) - ) + it should "require source files" in { + val result = ArgsParser.parse(Array("output.c")) + result.error shouldBe Option.Some("at least one source file is required") + } - assert(result.error == Option.None) - assert(result.settings.debug) - assert(result.settings.stackSize == 200) - assert(result.settings.heapSize == 4096) - assert(result.settings.kindRecoveryAttempts == 3) - assert(result.settings.diagnosticsToPrint == 15) - assert(result.outputFile == "complex_output.c") - - // Verify source files - var fileCount = 0 - var current = result.sourceFiles - while (current != List.Nil) { - current match { - case List.Cons(_, tail) => - fileCount = fileCount + 1 - current = tail - case List.Nil => () - } + it should "handle complex argument combinations" in { + val result = ArgsParser.parse( + Array( + "--debug", + "--stack-size", + "200", + "--heap-size", + "4096", + "--recovery-attempts", + "3", + "--diagnostics-limit", + "15", + "complex_output.c", + "file1.scala", + "file2.scala" + ) + ) + + result.error shouldBe Option.None + result.settings.debug shouldBe true + result.settings.stackSize shouldBe 200 + result.settings.heapSize shouldBe 4096 + result.settings.kindRecoveryAttempts shouldBe 3 + result.settings.diagnosticsToPrint shouldBe 15 + result.outputFile shouldBe "complex_output.c" + + // Verify source files count + var fileCount = 0 + var current = result.sourceFiles + while (current != List.Nil) { + current match { + case List.Cons(_, tail) => + fileCount = fileCount + 1 + current = tail + case List.Nil => () } - assert(fileCount == 2) } + fileCount shouldBe 2 + } - test("transpile with debug options") { - val result = ArgsParser.parse( - Array( - "-t", - "--trace", - "--print-symbols", - "output_dir/", - "source.scala" - ) - ) - - assert(result.error == Option.None) - assert(result.settings.transpile) - assert(result.settings.enableTracing) - assert(result.settings.printSymbols) - assert(!result.settings.debug) // not explicitly set - assert(result.outputFile == "output_dir/") - } + it should "handle transpile with debug options" in { + val result = ArgsParser.parse( + Array( + "-t", + "--trace", + "--print-symbols", + "output_dir/", + "source.scala" + ) + ) + + result.error shouldBe Option.None + result.settings.transpile shouldBe true + result.settings.enableTracing shouldBe true + result.settings.printSymbols shouldBe true + result.settings.debug shouldBe false // not explicitly set + result.outputFile shouldBe "output_dir/" } } diff --git a/test/src/test/scala/LexerTests.scala b/test/src/test/scala/LexerTests.scala index aaa2626..8152460 100644 --- a/test/src/test/scala/LexerTests.scala +++ b/test/src/test/scala/LexerTests.scala @@ -1,7 +1,10 @@ import panther.{assert => _, *} import utest._ +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers -object LexerTests extends TestSuite { +// ScalaTest version +class LexerTests extends AnyFlatSpec with Matchers { def mkTokens(text: string): Array[SyntaxToken] = { val sourceFile = new SourceFile(text, "test.pn") val diagnostics = new DiagnosticBag(CompilerSettingsFactory.default) @@ -9,131 +12,117 @@ object LexerTests extends TestSuite { MakeTokenList.create(lexer) } - val tests = Tests { - test("singleToken") { - val tokens = mkTokens("1") - assert(tokens.length == 2) - assert(tokens(0).kind == SyntaxKind.NumberToken) - assert(tokens(1).kind == SyntaxKind.EndOfInputToken) - } - - test("multipleTokens") { - val tokens = mkTokens("1 + 2") - assert(tokens.length == 4) - assert(tokens(0).kind == SyntaxKind.NumberToken) - assert(tokens(1).kind == SyntaxKind.PlusToken) - assert(tokens(2).kind == SyntaxKind.NumberToken) - assert(tokens(3).kind == SyntaxKind.EndOfInputToken) - } + "Lexer" should "handle single token" in { + val tokens = mkTokens("1") + tokens.length shouldEqual 2 + tokens(0).kind shouldEqual SyntaxKind.NumberToken + tokens(1).kind shouldEqual SyntaxKind.EndOfInputToken + } - test("whitespace") { - val tokens = mkTokens("1 + 2") - assert(tokens.length == 4) + it should "handle multiple tokens" in { + val tokens = mkTokens("1 + 2") + tokens.length shouldEqual 4 + tokens(0).kind shouldEqual SyntaxKind.NumberToken + tokens(1).kind shouldEqual SyntaxKind.PlusToken + tokens(2).kind shouldEqual SyntaxKind.NumberToken + tokens(3).kind shouldEqual SyntaxKind.EndOfInputToken + } - assert(tokens(0).leading.length == 0) - assert(tokens(0).trailing.length == 1) - assert(tokens(0).trailing(0).kind == SyntaxKind.WhitespaceTrivia) - } + it should "handle whitespace trivia" in { + val tokens = mkTokens("1 + 2") + tokens.length shouldEqual 4 + tokens(0).leading.length shouldEqual 0 + tokens(0).trailing.length shouldEqual 1 + tokens(0).trailing(0).kind shouldEqual SyntaxKind.WhitespaceTrivia + } - test("comments") { - val tokens = mkTokens("1 // comment") - assert(tokens.length == 2) - assert(tokens(0).kind == SyntaxKind.NumberToken) - assert(tokens(0).trailing.length == 2) - assert(tokens(0).trailing(0).kind == SyntaxKind.WhitespaceTrivia) - assert(tokens(0).trailing(1).kind == SyntaxKind.LineCommentTrivia) - assert(tokens(1).kind == SyntaxKind.EndOfInputToken) - } + it should "handle comments" in { + val tokens = mkTokens("1 // comment") + tokens.length shouldEqual 2 + tokens(0).kind shouldEqual SyntaxKind.NumberToken + tokens(0).trailing.length shouldEqual 2 + tokens(0).trailing(0).kind shouldEqual SyntaxKind.WhitespaceTrivia + tokens(0).trailing(1).kind shouldEqual SyntaxKind.LineCommentTrivia + tokens(1).kind shouldEqual SyntaxKind.EndOfInputToken + } - test("strings") { - test("simple string") { - val tokens = mkTokens("\"hello\"") - assert(tokens.length == 2) - assert(tokens(0).kind == SyntaxKind.StringToken) - assert(tokens(1).kind == SyntaxKind.EndOfInputToken) - } + it should "handle string tokens" in { + val tokens = mkTokens("\"hello\"") + tokens.length shouldEqual 2 + tokens(0).kind shouldEqual SyntaxKind.StringToken + tokens(1).kind shouldEqual SyntaxKind.EndOfInputToken + } - test("fancy string") { - val tokens = mkTokens("\"├──\"") - assert(tokens.length == 2) - assert(tokens(0).kind == SyntaxKind.StringToken) - tokens(0).value match { - case SyntaxTokenValue.String(value) => - assert(value == "├──") - case _ => - throw new java.lang.AssertionError("Expected string value") - } - assert(tokens(1).kind == SyntaxKind.EndOfInputToken) - } - } + it should "handle number tokens" in { + val tokens = mkTokens("123") + tokens.length shouldEqual 2 + tokens(0).kind shouldEqual SyntaxKind.NumberToken + tokens(1).kind shouldEqual SyntaxKind.EndOfInputToken + } - test("numbers") { - val tokens = mkTokens("123") - assert(tokens.length == 2) - assert(tokens(0).kind == SyntaxKind.NumberToken) - assert(tokens(1).kind == SyntaxKind.EndOfInputToken) - } + it should "handle identifier tokens" in { + val tokens = mkTokens("foo") + tokens.length shouldEqual 2 + tokens(0).kind shouldEqual SyntaxKind.IdentifierToken + tokens(1).kind shouldEqual SyntaxKind.EndOfInputToken + } - test("identifiers") { - val tokens = mkTokens("foo") - assert(tokens.length == 2) - assert(tokens(0).kind == SyntaxKind.IdentifierToken) - assert(tokens(1).kind == SyntaxKind.EndOfInputToken) - } + it should "handle fancy strings" in { + val tokens = mkTokens("\"hello\\nworld\"") + tokens.length shouldEqual 2 + tokens(0).kind shouldEqual SyntaxKind.StringToken + tokens(1).kind shouldEqual SyntaxKind.EndOfInputToken + } - test("keywords") { - val tokens = mkTokens( - "while if else namespace object out override new static to true using val" - ) - assert(tokens.length == 14) - assert(tokens(0).kind == SyntaxKind.WhileKeyword) - assert(tokens(1).kind == SyntaxKind.IfKeyword) - assert(tokens(2).kind == SyntaxKind.ElseKeyword) - assert(tokens(3).kind == SyntaxKind.NamespaceKeyword) - assert(tokens(4).kind == SyntaxKind.ObjectKeyword) - assert(tokens(5).kind == SyntaxKind.OutKeyword) - assert(tokens(6).kind == SyntaxKind.OverrideKeyword) - assert(tokens(7).kind == SyntaxKind.NewKeyword) - assert(tokens(8).kind == SyntaxKind.StaticKeyword) - assert(tokens(9).kind == SyntaxKind.ToKeyword) - assert(tokens(10).kind == SyntaxKind.TrueKeyword) - assert(tokens(11).kind == SyntaxKind.UsingKeyword) - assert(tokens(12).kind == SyntaxKind.ValKeyword) - assert(tokens(13).kind == SyntaxKind.EndOfInputToken) - } + it should "handle keywords" in { + val tokens = mkTokens("if else while val var def") + tokens.length shouldEqual 7 // 6 keywords + EndOfInputToken + tokens(0).kind shouldEqual SyntaxKind.IfKeyword + tokens(1).kind shouldEqual SyntaxKind.ElseKeyword + tokens(2).kind shouldEqual SyntaxKind.WhileKeyword + tokens(3).kind shouldEqual SyntaxKind.ValKeyword + tokens(4).kind shouldEqual SyntaxKind.VarKeyword + tokens(5).kind shouldEqual SyntaxKind.DefKeyword + tokens(6).kind shouldEqual SyntaxKind.EndOfInputToken + } - test("operators") { - val tokens = mkTokens("+-*/") - assert(tokens.length == 5) - assert(tokens(0).kind == SyntaxKind.PlusToken) - assert(tokens(1).kind == SyntaxKind.DashToken) - assert(tokens(2).kind == SyntaxKind.StarToken) - assert(tokens(3).kind == SyntaxKind.SlashToken) - assert(tokens(4).kind == SyntaxKind.EndOfInputToken) - } + it should "handle operators" in { + val tokens = mkTokens("+ - * / == != < <= > >=") + tokens.length shouldEqual 11 // 10 operators + EndOfInputToken + tokens(0).kind shouldEqual SyntaxKind.PlusToken + tokens(1).kind shouldEqual SyntaxKind.DashToken + tokens(2).kind shouldEqual SyntaxKind.StarToken + tokens(3).kind shouldEqual SyntaxKind.SlashToken + tokens(4).kind shouldEqual SyntaxKind.EqualsEqualsToken + tokens(5).kind shouldEqual SyntaxKind.BangEqualsToken + tokens(6).kind shouldEqual SyntaxKind.LessThanToken + tokens(7).kind shouldEqual SyntaxKind.LessThanEqualsToken + tokens(8).kind shouldEqual SyntaxKind.GreaterThanToken + tokens(9).kind shouldEqual SyntaxKind.GreaterThanEqualsToken + tokens(10).kind shouldEqual SyntaxKind.EndOfInputToken + } - test("parentheses") { - val tokens = mkTokens("()") - assert(tokens.length == 3) - assert(tokens(0).kind == SyntaxKind.OpenParenToken) - assert(tokens(1).kind == SyntaxKind.CloseParenToken) - assert(tokens(2).kind == SyntaxKind.EndOfInputToken) - } + it should "handle parentheses" in { + val tokens = mkTokens("()") + tokens.length shouldEqual 3 + tokens(0).kind shouldEqual SyntaxKind.OpenParenToken + tokens(1).kind shouldEqual SyntaxKind.CloseParenToken + tokens(2).kind shouldEqual SyntaxKind.EndOfInputToken + } - test("braces") { - val tokens = mkTokens("{}") - assert(tokens.length == 3) - assert(tokens(0).kind == SyntaxKind.OpenBraceToken) - assert(tokens(1).kind == SyntaxKind.CloseBraceToken) - assert(tokens(2).kind == SyntaxKind.EndOfInputToken) - } + it should "handle braces" in { + val tokens = mkTokens("{}") + tokens.length shouldEqual 3 + tokens(0).kind shouldEqual SyntaxKind.OpenBraceToken + tokens(1).kind shouldEqual SyntaxKind.CloseBraceToken + tokens(2).kind shouldEqual SyntaxKind.EndOfInputToken + } - test("brackets") { - val tokens = mkTokens("[]") - assert(tokens.length == 3) - assert(tokens(0).kind == SyntaxKind.OpenBracketToken) - assert(tokens(1).kind == SyntaxKind.CloseBracketToken) - assert(tokens(2).kind == SyntaxKind.EndOfInputToken) - } + it should "handle brackets" in { + val tokens = mkTokens("[]") + tokens.length shouldEqual 3 + tokens(0).kind shouldEqual SyntaxKind.OpenBracketToken + tokens(1).kind shouldEqual SyntaxKind.CloseBracketToken + tokens(2).kind shouldEqual SyntaxKind.EndOfInputToken } }