diff --git a/test/src/test/scala/ArgsParserTests.scala b/test/src/test/scala/ArgsParserTests.scala index b9bf64f..bc3edc8 100644 --- a/test/src/test/scala/ArgsParserTests.scala +++ b/test/src/test/scala/ArgsParserTests.scala @@ -1,227 +1,236 @@ import TestHelpers._ -import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers -class ArgsParserTests extends AnyFlatSpec with Matchers { +class ArgsParserTests extends AnyFunSpec with Matchers { - "ArgsParser" should "show help when no args provided" in { - val result = ArgsParser.parse(Array()) - result.showHelp shouldBe true - result.error shouldBe Option.None - } + describe("ArgsParser") { + it("should show help when no args provided") { + val result = ArgsParser.parse(Array()) + result.showHelp shouldBe true + result.error shouldBe Option.None + } - it should "handle help flags" in { - val result1 = ArgsParser.parse(Array("--help")) - result1.showHelp shouldBe true - result1.error shouldBe Option.None + it("should handle help flags") { + val result1 = ArgsParser.parse(Array("--help")) + result1.showHelp shouldBe true + result1.error shouldBe Option.None - val result2 = ArgsParser.parse(Array("-h")) - result2.showHelp shouldBe true - result2.error shouldBe Option.None - } + val result2 = ArgsParser.parse(Array("-h")) + result2.showHelp shouldBe true + result2.error shouldBe Option.None + } - 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" + it("should parse basic compilation arguments") { + val result = ArgsParser.parse(Array("output.c", "source.scala")) + result.error shouldBe Option.None + result.showHelp shouldBe false + result.outputFile shouldBe "output.c" - result.sourceFiles match { - case List.Cons("source.scala", List.Nil) => // Success - case _ => fail("Expected single source file") + result.sourceFiles match { + case List.Cons("source.scala", List.Nil) => // Success + case _ => fail("Expected single source file") + } } - } - 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") + it("should handle multiple source files") { + 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") + } } - } - 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/" - } + it("should handle transpile flag") { + 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/" + } - 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 - } + it("should handle debug flags") { + 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 + } - 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" + it("should handle individual debug flags") { + 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 - } + 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 + } - 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 + it("should handle stack size option") { + val result = + ArgsParser.parse( + Array("--stack-size", "100", "output.c", "source.scala") + ) + result.error shouldBe Option.None + result.settings.stackSize shouldBe 100 + + // 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 missing stack size value + val resultMissing = ArgsParser.parse(Array("--stack-size")) + resultMissing.error shouldBe Option.Some("--stack-size requires a value") + } - // 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") + it("should handle heap size option") { + val result = + ArgsParser.parse( + Array("--heap-size", "2048", "output.c", "source.scala") + ) + result.error shouldBe Option.None + result.settings.heapSize shouldBe 2048 + + // 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 missing stack size value - val resultMissing = ArgsParser.parse(Array("--stack-size")) - resultMissing.error shouldBe Option.Some("--stack-size requires a value") - } + it("should handle recovery attempts option") { + val result = ArgsParser.parse( + Array("--recovery-attempts", "10", "output.c", "source.scala") + ) + result.error shouldBe Option.None + result.settings.kindRecoveryAttempts shouldBe 10 - 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 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 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 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" + ) + } - 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" - ) - } + it("should handle diagnostics limit option") { + val result = ArgsParser.parse( + Array("--diagnostics-limit", "50", "output.c", "source.scala") + ) + result.error shouldBe Option.None + result.settings.diagnosticsToPrint shouldBe 50 - 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 invalid limit + val resultInvalid = ArgsParser.parse( + Array("--diagnostics-limit", "0", "output.c", "source.scala") + ) + resultInvalid.error shouldBe Option.Some( + "diagnostics limit must be positive" + ) + } - 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") - } + it("should handle unknown options") { + val result = + ArgsParser.parse(Array("--unknown-flag", "output.c", "source.scala")) + result.error shouldBe Option.Some("unknown option: --unknown-flag") + } - it should "require output file" in { - val result = ArgsParser.parse(Array("--debug")) - result.error shouldBe Option.Some("output file is required") - } + it("should require output file") { + val result = ArgsParser.parse(Array("--debug")) + result.error shouldBe Option.Some("output file is required") + } - 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") - } + it("should require source files") { + val result = ArgsParser.parse(Array("output.c")) + result.error shouldBe Option.Some("at least one source file is required") + } - 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" + it("should handle complex argument combinations") { + 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 => () + + 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 => () + } } + fileCount shouldBe 2 } - fileCount shouldBe 2 - } - it should "handle transpile with debug options" in { - val result = ArgsParser.parse( - Array( - "-t", - "--trace", - "--print-symbols", - "output_dir/", - "source.scala" + it("should handle transpile with debug options") { + 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/" + + 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/BinderTests.scala b/test/src/test/scala/BinderTests.scala index 7121592..40e0e4d 100644 --- a/test/src/test/scala/BinderTests.scala +++ b/test/src/test/scala/BinderTests.scala @@ -1,263 +1,265 @@ import TestHelpers.* -import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers -class BinderTests extends AnyFlatSpec with Matchers { +class BinderTests extends AnyFunSpec with Matchers { - "Binder" should "create builtin symbols" in { - val comp = mkCompilation("") - val symbols = enumSymbols(comp) - assertSymbol(symbols, SymbolKind.Class, "any") - assertSymbol(symbols, SymbolKind.Class, "int") - assertConversionMethod(symbols) - assertSymbol(symbols, SymbolKind.Class, "string") - assertSymbol(symbols, SymbolKind.Field, "length") - assertConversionMethod(symbols) - assertSymbol(symbols, SymbolKind.Class, "bool") - assertConversionMethod(symbols) - assertSymbol(symbols, SymbolKind.Class, "char") - assertConversionMethod(symbols) - assertSymbol(symbols, SymbolKind.Class, "unit") - assertSymbol(symbols, SymbolKind.Class, "Array") - assertSymbol(symbols, SymbolKind.TypeParameter(Variance.Invariant), "T") - assertSymbol(symbols, SymbolKind.Constructor, ".ctor") - assertSymbol(symbols, SymbolKind.Parameter, "size") - assertSymbol(symbols, SymbolKind.Field, "length") - assertSymbol(symbols, SymbolKind.Method, "apply") - assertSymbol(symbols, SymbolKind.Parameter, "index") - // assertSymbol(symbols, SymbolKind.Object, "predef") - assertSymbol(symbols, SymbolKind.Method, "println") - assertSymbol(symbols, SymbolKind.Parameter, "message") - assertSymbol(symbols, SymbolKind.Method, "print") - assertSymbol(symbols, SymbolKind.Parameter, "message") - assertSymbol(symbols, SymbolKind.Method, "panic") - assertSymbol(symbols, SymbolKind.Parameter, "message") - assertSymbol(symbols, SymbolKind.Method, "assert") - assertSymbol(symbols, SymbolKind.Parameter, "condition") - assertSymbol(symbols, SymbolKind.Parameter, "message") - assertSymbol(symbols, SymbolKind.Method, "mod") - assertSymbol(symbols, SymbolKind.Parameter, "a") - assertSymbol(symbols, SymbolKind.Parameter, "b") - assertSymbol(symbols, SymbolKind.Object, "$Program") - assertSymbol(symbols, SymbolKind.Method, "$runtimeInit") - assertSymbol(symbols, SymbolKind.Method, "main") - assertNoSymbols(symbols) - } + describe("Binder") { + it("should create builtin symbols") { + val comp = mkCompilation("") + val symbols = enumSymbols(comp) + assertSymbol(symbols, SymbolKind.Class, "any") + assertSymbol(symbols, SymbolKind.Class, "int") + assertConversionMethod(symbols) + assertSymbol(symbols, SymbolKind.Class, "string") + assertSymbol(symbols, SymbolKind.Field, "length") + assertConversionMethod(symbols) + assertSymbol(symbols, SymbolKind.Class, "bool") + assertConversionMethod(symbols) + assertSymbol(symbols, SymbolKind.Class, "char") + assertConversionMethod(symbols) + assertSymbol(symbols, SymbolKind.Class, "unit") + assertSymbol(symbols, SymbolKind.Class, "Array") + assertSymbol(symbols, SymbolKind.TypeParameter(Variance.Invariant), "T") + assertSymbol(symbols, SymbolKind.Constructor, ".ctor") + assertSymbol(symbols, SymbolKind.Parameter, "size") + assertSymbol(symbols, SymbolKind.Field, "length") + assertSymbol(symbols, SymbolKind.Method, "apply") + assertSymbol(symbols, SymbolKind.Parameter, "index") + // assertSymbol(symbols, SymbolKind.Object, "predef") + assertSymbol(symbols, SymbolKind.Method, "println") + assertSymbol(symbols, SymbolKind.Parameter, "message") + assertSymbol(symbols, SymbolKind.Method, "print") + assertSymbol(symbols, SymbolKind.Parameter, "message") + assertSymbol(symbols, SymbolKind.Method, "panic") + assertSymbol(symbols, SymbolKind.Parameter, "message") + assertSymbol(symbols, SymbolKind.Method, "assert") + assertSymbol(symbols, SymbolKind.Parameter, "condition") + assertSymbol(symbols, SymbolKind.Parameter, "message") + assertSymbol(symbols, SymbolKind.Method, "mod") + assertSymbol(symbols, SymbolKind.Parameter, "a") + assertSymbol(symbols, SymbolKind.Parameter, "b") + assertSymbol(symbols, SymbolKind.Object, "$Program") + assertSymbol(symbols, SymbolKind.Method, "$runtimeInit") + assertSymbol(symbols, SymbolKind.Method, "main") + assertNoSymbols(symbols) + } - it should "bind top level fields" in { - val comp = mkCompilation("val x = 12") - val symbols = enumNonBuiltinSymbols(comp) - assertProgramSymbol(symbols) - assertSymbol(symbols, SymbolKind.Field, "x") - assertSymbol(symbols, SymbolKind.Method, "main") - assertNoSymbols(symbols) - } + it("should bind top level fields") { + val comp = mkCompilation("val x = 12") + val symbols = enumNonBuiltinSymbols(comp) + assertProgramSymbol(symbols) + assertSymbol(symbols, SymbolKind.Field, "x") + assertSymbol(symbols, SymbolKind.Method, "main") + assertNoSymbols(symbols) + } - it should "bind methods" in { - val comp = mkCompilation("def foo() = 12") - val symbols = enumNonBuiltinSymbols(comp) + it("should bind methods") { + val comp = mkCompilation("def foo() = 12") + val symbols = enumNonBuiltinSymbols(comp) - assertProgramSymbol(symbols) - assertSymbol(symbols, SymbolKind.Method, "foo") - assertMainSymbol(symbols) - assertNoSymbols(symbols) - } + assertProgramSymbol(symbols) + assertSymbol(symbols, SymbolKind.Method, "foo") + assertMainSymbol(symbols) + assertNoSymbols(symbols) + } - it should "bind classes without args" in { - val comp = mkCompilation("class Foo()") - val symbols = enumNonBuiltinSymbols(comp) - assertSymbol(symbols, SymbolKind.Class, "Foo") - assertSymbol(symbols, SymbolKind.Constructor, ".ctor") - assertProgramSymbol(symbols) - assertMainSymbol(symbols) - assertNoSymbols(symbols) - } + it("should bind classes without args") { + val comp = mkCompilation("class Foo()") + val symbols = enumNonBuiltinSymbols(comp) + assertSymbol(symbols, SymbolKind.Class, "Foo") + assertSymbol(symbols, SymbolKind.Constructor, ".ctor") + assertProgramSymbol(symbols) + assertMainSymbol(symbols) + assertNoSymbols(symbols) + } - it should "bind classes with args" in { - val comp = mkCompilation("class Foo(x: int, y: int)") - val symbols = enumNonBuiltinSymbols(comp) - assertSymbol(symbols, SymbolKind.Class, "Foo") - assertSymbol(symbols, SymbolKind.Field, "y") - assertSymbol(symbols, SymbolKind.Field, "x") - assertSymbol(symbols, SymbolKind.Constructor, ".ctor") - assertSymbol(symbols, SymbolKind.Parameter, "x") - assertSymbol(symbols, SymbolKind.Parameter, "y") + it("should bind classes with args") { + val comp = mkCompilation("class Foo(x: int, y: int)") + val symbols = enumNonBuiltinSymbols(comp) + assertSymbol(symbols, SymbolKind.Class, "Foo") + assertSymbol(symbols, SymbolKind.Field, "y") + assertSymbol(symbols, SymbolKind.Field, "x") + assertSymbol(symbols, SymbolKind.Constructor, ".ctor") + assertSymbol(symbols, SymbolKind.Parameter, "x") + assertSymbol(symbols, SymbolKind.Parameter, "y") - assertProgramSymbol(symbols) - assertMainSymbol(symbols) + assertProgramSymbol(symbols) + assertMainSymbol(symbols) - assertNoSymbols(symbols) - } + assertNoSymbols(symbols) + } - it should "bind class fields" in { - val comp = mkCompilation( - "class Foo() {\n" + - " var z = 0\n" + - "}" - ) + it("should bind class fields") { + val comp = mkCompilation( + "class Foo() {\n" + + " var z = 0\n" + + "}" + ) - val symbols = enumNonBuiltinSymbols(comp) - assertSymbol(symbols, SymbolKind.Class, "Foo") - assertSymbol(symbols, SymbolKind.Field, "z") - assertSymbol(symbols, SymbolKind.Constructor, ".ctor") - } + val symbols = enumNonBuiltinSymbols(comp) + assertSymbol(symbols, SymbolKind.Class, "Foo") + assertSymbol(symbols, SymbolKind.Field, "z") + assertSymbol(symbols, SymbolKind.Constructor, ".ctor") + } - it should "bind enums without args" in { - val comp = mkCompilation( - "enum Foo {\n" + - " case Bar\n" + - " case Baz\n" + - "}" - ) - val symbols = enumNonBuiltinSymbols(comp) - assertSymbol(symbols, SymbolKind.Alias, "Foo") - assertSymbol(symbols, SymbolKind.Class, "Bar") - assertSymbol(symbols, SymbolKind.Class, "Baz") + it("should bind enums without args") { + val comp = mkCompilation( + "enum Foo {\n" + + " case Bar\n" + + " case Baz\n" + + "}" + ) + val symbols = enumNonBuiltinSymbols(comp) + assertSymbol(symbols, SymbolKind.Alias, "Foo") + assertSymbol(symbols, SymbolKind.Class, "Bar") + assertSymbol(symbols, SymbolKind.Class, "Baz") - assertProgramSymbol(symbols) - assertMainSymbol(symbols) + assertProgramSymbol(symbols) + assertMainSymbol(symbols) - assertNoSymbols(symbols) - } + assertNoSymbols(symbols) + } - it should "bind enums with args" in { - val comp = mkCompilation( - "enum Foo {\n" + - " case Bar(x: int)\n" + - " case Baz(y: int)\n" + - "}" - ) - val symbols = enumNonBuiltinSymbols(comp) - assertSymbol(symbols, SymbolKind.Alias, "Foo") - assertSymbol(symbols, SymbolKind.Class, "Bar") - assertSymbol(symbols, SymbolKind.Field, "x") - assertSymbol(symbols, SymbolKind.Constructor, ".ctor") - assertSymbol(symbols, SymbolKind.Parameter, "x") + it("should bind enums with args") { + val comp = mkCompilation( + "enum Foo {\n" + + " case Bar(x: int)\n" + + " case Baz(y: int)\n" + + "}" + ) + val symbols = enumNonBuiltinSymbols(comp) + assertSymbol(symbols, SymbolKind.Alias, "Foo") + assertSymbol(symbols, SymbolKind.Class, "Bar") + assertSymbol(symbols, SymbolKind.Field, "x") + assertSymbol(symbols, SymbolKind.Constructor, ".ctor") + assertSymbol(symbols, SymbolKind.Parameter, "x") - assertSymbol(symbols, SymbolKind.Class, "Baz") - assertSymbol(symbols, SymbolKind.Field, "y") - assertSymbol(symbols, SymbolKind.Constructor, ".ctor") - assertSymbol(symbols, SymbolKind.Parameter, "y") + assertSymbol(symbols, SymbolKind.Class, "Baz") + assertSymbol(symbols, SymbolKind.Field, "y") + assertSymbol(symbols, SymbolKind.Constructor, ".ctor") + assertSymbol(symbols, SymbolKind.Parameter, "y") - assertProgramSymbol(symbols) - assertMainSymbol(symbols) + assertProgramSymbol(symbols) + assertMainSymbol(symbols) - assertNoSymbols(symbols) - } + assertNoSymbols(symbols) + } - it should "create locals for extract patterns with variables" in { - val comp = mkCompilation( - "enum Option[T] {\n" + - " case Some(value: T)\n" + - " case None\n" + - "}\n" + - "val opt = Option.Some(42)\n" + - "val result = opt match {\n" + - " case Option.Some(value) => value\n" + - " case Option.None => 0\n" + - "}" - ) - val symbols = enumNonBuiltinSymbols(comp) - assertSymbol(symbols, SymbolKind.Alias, "Option") - assertSymbol(symbols, SymbolKind.TypeParameter(Variance.Invariant), "T") - assertSymbol(symbols, SymbolKind.Class, "Some") - assertSymbol(symbols, SymbolKind.Field, "value") - assertSymbol(symbols, SymbolKind.Constructor, ".ctor") - assertSymbol(symbols, SymbolKind.Parameter, "value") + it("should create locals for extract patterns with variables") { + val comp = mkCompilation( + "enum Option[T] {\n" + + " case Some(value: T)\n" + + " case None\n" + + "}\n" + + "val opt = Option.Some(42)\n" + + "val result = opt match {\n" + + " case Option.Some(value) => value\n" + + " case Option.None => 0\n" + + "}" + ) + val symbols = enumNonBuiltinSymbols(comp) + assertSymbol(symbols, SymbolKind.Alias, "Option") + assertSymbol(symbols, SymbolKind.TypeParameter(Variance.Invariant), "T") + assertSymbol(symbols, SymbolKind.Class, "Some") + assertSymbol(symbols, SymbolKind.Field, "value") + assertSymbol(symbols, SymbolKind.Constructor, ".ctor") + assertSymbol(symbols, SymbolKind.Parameter, "value") - assertSymbol(symbols, SymbolKind.Class, "None") + assertSymbol(symbols, SymbolKind.Class, "None") - assertProgramSymbol(symbols) - assertSymbol(symbols, SymbolKind.Field, "result") - assertSymbol(symbols, SymbolKind.Field, "opt") - assertMainSymbol(symbols) + assertProgramSymbol(symbols) + assertSymbol(symbols, SymbolKind.Field, "result") + assertSymbol(symbols, SymbolKind.Field, "opt") + assertMainSymbol(symbols) - // Look for pattern variables among remaining symbols - var foundPatternVariable = false - while (symbols.moveNext()) { - val symbol = symbols.current() - if (symbol.kind == SymbolKind.Local && symbol.name == "value") { - foundPatternVariable = true + // Look for pattern variables among remaining symbols + var foundPatternVariable = false + while (symbols.moveNext()) { + val symbol = symbols.current() + if (symbol.kind == SymbolKind.Local && symbol.name == "value") { + foundPatternVariable = true + } } + foundPatternVariable shouldBe true } - foundPatternVariable shouldBe true - } - it should "not create named locals for discard patterns" in { - val comp = mkCompilation( - "enum Option[T] {\n" + - " case Some(value: T)\n" + - " case None\n" + - "}\n" + - "val opt = Option.Some(42)\n" + - "val result = opt match {\n" + - " case Option.Some(_) => 1\n" + - " case Option.None => 0\n" + - "}" - ) - val symbols = enumNonBuiltinSymbols(comp) - assertSymbol(symbols, SymbolKind.Alias, "Option") - assertSymbol(symbols, SymbolKind.TypeParameter(Variance.Invariant), "T") - assertSymbol(symbols, SymbolKind.Class, "Some") - assertSymbol(symbols, SymbolKind.Field, "value") - assertSymbol(symbols, SymbolKind.Constructor, ".ctor") - assertSymbol(symbols, SymbolKind.Parameter, "value") + it("should not create named locals for discard patterns") { + val comp = mkCompilation( + "enum Option[T] {\n" + + " case Some(value: T)\n" + + " case None\n" + + "}\n" + + "val opt = Option.Some(42)\n" + + "val result = opt match {\n" + + " case Option.Some(_) => 1\n" + + " case Option.None => 0\n" + + "}" + ) + val symbols = enumNonBuiltinSymbols(comp) + assertSymbol(symbols, SymbolKind.Alias, "Option") + assertSymbol(symbols, SymbolKind.TypeParameter(Variance.Invariant), "T") + assertSymbol(symbols, SymbolKind.Class, "Some") + assertSymbol(symbols, SymbolKind.Field, "value") + assertSymbol(symbols, SymbolKind.Constructor, ".ctor") + assertSymbol(symbols, SymbolKind.Parameter, "value") - assertSymbol(symbols, SymbolKind.Class, "None") + assertSymbol(symbols, SymbolKind.Class, "None") - assertProgramSymbol(symbols) - assertSymbol(symbols, SymbolKind.Field, "result") - assertSymbol(symbols, SymbolKind.Field, "opt") - assertMainSymbol(symbols) + assertProgramSymbol(symbols) + assertSymbol(symbols, SymbolKind.Field, "result") + assertSymbol(symbols, SymbolKind.Field, "opt") + assertMainSymbol(symbols) - // Ensure no local symbol named "_" was created (generated temporaries like $1, $2 are ok) - while (symbols.moveNext()) { - val symbol = symbols.current() - if (symbol.kind == SymbolKind.Local && symbol.name == "_") { - symbol.name should not be "_" + // Ensure no local symbol named "_" was created (generated temporaries like $1, $2 are ok) + while (symbols.moveNext()) { + val symbol = symbols.current() + if (symbol.kind == SymbolKind.Local && symbol.name == "_") { + symbol.name should not be "_" + } } } - } - it should "handle mixed extract and discard patterns" in { - val comp = mkCompilation( - "enum Option[T] {\n" + - " case Some(value: T)\n" + - " case None\n" + - "}\n" + - "val opt = Option.Some(42)\n" + - "val result = opt match {\n" + - " case Option.Some(x) => x\n" + - " case Option.None => 0\n" + - " case _ => -1\n" + - "}" - ) - val symbols = enumNonBuiltinSymbols(comp) - assertSymbol(symbols, SymbolKind.Alias, "Option") - assertSymbol(symbols, SymbolKind.TypeParameter(Variance.Invariant), "T") - assertSymbol(symbols, SymbolKind.Class, "Some") - assertSymbol(symbols, SymbolKind.Field, "value") - assertSymbol(symbols, SymbolKind.Constructor, ".ctor") - assertSymbol(symbols, SymbolKind.Parameter, "value") + it("should handle mixed extract and discard patterns") { + val comp = mkCompilation( + "enum Option[T] {\n" + + " case Some(value: T)\n" + + " case None\n" + + "}\n" + + "val opt = Option.Some(42)\n" + + "val result = opt match {\n" + + " case Option.Some(x) => x\n" + + " case Option.None => 0\n" + + " case _ => -1\n" + + "}" + ) + val symbols = enumNonBuiltinSymbols(comp) + assertSymbol(symbols, SymbolKind.Alias, "Option") + assertSymbol(symbols, SymbolKind.TypeParameter(Variance.Invariant), "T") + assertSymbol(symbols, SymbolKind.Class, "Some") + assertSymbol(symbols, SymbolKind.Field, "value") + assertSymbol(symbols, SymbolKind.Constructor, ".ctor") + assertSymbol(symbols, SymbolKind.Parameter, "value") - assertSymbol(symbols, SymbolKind.Class, "None") + assertSymbol(symbols, SymbolKind.Class, "None") - assertProgramSymbol(symbols) - assertSymbol(symbols, SymbolKind.Field, "result") - assertSymbol(symbols, SymbolKind.Field, "opt") - assertMainSymbol(symbols) + assertProgramSymbol(symbols) + assertSymbol(symbols, SymbolKind.Field, "result") + assertSymbol(symbols, SymbolKind.Field, "opt") + assertMainSymbol(symbols) - // Look for the pattern variable 'x' but not for any '_' locals - var foundPatternVariable = false - while (symbols.moveNext()) { - val symbol = symbols.current() - if (symbol.kind == SymbolKind.Local && symbol.name == "x") { - foundPatternVariable = true - } - if (symbol.kind == SymbolKind.Local && symbol.name == "_") { - symbol.name should not be "_" + // Look for the pattern variable 'x' but not for any '_' locals + var foundPatternVariable = false + while (symbols.moveNext()) { + val symbol = symbols.current() + if (symbol.kind == SymbolKind.Local && symbol.name == "x") { + foundPatternVariable = true + } + if (symbol.kind == SymbolKind.Local && symbol.name == "_") { + symbol.name should not be "_" + } } + foundPatternVariable shouldBe true } - foundPatternVariable shouldBe true } } diff --git a/test/src/test/scala/LexerTests.scala b/test/src/test/scala/LexerTests.scala index c9a1fc5..29923f6 100644 --- a/test/src/test/scala/LexerTests.scala +++ b/test/src/test/scala/LexerTests.scala @@ -1,8 +1,8 @@ import panther.{assert => _, *} -import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers -class LexerTests extends AnyFlatSpec with Matchers { +class LexerTests extends AnyFunSpec with Matchers { def mkTokens(text: string): Array[SyntaxToken] = { val sourceFile = new SourceFile(text, "test.pn") val diagnostics = new DiagnosticBag(CompilerSettingsFactory.default) @@ -10,117 +10,119 @@ class LexerTests extends AnyFlatSpec with Matchers { MakeTokenList.create(lexer) } - "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 - } + describe("Lexer") { + it("should handle single token") { + val tokens = mkTokens("1") + tokens.length shouldEqual 2 + tokens(0).kind shouldEqual SyntaxKind.NumberToken + tokens(1).kind shouldEqual SyntaxKind.EndOfInputToken + } - 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 - } + it("should handle multiple tokens") { + 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 + } - 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 - } + it("should handle whitespace trivia") { + 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 + } - 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 - } + it("should handle comments") { + 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 + } - 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 - } + it("should handle string tokens") { + val tokens = mkTokens("\"hello\"") + tokens.length shouldEqual 2 + tokens(0).kind shouldEqual SyntaxKind.StringToken + tokens(1).kind shouldEqual 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 - } + it("should handle number tokens") { + val tokens = mkTokens("123") + tokens.length shouldEqual 2 + tokens(0).kind shouldEqual SyntaxKind.NumberToken + tokens(1).kind shouldEqual 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 - } + it("should handle identifier tokens") { + val tokens = mkTokens("foo") + tokens.length shouldEqual 2 + tokens(0).kind shouldEqual SyntaxKind.IdentifierToken + tokens(1).kind shouldEqual 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 - } + it("should handle fancy strings") { + val tokens = mkTokens("\"hello\\nworld\"") + tokens.length shouldEqual 2 + tokens(0).kind shouldEqual SyntaxKind.StringToken + tokens(1).kind shouldEqual 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 - } + it("should handle keywords") { + 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 + } - 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 - } + it("should handle operators") { + 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 + } - 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 - } + it("should handle parentheses") { + 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 + } - 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 - } + it("should handle braces") { + 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 + } - 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 + it("should handle brackets") { + 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 + } } } diff --git a/test/src/test/scala/MetadataTests.scala b/test/src/test/scala/MetadataTests.scala index 514055a..a6dd493 100644 --- a/test/src/test/scala/MetadataTests.scala +++ b/test/src/test/scala/MetadataTests.scala @@ -1,8 +1,8 @@ import panther.* -import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers -class MetadataTests extends AnyFlatSpec with Matchers { +class MetadataTests extends AnyFunSpec with Matchers { val metadata: Metadata = Metadata() val point: TypeDefToken = metadata.addTypeDef("Point", "", MetadataFlags.None) @@ -54,29 +54,31 @@ class MetadataTests extends AnyFlatSpec with Matchers { val circleRadius: FieldToken = metadata.addField("radius", MetadataFlags.None, 1, 0) - "Metadata" should "find type def for methods" in { - point.token shouldBe metadata.findTypeDefForMethod(pointCtor).token - point.token shouldBe metadata.findTypeDefForMethod(pointGetX).token - point.token shouldBe metadata.findTypeDefForMethod(pointGetY).token - point3.token shouldBe metadata.findTypeDefForMethod(point3Ctor).token - point3.token shouldBe metadata.findTypeDefForMethod(point3GetX).token - point3.token shouldBe metadata.findTypeDefForMethod(point3GetY).token - point3.token shouldBe metadata.findTypeDefForMethod(point3GetZ).token - circle.token shouldBe metadata.findTypeDefForMethod(circleCtor).token - circle.token shouldBe metadata.findTypeDefForMethod(circleGetCenter).token - circle.token shouldBe metadata.findTypeDefForMethod(circleGetRadius).token - } + describe("Metadata") { + it("should find type def for methods") { + point.token shouldBe metadata.findTypeDefForMethod(pointCtor).token + point.token shouldBe metadata.findTypeDefForMethod(pointGetX).token + point.token shouldBe metadata.findTypeDefForMethod(pointGetY).token + point3.token shouldBe metadata.findTypeDefForMethod(point3Ctor).token + point3.token shouldBe metadata.findTypeDefForMethod(point3GetX).token + point3.token shouldBe metadata.findTypeDefForMethod(point3GetY).token + point3.token shouldBe metadata.findTypeDefForMethod(point3GetZ).token + circle.token shouldBe metadata.findTypeDefForMethod(circleCtor).token + circle.token shouldBe metadata.findTypeDefForMethod(circleGetCenter).token + circle.token shouldBe metadata.findTypeDefForMethod(circleGetRadius).token + } - it should "get method parameter count" in { - metadata.getMethodParameterCount(pointCtor) shouldBe 2 - metadata.getMethodParameterCount(pointGetX) shouldBe 0 - metadata.getMethodParameterCount(pointGetY) shouldBe 0 - metadata.getMethodParameterCount(point3Ctor) shouldBe 3 - metadata.getMethodParameterCount(point3GetX) shouldBe 0 - metadata.getMethodParameterCount(point3GetY) shouldBe 0 - metadata.getMethodParameterCount(point3GetZ) shouldBe 0 - metadata.getMethodParameterCount(circleCtor) shouldBe 2 - metadata.getMethodParameterCount(circleGetCenter) shouldBe 0 - metadata.getMethodParameterCount(circleGetRadius) shouldBe 0 + it("should get method parameter count") { + metadata.getMethodParameterCount(pointCtor) shouldBe 2 + metadata.getMethodParameterCount(pointGetX) shouldBe 0 + metadata.getMethodParameterCount(pointGetY) shouldBe 0 + metadata.getMethodParameterCount(point3Ctor) shouldBe 3 + metadata.getMethodParameterCount(point3GetX) shouldBe 0 + metadata.getMethodParameterCount(point3GetY) shouldBe 0 + metadata.getMethodParameterCount(point3GetZ) shouldBe 0 + metadata.getMethodParameterCount(circleCtor) shouldBe 2 + metadata.getMethodParameterCount(circleGetCenter) shouldBe 0 + metadata.getMethodParameterCount(circleGetRadius) shouldBe 0 + } } } diff --git a/test/src/test/scala/ParserTests.scala b/test/src/test/scala/ParserTests.scala index 04acd8f..d074279 100644 --- a/test/src/test/scala/ParserTests.scala +++ b/test/src/test/scala/ParserTests.scala @@ -1,269 +1,271 @@ import panther.* import TestHelpers.* -import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers -class ParserTests extends AnyFlatSpec with Matchers { - - "Parser" should "parse binary expressions" in { - val expr = mkBinaryExpr("1 + 2") - assertNumberExpr(1, expr.left) - assertNumberExpr(2, expr.right) - assertTokenKind(SyntaxKind.PlusToken, expr.operator) - } - - it should "parse unary expressions" in { - val expr = mkUnaryExpr("-1") - assertTokenKind(SyntaxKind.DashToken, expr.operator) - assertNumberExpr(1, expr.expression) - } - - it should "parse grouped expressions" in { - val expr = mkGroupExpr("(12)") - assertTokenKind(SyntaxKind.OpenParenToken, expr.openParen) - assertNumberExpr(12, expr.expression) - assertTokenKind(SyntaxKind.CloseParenToken, expr.closeParen) - } - - it should "handle operator precedence" in { - val expr = mkBinaryExpr("1 + 2 * 3") - assertNumberExpr(1, expr.left) - assertTokenKind(SyntaxKind.PlusToken, expr.operator) - val right = assertBinaryExpr(expr.right) - assertNumberExpr(2, right.left) - assertTokenKind(SyntaxKind.StarToken, right.operator) - assertNumberExpr(3, right.right) - } - - it should "handle operator associativity" in { - val expr = mkBinaryExpr("1 - 2 - 3") - val left = assertBinaryExpr(expr.left) - assertNumberExpr(1, left.left) - assertTokenKind(SyntaxKind.DashToken, left.operator) - assertNumberExpr(2, left.right) - assertTokenKind(SyntaxKind.DashToken, expr.operator) - assertNumberExpr(3, expr.right) - } - - it should "parse assignment expressions" in { - val expr = mkAssignmentExpr("a = 1") - assertTokenKind(SyntaxKind.EqualsToken, expr.equals) - assertNumberExpr(1, expr.right) - assertIdentifierExpr("a", expr.left) - } - - it should "parse call expressions with no arguments" in { - val expr = mkCallExpr("f()") - assertIdentifierExpr("f", expr.name) - assertNone(expr.genericArguments) - assertTokenKind(SyntaxKind.OpenParenToken, expr.openParen) - assertEmpty(expr.arguments.expressions) - assertTokenKind(SyntaxKind.CloseParenToken, expr.closeParen) - } - - it should "parse call expressions with one argument" in { - val expr = mkCallExpr("f(1)") - assertIdentifierExpr("f", expr.name) - assertNone(expr.genericArguments) - assertTokenKind(SyntaxKind.OpenParenToken, expr.openParen) - val arg = assertSingle(expr.arguments.expressions) - assertNumberExpr(1, arg.expression) - assertTokenKind(SyntaxKind.CloseParenToken, expr.closeParen) - } - - it should "parse call expressions with multiple arguments" in { - val expr = mkCallExpr("f(1, 2)") - assertIdentifierExpr("f", expr.name) - val args = expr.arguments - assertNumberExpr(1, assertIndex(0, args.expressions).expression) - assertNumberExpr(2, assertIndex(1, args.expressions).expression) - } - - it should "parse call expressions with generic arguments" in { - val expr = mkCallExpr("f[int](1)") - val ident = assertGenericIdentifierExpr(expr.name) - assertTokenText("f", ident.identifier) - assertTokenKind( - SyntaxKind.OpenBracketToken, - ident.typeArgumentlist.lessThanToken - ) - ident.typeArgumentlist.arguments.length shouldBe 1 - val genArg = ident.typeArgumentlist.arguments(0) - assertName("int", genArg.name) - assertNone(genArg.separator) - assertTokenKind( - SyntaxKind.CloseBracketToken, - ident.typeArgumentlist.greaterThanToken - ) - - val args = expr.arguments - assertNumberExpr(1, assertSingle(args.expressions).expression) - } - - it should "parse if expressions" in { - val expr = mkIfExpr("if (true) 1 else 2") - assertTokenKind(SyntaxKind.IfKeyword, expr.ifKeyword) - assertTrueExpr(expr.condition) - assertNumberExpr(1, expr.thenExpr) - - val elseExpr = assertSome(expr.elseExpr) - assertTokenKind(SyntaxKind.ElseKeyword, elseExpr.elseKeyword) - assertNumberExpr(2, elseExpr.expression) - } - - it should "parse while expressions" in { - val expr = mkWhileExpr("while (true) 1") - assertTokenKind(SyntaxKind.WhileKeyword, expr.whileKeyword) - assertTokenKind(SyntaxKind.OpenParenToken, expr.openParen) - assertTrueExpr(expr.condition) - assertTokenKind(SyntaxKind.CloseParenToken, expr.closeParen) - assertNumberExpr(1, expr.body) - } - - it should "parse block expressions with expression" in { - val expr = mkBlockExpr("{ 1 }") - assertTokenKind(SyntaxKind.OpenBraceToken, expr.openBrace) - assertEmpty(expr.block.statements) - val blockExpr = assertSome(expr.block.expression) - assertNumberExpr(1, blockExpr) - assertTokenKind(SyntaxKind.CloseBraceToken, expr.closeBrace) - } - - it should "parse block expressions with statement" in { - val expr = mkBlockExpr( - "{\n" + - " val a = 1\n" + - " a\n" + - "}" - ) - // { - assertTokenKind(SyntaxKind.OpenBraceToken, expr.openBrace) - - // val a = 1 - val statements = expr.block.statements - val statement = assertSingle(statements) - val declaration = assertVariableDeclaration(statement) - assertTokenKind(SyntaxKind.ValKeyword, declaration.valOrVarKeyword) - assertTokenText("a", declaration.identifier) - assertTokenKind(SyntaxKind.EqualsToken, declaration.equalToken) - assertNumberExpr(1, declaration.expression) - - // a - val blockExpr = assertSome(expr.block.expression) - assertIdentifierExpr("a", blockExpr) - - // } - assertTokenKind(SyntaxKind.CloseBraceToken, expr.closeBrace) - } - - it should "parse dot on new line" in { - val expr = mkMemberAccessExpr("a\n.b") - assertIdentifierExpr("a", expr.left) - assertTokenKind(SyntaxKind.DotToken, expr.dotToken) - assertSimpleNameIdentifierExpr("b", expr.right) - } - - it should "parse simple match expressions" in { - val expr = mkMatchExpr("1 match { case 1 => 2 }") - assertTokenKind(SyntaxKind.MatchKeyword, expr.matchKeyword) - assertNumberExpr(1, expr.expression) - - val kase = expr.cases.head - assertTokenKind(SyntaxKind.CaseKeyword, kase.caseKeyword) - val pattern = assertLiteralPattern(kase.pattern) - - assertNumberToken(1, pattern.value) - assertTokenKind(SyntaxKind.EqualsGreaterThanToken, kase.arrow) - assertNumberExpr(2, assertSome(kase.block.expression)) - } - - it should "parse empty match case" in { - val expr = mkSyntaxTree( - "enum Test {\n" + - " case Empty()\n" + - " case One(one: int)\n" + - "}\n" + - "\n" + - "x match {\n" + - " case Test.Empty() => 2\n" + - " case Test.One(1) => 3\n" + - "}" - ) - expr.diagnostics.count() shouldBe 0 - } - - it should "parse is expressions" in { - val expr = mkIsExpr("x is int") - assertIdentifierExpr("x", expr.expression) - assertTokenKind(SyntaxKind.IsKeyword, expr.isKeyword) - // TODO: add assertion for the type name - } - - it should "parse functions with no parameters" in { - val fn = mkFunctionMember("def f() = { 1 }") - assertTokenKind(SyntaxKind.DefKeyword, fn.defKeyword) - assertTokenText("f", fn.identifier) - assertNone(fn.genericParameters) - assertTokenKind(SyntaxKind.OpenParenToken, fn.openParenToken) - assertEmpty(fn.parameters) - assertTokenKind(SyntaxKind.CloseParenToken, fn.closeParenToken) - assertNone(fn.typeAnnotation) - - val body = assertSome(fn.body) - assertTokenKind(SyntaxKind.EqualsToken, body.equalToken) - val block = assertBlockExpr(body.expression) - val expr = assertSome(block.block.expression) - assertNumberExpr(1, expr) - } - - it should "parse functions with parameters" in { - val fn = mkFunctionMember("def f(a: int, b: int) = { a + b }") - assertTokenKind(SyntaxKind.DefKeyword, fn.defKeyword) - assertTokenText("f", fn.identifier) - assertNone(fn.genericParameters) - assertTokenKind(SyntaxKind.OpenParenToken, fn.openParenToken) - - val parameters = fn.parameters - val a = assertIndex(0, parameters) - assertTokenText("a", a.identifier) - assertTokenKind(SyntaxKind.ColonToken, a.typeAnnotation.colonToken) - assertName("int", a.typeAnnotation.typ) - - val b = assertIndex(1, parameters) - assertTokenText("b", b.identifier) - assertTokenKind(SyntaxKind.ColonToken, b.typeAnnotation.colonToken) - assertName("int", b.typeAnnotation.typ) - - assertTokenKind(SyntaxKind.CloseParenToken, fn.closeParenToken) - assertNone(fn.typeAnnotation) - - val body = assertSome(fn.body) - assertTokenKind(SyntaxKind.EqualsToken, body.equalToken) - val block = assertBlockExpr(body.expression) - val expr = assertSome(block.block.expression) - val binary = assertBinaryExpr(expr) - assertIdentifierExpr("a", binary.left) - assertTokenKind(SyntaxKind.PlusToken, binary.operator) - assertIdentifierExpr("b", binary.right) - } - - it should "parse functions with return type" in { - val fn = mkFunctionMember("def f(): int = { 1 }") - assertTokenKind(SyntaxKind.DefKeyword, fn.defKeyword) - assertTokenText("f", fn.identifier) - assertNone(fn.genericParameters) - assertTokenKind(SyntaxKind.OpenParenToken, fn.openParenToken) - assertEmpty(fn.parameters) - assertTokenKind(SyntaxKind.CloseParenToken, fn.closeParenToken) - - val typeAnnotation = assertSome(fn.typeAnnotation) - assertTokenKind(SyntaxKind.ColonToken, typeAnnotation.colonToken) - assertName("int", typeAnnotation.typ) - - val body = assertSome(fn.body) - assertTokenKind(SyntaxKind.EqualsToken, body.equalToken) - val block = assertBlockExpr(body.expression) - val expr = assertSome(block.block.expression) - assertNumberExpr(1, expr) +class ParserTests extends AnyFunSpec with Matchers { + + describe("Parser") { + it("should parse binary expressions") { + val expr = mkBinaryExpr("1 + 2") + assertNumberExpr(1, expr.left) + assertNumberExpr(2, expr.right) + assertTokenKind(SyntaxKind.PlusToken, expr.operator) + } + + it("should parse unary expressions") { + val expr = mkUnaryExpr("-1") + assertTokenKind(SyntaxKind.DashToken, expr.operator) + assertNumberExpr(1, expr.expression) + } + + it("should parse grouped expressions") { + val expr = mkGroupExpr("(12)") + assertTokenKind(SyntaxKind.OpenParenToken, expr.openParen) + assertNumberExpr(12, expr.expression) + assertTokenKind(SyntaxKind.CloseParenToken, expr.closeParen) + } + + it("should handle operator precedence") { + val expr = mkBinaryExpr("1 + 2 * 3") + assertNumberExpr(1, expr.left) + assertTokenKind(SyntaxKind.PlusToken, expr.operator) + val right = assertBinaryExpr(expr.right) + assertNumberExpr(2, right.left) + assertTokenKind(SyntaxKind.StarToken, right.operator) + assertNumberExpr(3, right.right) + } + + it("should handle operator associativity") { + val expr = mkBinaryExpr("1 - 2 - 3") + val left = assertBinaryExpr(expr.left) + assertNumberExpr(1, left.left) + assertTokenKind(SyntaxKind.DashToken, left.operator) + assertNumberExpr(2, left.right) + assertTokenKind(SyntaxKind.DashToken, expr.operator) + assertNumberExpr(3, expr.right) + } + + it("should parse assignment expressions") { + val expr = mkAssignmentExpr("a = 1") + assertTokenKind(SyntaxKind.EqualsToken, expr.equals) + assertNumberExpr(1, expr.right) + assertIdentifierExpr("a", expr.left) + } + + it("should parse call expressions with no arguments") { + val expr = mkCallExpr("f()") + assertIdentifierExpr("f", expr.name) + assertNone(expr.genericArguments) + assertTokenKind(SyntaxKind.OpenParenToken, expr.openParen) + assertEmpty(expr.arguments.expressions) + assertTokenKind(SyntaxKind.CloseParenToken, expr.closeParen) + } + + it("should parse call expressions with one argument") { + val expr = mkCallExpr("f(1)") + assertIdentifierExpr("f", expr.name) + assertNone(expr.genericArguments) + assertTokenKind(SyntaxKind.OpenParenToken, expr.openParen) + val arg = assertSingle(expr.arguments.expressions) + assertNumberExpr(1, arg.expression) + assertTokenKind(SyntaxKind.CloseParenToken, expr.closeParen) + } + + it("should parse call expressions with multiple arguments") { + val expr = mkCallExpr("f(1, 2)") + assertIdentifierExpr("f", expr.name) + val args = expr.arguments + assertNumberExpr(1, assertIndex(0, args.expressions).expression) + assertNumberExpr(2, assertIndex(1, args.expressions).expression) + } + + it("should parse call expressions with generic arguments") { + val expr = mkCallExpr("f[int](1)") + val ident = assertGenericIdentifierExpr(expr.name) + assertTokenText("f", ident.identifier) + assertTokenKind( + SyntaxKind.OpenBracketToken, + ident.typeArgumentlist.lessThanToken + ) + ident.typeArgumentlist.arguments.length shouldBe 1 + val genArg = ident.typeArgumentlist.arguments(0) + assertName("int", genArg.name) + assertNone(genArg.separator) + assertTokenKind( + SyntaxKind.CloseBracketToken, + ident.typeArgumentlist.greaterThanToken + ) + + val args = expr.arguments + assertNumberExpr(1, assertSingle(args.expressions).expression) + } + + it("should parse if expressions") { + val expr = mkIfExpr("if (true) 1 else 2") + assertTokenKind(SyntaxKind.IfKeyword, expr.ifKeyword) + assertTrueExpr(expr.condition) + assertNumberExpr(1, expr.thenExpr) + + val elseExpr = assertSome(expr.elseExpr) + assertTokenKind(SyntaxKind.ElseKeyword, elseExpr.elseKeyword) + assertNumberExpr(2, elseExpr.expression) + } + + it("should parse while expressions") { + val expr = mkWhileExpr("while (true) 1") + assertTokenKind(SyntaxKind.WhileKeyword, expr.whileKeyword) + assertTokenKind(SyntaxKind.OpenParenToken, expr.openParen) + assertTrueExpr(expr.condition) + assertTokenKind(SyntaxKind.CloseParenToken, expr.closeParen) + assertNumberExpr(1, expr.body) + } + + it("should parse block expressions with expression") { + val expr = mkBlockExpr("{ 1 }") + assertTokenKind(SyntaxKind.OpenBraceToken, expr.openBrace) + assertEmpty(expr.block.statements) + val blockExpr = assertSome(expr.block.expression) + assertNumberExpr(1, blockExpr) + assertTokenKind(SyntaxKind.CloseBraceToken, expr.closeBrace) + } + + it("should parse block expressions with statement") { + val expr = mkBlockExpr( + "{\n" + + " val a = 1\n" + + " a\n" + + "}" + ) + // { + assertTokenKind(SyntaxKind.OpenBraceToken, expr.openBrace) + + // val a = 1 + val statements = expr.block.statements + val statement = assertSingle(statements) + val declaration = assertVariableDeclaration(statement) + assertTokenKind(SyntaxKind.ValKeyword, declaration.valOrVarKeyword) + assertTokenText("a", declaration.identifier) + assertTokenKind(SyntaxKind.EqualsToken, declaration.equalToken) + assertNumberExpr(1, declaration.expression) + + // a + val blockExpr = assertSome(expr.block.expression) + assertIdentifierExpr("a", blockExpr) + + // } + assertTokenKind(SyntaxKind.CloseBraceToken, expr.closeBrace) + } + + it("should parse dot on new line") { + val expr = mkMemberAccessExpr("a\n.b") + assertIdentifierExpr("a", expr.left) + assertTokenKind(SyntaxKind.DotToken, expr.dotToken) + assertSimpleNameIdentifierExpr("b", expr.right) + } + + it("should parse simple match expressions") { + val expr = mkMatchExpr("1 match { case 1 => 2 }") + assertTokenKind(SyntaxKind.MatchKeyword, expr.matchKeyword) + assertNumberExpr(1, expr.expression) + + val kase = expr.cases.head + assertTokenKind(SyntaxKind.CaseKeyword, kase.caseKeyword) + val pattern = assertLiteralPattern(kase.pattern) + + assertNumberToken(1, pattern.value) + assertTokenKind(SyntaxKind.EqualsGreaterThanToken, kase.arrow) + assertNumberExpr(2, assertSome(kase.block.expression)) + } + + it("should parse empty match case") { + val expr = mkSyntaxTree( + "enum Test {\n" + + " case Empty()\n" + + " case One(one: int)\n" + + "}\n" + + "\n" + + "x match {\n" + + " case Test.Empty() => 2\n" + + " case Test.One(1) => 3\n" + + "}" + ) + expr.diagnostics.count() shouldBe 0 + } + + it("should parse is expressions") { + val expr = mkIsExpr("x is int") + assertIdentifierExpr("x", expr.expression) + assertTokenKind(SyntaxKind.IsKeyword, expr.isKeyword) + // TODO: add assertion for the type name + } + + it("should parse functions with no parameters") { + val fn = mkFunctionMember("def f() = { 1 }") + assertTokenKind(SyntaxKind.DefKeyword, fn.defKeyword) + assertTokenText("f", fn.identifier) + assertNone(fn.genericParameters) + assertTokenKind(SyntaxKind.OpenParenToken, fn.openParenToken) + assertEmpty(fn.parameters) + assertTokenKind(SyntaxKind.CloseParenToken, fn.closeParenToken) + assertNone(fn.typeAnnotation) + + val body = assertSome(fn.body) + assertTokenKind(SyntaxKind.EqualsToken, body.equalToken) + val block = assertBlockExpr(body.expression) + val expr = assertSome(block.block.expression) + assertNumberExpr(1, expr) + } + + it("should parse functions with parameters") { + val fn = mkFunctionMember("def f(a: int, b: int) = { a + b }") + assertTokenKind(SyntaxKind.DefKeyword, fn.defKeyword) + assertTokenText("f", fn.identifier) + assertNone(fn.genericParameters) + assertTokenKind(SyntaxKind.OpenParenToken, fn.openParenToken) + + val parameters = fn.parameters + val a = assertIndex(0, parameters) + assertTokenText("a", a.identifier) + assertTokenKind(SyntaxKind.ColonToken, a.typeAnnotation.colonToken) + assertName("int", a.typeAnnotation.typ) + + val b = assertIndex(1, parameters) + assertTokenText("b", b.identifier) + assertTokenKind(SyntaxKind.ColonToken, b.typeAnnotation.colonToken) + assertName("int", b.typeAnnotation.typ) + + assertTokenKind(SyntaxKind.CloseParenToken, fn.closeParenToken) + assertNone(fn.typeAnnotation) + + val body = assertSome(fn.body) + assertTokenKind(SyntaxKind.EqualsToken, body.equalToken) + val block = assertBlockExpr(body.expression) + val expr = assertSome(block.block.expression) + val binary = assertBinaryExpr(expr) + assertIdentifierExpr("a", binary.left) + assertTokenKind(SyntaxKind.PlusToken, binary.operator) + assertIdentifierExpr("b", binary.right) + } + + it("should parse functions with return type") { + val fn = mkFunctionMember("def f(): int = { 1 }") + assertTokenKind(SyntaxKind.DefKeyword, fn.defKeyword) + assertTokenText("f", fn.identifier) + assertNone(fn.genericParameters) + assertTokenKind(SyntaxKind.OpenParenToken, fn.openParenToken) + assertEmpty(fn.parameters) + assertTokenKind(SyntaxKind.CloseParenToken, fn.closeParenToken) + + val typeAnnotation = assertSome(fn.typeAnnotation) + assertTokenKind(SyntaxKind.ColonToken, typeAnnotation.colonToken) + assertName("int", typeAnnotation.typ) + + val body = assertSome(fn.body) + assertTokenKind(SyntaxKind.EqualsToken, body.equalToken) + val block = assertBlockExpr(body.expression) + val expr = assertSome(block.block.expression) + assertNumberExpr(1, expr) + } } } diff --git a/test/src/test/scala/TypeTests.scala b/test/src/test/scala/TypeTests.scala index 625df7b..f6f8e71 100644 --- a/test/src/test/scala/TypeTests.scala +++ b/test/src/test/scala/TypeTests.scala @@ -1,621 +1,623 @@ import panther.* import TestHelpers.* -import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers -class TypeTests extends AnyFlatSpec with Matchers { - - "Type checker" should "handle primitive types" in { - assertExprTypeTest("12", "int") - assertExprTypeTest("0", "int") - assertExprTypeTest("true", "bool") - assertExprTypeTest("false", "bool") - assertExprTypeTest("\"hello\"", "string") - assertExprTypeTest("'a'", "char") - assertExprTypeTest("()", "unit") - } - - it should "handle binary expressions" in { - assertExprTypeTest("1 + 2", "int") - assertExprTypeTest("1 - 2", "int") - assertExprTypeTest("1 * 2", "int") - assertExprTypeTest("1 / 2", "int") - assertExprTypeTest("1 % 2", "int") - assertExprTypeTest("1 == 2", "bool") - assertExprTypeTest("1 != 2", "bool") - assertExprTypeTest("1 < 2", "bool") - assertExprTypeTest("1 <= 2", "bool") - assertExprTypeTest("1 > 2", "bool") - assertExprTypeTest("1 >= 2", "bool") - assertExprTypeTest("true && false", "bool") - assertExprTypeTest("true || false", "bool") - } - - it should "handle unary expressions" in { - assertExprTypeTest("-1", "int") - assertExprTypeTest("+1", "int") - // FIXME: assertExprType("~7", "int") - assertExprTypeTest("!true", "bool") - } - - it should "handle grouped expressions" in { - assertExprTypeTest("(12)", "int") - assertExprTypeTest("(true)", "bool") - } - - it should "handle int variables" in { - assertExprTypeWithSetup("val x = 12", "x", "int") - } - - it should "handle bool variables" in { - assertExprTypeWithSetup("val x = true", "x", "bool") - } - - it should "handle string variables" in { - assertExprTypeWithSetup("val x = \"hello\"", "x", "string") - } - - it should "handle char variables" in { - assertExprTypeWithSetup("val x = 'a'", "x", "char") - } - - it should "handle variable addition" in { - assertExprTypeWithSetup("val x = 12", "x + 12", "int") - } - - it should "handle variable equality" in { - assertExprTypeWithSetup("val x = 12", "12 == x", "bool") - } - - it should "handle variable assignment" in { - assertExprTypeWithSetup("var x = 12", "x = 10", "unit") - } - - it should "handle conversions to any" in { - assertAssignableTo("12", "any") - assertAssignableTo("true", "any") - assertAssignableTo("\"hello\"", "any") - assertAssignableTo("'a'", "any") - } - - it should "handle function calls" in { - assertExprTypeTest("println(12)", "unit") - assertExprTypeTest("print(12)", "unit") - assertExprTypeTest("print(\"hello\")", "unit") - assertExprTypeTest("print('a')", "unit") - assertExprTypeTest("print(true)", "unit") - assertExprTypeTest("print(12 + 12)", "unit") - assertExprTypeTest("print(12 == 12)", "unit") - assertExprTypeTest("print(12 < 12)", "unit") - assertExprTypeTest("print(true && false)", "unit") - assertExprTypeTest("print(true || false)", "unit") - } - - it should "handle casts" in { - assertExprTypeTest("'a' as char", "char") - assertExprTypeTest("12 as char", "char") - assertExprTypeTest("'a' as int", "int") - assertExprTypeTest("12 as int", "int") - assertExprTypeTest("'a' as string", "string") - assertExprTypeTest("12 as string", "string") - assertExprTypeTest("\"hello\" as string", "string") - assertExprTypeTest("true as string", "string") - } - - it should "handle is expressions" in { - assertExprTypeTest("12 is int", "bool") - assertExprTypeTest("'a' is char", "bool") - assertExprTypeTest("\"hello\" is string", "bool") - assertExprTypeTest("true is bool", "bool") - assertExprTypeTest("12 is bool", "bool") // should return false at runtime - } - - it should "handle block expressions" in { - assertExprTypeTest("{ 1 }", "int") - assertExprTypeTest("{ true }", "bool") - assertExprTypeTest("{ }", "unit") - assertExprTypeTest( - "{\n" + - " 1\n" + - " 2\n" + - "}", - "int" - ) - assertExprTypeTest( - "{\n" + - "true\n" + - "false\n" + - "}", - "bool" - ) - assertExprTypeTest( - "{\n" + - " 1\n" + - " true\n" + - "}", - "bool" - ) - assertExprTypeTest( - "{\n" + - " true\n" + - " 1\n" + - "}", - "int" - ) - } - - it should "handle if expressions" in { - assertExprTypeTest("if (true) 1 else 2", "int") - assertExprTypeTest("if (false) 1 else 2", "int") - assertExprTypeTest("if (true) true else false", "bool") - assertExprTypeTest("if (false) true else false", "bool") - assertExprTypeTest("if (false) true", "unit") - } - - it should "handle while expressions" in { - assertExprTypeTest("while (true) 1", "unit") - assertExprTypeTest("while (false) 1", "unit") - } - - it should "handle literal patterns" in { - assertExprTypeTest("1 match { case 1 => 2 }", "int") - assertExprTypeTest("1 match { case 2 => 3 }", "int") - assertExprTypeTest("true match { case true => false }", "bool") - assertExprTypeTest( - "\"hello\" match { case \"world\" => \"hi\" }", - "string" - ) - assertExprTypeTest("'a' match { case 'b' => 'c' }", "char") - } - - it should "handle wildcard patterns" in { - assertExprTypeTest("1 match { case _ => 2 }", "int") - assertExprTypeTest("true match { case _ => false }", "bool") - assertExprTypeTest("\"hello\" match { case _ => \"world\" }", "string") - } - - it should "handle multiple cases with same type" in { - assertExprTypeTest( - "1 match { case 1 => 10\n case 2 => 20 }", - "int" - ) - assertExprTypeTest( - "true match { case true => 1\n case false => 0 }", - "int" - ) - } - - it should "handle multiple cases with different types" in { - // When cases have different types, result should be 'any' (least upper bound) - assertExprTypeTest( - "1 match { case 1 => 42\n case 2 => \"hello\" }", - "int | string" - ) - assertExprTypeTest( - "1 match { case 1 => true\n case 2 => 123 }", - "bool | int" - ) - } - - it should "handle unit result cases" in { - assertExprTypeTest("1 match { case x: int => () }", "unit") - assertExprTypeTest("1 match { case 1 => println(\"test\") }", "unit") - } - - it should "handle nested matches" in { - assertExprTypeTest( - "1 match { case x: int => x match { case y: int => y * 2 } }", - "int" - ) - } - - it should "handle match with blocks" in { - assertExprTypeTest( - "1 match { case x: int => { val y = x + 1\n y * 2 } }", - "int" - ) - assertExprTypeTest( - "true match { case b: bool => { println(\"test\")\n b } }", - "bool" - ) - } - - it should "handle methods without return type" in { - val comp = mkCompilation("def foo() = 12") - val symbols = enumNonBuiltinSymbols(comp) - assertProgramSymbol(symbols) - val foo = assertSymbol(symbols, SymbolKind.Method, "foo") - assertSymbolType(comp, foo, "() -> int") - assertMainSymbol(symbols) - assertNoSymbols(symbols) - } - - it should "handle methods with parameters" in { - val comp = mkCompilation("def foo(x: int, y: int) = 12") - val symbols = enumNonBuiltinSymbols(comp) - assertProgramSymbol(symbols) - val foo = assertSymbol(symbols, SymbolKind.Method, "foo") - assertSymbolType(comp, foo, "(x: int, y: int) -> int") - val x = assertSymbol(symbols, SymbolKind.Parameter, "x") - assertSymbolType(comp, x, "int") - val y = assertSymbol(symbols, SymbolKind.Parameter, "y") - assertSymbolType(comp, y, "int") - assertMainSymbol(symbols) - assertNoSymbols(symbols) - } - - it should "handle simple identity generic method" in { - val setup = "def identity[T](x: T): T = x" - - assertExprTypeWithSetup(setup, "identity(42)", "int") - assertExprTypeWithSetup(setup, "identity(true)", "bool") - assertExprTypeWithSetup(setup, "identity(\"hello\")", "string") - assertExprTypeWithSetup(setup, "identity('a')", "char") - } - - it should "handle generic method returning concrete type" in { - val setup = "def getValue[T](x: T): int = 42" - - // These should work because the return type is concrete - assertExprTypeWithSetup(setup, "getValue(42)", "int") - assertExprTypeWithSetup(setup, "getValue(\"hello\")", "int") - assertExprTypeWithSetup(setup, "getValue(true)", "int") - } - - it should "handle generic parameter with concrete return" in { - // Test methods that accept generic parameters but return concrete types - val setup = "def stringify[T](x: T): string = string(x)" - - assertExprTypeWithSetup(setup, "stringify(42)", "string") - assertExprTypeWithSetup(setup, "stringify(true)", "string") - assertExprTypeWithSetup(setup, "stringify('a')", "string") - } - - it should "handle generic container creation" in { - val containerSetup = "class Container[T](value: T)\n" + - "def wrap[T](x: T): Container[T] = new Container(x)" - - assertExprTypeWithSetup(containerSetup, "wrap(42)", "Container") - assertExprTypeWithSetup(containerSetup, "wrap(true)", "Container") - assertExprTypeWithSetup( - containerSetup, - "wrap(\"test\")", - "Container" - ) - } - - it should "handle enums without args" in { - val setup = "enum Foo {\n" + - " case Bar\n" + - " case Baz\n" + - "}" - assertExprTypeWithSetup(setup, "Foo.Bar", "Foo.Bar") - assertExprTypeWithSetup(setup, "Foo.Baz", "Foo.Baz") - } - - it should "handle enums with args" in { - val setup = "enum Foo {\n" + - " case Bar(x: int)\n" + - " case Baz(y: string)\n" + - "}" - assertExprTypeWithSetup(setup, "Foo.Bar(12)", "Foo.Bar") - assertExprTypeWithSetup(setup, "Foo.Baz(\"taco\")", "Foo.Baz") - assertExprTypeWithSetup(setup, "new Foo.Bar(12)", "Foo.Bar") - assertExprTypeWithSetup(setup, "new Foo.Baz(\"taco\")", "Foo.Baz") - } - - it should "check enum assignments" in { - val setup = "enum Foo {\n" + - " case Bar(x: int)\n" + - " case Baz(y: string)\n" + - "}" - assertAssignableToWithSetup(setup, "Foo.Bar(12)", "Foo") - assertAssignableToWithSetup(setup, "Foo.Baz(\"taco\")", "Foo") - } - - it should "handle enums with generic type" in { - val setup = "enum Option[T] {\n" + - " case Some(value: T)\n" + - " case None\n" + - "}" - assertAssignableToWithSetup(setup, "new Option.Some(12)", "Option[int]") - assertAssignableToWithSetup( - setup, - "new Option.Some(12)", - "Option.Some[int]" - ) - assertAssignableToWithSetup(setup, "Option.Some(12)", "Option[int]") - assertAssignableToWithSetup(setup, "Option.None", "Option[int]") - assertAssignableToWithSetup(setup, "Option.None", "Option[never]") - } - - it should "handle list examples" in { - val setup = "enum List[T] {\n" + - " case Cons(head: T, tail: List[T])\n" + - " case Nil\n" + - "}" - assertAssignableToWithSetup( - setup, - "new List.Cons(1, List.Nil)", - "List[int]" - ) - assertAssignableToWithSetup( - setup, - "List.Cons(1, List.Nil)", - "List[int]" - ) - assertAssignableToWithSetup( - setup, - "new List.Cons(\"hello\", List.Nil)", - "List[string]" - ) - assertAssignableToWithSetup( - setup, - "List.Cons(\"hello\", List.Nil)", - "List[string]" - ) - assertAssignableToWithSetup(setup, "List.Nil", "List[int]") - assertAssignableToWithSetup(setup, "List.Nil", "List[string]") - } - - it should "handle classes without args" in { - val setup = "class Foo()" - assertExprTypeWithSetup(setup, "new Foo()", "Foo") - } - - it should "handle classes with args" in { - val setup = "class Foo(x: int, y: string)" - assertExprTypeWithSetup(setup, "new Foo(12, \"taco\")", "Foo") - } - - it should "handle array length type" in { - val setup = "val array = new Array[int](0)" - assertExprTypeWithSetup(setup, "array.length", "int") - } - - it should "handle array apply with method call" in { - val setup = "val array = new Array[int](1)" - assertExprTypeWithSetup(setup, "array.apply(0)", "int") - } - - it should "handle array apply with indexer syntax for basic types" in { - val setup = "val intArray = new Array[int](1)" - assertExprTypeWithSetup(setup, "intArray(0)", "int") - - val boolSetup = "val boolArray = new Array[bool](1)" - assertExprTypeWithSetup(boolSetup, "boolArray(0)", "bool") - - val stringSetup = "val stringArray = new Array[string](1)" - assertExprTypeWithSetup(stringSetup, "stringArray(0)", "string") - - val charSetup = "val charArray = new Array[char](1)" - assertExprTypeWithSetup(charSetup, "charArray(0)", "char") - } - - it should "handle array indexing in expressions" in { - val setup = "val array = new Array[int](5)" - assertExprTypeWithSetup(setup, "array(0) + array(1)", "int") - assertExprTypeWithSetup(setup, "array(2) * 3", "int") - assertExprTypeWithSetup(setup, "array(0) == array(1)", "bool") - } - - it should "handle array indexing with computed indices" in { - val setup = "val array = new Array[int](10)\nval i = 5" - assertExprTypeWithSetup(setup, "array(i)", "int") - assertExprTypeWithSetup(setup, "array(1 + 2)", "int") - assertExprTypeWithSetup(setup, "array(i * 2)", "int") - } - - it should "handle array indexing assignment - LHS binding fix" in { - // This tests the specific fix for "NewExpression in bindLHS" error - val setup = "var array = new Array[int](5)" - assertExprTypeWithSetup(setup, "array(0) = 42", "unit") - assertExprTypeWithSetup(setup, "array(1) = array(0) + 1", "unit") - } - - it should "handle primitive casts" in { - // Basic numeric casting - assertExprTypeTest("42 as int", "int") - assertExprTypeTest("42 as bool", "bool") - assertExprTypeTest("42 as char", "char") - assertExprTypeTest("42 as string", "string") - - // Boolean casting - assertExprTypeTest("true as int", "int") - assertExprTypeTest("true as bool", "bool") - assertExprTypeTest("false as string", "string") - - // Character casting - assertExprTypeTest("'a' as int", "int") - assertExprTypeTest("'a' as char", "char") - assertExprTypeTest("'a' as string", "string") - - // String casting - assertExprTypeTest("\"hello\" as string", "string") - assertExprTypeTest("\"hello\" as any", "any") - } - - it should "handle variable casts" in { - val setup = - "val x = 42\nval flag = true\nval ch = 'a'\nval text = \"hello\"" - - assertExprTypeWithSetup(setup, "x as bool", "bool") - assertExprTypeWithSetup(setup, "x as char", "char") - assertExprTypeWithSetup(setup, "x as string", "string") - - assertExprTypeWithSetup(setup, "flag as int", "int") - assertExprTypeWithSetup(setup, "flag as string", "string") - - assertExprTypeWithSetup(setup, "ch as int", "int") - assertExprTypeWithSetup(setup, "ch as string", "string") - - assertExprTypeWithSetup(setup, "text as any", "any") - } - - it should "handle cast to any type" in { - assertExprTypeTest("42 as any", "any") - assertExprTypeTest("true as any", "any") - assertExprTypeTest("\"hello\" as any", "any") - assertExprTypeTest("'a' as any", "any") - assertExprTypeTest("() as any", "any") - } - - it should "handle cast from any type" in { - val setup = "val obj: any = 42" - - assertExprTypeWithSetup(setup, "obj as int", "int") - assertExprTypeWithSetup(setup, "obj as bool", "bool") - assertExprTypeWithSetup(setup, "obj as char", "char") - assertExprTypeWithSetup(setup, "obj as string", "string") - assertExprTypeWithSetup(setup, "obj as unit", "unit") - } - - it should "handle expression casts" in { - // Cast results of binary operations - assertExprTypeTest("(1 + 2) as bool", "bool") - assertExprTypeTest("(true && false) as int", "int") - assertExprTypeTest("(1 == 2) as string", "string") - - // Cast results of unary operations - assertExprTypeTest("(!true) as int", "int") - assertExprTypeTest("(-42) as bool", "bool") - } - - it should "handle cast with parentheses" in { - assertExprTypeTest("(42) as string", "string") - assertExprTypeTest("(true) as int", "int") - assertExprTypeTest("(\"hello\") as any", "any") - } - - it should "handle chained operations with casts" in { - val setup = "val x = 42" - - // Cast should have appropriate precedence - assertExprTypeWithSetup(setup, "x as bool == true", "bool") - assertExprTypeWithSetup(setup, "(x as bool) == true", "bool") - } - - it should "handle basic string conversions" in { - // Convert literals to string - assertExprTypeTest("string(42)", "string") - assertExprTypeTest("string(0)", "string") - assertExprTypeTest("string(-123)", "string") - assertExprTypeTest("string(true)", "string") - assertExprTypeTest("string(false)", "string") - assertExprTypeTest("string('a')", "string") - assertExprTypeTest("string('z')", "string") - assertExprTypeTest("string(())", "string") - } - - it should "handle string conversions with variables" in { - val intSetup = "val num = 123" - assertExprTypeWithSetup(intSetup, "string(num)", "string") - - val boolSetup = "val flag = true" - assertExprTypeWithSetup(boolSetup, "string(flag)", "string") - - val charSetup = "val ch = 'A'" - assertExprTypeWithSetup(charSetup, "string(ch)", "string") - - val unitSetup = "val nothing = ()" - assertExprTypeWithSetup(unitSetup, "string(nothing)", "string") - } - - it should "handle string conversions with expressions" in { - // Arithmetic expressions - assertExprTypeTest("string(1 + 2)", "string") - assertExprTypeTest("string(10 - 5)", "string") - assertExprTypeTest("string(3 * 4)", "string") - assertExprTypeTest("string(15 / 3)", "string") - assertExprTypeTest("string(17 % 5)", "string") - - // Boolean expressions - assertExprTypeTest("string(true && false)", "string") - assertExprTypeTest("string(true || false)", "string") - assertExprTypeTest("string(!true)", "string") - assertExprTypeTest("string(5 > 3)", "string") - assertExprTypeTest("string(5 == 5)", "string") - assertExprTypeTest("string(5 != 3)", "string") - - // Unary expressions - assertExprTypeTest("string(-42)", "string") - assertExprTypeTest("string(+42)", "string") - } - - it should "handle string conversions in complex expressions" in { - val setup = "val x = 42\nval y = true" - - // String conversions in comparisons - assertExprTypeWithSetup(setup, "string(x) == \"42\"", "bool") - assertExprTypeWithSetup(setup, "string(y) == \"true\"", "bool") - assertExprTypeWithSetup(setup, "string(x) != \"0\"", "bool") - - // String conversions in arithmetic context - assertExprTypeWithSetup(setup, "string(x + 10)", "string") - assertExprTypeWithSetup(setup, "string(x * 2)", "string") - } - - it should "handle string conversions with method calls" in { - // Convert results of method calls to string - assertExprTypeTest("string(println(\"test\"))", "string") - assertExprTypeTest("string(print(42))", "string") - } - - it should "handle string conversions with control flow" in { - // String conversions with if expressions - assertExprTypeTest("string(if (true) 1 else 2)", "string") - assertExprTypeTest("string(if (false) true else false)", "string") - - // String conversions with block expressions - assertExprTypeTest("string({ 42 })", "string") - assertExprTypeTest("string({ true })", "string") - } - - it should "handle nested string conversions" in { - val setup = "val x = 42" - - // String conversion of string (should still work) - assertExprTypeTest("string(\"hello\")", "string") - assertExprTypeWithSetup(setup, "string(string(x))", "string") - - // String conversions in nested expressions - assertExprTypeWithSetup(setup, "string(string(x) == \"42\")", "string") - } - - it should "handle int conversions" in { - // Convert various types to int - assertExprTypeTest("int(42)", "int") - assertExprTypeTest("int(true)", "int") - assertExprTypeTest("int(false)", "int") - assertExprTypeTest("int('a')", "int") - - // Int conversions with variables and expressions - val setup = "val flag = true\nval ch = 'A'" - assertExprTypeWithSetup(setup, "int(flag)", "int") - assertExprTypeWithSetup(setup, "int(ch)", "int") - assertExprTypeTest("int(1 + 2)", "int") - } - - it should "handle bool conversions" in { - // Convert various types to bool - assertExprTypeTest("bool(true)", "bool") - assertExprTypeTest("bool(false)", "bool") - assertExprTypeTest("bool(42)", "bool") - assertExprTypeTest("bool(0)", "bool") - - // Bool conversions with variables and expressions - val setup = "val num = 123" - assertExprTypeWithSetup(setup, "bool(num)", "bool") - assertExprTypeTest("bool(5 > 3)", "bool") - } - - it should "handle char conversions" in { - // Convert various types to char - assertExprTypeTest("char('a')", "char") - assertExprTypeTest("char(65)", "char") - - // Char conversions with variables - val setup = "val ascii = 97" - assertExprTypeWithSetup(setup, "char(ascii)", "char") +class TypeTests extends AnyFunSpec with Matchers { + + describe("Type checker") { + it("should handle primitive types") { + assertExprTypeTest("12", "int") + assertExprTypeTest("0", "int") + assertExprTypeTest("true", "bool") + assertExprTypeTest("false", "bool") + assertExprTypeTest("\"hello\"", "string") + assertExprTypeTest("'a'", "char") + assertExprTypeTest("()", "unit") + } + + it("should handle binary expressions") { + assertExprTypeTest("1 + 2", "int") + assertExprTypeTest("1 - 2", "int") + assertExprTypeTest("1 * 2", "int") + assertExprTypeTest("1 / 2", "int") + assertExprTypeTest("1 % 2", "int") + assertExprTypeTest("1 == 2", "bool") + assertExprTypeTest("1 != 2", "bool") + assertExprTypeTest("1 < 2", "bool") + assertExprTypeTest("1 <= 2", "bool") + assertExprTypeTest("1 > 2", "bool") + assertExprTypeTest("1 >= 2", "bool") + assertExprTypeTest("true && false", "bool") + assertExprTypeTest("true || false", "bool") + } + + it("should handle unary expressions") { + assertExprTypeTest("-1", "int") + assertExprTypeTest("+1", "int") + // FIXME: assertExprType("~7", "int") + assertExprTypeTest("!true", "bool") + } + + it("should handle grouped expressions") { + assertExprTypeTest("(12)", "int") + assertExprTypeTest("(true)", "bool") + } + + it("should handle int variables") { + assertExprTypeWithSetup("val x = 12", "x", "int") + } + + it("should handle bool variables") { + assertExprTypeWithSetup("val x = true", "x", "bool") + } + + it("should handle string variables") { + assertExprTypeWithSetup("val x = \"hello\"", "x", "string") + } + + it("should handle char variables") { + assertExprTypeWithSetup("val x = 'a'", "x", "char") + } + + it("should handle variable addition") { + assertExprTypeWithSetup("val x = 12", "x + 12", "int") + } + + it("should handle variable equality") { + assertExprTypeWithSetup("val x = 12", "12 == x", "bool") + } + + it("should handle variable assignment") { + assertExprTypeWithSetup("var x = 12", "x = 10", "unit") + } + + it("should handle conversions to any") { + assertAssignableTo("12", "any") + assertAssignableTo("true", "any") + assertAssignableTo("\"hello\"", "any") + assertAssignableTo("'a'", "any") + } + + it("should handle function calls") { + assertExprTypeTest("println(12)", "unit") + assertExprTypeTest("print(12)", "unit") + assertExprTypeTest("print(\"hello\")", "unit") + assertExprTypeTest("print('a')", "unit") + assertExprTypeTest("print(true)", "unit") + assertExprTypeTest("print(12 + 12)", "unit") + assertExprTypeTest("print(12 == 12)", "unit") + assertExprTypeTest("print(12 < 12)", "unit") + assertExprTypeTest("print(true && false)", "unit") + assertExprTypeTest("print(true || false)", "unit") + } + + it("should handle casts") { + assertExprTypeTest("'a' as char", "char") + assertExprTypeTest("12 as char", "char") + assertExprTypeTest("'a' as int", "int") + assertExprTypeTest("12 as int", "int") + assertExprTypeTest("'a' as string", "string") + assertExprTypeTest("12 as string", "string") + assertExprTypeTest("\"hello\" as string", "string") + assertExprTypeTest("true as string", "string") + } + + it("should handle is expressions") { + assertExprTypeTest("12 is int", "bool") + assertExprTypeTest("'a' is char", "bool") + assertExprTypeTest("\"hello\" is string", "bool") + assertExprTypeTest("true is bool", "bool") + assertExprTypeTest("12 is bool", "bool") // should return false at runtime + } + + it("should handle block expressions") { + assertExprTypeTest("{ 1 }", "int") + assertExprTypeTest("{ true }", "bool") + assertExprTypeTest("{ }", "unit") + assertExprTypeTest( + "{\n" + + " 1\n" + + " 2\n" + + "}", + "int" + ) + assertExprTypeTest( + "{\n" + + "true\n" + + "false\n" + + "}", + "bool" + ) + assertExprTypeTest( + "{\n" + + " 1\n" + + " true\n" + + "}", + "bool" + ) + assertExprTypeTest( + "{\n" + + " true\n" + + " 1\n" + + "}", + "int" + ) + } + + it("should handle if expressions") { + assertExprTypeTest("if (true) 1 else 2", "int") + assertExprTypeTest("if (false) 1 else 2", "int") + assertExprTypeTest("if (true) true else false", "bool") + assertExprTypeTest("if (false) true else false", "bool") + assertExprTypeTest("if (false) true", "unit") + } + + it("should handle while expressions") { + assertExprTypeTest("while (true) 1", "unit") + assertExprTypeTest("while (false) 1", "unit") + } + + it("should handle literal patterns") { + assertExprTypeTest("1 match { case 1 => 2 }", "int") + assertExprTypeTest("1 match { case 2 => 3 }", "int") + assertExprTypeTest("true match { case true => false }", "bool") + assertExprTypeTest( + "\"hello\" match { case \"world\" => \"hi\" }", + "string" + ) + assertExprTypeTest("'a' match { case 'b' => 'c' }", "char") + } + + it("should handle wildcard patterns") { + assertExprTypeTest("1 match { case _ => 2 }", "int") + assertExprTypeTest("true match { case _ => false }", "bool") + assertExprTypeTest("\"hello\" match { case _ => \"world\" }", "string") + } + + it("should handle multiple cases with same type") { + assertExprTypeTest( + "1 match { case 1 => 10\n case 2 => 20 }", + "int" + ) + assertExprTypeTest( + "true match { case true => 1\n case false => 0 }", + "int" + ) + } + + it("should handle multiple cases with different types") { + // When cases have different types, result should be 'any' (least upper bound) + assertExprTypeTest( + "1 match { case 1 => 42\n case 2 => \"hello\" }", + "int | string" + ) + assertExprTypeTest( + "1 match { case 1 => true\n case 2 => 123 }", + "bool | int" + ) + } + + it("should handle unit result cases") { + assertExprTypeTest("1 match { case x: int => () }", "unit") + assertExprTypeTest("1 match { case 1 => println(\"test\") }", "unit") + } + + it("should handle nested matches") { + assertExprTypeTest( + "1 match { case x: int => x match { case y: int => y * 2 } }", + "int" + ) + } + + it("should handle match with blocks") { + assertExprTypeTest( + "1 match { case x: int => { val y = x + 1\n y * 2 } }", + "int" + ) + assertExprTypeTest( + "true match { case b: bool => { println(\"test\")\n b } }", + "bool" + ) + } + + it("should handle methods without return type") { + val comp = mkCompilation("def foo() = 12") + val symbols = enumNonBuiltinSymbols(comp) + assertProgramSymbol(symbols) + val foo = assertSymbol(symbols, SymbolKind.Method, "foo") + assertSymbolType(comp, foo, "() -> int") + assertMainSymbol(symbols) + assertNoSymbols(symbols) + } + + it("should handle methods with parameters") { + val comp = mkCompilation("def foo(x: int, y: int) = 12") + val symbols = enumNonBuiltinSymbols(comp) + assertProgramSymbol(symbols) + val foo = assertSymbol(symbols, SymbolKind.Method, "foo") + assertSymbolType(comp, foo, "(x: int, y: int) -> int") + val x = assertSymbol(symbols, SymbolKind.Parameter, "x") + assertSymbolType(comp, x, "int") + val y = assertSymbol(symbols, SymbolKind.Parameter, "y") + assertSymbolType(comp, y, "int") + assertMainSymbol(symbols) + assertNoSymbols(symbols) + } + + it("should handle simple identity generic method") { + val setup = "def identity[T](x: T): T = x" + + assertExprTypeWithSetup(setup, "identity(42)", "int") + assertExprTypeWithSetup(setup, "identity(true)", "bool") + assertExprTypeWithSetup(setup, "identity(\"hello\")", "string") + assertExprTypeWithSetup(setup, "identity('a')", "char") + } + + it("should handle generic method returning concrete type") { + val setup = "def getValue[T](x: T): int = 42" + + // These should work because the return type is concrete + assertExprTypeWithSetup(setup, "getValue(42)", "int") + assertExprTypeWithSetup(setup, "getValue(\"hello\")", "int") + assertExprTypeWithSetup(setup, "getValue(true)", "int") + } + + it("should handle generic parameter with concrete return") { + // Test methods that accept generic parameters but return concrete types + val setup = "def stringify[T](x: T): string = string(x)" + + assertExprTypeWithSetup(setup, "stringify(42)", "string") + assertExprTypeWithSetup(setup, "stringify(true)", "string") + assertExprTypeWithSetup(setup, "stringify('a')", "string") + } + + it("should handle generic container creation") { + val containerSetup = "class Container[T](value: T)\n" + + "def wrap[T](x: T): Container[T] = new Container(x)" + + assertExprTypeWithSetup(containerSetup, "wrap(42)", "Container") + assertExprTypeWithSetup(containerSetup, "wrap(true)", "Container") + assertExprTypeWithSetup( + containerSetup, + "wrap(\"test\")", + "Container" + ) + } + + it("should handle enums without args") { + val setup = "enum Foo {\n" + + " case Bar\n" + + " case Baz\n" + + "}" + assertExprTypeWithSetup(setup, "Foo.Bar", "Foo.Bar") + assertExprTypeWithSetup(setup, "Foo.Baz", "Foo.Baz") + } + + it("should handle enums with args") { + val setup = "enum Foo {\n" + + " case Bar(x: int)\n" + + " case Baz(y: string)\n" + + "}" + assertExprTypeWithSetup(setup, "Foo.Bar(12)", "Foo.Bar") + assertExprTypeWithSetup(setup, "Foo.Baz(\"taco\")", "Foo.Baz") + assertExprTypeWithSetup(setup, "new Foo.Bar(12)", "Foo.Bar") + assertExprTypeWithSetup(setup, "new Foo.Baz(\"taco\")", "Foo.Baz") + } + + it("should check enum assignments") { + val setup = "enum Foo {\n" + + " case Bar(x: int)\n" + + " case Baz(y: string)\n" + + "}" + assertAssignableToWithSetup(setup, "Foo.Bar(12)", "Foo") + assertAssignableToWithSetup(setup, "Foo.Baz(\"taco\")", "Foo") + } + + it("should handle enums with generic type") { + val setup = "enum Option[T] {\n" + + " case Some(value: T)\n" + + " case None\n" + + "}" + assertAssignableToWithSetup(setup, "new Option.Some(12)", "Option[int]") + assertAssignableToWithSetup( + setup, + "new Option.Some(12)", + "Option.Some[int]" + ) + assertAssignableToWithSetup(setup, "Option.Some(12)", "Option[int]") + assertAssignableToWithSetup(setup, "Option.None", "Option[int]") + assertAssignableToWithSetup(setup, "Option.None", "Option[never]") + } + + it("should handle list examples") { + val setup = "enum List[T] {\n" + + " case Cons(head: T, tail: List[T])\n" + + " case Nil\n" + + "}" + assertAssignableToWithSetup( + setup, + "new List.Cons(1, List.Nil)", + "List[int]" + ) + assertAssignableToWithSetup( + setup, + "List.Cons(1, List.Nil)", + "List[int]" + ) + assertAssignableToWithSetup( + setup, + "new List.Cons(\"hello\", List.Nil)", + "List[string]" + ) + assertAssignableToWithSetup( + setup, + "List.Cons(\"hello\", List.Nil)", + "List[string]" + ) + assertAssignableToWithSetup(setup, "List.Nil", "List[int]") + assertAssignableToWithSetup(setup, "List.Nil", "List[string]") + } + + it("should handle classes without args") { + val setup = "class Foo()" + assertExprTypeWithSetup(setup, "new Foo()", "Foo") + } + + it("should handle classes with args") { + val setup = "class Foo(x: int, y: string)" + assertExprTypeWithSetup(setup, "new Foo(12, \"taco\")", "Foo") + } + + it("should handle array length type") { + val setup = "val array = new Array[int](0)" + assertExprTypeWithSetup(setup, "array.length", "int") + } + + it("should handle array apply with method call") { + val setup = "val array = new Array[int](1)" + assertExprTypeWithSetup(setup, "array.apply(0)", "int") + } + + it("should handle array apply with indexer syntax for basic types") { + val setup = "val intArray = new Array[int](1)" + assertExprTypeWithSetup(setup, "intArray(0)", "int") + + val boolSetup = "val boolArray = new Array[bool](1)" + assertExprTypeWithSetup(boolSetup, "boolArray(0)", "bool") + + val stringSetup = "val stringArray = new Array[string](1)" + assertExprTypeWithSetup(stringSetup, "stringArray(0)", "string") + + val charSetup = "val charArray = new Array[char](1)" + assertExprTypeWithSetup(charSetup, "charArray(0)", "char") + } + + it("should handle array indexing in expressions") { + val setup = "val array = new Array[int](5)" + assertExprTypeWithSetup(setup, "array(0) + array(1)", "int") + assertExprTypeWithSetup(setup, "array(2) * 3", "int") + assertExprTypeWithSetup(setup, "array(0) == array(1)", "bool") + } + + it("should handle array indexing with computed indices") { + val setup = "val array = new Array[int](10)\nval i = 5" + assertExprTypeWithSetup(setup, "array(i)", "int") + assertExprTypeWithSetup(setup, "array(1 + 2)", "int") + assertExprTypeWithSetup(setup, "array(i * 2)", "int") + } + + it("should handle array indexing assignment - LHS binding fix") { + // This tests the specific fix for "NewExpression in bindLHS" error + val setup = "var array = new Array[int](5)" + assertExprTypeWithSetup(setup, "array(0) = 42", "unit") + assertExprTypeWithSetup(setup, "array(1) = array(0) + 1", "unit") + } + + it("should handle primitive casts") { + // Basic numeric casting + assertExprTypeTest("42 as int", "int") + assertExprTypeTest("42 as bool", "bool") + assertExprTypeTest("42 as char", "char") + assertExprTypeTest("42 as string", "string") + + // Boolean casting + assertExprTypeTest("true as int", "int") + assertExprTypeTest("true as bool", "bool") + assertExprTypeTest("false as string", "string") + + // Character casting + assertExprTypeTest("'a' as int", "int") + assertExprTypeTest("'a' as char", "char") + assertExprTypeTest("'a' as string", "string") + + // String casting + assertExprTypeTest("\"hello\" as string", "string") + assertExprTypeTest("\"hello\" as any", "any") + } + + it("should handle variable casts") { + val setup = + "val x = 42\nval flag = true\nval ch = 'a'\nval text = \"hello\"" + + assertExprTypeWithSetup(setup, "x as bool", "bool") + assertExprTypeWithSetup(setup, "x as char", "char") + assertExprTypeWithSetup(setup, "x as string", "string") + + assertExprTypeWithSetup(setup, "flag as int", "int") + assertExprTypeWithSetup(setup, "flag as string", "string") + + assertExprTypeWithSetup(setup, "ch as int", "int") + assertExprTypeWithSetup(setup, "ch as string", "string") + + assertExprTypeWithSetup(setup, "text as any", "any") + } + + it("should handle cast to any type") { + assertExprTypeTest("42 as any", "any") + assertExprTypeTest("true as any", "any") + assertExprTypeTest("\"hello\" as any", "any") + assertExprTypeTest("'a' as any", "any") + assertExprTypeTest("() as any", "any") + } + + it("should handle cast from any type") { + val setup = "val obj: any = 42" + + assertExprTypeWithSetup(setup, "obj as int", "int") + assertExprTypeWithSetup(setup, "obj as bool", "bool") + assertExprTypeWithSetup(setup, "obj as char", "char") + assertExprTypeWithSetup(setup, "obj as string", "string") + assertExprTypeWithSetup(setup, "obj as unit", "unit") + } + + it("should handle expression casts") { + // Cast results of binary operations + assertExprTypeTest("(1 + 2) as bool", "bool") + assertExprTypeTest("(true && false) as int", "int") + assertExprTypeTest("(1 == 2) as string", "string") + + // Cast results of unary operations + assertExprTypeTest("(!true) as int", "int") + assertExprTypeTest("(-42) as bool", "bool") + } + + it("should handle cast with parentheses") { + assertExprTypeTest("(42) as string", "string") + assertExprTypeTest("(true) as int", "int") + assertExprTypeTest("(\"hello\") as any", "any") + } + + it("should handle chained operations with casts") { + val setup = "val x = 42" + + // Cast should have appropriate precedence + assertExprTypeWithSetup(setup, "x as bool == true", "bool") + assertExprTypeWithSetup(setup, "(x as bool) == true", "bool") + } + + it("should handle basic string conversions") { + // Convert literals to string + assertExprTypeTest("string(42)", "string") + assertExprTypeTest("string(0)", "string") + assertExprTypeTest("string(-123)", "string") + assertExprTypeTest("string(true)", "string") + assertExprTypeTest("string(false)", "string") + assertExprTypeTest("string('a')", "string") + assertExprTypeTest("string('z')", "string") + assertExprTypeTest("string(())", "string") + } + + it("should handle string conversions with variables") { + val intSetup = "val num = 123" + assertExprTypeWithSetup(intSetup, "string(num)", "string") + + val boolSetup = "val flag = true" + assertExprTypeWithSetup(boolSetup, "string(flag)", "string") + + val charSetup = "val ch = 'A'" + assertExprTypeWithSetup(charSetup, "string(ch)", "string") + + val unitSetup = "val nothing = ()" + assertExprTypeWithSetup(unitSetup, "string(nothing)", "string") + } + + it("should handle string conversions with expressions") { + // Arithmetic expressions + assertExprTypeTest("string(1 + 2)", "string") + assertExprTypeTest("string(10 - 5)", "string") + assertExprTypeTest("string(3 * 4)", "string") + assertExprTypeTest("string(15 / 3)", "string") + assertExprTypeTest("string(17 % 5)", "string") + + // Boolean expressions + assertExprTypeTest("string(true && false)", "string") + assertExprTypeTest("string(true || false)", "string") + assertExprTypeTest("string(!true)", "string") + assertExprTypeTest("string(5 > 3)", "string") + assertExprTypeTest("string(5 == 5)", "string") + assertExprTypeTest("string(5 != 3)", "string") + + // Unary expressions + assertExprTypeTest("string(-42)", "string") + assertExprTypeTest("string(+42)", "string") + } + + it("should handle string conversions in complex expressions") { + val setup = "val x = 42\nval y = true" + + // String conversions in comparisons + assertExprTypeWithSetup(setup, "string(x) == \"42\"", "bool") + assertExprTypeWithSetup(setup, "string(y) == \"true\"", "bool") + assertExprTypeWithSetup(setup, "string(x) != \"0\"", "bool") + + // String conversions in arithmetic context + assertExprTypeWithSetup(setup, "string(x + 10)", "string") + assertExprTypeWithSetup(setup, "string(x * 2)", "string") + } + + it("should handle string conversions with method calls") { + // Convert results of method calls to string + assertExprTypeTest("string(println(\"test\"))", "string") + assertExprTypeTest("string(print(42))", "string") + } + + it("should handle string conversions with control flow") { + // String conversions with if expressions + assertExprTypeTest("string(if (true) 1 else 2)", "string") + assertExprTypeTest("string(if (false) true else false)", "string") + + // String conversions with block expressions + assertExprTypeTest("string({ 42 })", "string") + assertExprTypeTest("string({ true })", "string") + } + + it("should handle nested string conversions") { + val setup = "val x = 42" + + // String conversion of string (should still work) + assertExprTypeTest("string(\"hello\")", "string") + assertExprTypeWithSetup(setup, "string(string(x))", "string") + + // String conversions in nested expressions + assertExprTypeWithSetup(setup, "string(string(x) == \"42\")", "string") + } + + it("should handle int conversions") { + // Convert various types to int + assertExprTypeTest("int(42)", "int") + assertExprTypeTest("int(true)", "int") + assertExprTypeTest("int(false)", "int") + assertExprTypeTest("int('a')", "int") + + // Int conversions with variables and expressions + val setup = "val flag = true\nval ch = 'A'" + assertExprTypeWithSetup(setup, "int(flag)", "int") + assertExprTypeWithSetup(setup, "int(ch)", "int") + assertExprTypeTest("int(1 + 2)", "int") + } + + it("should handle bool conversions") { + // Convert various types to bool + assertExprTypeTest("bool(true)", "bool") + assertExprTypeTest("bool(false)", "bool") + assertExprTypeTest("bool(42)", "bool") + assertExprTypeTest("bool(0)", "bool") + + // Bool conversions with variables and expressions + val setup = "val num = 123" + assertExprTypeWithSetup(setup, "bool(num)", "bool") + assertExprTypeTest("bool(5 > 3)", "bool") + } + + it("should handle char conversions") { + // Convert various types to char + assertExprTypeTest("char('a')", "char") + assertExprTypeTest("char(65)", "char") + + // Char conversions with variables + val setup = "val ascii = 97" + assertExprTypeWithSetup(setup, "char(ascii)", "char") + } } } diff --git a/test/src/test/scala/VmTests.scala b/test/src/test/scala/VmTests.scala index 643b823..84a15a6 100644 --- a/test/src/test/scala/VmTests.scala +++ b/test/src/test/scala/VmTests.scala @@ -1,680 +1,682 @@ import TestHelpers._ -import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers -class VmTests extends AnyFlatSpec with Matchers { - - "VM" should "execute constants" in { - assertExecValueInt("12", 12) - assertExecValueInt("0", 0) - assertExecValueInt("-5", -5) - assertExecValueBool("true", true) - assertExecValueBool("false", false) - assertExecValueString("\"hello\"", "hello") - } - - it should "execute unary operations" in { - assertExecValueInt("-12", -12) - assertExecValueInt("+12", 12) - assertExecValueBool("!true", false) - assertExecValueBool("!false", true) - } - - it should "execute binary operations" in { - assertExecValueInt("1 + 2", 3) - assertExecValueInt("5 - 3", 2) - assertExecValueInt("4 * 3", 12) - assertExecValueInt("15 / 3", 5) - assertExecValueInt("17 % 5", 2) - assertExecValueInt("17 & 5", 1) - assertExecValueInt("17 | 5", 21) - - assertExecValueBool("5 == 5", true) - assertExecValueBool("5 == 3", false) - assertExecValueBool("5 != 3", true) - assertExecValueBool("5 != 5", false) - assertExecValueBool("5 < 10", true) - assertExecValueBool("10 < 5", false) - assertExecValueBool("5 <= 5", true) - assertExecValueBool("5 <= 3", false) - assertExecValueBool("10 > 5", true) - assertExecValueBool("5 > 10", false) - assertExecValueBool("5 >= 5", true) - assertExecValueBool("3 >= 5", false) - - assertExecValueBool("true && true", true) - assertExecValueBool("true && false", false) - assertExecValueBool("false && true", false) - assertExecValueBool("false && false", false) - assertExecValueBool("true || true", true) - assertExecValueBool("true || false", true) - assertExecValueBool("false || true", true) - assertExecValueBool("false || false", false) - } - - it should "handle binary precedence correctly" in { - assertExecValueInt("1 + 2 * 3", 7) // 1 + (2 * 3) - assertExecValueInt("2 * 3 + 1", 7) // (2 * 3) + 1 - assertExecValueInt("10 - 4 / 2", 8) // 10 - (4 / 2) - } - - it should "handle unary precedence correctly" in { - assertExecValueInt("-2 + 3", 1) // (-2) + 3 - assertExecValueInt("-(2 + 3)", -5) // -(2 + 3) - } - - it should "execute local variable declarations and usage" in { - assertExecValueIntWithSetup("val x = 12", "x", 12) - assertExecValueIntWithSetup("val x = 12", "x + 7", 19) - assertExecValueIntWithSetup("val x = 12", "7 + x", 19) - assertExecValueIntWithSetup("val x = 12\nval y = x + 1", "x", 12) - assertExecValueIntWithSetup("val x = 12\nval y = x + 1", "y", 13) - } - - it should "execute if expressions" in { - assertExecValueInt("if (true) 1 else 2", 1) - assertExecValueInt("if (false) 1 else 2", 2) - assertExecValueInt("if (5 > 3) 10 else 20", 10) - assertExecValueInt("if (3 > 5) 10 else 20", 20) - } - - it should "execute function calls" in { - assertExecValueIntWithSetup( - "def noargs(): int = 7", - "noargs()", - 7 - ) - assertExecValueIntWithSetup( - "def onearg(x: int): int = x + 1", - "onearg(12)", - 13 - ) - assertExecValueIntWithSetup( - "def twoargs(x: int, y: int): int = x + y", - "twoargs(12, 13)", - 25 - ) - assertExecValueIntWithSetup( - "def threeargs(x: int, y: int, z: int): int = x + y + z", - "threeargs(12, 13, 14)", - 39 - ) - } - - it should "execute object methods" in { - val setup = "object TestObject {\n" + - " def testMethod(x: int): int = x + 1\n" + - " def testMethod2(x: int, y: int): int = x + y\n" + - " def noArgs(): int = 42\n" + - " def threeArgs(a: int, b: int, c: int): int = a * b + c\n" + - " def multiply(x: int, y: int): int = x * y\n" + - " def subtract(x: int, y: int): int = x - y\n" + - " def negate(x: int): int = -x\n" + - "}\n" - - assertExecValueIntWithSetup(setup, "TestObject.testMethod(0)", 1) - assertExecValueIntWithSetup(setup, "TestObject.testMethod(11)", 12) - assertExecValueIntWithSetup(setup, "TestObject.testMethod(12)", 13) - assertExecValueIntWithSetup(setup, "TestObject.testMethod2(11, 12)", 23) - assertExecValueIntWithSetup(setup, "TestObject.testMethod2(12, 13)", 25) - - // No arguments - assertExecValueIntWithSetup(setup, "TestObject.noArgs()", 42) - - // Three arguments - assertExecValueIntWithSetup( - setup, - "TestObject.threeArgs(2, 3, 4)", - 10 - ) // 2 * 3 + 4 - assertExecValueIntWithSetup( - setup, - "TestObject.threeArgs(5, 6, 7)", - 37 - ) // 5 * 6 + 7 - assertExecValueIntWithSetup( - setup, - "TestObject.threeArgs(0, 10, 5)", - 5 - ) // 0 * 10 + 5 - - // More arithmetic operations - assertExecValueIntWithSetup(setup, "TestObject.multiply(7, 8)", 56) - assertExecValueIntWithSetup(setup, "TestObject.multiply(-3, 4)", -12) - assertExecValueIntWithSetup(setup, "TestObject.subtract(10, 3)", 7) - assertExecValueIntWithSetup(setup, "TestObject.subtract(5, 8)", -3) - assertExecValueIntWithSetup(setup, "TestObject.negate(42)", -42) - assertExecValueIntWithSetup(setup, "TestObject.negate(-15)", 15) - - // Nested calls - assertExecValueIntWithSetup( - setup, - "TestObject.testMethod(TestObject.noArgs())", - 43 - ) // testMethod(42) = 43 - assertExecValueIntWithSetup( - setup, - "TestObject.multiply(TestObject.testMethod(2), 5)", - 15 - ) // multiply(3, 5) = 15 - } - - it should "access object fields" in { - val setup = "object Taco {\n" + - " val field1 = 12\n" + - " val field2 = 13\n" + - "}\n" + - "object Calculator {\n" + - " val zero = 0\n" + - " val one = 1\n" + - " val negativeValue = -42\n" + - " val largeValue = 9999\n" + - "}\n" + - "object MathConstants {\n" + - " val pi = 3\n" + // Simplified for int - " val e = 2\n" + // Simplified for int - "}\n" - - assertExecValueIntWithSetup(setup, "Taco.field1", 12) - assertExecValueIntWithSetup(setup, "Taco.field2", 13) - - // Calculator object tests - assertExecValueIntWithSetup(setup, "Calculator.zero", 0) - assertExecValueIntWithSetup(setup, "Calculator.one", 1) - assertExecValueIntWithSetup(setup, "Calculator.negativeValue", -42) - assertExecValueIntWithSetup(setup, "Calculator.largeValue", 9999) - - // MathConstants tests - assertExecValueIntWithSetup(setup, "MathConstants.pi", 3) - assertExecValueIntWithSetup(setup, "MathConstants.e", 2) - - // Field access in expressions - assertExecValueIntWithSetup( - setup, - "Taco.field1 + Taco.field2", - 25 - ) // 12 + 13 - assertExecValueIntWithSetup( - setup, - "Calculator.zero + Calculator.one", - 1 - ) // 0 + 1 - assertExecValueIntWithSetup( - setup, - "MathConstants.pi * MathConstants.e", - 6 - ) // 3 * 2 - assertExecValueIntWithSetup( - setup, - "Taco.field1 * Calculator.one", - 12 - ) // 12 * 1 - assertExecValueIntWithSetup( - setup, - "Calculator.largeValue - Taco.field1", - 9987 - ) // 9999 - 12 - - // More complex expressions - assertExecValueIntWithSetup( - setup, - "Taco.field1 + Taco.field2 * Calculator.one", - 25 - ) // 12 + 13 * 1 - assertExecValueIntWithSetup( - setup, - "(Taco.field1 + Taco.field2) * MathConstants.e", - 50 - ) // (12 + 13) * 2 - assertExecValueIntWithSetup( - setup, - "Calculator.largeValue + Calculator.negativeValue", - 9957 - ) // 9999 + (-42) - - // Test with unary operations - assertExecValueIntWithSetup(setup, "-Taco.field1", -12) - assertExecValueIntWithSetup(setup, "-Calculator.negativeValue", 42) - } - - it should "execute classes without args" in { - assertExecValueIntWithSetup( - "class Foo() {\n" + - " def bar() = 12\n" + - "}", - "new Foo().bar()", - 12 - ) - } - -// it should "execute classes with args" in { -// assertExecValueIntWithSetup( -// "class Foo(x: int, y: int) {\n" + -// " def add() = x + y\n" + -// "}", -// "new Foo(12, 13).add()", -// 25 -// ) -// } -// -// it should "access class fields via constructor" in { -// assertExecValueIntWithSetup( -// "class Foo(x: int, y: int)", -// "new Foo(1, 2).x + new Foo(3,5).y", -// 6 -// ) -// } - - it should "access class fields via field declaration" in { - assertExecValueIntWithSetup( - "class Foo() {\n" + - " var x = 12\n" + - " var y = 13\n" + - "}", - "new Foo().x + new Foo().y", - 25 - ) - } - - it should "execute pattern matching with integer cases" in { - assertExecValueIntWithSetup( - "val x = 1", - "x match {\n case 1 => 10\n case 2 => 20\n case _ => 0\n}", - 10 - ) - assertExecValueIntWithSetup( - "val x = 2", - "x match {\n case 1 => 10\n case 2 => 20\n case _ => 0\n}", - 20 - ) - assertExecValueIntWithSetup( - "val x = 5", - "x match {\n case 1 => 10\n case 2 => 20\n case _ => 0\n}", - 0 - ) - } - - it should "execute pattern matching with wildcard only" in { - assertExecValueIntWithSetup( - "val x = 42", - "x match {\n case _ => 99\n}", - 99 - ) - } - - it should "execute is expression basic functionality" in { - // Test that is expressions execute properly and return correct boolean values - assertExecValueBool("12 is int", true) - assertExecValueBool("12 is bool", false) - assertExecValueBool("true is bool", true) - assertExecValueBool("true is int", false) - assertExecValueBool("\"hello\" is string", true) - assertExecValueBool("\"hello\" is int", false) - - // Test with variables - assertExecValueIntWithSetup("val x = 12", "if (x is int) 1 else 0", 1) - assertExecValueIntWithSetup("val x = 12", "if (x is bool) 1 else 0", 0) - assertExecValueIntWithSetup("val y = true", "if (y is bool) 1 else 0", 1) - assertExecValueIntWithSetup( - "val y = true", - "if (y is string) 1 else 0", - 0 - ) - } - - it should "execute cast expressions - identity casts" in { - // Test identity casts (casting to the same type) - assertExecValueInt("12 as int", 12) - assertExecValueBool("true as bool", true) - assertExecValueBool("false as bool", false) - assertExecValueString("\"hello\" as string", "hello") - - // Test with variables - assertExecValueIntWithSetup("val x = 42", "x as int", 42) - assertExecValueBoolWithSetup("val flag = true", "flag as bool", true) - assertExecValueStringWithSetup( - "val text = \"world\"", - "text as string", - "world" - ) - } - - it should "execute cast expressions - valid conversions" in { - // Test casts that should succeed at runtime - // Skip char literals for now since emitCharacterLiteral is not implemented - assertExecValueString("42 as string", "42") // int to string - assertExecValueString("true as string", "true") // bool to string - assertExecValueString("false as string", "false") // bool to string - - // Test with expressions - assertExecValueInt("(1 + 2) as int", 3) - assertExecValueString("(5 * 6) as string", "30") - assertExecValueBool("(10 > 5) as bool", true) - } - -// it should "execute cast expressions - cast to any type" in { -// // Test casting various types to 'any' type -// // Note: The actual value should remain the same, just the type changes -// assertExecValueInt("42 as any as int", 42) -// assertExecValueBool("true as any as bool", true) -// assertExecValueString("\"test\" as any as string", "test") -// -// // Test with variables -// assertExecValueIntWithSetup("val x = 100", "x as any as int", 100) -// assertExecValueBoolWithSetup("val flag = false", "flag as any as bool", false) -// } -// -// it should "execute cast expressions - cast from any type" in { -// // Test casting from 'any' type to specific types -// val anySetup = "val obj: any = 42" -// assertExecValueIntWithSetup(anySetup, "obj as int", 42) -// -// val anyBoolSetup = "val obj: any = true" -// assertExecValueBoolWithSetup(anyBoolSetup, "obj as bool", true) -// -// val anyStringSetup = "val obj: any = \"hello\"" -// assertExecValueStringWithSetup(anyStringSetup, "obj as string", "hello") -// } -// -// it should "execute cast expressions - chained casts" in { -// // Test multiple casts in sequence -// assertExecValueInt("42 as any as int", 42) -// assertExecValueString("42 as string as any as string", "42") -// assertExecValueBool("true as any as bool as any as bool", true) -// -// // Test with expressions in between -// assertExecValueInt("(20 + 22) as any as int", 42) -// assertExecValueString("(\"hel\" + \"lo\") as any as string", "hello") -// } - - it should "execute cast expressions - cast with operators" in { - // Test that cast has correct precedence with other operators - assertExecValueBool("42 as int == 42", true) - assertExecValueBool("42 as string == \"42\"", true) - assertExecValueBool("true as bool && false", false) - assertExecValueBool("false as bool || true", true) - - // Test cast in arithmetic expressions - assertExecValueInt("(40 + 2) as int", 42) - assertExecValueInt("40 + 2 as int", 42) // Should be: 40 + (2 as int) - } - - it should "execute cast expressions - cast in control flow" in { - // Test cast expressions in if conditions - assertExecValueInt("if (42 as int == 42) 1 else 0", 1) - assertExecValueInt("if (true as bool) 100 else 200", 100) - assertExecValueInt("if (false as bool) 100 else 200", 200) - - // Test cast in if branches - assertExecValueInt("if (true) 42 as int else 0", 42) - assertExecValueString("if (false) \"no\" else \"yes\" as string", "yes") - - // Test with variables - assertExecValueIntWithSetup( - "val x = 50", - "if (x as int > 40) 1 else 0", - 1 - ) - } - - it should "execute array indexing - basic access" in { - // Test basic array indexing with literal indices - val setup = "val array = new Array[int](3)" - // Note: These tests assume array elements are initialized to 0 - // If that's not the case, we may need to modify the setup - assertExecValueIntWithSetup(setup, "array(0)", 0) - assertExecValueIntWithSetup(setup, "array(1)", 0) - assertExecValueIntWithSetup(setup, "array(2)", 0) - } - - it should "execute array indexing - with assignment" in { - // Test the specific "NewExpression in bindLHS" fix - val setup = "var array = new Array[int](5)" - // First test assignment returns unit - assertExecValueIntWithSetup( - setup + "\narray(0) = 42", - "0", - 0 - ) // dummy assertion to ensure compilation - - // Test assignment followed by access - val assignAndRead = setup + "\narray(0) = 42\narray(1) = 13" - assertExecValueIntWithSetup(assignAndRead, "array(0)", 42) - assertExecValueIntWithSetup(assignAndRead, "array(1)", 13) - } - - it should "execute array indexing - computed indices" in { - val setup = "var array = new Array[int](10)\nval i = 5\narray(i) = 99" - assertExecValueIntWithSetup(setup, "array(i)", 99) - assertExecValueIntWithSetup(setup, "array(5)", 99) - - // Test with arithmetic expressions as indices - val complexSetup = setup + "\narray(2 * 3) = 77\narray(1 + 4) = 55" - assertExecValueIntWithSetup(complexSetup, "array(6)", 77) - assertExecValueIntWithSetup( - complexSetup, - "array(5)", - 55 - ) // This overwrites the previous value at index 5 - } - - it should "execute array indexing - in expressions" in { - val setup = - "var array = new Array[int](5)\narray(0) = 10\narray(1) = 20\narray(2) = 30" - - // Test array indexing in arithmetic expressions - assertExecValueIntWithSetup(setup, "array(0) + array(1)", 30) - assertExecValueIntWithSetup(setup, "array(2) - array(0)", 20) - assertExecValueIntWithSetup(setup, "array(1) * 2", 40) - - // Test array indexing in boolean expressions - assertExecValueBoolWithSetup(setup, "array(0) == 10", true) - assertExecValueBoolWithSetup(setup, "array(1) > array(0)", true) - assertExecValueBoolWithSetup(setup, "array(2) < array(1)", false) - } - - it should "execute array indexing - different types" in { - // Test array indexing with different element types - val boolSetup = - "var boolArray = new Array[bool](3)\nboolArray(0) = true\nboolArray(1) = false" - assertExecValueBoolWithSetup(boolSetup, "boolArray(0)", true) - assertExecValueBoolWithSetup(boolSetup, "boolArray(1)", false) - - val stringSetup = - "var stringArray = new Array[string](2)\nstringArray(0) = \"hello\"\nstringArray(1) = \"world\"" - assertExecValueStringWithSetup(stringSetup, "stringArray(0)", "hello") - assertExecValueStringWithSetup(stringSetup, "stringArray(1)", "world") - } - - it should "execute string conversions - basic literals" in { - // Test string conversion of basic literals - assertExecValueString("string(42)", "42") - assertExecValueString("string(0)", "0") - assertExecValueString("string(-123)", "-123") - assertExecValueString("string(true)", "true") - assertExecValueString("string(false)", "false") - - // String conversion of string (identity) - assertExecValueString("string(\"hello\")", "hello") - assertExecValueString("string(\"\")", "") - } - - it should "execute string conversions - with variables" in { - // Test string conversion of variables - assertExecValueStringWithSetup("val num = 123", "string(num)", "123") - assertExecValueStringWithSetup("val flag = true", "string(flag)", "true") - assertExecValueStringWithSetup( - "val flag2 = false", - "string(flag2)", - "false" - ) - assertExecValueStringWithSetup( - "val text = \"world\"", - "string(text)", - "world" - ) - - // Negative numbers - assertExecValueStringWithSetup("val neg = -42", "string(neg)", "-42") - } - - it should "execute string conversions - with expressions" in { - // Test string conversion of arithmetic expressions - assertExecValueString("string(1 + 2)", "3") - assertExecValueString("string(10 - 5)", "5") - assertExecValueString("string(3 * 4)", "12") - assertExecValueString("string(15 / 3)", "5") - assertExecValueString("string(17 % 5)", "2") - - // Test string conversion of boolean expressions - assertExecValueString("string(true && false)", "false") - assertExecValueString("string(true || false)", "true") - assertExecValueString("string(!true)", "false") - assertExecValueString("string(!false)", "true") - assertExecValueString("string(5 > 3)", "true") - assertExecValueString("string(5 == 5)", "true") - assertExecValueString("string(5 != 3)", "true") - assertExecValueString("string(5 < 3)", "false") - - // Test string conversion of unary expressions - assertExecValueString("string(-42)", "-42") - assertExecValueString("string(+42)", "42") - } - - it should "execute string conversions - complex expressions" in { - val setup = "val x = 42\nval y = true" - - // String conversions in comparisons - assertExecValueBoolWithSetup(setup, "string(x) == \"42\"", true) - assertExecValueBoolWithSetup(setup, "string(y) == \"true\"", true) - assertExecValueBoolWithSetup(setup, "string(x) != \"0\"", true) - assertExecValueBoolWithSetup(setup, "string(y) != \"false\"", true) - - // String conversions of computed values - assertExecValueStringWithSetup(setup, "string(x + 10)", "52") - assertExecValueStringWithSetup(setup, "string(x * 2)", "84") - assertExecValueStringWithSetup(setup, "string(x - 10)", "32") - } - - it should "execute string conversions - with control flow" in { - // String conversions with if expressions - assertExecValueString("string(if (true) 1 else 2)", "1") - assertExecValueString("string(if (false) 1 else 2)", "2") - assertExecValueString("string(if (5 > 3) 100 else 200)", "100") - assertExecValueString("string(if (3 > 5) true else false)", "false") - - // String conversions with block expressions - assertExecValueString("string({ 42 })", "42") - assertExecValueString("string({ true })", "true") - assertExecValueString( - "string({ val temp = 10\n temp * 2 })", - "20" - ) - } - - it should "execute string conversions - nested and chained" in { - val setup = "val x = 42" - - // Nested string conversions - assertExecValueStringWithSetup(setup, "string(string(x))", "42") - assertExecValueString("string(string(\"hello\"))", "hello") - - // String conversions in nested expressions - assertExecValueStringWithSetup( - setup, - "string(string(x) == \"42\")", - "true" - ) - assertExecValueStringWithSetup( - setup, - "string(string(x + 8) == \"50\")", - "true" - ) - } - - it should "execute string conversions - edge cases" in { - // Large numbers - assertExecValueString("string(999999)", "999999") - assertExecValueString("string(-999999)", "-999999") - - // Complex arithmetic - assertExecValueString("string((10 + 5) * (20 / 4))", "75") - assertExecValueString("string(100 - 50 + 25)", "75") - - // Complex boolean logic - assertExecValueString("string((5 > 3) && (10 < 20))", "true") - assertExecValueString("string((5 < 3) || (10 > 20))", "false") - } - - it should "execute int conversions - basic functionality" in { - // Test int conversion of basic literals - assertExecValueInt("int(42)", 42) - assertExecValueInt("int(0)", 0) - assertExecValueInt("int(-123)", -123) - assertExecValueInt("int(true)", 1) - assertExecValueInt("int(false)", 0) - - // Int conversion with variables - assertExecValueIntWithSetup("val flag = true", "int(flag)", 1) - assertExecValueIntWithSetup("val flag2 = false", "int(flag2)", 0) - assertExecValueIntWithSetup("val num = 123", "int(num)", 123) - } - - it should "execute int conversions - string cases" in { - assertExecValueInt("int(\"42\")", 42) - assertExecValueInt("int(\"0\")", 0) - assertExecValueInt("int(\"-123\")", -123) - assertExecValueIntWithSetup("val str = \"56\"", "int(str)", 56) - assertExecValueIntWithSetup("val str = \"-78\"", "int(str)", -78) - // Edge cases - assertExecValueInt("int(\"00123\")", 123) - assertExecValueInt("int(\"-00123\")", -123) - assertExecValueInt("int(\"2147483647\")", 2147483647) // Max int - assertExecValueInt("int(\"-2147483648\")", -2147483648) // Min int - } - - it should "execute bool conversions - basic functionality" in { - // Test bool conversion of basic literals - assertExecValueBool("bool(true)", true) - assertExecValueBool("bool(false)", false) - assertExecValueBool("bool(42)", true) - assertExecValueBool("bool(0)", false) - assertExecValueBool("bool(-5)", true) - - // Bool conversion with variables - assertExecValueBoolWithSetup("val num = 123", "bool(num)", true) - assertExecValueBoolWithSetup("val zero = 0", "bool(zero)", false) - assertExecValueBoolWithSetup("val flag = true", "bool(flag)", true) - } - - it should "execute conversion edge cases" in { - // Test conversion of expressions - assertExecValueString("string(1 + 2 * 3)", "7") - assertExecValueInt("int(5 > 3)", 1) - assertExecValueInt("int(3 > 5)", 0) - assertExecValueBool("bool(10 - 10)", false) - assertExecValueBool("bool(10 - 9)", true) - - // Test nested conversions - assertExecValueString("string(int(true))", "1") - assertExecValueString("string(int(false))", "0") - assertExecValueInt("int(bool(42))", 1) - assertExecValueInt("int(bool(0))", 0) - } - - it should "execute conversions with complex expressions" in { - val setup = "val x = 42\nval y = true" - - // Complex conversions - assertExecValueStringWithSetup(setup, "string(int(y) + x)", "43") - assertExecValueIntWithSetup(setup, "int(string(x) == \"42\")", 1) - assertExecValueBoolWithSetup(setup, "bool(int(y) * x)", true) - - // Conversions in control flow - assertExecValueStringWithSetup( - setup, - "string(if (bool(x)) int(y) else 0)", - "1" - ) +class VmTests extends AnyFunSpec with Matchers { + + describe("VM") { + it("should execute constants") { + assertExecValueInt("12", 12) + assertExecValueInt("0", 0) + assertExecValueInt("-5", -5) + assertExecValueBool("true", true) + assertExecValueBool("false", false) + assertExecValueString("\"hello\"", "hello") + } + + it("should execute unary operations") { + assertExecValueInt("-12", -12) + assertExecValueInt("+12", 12) + assertExecValueBool("!true", false) + assertExecValueBool("!false", true) + } + + it("should execute binary operations") { + assertExecValueInt("1 + 2", 3) + assertExecValueInt("5 - 3", 2) + assertExecValueInt("4 * 3", 12) + assertExecValueInt("15 / 3", 5) + assertExecValueInt("17 % 5", 2) + assertExecValueInt("17 & 5", 1) + assertExecValueInt("17 | 5", 21) + + assertExecValueBool("5 == 5", true) + assertExecValueBool("5 == 3", false) + assertExecValueBool("5 != 3", true) + assertExecValueBool("5 != 5", false) + assertExecValueBool("5 < 10", true) + assertExecValueBool("10 < 5", false) + assertExecValueBool("5 <= 5", true) + assertExecValueBool("5 <= 3", false) + assertExecValueBool("10 > 5", true) + assertExecValueBool("5 > 10", false) + assertExecValueBool("5 >= 5", true) + assertExecValueBool("3 >= 5", false) + + assertExecValueBool("true && true", true) + assertExecValueBool("true && false", false) + assertExecValueBool("false && true", false) + assertExecValueBool("false && false", false) + assertExecValueBool("true || true", true) + assertExecValueBool("true || false", true) + assertExecValueBool("false || true", true) + assertExecValueBool("false || false", false) + } + + it("should handle binary precedence correctly") { + assertExecValueInt("1 + 2 * 3", 7) // 1 + (2 * 3) + assertExecValueInt("2 * 3 + 1", 7) // (2 * 3) + 1 + assertExecValueInt("10 - 4 / 2", 8) // 10 - (4 / 2) + } + + it("should handle unary precedence correctly") { + assertExecValueInt("-2 + 3", 1) // (-2) + 3 + assertExecValueInt("-(2 + 3)", -5) // -(2 + 3) + } + + it("should execute local variable declarations and usage") { + assertExecValueIntWithSetup("val x = 12", "x", 12) + assertExecValueIntWithSetup("val x = 12", "x + 7", 19) + assertExecValueIntWithSetup("val x = 12", "7 + x", 19) + assertExecValueIntWithSetup("val x = 12\nval y = x + 1", "x", 12) + assertExecValueIntWithSetup("val x = 12\nval y = x + 1", "y", 13) + } + + it("should execute if expressions") { + assertExecValueInt("if (true) 1 else 2", 1) + assertExecValueInt("if (false) 1 else 2", 2) + assertExecValueInt("if (5 > 3) 10 else 20", 10) + assertExecValueInt("if (3 > 5) 10 else 20", 20) + } + + it("should execute function calls") { + assertExecValueIntWithSetup( + "def noargs(): int = 7", + "noargs()", + 7 + ) + assertExecValueIntWithSetup( + "def onearg(x: int): int = x + 1", + "onearg(12)", + 13 + ) + assertExecValueIntWithSetup( + "def twoargs(x: int, y: int): int = x + y", + "twoargs(12, 13)", + 25 + ) + assertExecValueIntWithSetup( + "def threeargs(x: int, y: int, z: int): int = x + y + z", + "threeargs(12, 13, 14)", + 39 + ) + } + + it("should execute object methods") { + val setup = "object TestObject {\n" + + " def testMethod(x: int): int = x + 1\n" + + " def testMethod2(x: int, y: int): int = x + y\n" + + " def noArgs(): int = 42\n" + + " def threeArgs(a: int, b: int, c: int): int = a * b + c\n" + + " def multiply(x: int, y: int): int = x * y\n" + + " def subtract(x: int, y: int): int = x - y\n" + + " def negate(x: int): int = -x\n" + + "}\n" + + assertExecValueIntWithSetup(setup, "TestObject.testMethod(0)", 1) + assertExecValueIntWithSetup(setup, "TestObject.testMethod(11)", 12) + assertExecValueIntWithSetup(setup, "TestObject.testMethod(12)", 13) + assertExecValueIntWithSetup(setup, "TestObject.testMethod2(11, 12)", 23) + assertExecValueIntWithSetup(setup, "TestObject.testMethod2(12, 13)", 25) + + // No arguments + assertExecValueIntWithSetup(setup, "TestObject.noArgs()", 42) + + // Three arguments + assertExecValueIntWithSetup( + setup, + "TestObject.threeArgs(2, 3, 4)", + 10 + ) // 2 * 3 + 4 + assertExecValueIntWithSetup( + setup, + "TestObject.threeArgs(5, 6, 7)", + 37 + ) // 5 * 6 + 7 + assertExecValueIntWithSetup( + setup, + "TestObject.threeArgs(0, 10, 5)", + 5 + ) // 0 * 10 + 5 + + // More arithmetic operations + assertExecValueIntWithSetup(setup, "TestObject.multiply(7, 8)", 56) + assertExecValueIntWithSetup(setup, "TestObject.multiply(-3, 4)", -12) + assertExecValueIntWithSetup(setup, "TestObject.subtract(10, 3)", 7) + assertExecValueIntWithSetup(setup, "TestObject.subtract(5, 8)", -3) + assertExecValueIntWithSetup(setup, "TestObject.negate(42)", -42) + assertExecValueIntWithSetup(setup, "TestObject.negate(-15)", 15) + + // Nested calls + assertExecValueIntWithSetup( + setup, + "TestObject.testMethod(TestObject.noArgs())", + 43 + ) // testMethod(42) = 43 + assertExecValueIntWithSetup( + setup, + "TestObject.multiply(TestObject.testMethod(2), 5)", + 15 + ) // multiply(3, 5) = 15 + } + + it("should access object fields") { + val setup = "object Taco {\n" + + " val field1 = 12\n" + + " val field2 = 13\n" + + "}\n" + + "object Calculator {\n" + + " val zero = 0\n" + + " val one = 1\n" + + " val negativeValue = -42\n" + + " val largeValue = 9999\n" + + "}\n" + + "object MathConstants {\n" + + " val pi = 3\n" + // Simplified for int + " val e = 2\n" + // Simplified for int + "}\n" + + assertExecValueIntWithSetup(setup, "Taco.field1", 12) + assertExecValueIntWithSetup(setup, "Taco.field2", 13) + + // Calculator object tests + assertExecValueIntWithSetup(setup, "Calculator.zero", 0) + assertExecValueIntWithSetup(setup, "Calculator.one", 1) + assertExecValueIntWithSetup(setup, "Calculator.negativeValue", -42) + assertExecValueIntWithSetup(setup, "Calculator.largeValue", 9999) + + // MathConstants tests + assertExecValueIntWithSetup(setup, "MathConstants.pi", 3) + assertExecValueIntWithSetup(setup, "MathConstants.e", 2) + + // Field access in expressions + assertExecValueIntWithSetup( + setup, + "Taco.field1 + Taco.field2", + 25 + ) // 12 + 13 + assertExecValueIntWithSetup( + setup, + "Calculator.zero + Calculator.one", + 1 + ) // 0 + 1 + assertExecValueIntWithSetup( + setup, + "MathConstants.pi * MathConstants.e", + 6 + ) // 3 * 2 + assertExecValueIntWithSetup( + setup, + "Taco.field1 * Calculator.one", + 12 + ) // 12 * 1 + assertExecValueIntWithSetup( + setup, + "Calculator.largeValue - Taco.field1", + 9987 + ) // 9999 - 12 + + // More complex expressions + assertExecValueIntWithSetup( + setup, + "Taco.field1 + Taco.field2 * Calculator.one", + 25 + ) // 12 + 13 * 1 + assertExecValueIntWithSetup( + setup, + "(Taco.field1 + Taco.field2) * MathConstants.e", + 50 + ) // (12 + 13) * 2 + assertExecValueIntWithSetup( + setup, + "Calculator.largeValue + Calculator.negativeValue", + 9957 + ) // 9999 + (-42) + + // Test with unary operations + assertExecValueIntWithSetup(setup, "-Taco.field1", -12) + assertExecValueIntWithSetup(setup, "-Calculator.negativeValue", 42) + } + + it("should execute classes without args") { + assertExecValueIntWithSetup( + "class Foo() {\n" + + " def bar() = 12\n" + + "}", + "new Foo().bar()", + 12 + ) + } + + // it should "execute classes with args" in { + // assertExecValueIntWithSetup( + // "class Foo(x: int, y: int) {\n" + + // " def add() = x + y\n" + + // "}", + // "new Foo(12, 13).add()", + // 25 + // ) + // } + // + // it should "access class fields via constructor" in { + // assertExecValueIntWithSetup( + // "class Foo(x: int, y: int)", + // "new Foo(1, 2).x + new Foo(3,5).y", + // 6 + // ) + // } + + it("should access class fields via field declaration") { + assertExecValueIntWithSetup( + "class Foo() {\n" + + " var x = 12\n" + + " var y = 13\n" + + "}", + "new Foo().x + new Foo().y", + 25 + ) + } + + it("should execute pattern matching with integer cases") { + assertExecValueIntWithSetup( + "val x = 1", + "x match {\n case 1 => 10\n case 2 => 20\n case _ => 0\n}", + 10 + ) + assertExecValueIntWithSetup( + "val x = 2", + "x match {\n case 1 => 10\n case 2 => 20\n case _ => 0\n}", + 20 + ) + assertExecValueIntWithSetup( + "val x = 5", + "x match {\n case 1 => 10\n case 2 => 20\n case _ => 0\n}", + 0 + ) + } + + it("should execute pattern matching with wildcard only") { + assertExecValueIntWithSetup( + "val x = 42", + "x match {\n case _ => 99\n}", + 99 + ) + } + + it("should execute is expression basic functionality") { + // Test that is expressions execute properly and return correct boolean values + assertExecValueBool("12 is int", true) + assertExecValueBool("12 is bool", false) + assertExecValueBool("true is bool", true) + assertExecValueBool("true is int", false) + assertExecValueBool("\"hello\" is string", true) + assertExecValueBool("\"hello\" is int", false) + + // Test with variables + assertExecValueIntWithSetup("val x = 12", "if (x is int) 1 else 0", 1) + assertExecValueIntWithSetup("val x = 12", "if (x is bool) 1 else 0", 0) + assertExecValueIntWithSetup("val y = true", "if (y is bool) 1 else 0", 1) + assertExecValueIntWithSetup( + "val y = true", + "if (y is string) 1 else 0", + 0 + ) + } + + it("should execute cast expressions - identity casts") { + // Test identity casts (casting to the same type) + assertExecValueInt("12 as int", 12) + assertExecValueBool("true as bool", true) + assertExecValueBool("false as bool", false) + assertExecValueString("\"hello\" as string", "hello") + + // Test with variables + assertExecValueIntWithSetup("val x = 42", "x as int", 42) + assertExecValueBoolWithSetup("val flag = true", "flag as bool", true) + assertExecValueStringWithSetup( + "val text = \"world\"", + "text as string", + "world" + ) + } + + it("should execute cast expressions - valid conversions") { + // Test casts that should succeed at runtime + // Skip char literals for now since emitCharacterLiteral is not implemented + assertExecValueString("42 as string", "42") // int to string + assertExecValueString("true as string", "true") // bool to string + assertExecValueString("false as string", "false") // bool to string + + // Test with expressions + assertExecValueInt("(1 + 2) as int", 3) + assertExecValueString("(5 * 6) as string", "30") + assertExecValueBool("(10 > 5) as bool", true) + } + + // it should "execute cast expressions - cast to any type" in { + // // Test casting various types to 'any' type + // // Note: The actual value should remain the same, just the type changes + // assertExecValueInt("42 as any as int", 42) + // assertExecValueBool("true as any as bool", true) + // assertExecValueString("\"test\" as any as string", "test") + // + // // Test with variables + // assertExecValueIntWithSetup("val x = 100", "x as any as int", 100) + // assertExecValueBoolWithSetup("val flag = false", "flag as any as bool", false) + // } + // + // it should "execute cast expressions - cast from any type" in { + // // Test casting from 'any' type to specific types + // val anySetup = "val obj: any = 42" + // assertExecValueIntWithSetup(anySetup, "obj as int", 42) + // + // val anyBoolSetup = "val obj: any = true" + // assertExecValueBoolWithSetup(anyBoolSetup, "obj as bool", true) + // + // val anyStringSetup = "val obj: any = \"hello\"" + // assertExecValueStringWithSetup(anyStringSetup, "obj as string", "hello") + // } + // + // it should "execute cast expressions - chained casts" in { + // // Test multiple casts in sequence + // assertExecValueInt("42 as any as int", 42) + // assertExecValueString("42 as string as any as string", "42") + // assertExecValueBool("true as any as bool as any as bool", true) + // + // // Test with expressions in between + // assertExecValueInt("(20 + 22) as any as int", 42) + // assertExecValueString("(\"hel\" + \"lo\") as any as string", "hello") + // } + + it("should execute cast expressions - cast with operators") { + // Test that cast has correct precedence with other operators + assertExecValueBool("42 as int == 42", true) + assertExecValueBool("42 as string == \"42\"", true) + assertExecValueBool("true as bool && false", false) + assertExecValueBool("false as bool || true", true) + + // Test cast in arithmetic expressions + assertExecValueInt("(40 + 2) as int", 42) + assertExecValueInt("40 + 2 as int", 42) // Should be: 40 + (2 as int) + } + + it("should execute cast expressions - cast in control flow") { + // Test cast expressions in if conditions + assertExecValueInt("if (42 as int == 42) 1 else 0", 1) + assertExecValueInt("if (true as bool) 100 else 200", 100) + assertExecValueInt("if (false as bool) 100 else 200", 200) + + // Test cast in if branches + assertExecValueInt("if (true) 42 as int else 0", 42) + assertExecValueString("if (false) \"no\" else \"yes\" as string", "yes") + + // Test with variables + assertExecValueIntWithSetup( + "val x = 50", + "if (x as int > 40) 1 else 0", + 1 + ) + } + + it("should execute array indexing - basic access") { + // Test basic array indexing with literal indices + val setup = "val array = new Array[int](3)" + // Note: These tests assume array elements are initialized to 0 + // If that's not the case, we may need to modify the setup + assertExecValueIntWithSetup(setup, "array(0)", 0) + assertExecValueIntWithSetup(setup, "array(1)", 0) + assertExecValueIntWithSetup(setup, "array(2)", 0) + } + + it("should execute array indexing - with assignment") { + // Test the specific "NewExpression in bindLHS" fix + val setup = "var array = new Array[int](5)" + // First test assignment returns unit + assertExecValueIntWithSetup( + setup + "\narray(0) = 42", + "0", + 0 + ) // dummy assertion to ensure compilation + + // Test assignment followed by access + val assignAndRead = setup + "\narray(0) = 42\narray(1) = 13" + assertExecValueIntWithSetup(assignAndRead, "array(0)", 42) + assertExecValueIntWithSetup(assignAndRead, "array(1)", 13) + } + + it("should execute array indexing - computed indices") { + val setup = "var array = new Array[int](10)\nval i = 5\narray(i) = 99" + assertExecValueIntWithSetup(setup, "array(i)", 99) + assertExecValueIntWithSetup(setup, "array(5)", 99) + + // Test with arithmetic expressions as indices + val complexSetup = setup + "\narray(2 * 3) = 77\narray(1 + 4) = 55" + assertExecValueIntWithSetup(complexSetup, "array(6)", 77) + assertExecValueIntWithSetup( + complexSetup, + "array(5)", + 55 + ) // This overwrites the previous value at index 5 + } + + it("should execute array indexing - in expressions") { + val setup = + "var array = new Array[int](5)\narray(0) = 10\narray(1) = 20\narray(2) = 30" + + // Test array indexing in arithmetic expressions + assertExecValueIntWithSetup(setup, "array(0) + array(1)", 30) + assertExecValueIntWithSetup(setup, "array(2) - array(0)", 20) + assertExecValueIntWithSetup(setup, "array(1) * 2", 40) + + // Test array indexing in boolean expressions + assertExecValueBoolWithSetup(setup, "array(0) == 10", true) + assertExecValueBoolWithSetup(setup, "array(1) > array(0)", true) + assertExecValueBoolWithSetup(setup, "array(2) < array(1)", false) + } + + it("should execute array indexing - different types") { + // Test array indexing with different element types + val boolSetup = + "var boolArray = new Array[bool](3)\nboolArray(0) = true\nboolArray(1) = false" + assertExecValueBoolWithSetup(boolSetup, "boolArray(0)", true) + assertExecValueBoolWithSetup(boolSetup, "boolArray(1)", false) + + val stringSetup = + "var stringArray = new Array[string](2)\nstringArray(0) = \"hello\"\nstringArray(1) = \"world\"" + assertExecValueStringWithSetup(stringSetup, "stringArray(0)", "hello") + assertExecValueStringWithSetup(stringSetup, "stringArray(1)", "world") + } + + it("should execute string conversions - basic literals") { + // Test string conversion of basic literals + assertExecValueString("string(42)", "42") + assertExecValueString("string(0)", "0") + assertExecValueString("string(-123)", "-123") + assertExecValueString("string(true)", "true") + assertExecValueString("string(false)", "false") + + // String conversion of string (identity) + assertExecValueString("string(\"hello\")", "hello") + assertExecValueString("string(\"\")", "") + } + + it("should execute string conversions - with variables") { + // Test string conversion of variables + assertExecValueStringWithSetup("val num = 123", "string(num)", "123") + assertExecValueStringWithSetup("val flag = true", "string(flag)", "true") + assertExecValueStringWithSetup( + "val flag2 = false", + "string(flag2)", + "false" + ) + assertExecValueStringWithSetup( + "val text = \"world\"", + "string(text)", + "world" + ) + + // Negative numbers + assertExecValueStringWithSetup("val neg = -42", "string(neg)", "-42") + } + + it("should execute string conversions - with expressions") { + // Test string conversion of arithmetic expressions + assertExecValueString("string(1 + 2)", "3") + assertExecValueString("string(10 - 5)", "5") + assertExecValueString("string(3 * 4)", "12") + assertExecValueString("string(15 / 3)", "5") + assertExecValueString("string(17 % 5)", "2") + + // Test string conversion of boolean expressions + assertExecValueString("string(true && false)", "false") + assertExecValueString("string(true || false)", "true") + assertExecValueString("string(!true)", "false") + assertExecValueString("string(!false)", "true") + assertExecValueString("string(5 > 3)", "true") + assertExecValueString("string(5 == 5)", "true") + assertExecValueString("string(5 != 3)", "true") + assertExecValueString("string(5 < 3)", "false") + + // Test string conversion of unary expressions + assertExecValueString("string(-42)", "-42") + assertExecValueString("string(+42)", "42") + } + + it("should execute string conversions - complex expressions") { + val setup = "val x = 42\nval y = true" + + // String conversions in comparisons + assertExecValueBoolWithSetup(setup, "string(x) == \"42\"", true) + assertExecValueBoolWithSetup(setup, "string(y) == \"true\"", true) + assertExecValueBoolWithSetup(setup, "string(x) != \"0\"", true) + assertExecValueBoolWithSetup(setup, "string(y) != \"false\"", true) + + // String conversions of computed values + assertExecValueStringWithSetup(setup, "string(x + 10)", "52") + assertExecValueStringWithSetup(setup, "string(x * 2)", "84") + assertExecValueStringWithSetup(setup, "string(x - 10)", "32") + } + + it("should execute string conversions - with control flow") { + // String conversions with if expressions + assertExecValueString("string(if (true) 1 else 2)", "1") + assertExecValueString("string(if (false) 1 else 2)", "2") + assertExecValueString("string(if (5 > 3) 100 else 200)", "100") + assertExecValueString("string(if (3 > 5) true else false)", "false") + + // String conversions with block expressions + assertExecValueString("string({ 42 })", "42") + assertExecValueString("string({ true })", "true") + assertExecValueString( + "string({ val temp = 10\n temp * 2 })", + "20" + ) + } + + it("should execute string conversions - nested and chained") { + val setup = "val x = 42" + + // Nested string conversions + assertExecValueStringWithSetup(setup, "string(string(x))", "42") + assertExecValueString("string(string(\"hello\"))", "hello") + + // String conversions in nested expressions + assertExecValueStringWithSetup( + setup, + "string(string(x) == \"42\")", + "true" + ) + assertExecValueStringWithSetup( + setup, + "string(string(x + 8) == \"50\")", + "true" + ) + } + + it("should execute string conversions - edge cases") { + // Large numbers + assertExecValueString("string(999999)", "999999") + assertExecValueString("string(-999999)", "-999999") + + // Complex arithmetic + assertExecValueString("string((10 + 5) * (20 / 4))", "75") + assertExecValueString("string(100 - 50 + 25)", "75") + + // Complex boolean logic + assertExecValueString("string((5 > 3) && (10 < 20))", "true") + assertExecValueString("string((5 < 3) || (10 > 20))", "false") + } + + it("should execute int conversions - basic functionality") { + // Test int conversion of basic literals + assertExecValueInt("int(42)", 42) + assertExecValueInt("int(0)", 0) + assertExecValueInt("int(-123)", -123) + assertExecValueInt("int(true)", 1) + assertExecValueInt("int(false)", 0) + + // Int conversion with variables + assertExecValueIntWithSetup("val flag = true", "int(flag)", 1) + assertExecValueIntWithSetup("val flag2 = false", "int(flag2)", 0) + assertExecValueIntWithSetup("val num = 123", "int(num)", 123) + } + + it("should execute int conversions - string cases") { + assertExecValueInt("int(\"42\")", 42) + assertExecValueInt("int(\"0\")", 0) + assertExecValueInt("int(\"-123\")", -123) + assertExecValueIntWithSetup("val str = \"56\"", "int(str)", 56) + assertExecValueIntWithSetup("val str = \"-78\"", "int(str)", -78) + // Edge cases + assertExecValueInt("int(\"00123\")", 123) + assertExecValueInt("int(\"-00123\")", -123) + assertExecValueInt("int(\"2147483647\")", 2147483647) // Max int + assertExecValueInt("int(\"-2147483648\")", -2147483648) // Min int + } + + it("should execute bool conversions - basic functionality") { + // Test bool conversion of basic literals + assertExecValueBool("bool(true)", true) + assertExecValueBool("bool(false)", false) + assertExecValueBool("bool(42)", true) + assertExecValueBool("bool(0)", false) + assertExecValueBool("bool(-5)", true) + + // Bool conversion with variables + assertExecValueBoolWithSetup("val num = 123", "bool(num)", true) + assertExecValueBoolWithSetup("val zero = 0", "bool(zero)", false) + assertExecValueBoolWithSetup("val flag = true", "bool(flag)", true) + } + + it("should execute conversion edge cases") { + // Test conversion of expressions + assertExecValueString("string(1 + 2 * 3)", "7") + assertExecValueInt("int(5 > 3)", 1) + assertExecValueInt("int(3 > 5)", 0) + assertExecValueBool("bool(10 - 10)", false) + assertExecValueBool("bool(10 - 9)", true) + + // Test nested conversions + assertExecValueString("string(int(true))", "1") + assertExecValueString("string(int(false))", "0") + assertExecValueInt("int(bool(42))", 1) + assertExecValueInt("int(bool(0))", 0) + } + + it("should execute conversions with complex expressions") { + val setup = "val x = 42\nval y = true" + + // Complex conversions + assertExecValueStringWithSetup(setup, "string(int(y) + x)", "43") + assertExecValueIntWithSetup(setup, "int(string(x) == \"42\")", 1) + assertExecValueBoolWithSetup(setup, "bool(int(y) * x)", true) + + // Conversions in control flow + assertExecValueStringWithSetup( + setup, + "string(if (bool(x)) int(y) else 0)", + "1" + ) + } } }