From 4a760c341aff47f816ba602e886870d81b43b475 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Sat, 5 Mar 2022 23:39:36 +0100 Subject: [PATCH 001/112] Refactored and added implicit wrapper --- app/src/main/scala/veritas/PoSharp.scala | 81 ++++++++++++++++++++++++ app/src/main/scala/veritas/Veritas.scala | 67 ++------------------ 2 files changed, 87 insertions(+), 61 deletions(-) create mode 100644 app/src/main/scala/veritas/PoSharp.scala diff --git a/app/src/main/scala/veritas/PoSharp.scala b/app/src/main/scala/veritas/PoSharp.scala new file mode 100644 index 0000000..2a1d173 --- /dev/null +++ b/app/src/main/scala/veritas/PoSharp.scala @@ -0,0 +1,81 @@ +package veritas + +import scala.language.implicitConversions + +protected trait IPoSharp { + /** + * Asserts that the expected value is equal to the output of the code snippet. + * + * @param expected The expected value. + * @return True iff the expected value matches the output of the code snippet. + */ + def ShouldBe(expected: String): PoSharp.PoSharpScript + + /** + * Asserts that the given exception is thrown when the snippet executes. + * + * @note This is a terminal method, i.e. you do not need to call Run after it. + * @param expected The expected exception. + * @return True iff the exception thrown has the same type as the expected one. + */ + def ShouldThrow(expected: Throwable): (Boolean, String) +} + +object PoSharp { + /** + * Holds a code snippet to execute for testing purposes. + * + * @param code The code snippet. + */ + case class PoSharpScript(code: String) extends IPoSharp { + private var expected = "" + + def ShouldBe(expected: String): PoSharpScript = { + this.expected = expected + this + } + + def ShouldThrow(expected: Throwable): (Boolean, String) = { + try { + Veritas.GetOutput(code, Veritas.getTestName) + } catch { + case e: Exception => return handleException(e) + } + + def handleException(e: Exception): (Boolean, String) = { + if (expected.getClass == e.getClass) + (true, e.getMessage) + else + (false, s"Expected \"$expected.type\", \"$e.type\" was thrown instead") + } + + (false, "No exception was thrown") + } + + /** + * Compile, run the code snippet and check assertions. + * + * @return + */ + def Run(): (Boolean, String) = { + val output = Veritas.GetOutput(code, Veritas.getTestName) + + if (expected == output) { + (true, expected) + } else { + (false, s"was $expected") + } + } + } + + /** + * Implicit wrapper for PoSharpScript + * + * @param code The code snippet + */ + implicit class PoSharpImplicit(val code: String) extends IPoSharp { + def ShouldBe(expected: String): PoSharpScript = PoSharpScript(code).ShouldBe(expected) + + def ShouldThrow(expected: Throwable): (Boolean, String) = PoSharpScript(code).ShouldThrow(expected) + } +} \ No newline at end of file diff --git a/app/src/main/scala/veritas/Veritas.scala b/app/src/main/scala/veritas/Veritas.scala index 7be222d..1fe5f22 100644 --- a/app/src/main/scala/veritas/Veritas.scala +++ b/app/src/main/scala/veritas/Veritas.scala @@ -10,68 +10,8 @@ import java.util.concurrent.{Executors, TimeUnit} import scala.Main.writeToFile import scala.io.AnsiColor._ import scala.reflect.internal.util.ScalaClassLoader -import Parser._ import scala.sys.process.Process -/** - * Holds a code snippet to execute for testing purposes. - * - * @param code The code snippet. - */ -class PoSharpScript(code: String) { - private var expected = "" - - /** - * Asserts that the expected value is equal to the output of the code snippet. - * - * @param expected The expected value. - * @return True iff the expected value matches the output of the code snippet. - */ - def ShouldBe(expected: String): PoSharpScript = { - this.expected = expected - this - } - - /** - * Asserts that the given exception is thrown when the snippet executes. - * - * @note This is a terminal method, i.e. you do not need to call Run after it. - * @param expected The expected exception. - * @return True iff the exception thrown has the same type as the expected one. - */ - def ShouldThrow(expected: Throwable): (Boolean, String) = { - try { - Veritas.GetOutput(code, Veritas.getTestName) - } catch { - case e: Exception => return handleException(e) - } - - def handleException(e: Exception): (Boolean, String) = { - if (expected.getClass == e.getClass) - (true, e.getMessage) - else - (false, s"Expected \"$expected.type\", \"$e.type\" was thrown instead") - } - - (false, "No exception was thrown") - } - - /** - * Compile, run the code snippet and check assertions. - * - * @return - */ - def Run(): (Boolean, String) = { - val output = Veritas.GetOutput(code, Veritas.getTestName) - - if (expected == output) { - (true, expected) - } else { - (false, s"was $expected") - } - } -} - object Veritas { private val numOfThreads = 10 private val chunkSize = 1 @@ -183,7 +123,12 @@ object Veritas { val parsed = Parser.parseInput(input) val asm = ToAssembly.convertMain(parsed) writeToFile(asm, "compiled/", s"$fileName.asm") - val tmp = Process(if (IsWindows()) {"wsl "} + s"make TARGET_FILE=$fileName" else {""}).!! + + val tmp = Process(if (IsWindows()) { + "wsl " + } + s"make TARGET_FILE=$fileName" else { + "" + }).!! tmp.split("\n").last.trim } From 1f9a881a09e85b7f7444232390cda3c8d1a0a661 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Sat, 5 Mar 2022 23:39:57 +0100 Subject: [PATCH 002/112] Made use of the implicit wrapper --- app/src/main/scala/test/TestExample.scala | 88 ++++++++++------------- 1 file changed, 39 insertions(+), 49 deletions(-) diff --git a/app/src/main/scala/test/TestExample.scala b/app/src/main/scala/test/TestExample.scala index 3975df2..794dc8c 100644 --- a/app/src/main/scala/test/TestExample.scala +++ b/app/src/main/scala/test/TestExample.scala @@ -1,5 +1,5 @@ -import veritas.PoSharpScript import veritas.Veritas.Test +import veritas.PoSharp._ import scala.Parser.ParseException import scala.language.postfixOps @@ -8,70 +8,60 @@ package test { @Test class TestExample { - def runTest2(): (Boolean, String) = { - new PoSharpScript( - """def main(): int { - val a = 5; - print(a); - return 0; - }""") + def runTest2(): (Boolean, String) = + """def main(): int { + val a = 5; + print(a); + return 0; + }""" .ShouldBe("5") .Run() - } - def runTest3(): (Boolean, String) = { - new PoSharpScript( - """def main(): int { - val a = -1; - if(a >= 0) { - print(1); - } else { - print(0); - }; - return 0; - }""") + def runTest3(): (Boolean, String) = + """def main(): int { + val a = -1; + if(a >= 0) { + print(1); + } else { + print(0); + }; + return 0; + }""" .ShouldBe("0") .Run() - } - def runTest4(): (Boolean, String) = { - new PoSharpScript( - """def main(): int { - val a = 5; - while( a >= 0 ) { - a = (a - 1); - }; - print(a); - return 0; - }""") + def runTest4(): (Boolean, String) = + """def main(): int { + val a = 5; + while( a >= 0 ) { + a = (a - 1); + }; + print(a); + return 0; + }""" .ShouldBe("-1") .Run() - } - def runTest5(): (Boolean, String) = { - new PoSharpScript( - """def main(): int { - val a = array(1,2,3); - a[0] = 5; - print(a[0]); - return 0; - } - """) + def runTest5(): (Boolean, String) = + """def main(): int { + val a = array(1,2,3); + a[0] = 5; + print(a[0]); + return 0; + }""" .ShouldBe("5") .Run() - } - def runTest6(): (Boolean, String) = { - new PoSharpScript("def main(): int { val a = 10; print(a[0]); return 0;}") + def runTest6(): (Boolean, String) = + "def main(): int { val a = 10; print(a[0]); return 0;}" .ShouldThrow(new Exception) - } - def runTest7(): (Boolean, String) = { - new PoSharpScript("{def a; a = 5; print(a);}") + def runTest7(): (Boolean, String) = + "{def a; a = 5; print(a);}" .ShouldThrow(new ParseException("")) - } + def runTestBig(): (Boolean, String) = { - new PoSharpScript( + PoSharpScript( """ |object Dynamic { | size: int; From 7b6aade9e27efa05f8b6cf602cdba282081f2a08 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Sun, 6 Mar 2022 02:33:29 +0100 Subject: [PATCH 003/112] Refactored --- app/src/main/scala/veritas/Veritas.scala | 51 +++++++++++++----------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/app/src/main/scala/veritas/Veritas.scala b/app/src/main/scala/veritas/Veritas.scala index 1fe5f22..c568a19 100644 --- a/app/src/main/scala/veritas/Veritas.scala +++ b/app/src/main/scala/veritas/Veritas.scala @@ -17,7 +17,11 @@ object Veritas { private val chunkSize = 1 def main(args: Array[String]): Unit = { - time(() => RunTests()) + var exitCode = 0 + + time(() => exitCode = RunTests()) + + System.exit(exitCode) } /** @@ -38,7 +42,7 @@ object Veritas { * Runs all tests in the test package. The classes need to be annotated with the @Test * annotation. For a method to be considered a test it must be suffixed with "test". */ - def RunTests(): Unit = { + def RunTests(): Int = { val pool = Executors.newFixedThreadPool(numOfThreads) var exitCode = 0 @@ -63,28 +67,31 @@ object Veritas { val testClass = ScalaClassLoader(getClass.getClassLoader).tryToInitializeClass(c) var lastMethodName = "" - def runTest(instance: AnyRef, el: Method) = { + def runTest(instance: AnyRef, tests: Array[Method]): Unit = { // Put output here until all tests are done to avoid using synchronized val chunkedOut = new StringBuilder // Catches invalid tests (say main is missing from the code snippet) - try { - lastMethodName = el.getName - val (output, actual) = el.invoke(instance).asInstanceOf[(Boolean, String)] - if (output) { - chunkedOut.append(s"${el.getName}: $GREEN[PASSED]$RESET\n") - } else { - chunkedOut.append(s"${el.getName}: $RED[FAILED]$RESET | $actual\n") - exitCode = 1 + tests.foreach(el => { + // Catches invalid tests (say main is missing from the code snippet) + try { + lastMethodName = el.getName + val (output, actual) = el.invoke(instance).asInstanceOf[(Boolean, String)] + if (output) { + chunkedOut.append(s"$GREEN[PASSED]$RESET: ${el.getName}\n") + } else { + chunkedOut.append(s"$RED[FAILED]$RESET: ${el.getName} | $actual\n") + exitCode = 1 + } + } catch { + case e: Exception => + chunkedOut.append(s"$RED[ERROR]$RESET : ${el.getName} Could not instantiate $c.${el.getName} with: $e\n") + exitCode = 1 } - } catch { - case e: Exception => - chunkedOut.append(s"${el.getName}: $RED[ERROR]$RESET Could not instantiate $c.$lastMethodName with: $e\n") - exitCode = 1 - } finally { - // Add to actual string builder - this.synchronized(out.append(chunkedOut.toString)) - } + }) + + // Add to actual string builder + this.synchronized(out.append(chunkedOut.toString)) } try { @@ -95,9 +102,7 @@ object Veritas { m.getName.toLowerCase().contains("test")) // Filter out non-test methods .grouped(chunkSize) // Group in chunks .foreach(chunk => { - pool.execute(() => { - chunk.foreach(runTest(instance, _)) - }) + pool.execute(() => runTest(instance, chunk)) }) } catch { case e: Exception => @@ -116,7 +121,7 @@ object Veritas { .filter(_.getName.contains("test")) .foreach(el => el.delete()) - System.exit(exitCode) + exitCode } def GetOutput(input: String, fileName: String): String = { From 86cb41266898f5a3652dc78b2fab67fdbf2a78a0 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Sun, 6 Mar 2022 02:33:38 +0100 Subject: [PATCH 004/112] Moved package to the top --- app/src/main/scala/test/TestExample.scala | 228 +++++++++++----------- 1 file changed, 114 insertions(+), 114 deletions(-) diff --git a/app/src/main/scala/test/TestExample.scala b/app/src/main/scala/test/TestExample.scala index 794dc8c..bd2c13e 100644 --- a/app/src/main/scala/test/TestExample.scala +++ b/app/src/main/scala/test/TestExample.scala @@ -1,129 +1,129 @@ +package test + import veritas.Veritas.Test import veritas.PoSharp._ import scala.Parser.ParseException import scala.language.postfixOps -package test { - @Test - class TestExample { - def runTest2(): (Boolean, String) = - """def main(): int { - val a = 5; - print(a); - return 0; - }""" - .ShouldBe("5") - .Run() +@Test +class TestExample { + def runTest2(): (Boolean, String) = + """def main(): int { + val a = 5; + print(a); + return 0; + }""" + .ShouldBe("5") + .Run() - def runTest3(): (Boolean, String) = - """def main(): int { - val a = -1; - if(a >= 0) { - print(1); - } else { - print(0); - }; - return 0; - }""" - .ShouldBe("0") - .Run() + def runTest3(): (Boolean, String) = + """def main(): int { + val a = -1; + if(a >= 0) { + print(1); + } else { + print(0); + }; + return 0; + }""" + .ShouldBe("0") + .Run() - def runTest4(): (Boolean, String) = - """def main(): int { - val a = 5; - while( a >= 0 ) { - a = (a - 1); - }; - print(a); - return 0; - }""" - .ShouldBe("-1") - .Run() + def runTest4(): (Boolean, String) = + """def main(): int { + val a = 5; + while( a >= 0 ) { + a = (a - 1); + }; + print(a); + return 0; + }""" + .ShouldBe("-1") + .Run() - def runTest5(): (Boolean, String) = - """def main(): int { - val a = array(1,2,3); - a[0] = 5; - print(a[0]); - return 0; - }""" - .ShouldBe("5") - .Run() + def runTest5(): (Boolean, String) = + """def main(): int { + val a = array(1,2,3); + a[0] = 5; + print(a[0]); + return 0; + }""" + .ShouldBe("5") + .Run() - def runTest6(): (Boolean, String) = - "def main(): int { val a = 10; print(a[0]); return 0;}" - .ShouldThrow(new Exception) + def runTest6(): (Boolean, String) = + "def main(): int { val a = 10; print(a[0]); return 0;}" + .ShouldThrow(new Exception) - def runTest7(): (Boolean, String) = - "{def a; a = 5; print(a);}" - .ShouldThrow(new ParseException("")) + def runTest7(): (Boolean, String) = + "{def a; a = 5; print(a);}" + .ShouldThrow(new ParseException("")) - def runTestBig(): (Boolean, String) = { - PoSharpScript( - """ - |object Dynamic { - | size: int; - | allocated: int; - | arr: array[int]; - | def Dynamic(self: Dynamic) { - | self.arr = array[int][2]; - | self.allocated = 2; - | self.size = 0; - | } - | def push(self: Dynamic, value: int) { - | self.arr[self.size] = value; - | self.size += 1; - | if(self.allocated == self.size) { - | val newsize = (self.allocated * 2); - | val old = self.arr; - | self.arr = array[int][newsize]; - | for(val i = 0; i < self.size; i+= 1;) { - | self.arr[i] = old[i]; - | }; - | self.allocated = newsize; - | }; - | } - | def get(self: Dynamic, index: int): int { - | if(index >= self.size) { - | print("can not do that"); - | throw exception; - | }; - | return self.arr[index]; - | } - | def print_arr(self: Dynamic) { - | for(val i = 0; i < self.size; i+= 1;) { - | print(self.arr[i]); - | }; - | } - | def compare(self: Dynamic, other: Dynamic): bool { - | val same: bool = true; - | if(self.size != other.size) {return false;}; - | for(val i = 0; i < self.size; i+= 1;) { - | if(self.get(i) != other.get(i)) {same = false;}; - | }; - | return same; - | } - |} - |def main(): int { - | - | val a = new Dynamic(); - | //print(a.get(8)); - | a.push(1); - | a.push(2); - | a.push(3); - | a.print_arr(); - | val b = new Dynamic(); - | b.push(1); - | b.push(2); - | print(a.compare(b)); - | b.push(3); - | print(a.compare(b)); - |} - |""".stripMargin) - .ShouldBe("true") - .Run() - } + def runTestBig(): (Boolean, String) = { + PoSharpScript( + """ + |object Dynamic { + | size: int; + | allocated: int; + | arr: array[int]; + | def Dynamic(self: Dynamic) { + | self.arr = array[int][2]; + | self.allocated = 2; + | self.size = 0; + | } + | def push(self: Dynamic, value: int) { + | self.arr[self.size] = value; + | self.size += 1; + | if(self.allocated == self.size) { + | val newsize = (self.allocated * 2); + | val old = self.arr; + | self.arr = array[int][newsize]; + | for(val i = 0; i < self.size; i+= 1;) { + | self.arr[i] = old[i]; + | }; + | self.allocated = newsize; + | }; + | } + | def get(self: Dynamic, index: int): int { + | if(index >= self.size) { + | print("can not do that"); + | throw exception; + | }; + | return self.arr[index]; + | } + | def print_arr(self: Dynamic) { + | for(val i = 0; i < self.size; i+= 1;) { + | print(self.arr[i]); + | }; + | } + | def compare(self: Dynamic, other: Dynamic): bool { + | val same: bool = true; + | if(self.size != other.size) {return false;}; + | for(val i = 0; i < self.size; i+= 1;) { + | if(self.get(i) != other.get(i)) {same = false;}; + | }; + | return same; + | } + |} + |def main(): int { + | + | val a = new Dynamic(); + | //print(a.get(8)); + | a.push(1); + | a.push(2); + | a.push(3); + | a.print_arr(); + | val b = new Dynamic(); + | b.push(1); + | b.push(2); + | print(a.compare(b)); + | b.push(3); + | print(a.compare(b)); + |} + |""".stripMargin) + .ShouldBe("true") + .Run() } } From c8fec3b1b91f58ba24ce9756aac7b63632cfece4 Mon Sep 17 00:00:00 2001 From: Pijus Krisiukenas Date: Sun, 6 Mar 2022 23:12:30 +0100 Subject: [PATCH 005/112] exception message --- app/src/main/scala/scala/Definitions.scala | 2 +- app/src/main/scala/scala/Parser.scala | 4 ++-- app/src/main/scala/scala/ToAssembly.scala | 8 +++++++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app/src/main/scala/scala/Definitions.scala b/app/src/main/scala/scala/Definitions.scala index a3e4bbd..2abe6a0 100644 --- a/app/src/main/scala/scala/Definitions.scala +++ b/app/src/main/scala/scala/Definitions.scala @@ -62,7 +62,7 @@ object Expr{ case class Convert(value: Expr, to: Type) extends Expr case class TopLevel(functions: List[Func], interfaces: List[DefineInterface], enums: List[DefineEnum]) extends Expr - case class ThrowException() extends Expr + case class ThrowException(errorMsg: String) extends Expr case class Nothing() extends Expr case class RawReference() extends Expr } diff --git a/app/src/main/scala/scala/Parser.scala b/app/src/main/scala/scala/Parser.scala index 834bef0..6454ac1 100644 --- a/app/src/main/scala/scala/Parser.scala +++ b/app/src/main/scala/scala/Parser.scala @@ -259,7 +259,7 @@ object Parser { Expr.Block(List(input._1, Expr.While(input._2, Expr.Block(input._4.lines :+ input._3)))); }) - def str[_: P]: P[Expr] = P("\"" ~~/ CharsWhile(_ != '"', 0).! ~~ "\"").map(x => Expr.Str(x + "\u0000")) + def str[_: P]: P[Expr.Str] = P("\"" ~~/ CharsWhile(_ != '"', 0).! ~~ "\"").map(x => Expr.Str(x + "\u0000")) def ident[_: P]: P[Expr.Ident] = P(CharIn("a-zA-Z_") ~~ CharsWhileIn("a-zA-Z0-9_", 0)).!.map((input) => { Expr.Ident(input) @@ -279,7 +279,7 @@ object Parser { def print[_: P]: P[Expr.Print] = P("print" ~ "(" ~/ (NoCut(binOp) | prefixExpr) ~ ")").map(Expr.Print) - def throwException[_: P]: P[Expr.ThrowException] = P("throw" ~/ "exception").map(x => Expr.ThrowException()) + def throwException[_: P]: P[Expr.ThrowException] = P("throw" ~/ "exception" ~/ "(" ~/ str ~/ ")").map(x => Expr.ThrowException(x.s.dropRight(1))) class ParseException(s: String) extends RuntimeException(s) diff --git a/app/src/main/scala/scala/ToAssembly.scala b/app/src/main/scala/scala/ToAssembly.scala index de4cda5..01a9005 100644 --- a/app/src/main/scala/scala/ToAssembly.scala +++ b/app/src/main/scala/scala/ToAssembly.scala @@ -1,6 +1,7 @@ package scala import scala.Type.{UserType, shortS} +import scala.io.AnsiColor object ToAssembly { val defaultReg = List("rax", "rdi", "rsi", "rdx", "rcx", "r8", "r9", "r10", "r11") @@ -269,7 +270,12 @@ object ToAssembly { } } - case Expr.ThrowException() => "jmp exception\n" + case Expr.ThrowException(err) => { + val msg = AnsiColor.RED + "RuntimeException: " + err + AnsiColor.RESET + val name = s"exception_print_${stringLiterals.length}" + stringLiterals = stringLiterals :+ s"$name:\n db \"${msg}\", 10, 0\n" + s"mov rax, $name\n" + printTemplate("format_string") + "jmp exception\n" + } case x@Expr.CallF(n, a) => convert(x, reg, env)._1; case x@Expr.Block(n) => convert(x, reg, env)._1; case x@Expr.CallObjFunc(obj, func) => convert(x, reg, env)._1; From 07b0156efd81d34b3b2c3b1257f563bb133670ab Mon Sep 17 00:00:00 2001 From: Pijus Krisiukenas Date: Mon, 7 Mar 2022 21:05:35 +0100 Subject: [PATCH 006/112] anonymous lambdas --- app/src/main/scala/scala/Definitions.scala | 6 +- app/src/main/scala/scala/Parser.scala | 69 +++--------- app/src/main/scala/scala/ToAssembly.scala | 90 ++++++++++++---- toCompile.txt | 117 ++++++++++++++++++++- 4 files changed, 206 insertions(+), 76 deletions(-) diff --git a/app/src/main/scala/scala/Definitions.scala b/app/src/main/scala/scala/Definitions.scala index 2abe6a0..1ab7152 100644 --- a/app/src/main/scala/scala/Definitions.scala +++ b/app/src/main/scala/scala/Definitions.scala @@ -41,6 +41,7 @@ object Expr{ //case class StackVar(offset: Int) extends Expr case class Func(name: String, argNames: List[InputVar], retType: Type, body: Expr.Block) extends Expr + case class Lambda(argNames: List[InputVar], retType: Type, body: Expr.Block) extends Expr case class CallF(name: String, args: List[Expr]) extends Expr case class Return(value: Option[Expr]) extends Expr @@ -64,6 +65,7 @@ object Expr{ case class TopLevel(functions: List[Func], interfaces: List[DefineInterface], enums: List[DefineEnum]) extends Expr case class ThrowException(errorMsg: String) extends Expr case class Nothing() extends Expr + case class Compiled(code: String, raxType: Type) extends Expr case class RawReference() extends Expr } @@ -77,6 +79,7 @@ object Type { case class Bool() extends Type //case class Interface(properties: List[InputVar]) extends Type case class Interface(properties: List[InputVar], functions: List[FunctionInfo]) extends Type + case class Function(args: List[Type], retType: Type) extends Type case class T1() extends Type case class Enum(el: List[String]) extends Type @@ -90,7 +93,8 @@ object Type { case Bool() => "b" case Array(inner) => "arr_"+shortS(inner)+"_" //case Interface(inner) => "itf_"+inner.map(x=>shortS(x.varType))+"_" - case Interface(inner, innerf) => "itf_"+inner.map(x=>shortS(x.varType))+"_"+innerf.map(x=>x.name)+"_" + case Interface(inner, innerf) => "itf_"+inner.map(x=>shortS(x.varType)).mkString+"_"+innerf.map(x=>x.name)+"_" + case Function(args, retType) => "func_"+args.map(x=>shortS(x)).mkString+"_"+shortS(retType) case UserType(name) => name case T1() => "T1" } diff --git a/app/src/main/scala/scala/Parser.scala b/app/src/main/scala/scala/Parser.scala index 6454ac1..466d1be 100644 --- a/app/src/main/scala/scala/Parser.scala +++ b/app/src/main/scala/scala/Parser.scala @@ -59,7 +59,7 @@ object Parser { def expr[_: P]: P[Expr] = P(arrayDef | arrayDefDefault | defAndSetVal | defVal | NoCut(setVar) | callFuncInLine | retFunction | IfOp | whileLoop | forLoop | print | callFunction | throwException) - def prefixExpr[_: P]: P[Expr] = P( NoCut(callFunction) | NoCut(convert) | NoCut(parens) | NoCut(condition) | arrayDef | arrayDefDefault | instanceInterface | accessVar | NoCut(getArraySize) | + def prefixExpr[_: P]: P[Expr] = P( NoCut(callFunction) | defineLambda | NoCut(convert) | NoCut(parens) | NoCut(condition) | arrayDef | arrayDefDefault | instanceInterface | accessVar | NoCut(getArraySize) | numberFloat | number | ident | constant | str | char | trueC | falseC) def defVal[_: P]: P[Expr.DefVal] = P("val " ~/ ident ~ typeDef.?).map { @@ -81,7 +81,7 @@ object Parser { case x => false } - def setVar[_: P]: P[Expr] = P(accessVar ~ StringIn("+=", "-=", "*=", "/=", "=").! ~ prefixExpr ~ &(";")).map { + def setVar[_: P]: P[Expr] = P(accessVar ~ StringIn("+=", "-=", "*=", "/=", "=").! ~/ prefixExpr ~ &(";")).map { case (variable, op, value) => { val opType = op match { case "=" => value @@ -100,23 +100,17 @@ object Parser { } } - /* - def setVal[_: P]: P[Expr.SetVal] = P(ident ~ StringIn("+=", "-=", "*=", "/=", "=").! ~/ prefixExpr).map(x => { - val ret = x._2 match { - case "=" => x._3 - case "+=" => Expr.Plus(x._1, x._3) - case "-=" => Expr.Minus(x._1, x._3) - case "*=" => Expr.Mult(x._1, x._3) - case "/=" => Expr.Div(x._1, x._3) - } - Expr.SetVal(x._1, ret) - }) - - */ def defAndSetVal[_: P] = P(defVal ~ "=" ~ prefixExpr).map(x => Expr.ExtendBlock(List(x._1, Expr.SetVal(x._1.variable, x._2)))) - def arrayDef[_: P]: P[Expr.DefineArray] = P("array" ~ "[" ~/ typeBase ~ "]" ~ "[" ~/ prefixExpr ~ "]").map(x => Expr.DefineArray(x._2, x._1, List())) + def defineLambda[_: P]: P[Expr.Lambda] = P("lambda" ~/ "(" ~ (ident ~ typeDef).rep(sep=",") ~ ")" ~ typeDef ~ "=>" ~/ (block | prefixExpr) ).map{ case (args, ret, body) => { + val argsf = args.map(x=>InputVar(x._1.name, x._2)).toList + body match { + case b@Expr.Block(_) => Expr.Lambda(argsf, ret, b) + case b => Expr.Lambda(argsf, ret, Expr.Block(List(Expr.Return(Some(b))))) + } + }} + def arrayDef[_: P]: P[Expr.DefineArray] = P("array" ~ "[" ~/ typeBase ~ "]" ~ "[" ~/ prefixExpr ~ "]").map(x => Expr.DefineArray(x._2, x._1, List())) def arrayDefDefault[_: P]: P[Expr.DefineArray] = P("array" ~ "(" ~/ prefixExpr.rep(sep = ",") ~ ")").map(x => Expr.DefineArray(Expr.Num(x.size), Type.Undefined(), x.toList)) /* @@ -130,45 +124,12 @@ object Parser { def instanceInterface[_: P]: P[Expr.InstantiateInterface] = P("new " ~/ ident ~ "(" ~/ prefixExpr.rep(sep = ",") ~ ")").map(x => Expr.InstantiateInterface(x._1.name, x._2.toList)) - //def instanceInterface[_: P]: P[Expr.InstantiateInterface] = P("new " ~/ ident ~ "{" ~/ prefixExpr.rep(sep = ",") ~ "}").map(x=>Expr.InstantiateInterface(x._1.name,x._2.toList)) - /* - def returnsInterface[_: P] = P(NoCut(callFunction) | getArray | ident | ("(" ~ getProp ~ ")")) - def getProp[_: P]: P[Expr.GetInterfaceProp] = P(returnsInterface ~ "." ~/ ident).map(x=>Expr.GetInterfaceProp(x._1, x._2.name)) - def setProp[_: P]: P[Expr.SetInterfaceProp] = P(returnsInterface ~ "." ~/ ident ~ "=" ~ prefixExpr).map(x=>Expr.SetInterfaceProp(x._1,x._2.name, x._3)) - */ - //def returnsInterface[_: P] = P(NoCut(callFunction) | getArray | ident) - /* - def returnsInterface[_: P] = P(NoCut(callFunction) | ident) - def getProp[_: P]: P[Expr.GetProperty] = P(returnsInterface ~ "." ~ recGetProp.rep(0) ~/ ident).map{ - case (first, intermediate, Expr.Ident(name)) => - if(intermediate.nonEmpty) { - val ret = intermediate.tail.foldRight(Expr.GetProperty(first, intermediate.head.name))((v, acc) => - Expr.GetProperty(acc, v.name) - ) - Expr.GetProperty(ret, name) - } - else Expr.GetProperty(first, name); - } - def callObjFunc[_: P]: P[Expr.CallObjFunc] = P(returnsInterface ~ "." ~ recGetProp.rep(0) ~/ callFunction).map{ - case (first, intermediate, f@Expr.CallF(name, args)) => - if(intermediate.nonEmpty) { - val ret = intermediate.tail.foldRight(Expr.GetProperty(first, intermediate.head.name))((v, acc) => - Expr.GetProperty(acc, v.name) - ) - Expr.CallObjFunc(ret, f) - } - else Expr.CallObjFunc(first, f); - } - def recGetProp[_: P] = P(ident ~ ".") - def setProp[_: P]: P[Expr.SetInterfaceProp] = P(getProp ~ "=" ~ prefixExpr).map{ - case (Expr.GetProperty(obj, prop), toset) => Expr.SetInterfaceProp(obj,prop, toset) - } - - */ - def typeDef[_: P]: P[Type] = P(":" ~/ (typeBase | typeArray | typeUser)) + def typeDef[_: P]: P[Type] = P(":" ~/ (typeBase | typeArray | typeFunc | typeUser)) + def typeDefNoCol[_: P]: P[Type] = P(typeBase | typeArray | typeFunc | typeUser) - def typeArray[_: P]: P[Type] = P("array" ~/ "[" ~ typeBase ~ "]").map(x => Type.Array(x)) + def typeArray[_: P]: P[Type] = P("array" ~/ "[" ~ typeDefNoCol ~ "]").map(x => Type.Array(x)) + def typeFunc[_: P]: P[Type] = P("func" ~/ "[" ~ "(" ~ typeDefNoCol.rep(sep=",") ~ ")" ~/ "=>" ~ typeDefNoCol ~ "]").map(x => Type.Function(x._1.toList, x._2)) def typeUser[_: P]: P[Type] = P(ident).map(x => Type.UserType(x.name)) @@ -283,7 +244,7 @@ object Parser { class ParseException(s: String) extends RuntimeException(s) - val reservedKeywords = List("def", "val", "if", "while", "true", "false", "array", "for", "print", "new", "interface", "return", "object", "throw", "exception") + val reservedKeywords = List("def", "val", "if", "while", "true", "false", "array", "for", "print", "new", "interface", "return", "object", "throw", "exception", "lambda", "function") def checkForReservedKeyword(input: Expr.Ident): Unit = { if (reservedKeywords.contains(input.name)) throw new ParseException(s"${input.name} is a reserved keyword"); diff --git a/app/src/main/scala/scala/ToAssembly.scala b/app/src/main/scala/scala/ToAssembly.scala index 01a9005..5fafe52 100644 --- a/app/src/main/scala/scala/ToAssembly.scala +++ b/app/src/main/scala/scala/ToAssembly.scala @@ -1,5 +1,6 @@ package scala +import scala.Expr.GetProperty import scala.Type.{UserType, shortS} import scala.io.AnsiColor @@ -34,20 +35,22 @@ object ToAssembly { declareFunctions(x); declareInterfaces(x); declareEnums(x) - converted += defineFunctions(x.functions); + converted += defineFunctions(x.functions.map(y=>(y, Map())), false); converted += defineFunctions(x.interfaces.flatMap(intf=> intf.functions.map(func=>Expr.Func(intf.name + "_" + func.name, func.argNames, func.retType, func.body))) + .map(y=>(y, Map())), + false ); }} - - //converted += convert(input, defaultReg, Map() )._1; + converted += defineFunctions(lambdas, true); + converted += "exception:\nmov rdi, 1\ncall exit\n" converted += "format_num:\n db \"%d\", 10, 0\n" converted += "format_float:\n db \"%f\", 10, 0\n" converted += "format_string:\n db \"%s\", 10, 0\n" converted += "format_char:\n db \"%c\", 10, 0\n" converted += "format_true:\n db \"true\", 10, 0\n" converted += "format_false:\n db \"false\", 10, 0\n" - converted += "section .data\nmain_rbp DQ 0\nmain_rsp DQ 0\n" + //converted += "section .data\nmain_rbp DQ 0\nmain_rsp DQ 0\n" converted += stringLiterals.mkString //converted = converted.split("\n").zipWithIndex.foldLeft("")((acc, v)=> acc +s"\nline${v._2}:\n"+ v._1) converted = converted.split("\n").map(x=>if(x.contains(":")) x+"\n" else " "+x+"\n").mkString @@ -59,6 +62,7 @@ object ToAssembly { var functions: List[FunctionInfo] = List(); var interfaces: List[InterfaceInfo] = List(); var enums: List[EnumInfo] = List() + var lambdas: List[(Expr.Func, Env)] = List() var functionScope: FunctionInfo = FunctionInfo("main", List(), Type.Num()); private def convert(input: Expr, reg: List[String], env: Env): (String, Type) = { @@ -119,9 +123,12 @@ object ToAssembly { case Expr.InstantiateInterface(name, values) => interfaces.find(x=>x.name == name) match { case Some(intf) => { val array_def = s"mov rdi, ${intf.args.length}\n" + s"mov rsi, 8\n" + "call calloc\n" + "push rax\n" - val newenv = newVar("self", UserType(name), env) - val func_code = interpFunction(name+"_"+name, Expr.Ident("self") +: values, reg, newenv)._1 - val ret = array_def + setval("self", UserType(name), newenv)._1 + func_code + "pop rax\n"; + //val newenv = newVar("self", UserType(name), env) + //val func_code = interpFunction(name+"_"+name, Expr.Ident("self") +: values, reg, newenv)._1 + //val ret = array_def + setval("self", UserType(name), newenv)._1 + func_code + "pop rax\n"; + + val func_code = interpFunction(name+"_"+name, Expr.Compiled(array_def, UserType(name)) +: values, reg, env)._1 + val ret = func_code + "pop rax\n"; (ret, Type.Interface(intf.args, intf.funcs)) } case None => throw new Exception(s"no such interface defined") @@ -139,7 +146,7 @@ object ToAssembly { case (x, valType) => throw new Exception(s"expected a interface, got ${valType}") } case Expr.CallObjFunc(obj, func) => conv(obj) match { - case(code, Type.Interface(props, funcs)) => funcs.find(x=>x.name == func.name) match { + case(code, t@Type.Interface(props, funcs)) => funcs.find(x=>x.name == func.name) match { case Some(n) => { var args = func.args //TODO fails if interface has same attributes/functions but different name @@ -147,16 +154,32 @@ object ToAssembly { if(n.args.nonEmpty && n.args.head.name == "self") args = obj +: args interpFunction(intfName+"_"+func.name, args, reg, env) } - case None => throw new Exception(s"no such function") + case None => props.find(x=>x.name == func.name) match { + case Some(InputVar(_, Type.Function(_,_))) => { + //callLambda(Expr.GetProperty(Expr.Compiled(code, t), func.name), func.args, reg, env) + callLambda(Expr.GetProperty(obj, func.name), func.args, reg, env) + } + case None => throw new Exception(s"object has no property or function named $func") + } } } case Expr.GetArray(name, index) => getArray(name, index, reg, env); case Expr.ArraySize(name) => getArraySize(name, reg, env); - case Expr.CallF(name, args) => interpFunction(name, args, reg, env) + case Expr.CallF(name, args) => { + if(functions.exists(x=>x.name == name)) interpFunction(name, args, reg, env) + else if(env.contains(name)) callLambda(Expr.Ident(name), args, reg, env) + else throw new Exception(s"unknow identifier $name") + } //case Expr.Str(value) => (defineString(value, reg), Type.Str()) case Expr.Str(value) => (defineArrayKnown(value.length, Type.Character(), value.map(x=>Expr.Character(x)).toList, env)._1, Type.Array(Type.Character())) case Expr.Character(value) => (s"mov ${reg.head}, ${value.toInt}\n", Type.Character()) - case Expr.Nothing() => ("", Type.Undefined()); + + case Expr.Lambda(args, ret, body) => { + val label = "lambda_" + lambdas.size + functions = functions :+ FunctionInfo(label, args, ret) + lambdas = lambdas :+ (Expr.Func(label, args, ret, body), env) + (s"mov ${reg.head}, $label\n", Type.Function(args.map(x=>x.varType), ret)) + } case Expr.Equals(left, right) => { val ret = compareExpr(left, right, false, reg, env) + s"sete ${sizeToReg(1, reg.head)}\n" (ret, Type.Bool()) @@ -191,6 +214,9 @@ object ToAssembly { }) (ret, Type.Bool()) } + + case Expr.Nothing() => ("", Type.Undefined()); + case Expr.Compiled(code, retType) => (code, retType); case x => throw new Exception (s"$x is not interpreted yet :("); } (ret._1, makeUserTypesConcrete(ret._2)) @@ -391,12 +417,14 @@ object ToAssembly { */ val functionCallReg = List( "rdi", "rsi", "rdx", "rcx", "r8", "r9") def fNameSignature(name: String, args: List[Type]):String = name + (if(args.isEmpty) "" else "_") + args.map(x=>shortS(x)).mkString - private def defineFunctions(input: List[Expr.Func]): String = { - input.map(function => { + + private def defineFunctions(input: List[(Expr.Func, Env)], lambdaMode: Boolean): String = { + input.map{ case (function, upperScope) => { val info = functions.find(x=>x.name == function.name && x.args==function.argNames).get; functionScope = info; - var ret = "\n" + s"${fNameSignature(info.name, info.args.map(x=>x.varType))}:\n" - if(info.name == "main") ret += "mov [main_rbp], rbp\nmov [main_rsp], rsp\n" + val label = if(lambdaMode) info.name else fNameSignature(info.name, info.args.map(x=>x.varType)) + var ret = "\n" + s"${label}:\n" + //if(info.name == "main") ret += "mov [main_rbp], rbp\nmov [main_rsp], rsp\n" ret += """ push rbp | mov rbp, rsp @@ -410,17 +438,40 @@ object ToAssembly { regArgs = regArgs.tail; moveVar }).mkString - ret += convert(function.body, defaultReg, env)._1 + ret += convert(function.body, defaultReg, shiftEnvLocations(upperScope) ++ env)._1 if(info.retType == Type.Undefined()) ret += "leave\nret\n"; if(info.name == "main") { //ret += "exception:\nmov rdi, 1\nmov rbp, [main_rbp]\nmov rsp, [main_rsp]\nmov rdx, [rsp-8]\njmp rdx\n" - //ret += "exception:\nmov rax, 1\nmov rbx, 1\nint 0x80\n" ret += "mov rax, 0\nleave\nret\n"; - ret += "exception:\nmov rdi, 1\ncall exit\n" } ret - }).mkString + }}.mkString + } + def shiftEnvLocations(env: Env): Env = { + env.map(x=> (x._1, + Variable(x._2.pointer - 256 - 16 , x._2.varType) + )) + } + def callLambda(input: Expr, args: List[Expr], reg: List[String], env: Env): (String, Type) = convert(input, reg, env) match { + case (code, Type.Function(argTypes, retType)) => { + val usedReg = defaultReg.filter(x => !reg.contains(x)); + var ret = usedReg.map(x=>s"push $x\n").mkString + ret += code + s"push ${reg.head}\n" + if(argTypes.length != args.length) throw new Exception(s"wrong number of arguments: expected ${argTypes.length}, got ${args.length}"); + val argRet = (args zip argTypes).map{case (arg, argType) => convert(arg, reg, env) match { + case (argCode, t) if Type.compare(t, argType) => argCode + s"push ${reg.head}\n" + case (_, t) => throw new Exception(s"Wrong argument for function: expected $argType, got $t"); + }} + ret += argRet.mkString + ret += args.zipWithIndex.reverse.map{case (arg, index) => s"pop ${functionCallReg(index)}\n"}.mkString + ret += s"pop rax\n" + ret += s"call rax\n" + ret += s"mov ${reg.head}, rax\n" + ret += usedReg.reverse.map(x=>s"pop $x\n").mkString + (ret, retType) + } + case (_, x) => throw new Exception(s"Can not call variable of type $x"); } def interpFunction(name: String, args: List[Expr], reg: List[String], env: Env ): (String, Type) = { val usedReg = defaultReg.filter(x => !reg.contains(x)); @@ -529,6 +580,7 @@ object ToAssembly { case Type.Character() => 1 case Type.Num() => 8 case Type.NumFloat() => 8 + case Type.Function(_,_) => 8 case Type.T1() => 8 } diff --git a/toCompile.txt b/toCompile.txt index ddbc2f5..2411fc8 100644 --- a/toCompile.txt +++ b/toCompile.txt @@ -1,3 +1,116 @@ +/* +def apply(a: int, b: int, f: func[(int, int) => int]): int { + return f(a, b); +} def main(): int { - print("hello world!"); -} \ No newline at end of file + val c = 5; + val a = lambda (x: int, y: int): int => (x + y + c); + val b = apply(5,6,a); + print(b); +} +*/ + +object Dynamic { + size: int; + allocated: int; + arr: array[int]; + map: func[(func[(int) => int]) => Dynamic]; + //map: func[(int) => int]; + def Dynamic(self: Dynamic) { + self.arr = array[int][2]; + self.allocated = 2; + self.size = 0; + + self.map = lambda (f: func[(int) => int]): Dynamic => { + val ret = self.copy(); + + for(val i = 0; i < self.size; i+= 1;) { + ret.arr[i] = f(ret.arr[i]); + }; + return ret; + }; + + } + def push(self: Dynamic, value: int) { + self.arr[self.size] = value; + self.size += 1; + if(self.allocated == self.size) { + val newsize = (self.allocated * 2); + val old = self.arr; + self.arr = array[int][newsize]; + for(val i = 0; i < self.size; i+= 1;) { + self.arr[i] = old[i]; + }; + self.allocated = newsize; + }; + } + def get(self: Dynamic, index: int): int { + if(index >= self.size) { + throw exception("index out of bounds"); + }; + return self.arr[index]; + } + def print_arr(self: Dynamic) { + for(val i = 0; i < self.size; i+= 1;) { + print(self.arr[i]); + }; + } + def copy(self: Dynamic): Dynamic { + val arr_new = new Dynamic(); + for(val i = 0; i < self.size; i+= 1;) { + arr_new.push(self.arr[i]); + }; + return arr_new; + } + def compare(self: Dynamic, other: Dynamic): bool { + val same: bool = true; + if(self.size != other.size) {return false;}; + for(val i = 0; i < self.size; i+= 1;) { + if(self.get(i) != other.get(i)) {same = false;}; + }; + return same; + } +} +def main(): int { + + val a = new Dynamic(); + a.push(1); + a.push(2); + a.push(3); + a.push(4); + val f = lambda(x: int): int => (x * x); + val b = a.map(f); + b.print_arr(); + return 0; +} + + + +/* +def main(): int { + val a = array(1,2,3); + val b = array(4,5,6); + val c = concat(a,b); + print_arr(c); + return 0; +} +def concat(a: array[T1], b: array[T1]): array[T1] { + val combined = (a.size + b.size); + val ret = array[T1][combined]; + for(val i = 0; i < a.size; i += 1;) { + ret[i] = a[i]; + }; + for(val i = 0; i < b.size; i += 1;) { + ret[(i + a.size)] = b[i]; + }; + return ret; +} +def print_arr(a: array[int]) { + val b = 0; + while(b < a.size) { + print(a[b]); + b += 1; + }; + return; +} +*/ From fd215bb2e3f93bb708677902d9185b8b30cd26c7 Mon Sep 17 00:00:00 2001 From: Pijus Krisiukenas Date: Mon, 21 Mar 2022 00:41:40 +0100 Subject: [PATCH 007/112] some coding --- README.md | 6 +- app/src/main/scala/scala/ToAssembly.scala | 42 ------------- tasklist.md | 7 ++- toCompile.txt | 74 ++++++++++++++++------- 4 files changed, 60 insertions(+), 69 deletions(-) diff --git a/README.md b/README.md index 73b6633..514504a 100644 --- a/README.md +++ b/README.md @@ -150,20 +150,20 @@ def fib(n: int): int { * Strings * Enums * Objects +* runtime exceptions +* lambda functions ### To do
#### Major +* multiple files * Generics -* runtime exceptions * Object inheritance -* lambda functions * library functions * typeof * Garbage collector -* multiple files * packages * File i/o * Optimisation diff --git a/app/src/main/scala/scala/ToAssembly.scala b/app/src/main/scala/scala/ToAssembly.scala index 5fafe52..696f596 100644 --- a/app/src/main/scala/scala/ToAssembly.scala +++ b/app/src/main/scala/scala/ToAssembly.scala @@ -314,49 +314,7 @@ object ToAssembly { //defstring += freeMemory((newenv.toSet diff env.toSet).toMap) (defstring, Type.Undefined()); } - /* - //TODO improve with boolean operations - private def convertCondition(input: Expr, reg: List[String], env: Env, orMode: Boolean, trueLabel: String, falseLabel: String): String = { - //def compare(left: Expr, right: Expr): String = binOpTemplate(left, right, "cmp", reg, env) - def compare(left: Expr, right: Expr, numeric: Boolean): String = compareExpr(left, right, numeric, reg, env) - val newtrueLabel = s"cond_${subconditionCounter}_true" - val newfalseLabel = s"cond_${subconditionCounter}_false" - val ret = input match { - case Expr.True() => if(orMode) s"jmp ${trueLabel}\n" else "" - case Expr.False() => if(!orMode) s"jmp ${falseLabel}\n" else "" - case Expr.Equals(left, right) => { - compare(left, right, false) + ( if(orMode) s"je ${trueLabel}\n" else s"jne ${falseLabel}\n" ) - } - case Expr.LessThan(left, right) => { - compare(left, right, true) + ( if(orMode) s"jl ${trueLabel}\n" else s"jge ${falseLabel}\n" ) - } - case Expr.MoreThan(left, right) => { - compare(left, right, true) + ( if(orMode) s"jg ${trueLabel}\n" else s"jle ${falseLabel}\n" ) - } - case Expr.Not(cond) => { - subconditionCounter += 1 - convertCondition(cond, reg, env, orMode = orMode, newtrueLabel, newfalseLabel) + - s"${newtrueLabel}:\n" + s"jmp ${falseLabel}\n" + s"${newfalseLabel}:\n" + s"jmp ${trueLabel}\n" - } - case Expr.And(list) => { - subconditionCounter += 1; - list.foldLeft("")((acc, subcond) => acc + convertCondition(subcond, reg, env, orMode = false, newtrueLabel, newfalseLabel)) + - s"${newtrueLabel}:\n" + s"jmp ${trueLabel}\n" + s"${newfalseLabel}:\n" + s"jmp ${falseLabel}\n" - } - case Expr.Or(list) => { - subconditionCounter += 1; - list.foldLeft("")((acc, subcond) => acc + convertCondition(subcond, reg, env, orMode = true, newtrueLabel, newfalseLabel)) + - s"${newfalseLabel}:\n" + s"jmp ${falseLabel}\n" + s"${newtrueLabel}:\n" + s"jmp ${trueLabel}\n" - } - case expr => convert(expr, reg, env) match { - case (code, Type.Bool()) => compare(expr, Expr.True(), false) + ( if(orMode) s"je ${trueLabel}\n" else s"jne ${falseLabel}\n" ) - case (_, t) => throw new Exception(s"got type $t inside condition, expected bool") - } - } - ret - } - */ def compareExpr(left: Expr, right: Expr, numeric: Boolean, reg: List[String], env: Env): String = { val leftout = convert(left, reg, env); val rightout = convert(right, reg.tail, env); diff --git a/tasklist.md b/tasklist.md index 01b3975..ee593ce 100644 --- a/tasklist.md +++ b/tasklist.md @@ -1,9 +1,10 @@ ##To do -* ~~booleans~~ -* infer self in object functions + +* make functions use stack instead of registers * static variables * add prinf +* infer self in object functions * Prevent array deferencing (point to a pointer that points to array) * Add array copy function * add method to get all interface arguments/names @@ -14,7 +15,7 @@ * add runtime index checking for arrays * optimise string to char array conversion - +* ~~booleans~~ * ~~change functions names to consider their object~~ * ~~make self auto-pass~~ * ~~bug when comment at top of file~~ diff --git a/toCompile.txt b/toCompile.txt index 2411fc8..311f736 100644 --- a/toCompile.txt +++ b/toCompile.txt @@ -15,10 +15,9 @@ object Dynamic { allocated: int; arr: array[int]; map: func[(func[(int) => int]) => Dynamic]; - //map: func[(int) => int]; def Dynamic(self: Dynamic) { - self.arr = array[int][2]; - self.allocated = 2; + self.arr = array[int][4]; + self.allocated = 4; self.size = 0; self.map = lambda (f: func[(int) => int]): Dynamic => { @@ -31,19 +30,54 @@ object Dynamic { }; } + + def expand(self: Dynamic, req: int) { + val total = (req + self.size); + if(total >= self.allocated) { + val newsize = self.allocated; + while(newsize < (total+1)) { + newsize = (newsize * 2); + }; + val old = self.arr; + self.arr = array[int][newsize]; + for(val i = 0; i < self.size; i+= 1;) { + self.arr[i] = old[i]; + }; + self.allocated = newsize; + self.size = total; + } else { + self.size += req; + }; + } def push(self: Dynamic, value: int) { - self.arr[self.size] = value; - self.size += 1; - if(self.allocated == self.size) { - val newsize = (self.allocated * 2); - val old = self.arr; - self.arr = array[int][newsize]; - for(val i = 0; i < self.size; i+= 1;) { - self.arr[i] = old[i]; - }; - self.allocated = newsize; + self.expand(1); + self.arr[(self.size-1)] = value; + } + + def push(self: Dynamic, value: array[int]) { + val oldSize = self.size; + self.expand(value.size); + + for(val i = 0; i < value.size; i+= 1;) { + self.arr[(i + oldSize)] = value[i]; }; } + + def push(self: Dynamic, value: Dynamic) { + val oldSize = self.size; + self.expand(value.size); + + for(val i = 0; i < value.size; i+= 1;) { + self.arr[(i + oldSize)] = value.arr[i]; + }; + } + + def concat(self: Dynamic, other: Dynamic): Dynamic { + val ret = self.copy(); + ret.push(other); + return ret; + } + //filter def get(self: Dynamic, index: int): int { if(index >= self.size) { throw exception("index out of bounds"); @@ -74,14 +108,12 @@ object Dynamic { def main(): int { val a = new Dynamic(); - a.push(1); - a.push(2); - a.push(3); - a.push(4); - val f = lambda(x: int): int => (x * x); - val b = a.map(f); - b.print_arr(); - return 0; + a.push(array(3,5,5,7,8)); + a.print_arr(); + print(""); + a + .map(lambda(x: int): int => (x * x)) + .print_arr(); } From 5656e4c8b20d936d5c4fcbc1e813aa6bb46e6e31 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Fri, 25 Mar 2022 13:31:14 +0100 Subject: [PATCH 008/112] Made package top level --- app/src/main/scala/veritas/PoSharp.scala | 41 ++++++++++++------------ 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/app/src/main/scala/veritas/PoSharp.scala b/app/src/main/scala/veritas/PoSharp.scala index 2a1d173..856e4f6 100644 --- a/app/src/main/scala/veritas/PoSharp.scala +++ b/app/src/main/scala/veritas/PoSharp.scala @@ -1,10 +1,11 @@ package veritas import scala.language.implicitConversions +import scala.util.{Success, Failure} protected trait IPoSharp { /** - * Asserts that the expected value is equal to the output of the code snippet. + * Sets the expected value for the test. * * @param expected The expected value. * @return True iff the expected value matches the output of the code snippet. @@ -36,35 +37,35 @@ object PoSharp { } def ShouldThrow(expected: Throwable): (Boolean, String) = { - try { - Veritas.GetOutput(code, Veritas.getTestName) - } catch { - case e: Exception => return handleException(e) + Veritas.Compile(code) match { + case Failure(exception) => exception match { + case e: Exception if e.getClass == expected.getClass => (true, e.getMessage) + case e: Exception => (false, s"Expected \"$expected.type\", \"$e.type\" was thrown instead") + } + case Success(_) => (false, "No exception was thrown") } - - def handleException(e: Exception): (Boolean, String) = { - if (expected.getClass == e.getClass) - (true, e.getMessage) - else - (false, s"Expected \"$expected.type\", \"$e.type\" was thrown instead") - } - - (false, "No exception was thrown") } /** * Compile, run the code snippet and check assertions. * - * @return + * @return (Passed/Failed, Failure debug message) */ def Run(): (Boolean, String) = { - val output = Veritas.GetOutput(code, Veritas.getTestName) + Veritas.Compile(code) match { + case Success(value) => + val output = Veritas.GetOutput(value, Veritas.getTestName) - if (expected == output) { - (true, expected) - } else { - (false, s"was $expected") + if (expected == output) { + (true, expected) + } else { + (false, s"Expected $expected, was $output") + } + case Failure(exception) => + println(s"Compilation failed with $exception") + throw exception } + } } From e5092019d4c28696cc18fc15cbeee80f8241be4c Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Fri, 25 Mar 2022 13:31:38 +0100 Subject: [PATCH 009/112] Added compile error clarification --- app/src/main/scala/veritas/Veritas.scala | 48 ++++++++++++++++++++---- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/app/src/main/scala/veritas/Veritas.scala b/app/src/main/scala/veritas/Veritas.scala index c568a19..bfaacf8 100644 --- a/app/src/main/scala/veritas/Veritas.scala +++ b/app/src/main/scala/veritas/Veritas.scala @@ -11,6 +11,7 @@ import scala.Main.writeToFile import scala.io.AnsiColor._ import scala.reflect.internal.util.ScalaClassLoader import scala.sys.process.Process +import scala.util.{Failure, Success, Try} object Veritas { private val numOfThreads = 10 @@ -47,6 +48,7 @@ object Veritas { var exitCode = 0 val out = new StringBuilder + out.append('\n') // reflection stuff val reflections = new Reflections(new ConfigurationBuilder() @@ -59,8 +61,10 @@ object Veritas { .get("TypesAnnotated") .get("scala.reflect.ScalaSignature") .toArray - .filter(el => el.asInstanceOf[String].contains("test.")) - .map(el => el.asInstanceOf[String]) + .filter(_.asInstanceOf[String].contains("test.")) + .map(_.asInstanceOf[String]) + + println() // Get the class and instantiate it res.foreach(c => { @@ -76,7 +80,12 @@ object Veritas { // Catches invalid tests (say main is missing from the code snippet) try { lastMethodName = el.getName - val (output, actual) = el.invoke(instance).asInstanceOf[(Boolean, String)] + + val (output, actual) = el.invoke(instance) match { + case (output: Boolean, actual: String) => (output, actual) + case _ => throw InvalidReturnTypeException("Invalid test method return type. Should be (Boolean, String)") + } + if (output) { chunkedOut.append(s"$GREEN[PASSED]$RESET: ${el.getName}\n") } else { @@ -84,8 +93,9 @@ object Veritas { exitCode = 1 } } catch { - case e: Exception => - chunkedOut.append(s"$RED[ERROR]$RESET : ${el.getName} Could not instantiate $c.${el.getName} with: $e\n") + case _: Exception => + chunkedOut.append(s"$RED[ERROR]$RESET : ${el.getName} Could not instantiate $c.${el.getName}" + + s", check logs above for more info.\n") exitCode = 1 } }) @@ -124,9 +134,29 @@ object Veritas { exitCode } - def GetOutput(input: String, fileName: String): String = { - val parsed = Parser.parseInput(input) - val asm = ToAssembly.convertMain(parsed) + /** + * Parses and compiles the code to asm + * + * @param input The code + * @return Generated assembly + */ + def Compile(input: String): Try[String] = { + try { + val parsed = Parser.parseInput(input) + Success(ToAssembly.convertMain(parsed)) + } catch { + case e: Exception => Failure(e) + } + } + + /** + * Writes the code to a file, executes it and returns the output. + * + * @param asm Assembly + * @param fileName The filename + * @return The last thing printed by the code + */ + def GetOutput(asm: String, fileName: String): String = { writeToFile(asm, "compiled/", s"$fileName.asm") val tmp = Process(if (IsWindows()) { @@ -155,4 +185,6 @@ object Veritas { } class Test extends scala.annotation.ConstantAnnotation {} + + case class InvalidReturnTypeException(msg: String) extends RuntimeException(msg) } From 1c44240e1c2881c77cd4aa888a11123dafacb78d Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Sat, 26 Mar 2022 02:16:16 +0100 Subject: [PATCH 010/112] Added simple coverage --- app/src/main/scala/veritas/Coverage.scala | 107 ++++++++++++++++++++++ app/src/main/scala/veritas/Veritas.scala | 9 +- 2 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 app/src/main/scala/veritas/Coverage.scala diff --git a/app/src/main/scala/veritas/Coverage.scala b/app/src/main/scala/veritas/Coverage.scala new file mode 100644 index 0000000..ee5e385 --- /dev/null +++ b/app/src/main/scala/veritas/Coverage.scala @@ -0,0 +1,107 @@ +package veritas + +import org.reflections.Reflections + +import scala.collection.convert.ImplicitConversions._ +import scala.collection.immutable.ListMap + +/** + * Generates a simple coverage report indicating which case classes inheriting from [[Expr]] are being used by + * the tests. + */ +object Coverage { + private val reflections = new Reflections("scala") + private var exprs: List[List[(Class[_ <: Expr], String, Int)]] = List() + + // Put names of redundant expressions here so they are ignored in the report (such as "Ident") + private val redundantExprs: List[String] = List() + + /** + * Calculates the case classes the given expression covers and saves it for later. + * + * @param expr Parsed code + */ + def AddCoverage(expr: Expr): Unit = this.synchronized { + this.exprs = this.exprs ::: List(GetExprs(expr)) + } + + /** + * Returns the list of [[Expr]] case classes used in the given expression along with their counts. + * + * @param expr The expression + * @return List[ClassName, Count] + */ + private def GetExprs(expr: Expr): List[(Class[_ <: Expr], String, Int)] = { + val exprs = GetExprClassNameTuples + + val tmp = expr.toString + .split(",").flatMap(el => + el + .replaceAll("[()]", "\n") + .split("\n") + .map(_.trim) + .filterNot(el => el.isEmpty || el == "List") + .filterNot(redundantExprs.contains(_)) + ) + + exprs + .filter(el => tmp.contains(el._2)) + .map(el => (el._1, el._2, tmp.count(_ == el._2))) + } + + /** + * Get the list of case classes inheriting from [[Expr]] along with their names. + * + * @return (Class,ClassName) tuples + */ + private def GetExprClassNameTuples: List[(Class[_ <: Expr], String)] = { + reflections.getSubTypesOf(classOf[Expr]).toList + .map(el => (el, el.getName.split("\\$").last)) + } + + /** + * Generates a map between each case class and the amount of times they were used in the tests. + * + * @return Map[ClassName, TimesUsed] + */ + def CalculateCoverage(): Map[String, Int] = { + val coverages = SumCoverages(exprs) + + val res = GetAllExprCaseClasses() + .groupBy(identity) + .map(el => el._1 -> 0) + .filterNot(el => redundantExprs.contains(el._1)) + + ListMap.from((res ++ coverages).toSeq.sortBy(_._2)) + } + + /** + * Sums a list of results generated by the [[GetExprs]] the method; turns them into a map and sums the class usages. + * + * @param args List of `GetExprs` results + * @return Map[ClassName, TimesUsed] + * @note I'm bad at explaining. The difference between this and [[CalculateCoverage]] is that this method + * only includes used case classes in the return while the other one includes all of them (the rest just + * with a count of zero) + */ + private def SumCoverages(args: List[List[(Class[_ <: Expr], String, Int)]]): Map[String, Int] = { + args + .flatten + .groupBy(_._2) + .map(el => (el._1, el._2.reduce((op, x) => (op._1, op._2, op._3 + x._3)))) + .map(el => (el._1, el._2._3)) + } + + /** + * Gets all case classes inheriting from [[Expr]] + * + * @return List of class names. + */ + private def GetAllExprCaseClasses(): List[String] = { + classOf[Expr] + .getDeclaredClasses + .map(el => el.getName.split("\\$").last) + .distinct + .toList + } +} \ No newline at end of file diff --git a/app/src/main/scala/veritas/Veritas.scala b/app/src/main/scala/veritas/Veritas.scala index bfaacf8..77de1d2 100644 --- a/app/src/main/scala/veritas/Veritas.scala +++ b/app/src/main/scala/veritas/Veritas.scala @@ -16,6 +16,7 @@ import scala.util.{Failure, Success, Try} object Veritas { private val numOfThreads = 10 private val chunkSize = 1 + private val cov = Coverage def main(args: Array[String]): Unit = { var exitCode = 0 @@ -123,6 +124,8 @@ object Veritas { pool.shutdown() pool.awaitTermination(5, TimeUnit.MINUTES) println(out) + println() + cov.CalculateCoverage().foreach(println) // Delete all files created by writeToFile and the tests new File("compiled") @@ -139,11 +142,15 @@ object Veritas { * * @param input The code * @return Generated assembly + * @note [[ToAssembly.convertMain]] alters `ToAssembly`'s state and thus needs to be synchronized. */ def Compile(input: String): Try[String] = { try { val parsed = Parser.parseInput(input) - Success(ToAssembly.convertMain(parsed)) + + cov.AddCoverage(parsed) + + this.synchronized(Success(ToAssembly.convertMain(parsed))) } catch { case e: Exception => Failure(e) } From f383bee73abf017368418bd9ac394b1a78b945bc Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Sat, 26 Mar 2022 02:34:07 +0100 Subject: [PATCH 011/112] Trying to resolve pipeline issue Can no longer reproduce issue locally, trying to make the entire method synchronized --- app/src/main/scala/veritas/Veritas.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/scala/veritas/Veritas.scala b/app/src/main/scala/veritas/Veritas.scala index 77de1d2..3894c69 100644 --- a/app/src/main/scala/veritas/Veritas.scala +++ b/app/src/main/scala/veritas/Veritas.scala @@ -144,13 +144,13 @@ object Veritas { * @return Generated assembly * @note [[ToAssembly.convertMain]] alters `ToAssembly`'s state and thus needs to be synchronized. */ - def Compile(input: String): Try[String] = { + def Compile(input: String): Try[String] = this.synchronized { try { val parsed = Parser.parseInput(input) cov.AddCoverage(parsed) - this.synchronized(Success(ToAssembly.convertMain(parsed))) + Success(ToAssembly.convertMain(parsed)) } catch { case e: Exception => Failure(e) } From b014e996b6ec483782068c86fce0eca36eb18ada Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Sat, 26 Mar 2022 02:44:59 +0100 Subject: [PATCH 012/112] Upped gradle version and trying different JDK --- .github/workflows/workflow.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 0621d4f..872f7b7 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -17,13 +17,13 @@ jobs: - uses: actions/setup-java@v2 with: - distribution: temurin + distribution: adopt-hotspot java-version: 17 - name: Setup Gradle uses: gradle/gradle-build-action@v2 with: - gradle-version: 7.3 + gradle-version: 7.4 - name: Execute Gradle build run: gradle build From f0aa3c2b0302248ef62491f4a84c1ff89c66013c Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Sat, 26 Mar 2022 14:29:09 +0100 Subject: [PATCH 013/112] Removed '|'s --- app/src/main/scala/test/TestExample.scala | 118 +++++++++++----------- 1 file changed, 58 insertions(+), 60 deletions(-) diff --git a/app/src/main/scala/test/TestExample.scala b/app/src/main/scala/test/TestExample.scala index bd2c13e..f70d560 100644 --- a/app/src/main/scala/test/TestExample.scala +++ b/app/src/main/scala/test/TestExample.scala @@ -62,67 +62,65 @@ class TestExample { .ShouldThrow(new ParseException("")) def runTestBig(): (Boolean, String) = { - PoSharpScript( + """object Dynamic { + size: int; + allocated: int; + arr: array[int]; + def Dynamic(self: Dynamic) { + self.arr = array[int][2]; + self.allocated = 2; + self.size = 0; + } + def push(self: Dynamic, value: int) { + self.arr[self.size] = value; + self.size += 1; + if(self.allocated == self.size) { + val newsize = (self.allocated * 2); + val old = self.arr; + self.arr = array[int][newsize]; + for(val i = 0; i < self.size; i+= 1;) { + self.arr[i] = old[i]; + }; + self.allocated = newsize; + }; + } + def get(self: Dynamic, index: int): int { + if(index >= self.size) { + print("can not do that"); + throw exception; + }; + return self.arr[index]; + } + def print_arr(self: Dynamic) { + for(val i = 0; i < self.size; i+= 1;) { + print(self.arr[i]); + }; + } + def compare(self: Dynamic, other: Dynamic): bool { + val same: bool = true; + if(self.size != other.size) {return false;}; + for(val i = 0; i < self.size; i+= 1;) { + if(self.get(i) != other.get(i)) {same = false;}; + }; + return same; + } + } + def main(): int { + + val a = new Dynamic(); + //print(a.get(8)); + a.push(1); + a.push(2); + a.push(3); + a.print_arr(); + val b = new Dynamic(); + b.push(1); + b.push(2); + print(a.compare(b)); + b.push(3); + print(a.compare(b)); + } """ - |object Dynamic { - | size: int; - | allocated: int; - | arr: array[int]; - | def Dynamic(self: Dynamic) { - | self.arr = array[int][2]; - | self.allocated = 2; - | self.size = 0; - | } - | def push(self: Dynamic, value: int) { - | self.arr[self.size] = value; - | self.size += 1; - | if(self.allocated == self.size) { - | val newsize = (self.allocated * 2); - | val old = self.arr; - | self.arr = array[int][newsize]; - | for(val i = 0; i < self.size; i+= 1;) { - | self.arr[i] = old[i]; - | }; - | self.allocated = newsize; - | }; - | } - | def get(self: Dynamic, index: int): int { - | if(index >= self.size) { - | print("can not do that"); - | throw exception; - | }; - | return self.arr[index]; - | } - | def print_arr(self: Dynamic) { - | for(val i = 0; i < self.size; i+= 1;) { - | print(self.arr[i]); - | }; - | } - | def compare(self: Dynamic, other: Dynamic): bool { - | val same: bool = true; - | if(self.size != other.size) {return false;}; - | for(val i = 0; i < self.size; i+= 1;) { - | if(self.get(i) != other.get(i)) {same = false;}; - | }; - | return same; - | } - |} - |def main(): int { - | - | val a = new Dynamic(); - | //print(a.get(8)); - | a.push(1); - | a.push(2); - | a.push(3); - | a.print_arr(); - | val b = new Dynamic(); - | b.push(1); - | b.push(2); - | print(a.compare(b)); - | b.push(3); - | print(a.compare(b)); - |} - |""".stripMargin) .ShouldBe("true") .Run() } From e43f40f3c775206cc95601b7b9c35e77550c6277 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Sat, 26 Mar 2022 14:33:33 +0100 Subject: [PATCH 014/112] Switching WSL to an Ubuntu distro --- .github/workflows/workflow.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 872f7b7..1522e9a 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -14,6 +14,8 @@ jobs: - name: Set up WSL uses: Vampire/setup-wsl@v1 + with: + distribution: Ubuntu-20.04 - uses: actions/setup-java@v2 with: From 0972bcf2810a3cfb7ebf97c0b4e57a4b8e8f0550 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Sat, 26 Mar 2022 14:38:56 +0100 Subject: [PATCH 015/112] Using cache --- .github/workflows/workflow.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 1522e9a..1afecdb 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -21,6 +21,7 @@ jobs: with: distribution: adopt-hotspot java-version: 17 + cache: 'gradle' - name: Setup Gradle uses: gradle/gradle-build-action@v2 From fa7c4fa25a6b74018ee2da8e6fba55d63c1c3c65 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Sat, 26 Mar 2022 14:56:13 +0100 Subject: [PATCH 016/112] idk --- app/src/main/scala/test/TestExample.scala | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/src/main/scala/test/TestExample.scala b/app/src/main/scala/test/TestExample.scala index f70d560..6e90470 100644 --- a/app/src/main/scala/test/TestExample.scala +++ b/app/src/main/scala/test/TestExample.scala @@ -104,8 +104,8 @@ class TestExample { }; return same; } - } - def main(): int { + } + def main(): int { val a = new Dynamic(); //print(a.get(8)); @@ -119,8 +119,7 @@ class TestExample { print(a.compare(b)); b.push(3); print(a.compare(b)); - } - """ + }""" .ShouldBe("true") .Run() } From e1e29bd497ddb6e7c1e208372d76ad8c17461f54 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Sat, 26 Mar 2022 16:46:11 +0100 Subject: [PATCH 017/112] Hol up --- app/src/main/scala/test/TestExample.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/scala/test/TestExample.scala b/app/src/main/scala/test/TestExample.scala index 6e90470..c0bbb78 100644 --- a/app/src/main/scala/test/TestExample.scala +++ b/app/src/main/scala/test/TestExample.scala @@ -86,8 +86,7 @@ class TestExample { } def get(self: Dynamic, index: int): int { if(index >= self.size) { - print("can not do that"); - throw exception; + throw exception("index out of bounds"); }; return self.arr[index]; } From 8f19a8bb6e830428d17fb84ca94312b61064a7e1 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Sat, 26 Mar 2022 16:52:17 +0100 Subject: [PATCH 018/112] Removed random newline --- app/src/main/scala/test/TestExample.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/scala/test/TestExample.scala b/app/src/main/scala/test/TestExample.scala index c0bbb78..b5fa35e 100644 --- a/app/src/main/scala/test/TestExample.scala +++ b/app/src/main/scala/test/TestExample.scala @@ -105,7 +105,6 @@ class TestExample { } } def main(): int { - val a = new Dynamic(); //print(a.get(8)); a.push(1); From 093a32fe66763c5d2d5b5db0e47a313bddab5b4b Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Sat, 26 Mar 2022 16:55:32 +0100 Subject: [PATCH 019/112] Moved synchronized block back --- app/src/main/scala/veritas/Veritas.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/scala/veritas/Veritas.scala b/app/src/main/scala/veritas/Veritas.scala index 3894c69..77de1d2 100644 --- a/app/src/main/scala/veritas/Veritas.scala +++ b/app/src/main/scala/veritas/Veritas.scala @@ -144,13 +144,13 @@ object Veritas { * @return Generated assembly * @note [[ToAssembly.convertMain]] alters `ToAssembly`'s state and thus needs to be synchronized. */ - def Compile(input: String): Try[String] = this.synchronized { + def Compile(input: String): Try[String] = { try { val parsed = Parser.parseInput(input) cov.AddCoverage(parsed) - Success(ToAssembly.convertMain(parsed)) + this.synchronized(Success(ToAssembly.convertMain(parsed))) } catch { case e: Exception => Failure(e) } From db66f7da65f4ab77581c0363715f8e9efdb3d9fe Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Wed, 30 Mar 2022 11:01:43 +0200 Subject: [PATCH 020/112] Separated coverage to a separate task --- .github/workflows/workflow.yml | 2 +- app/build.gradle | 6 ++++++ app/src/main/scala/veritas/Veritas.scala | 19 ++++++++++++++++--- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 1afecdb..a64b9b5 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -38,7 +38,7 @@ jobs: apt install make nasm gcc -y - name: Run tests - run: gradle runTests + run: gradle runCoverage -q # Ubuntu workflow, something goes wrong in the test, perhaps fix in the future diff --git a/app/build.gradle b/app/build.gradle index ec7774e..51d6ef4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -47,4 +47,10 @@ application { task runTests(type: JavaExec) { classpath sourceSets.main.runtimeClasspath main = "veritas.Veritas" +} + +task runCoverage(type: JavaExec) { + classpath sourceSets.main.runtimeClasspath + main = "veritas.Veritas" + args = ["coverage"] } \ No newline at end of file diff --git a/app/src/main/scala/veritas/Veritas.scala b/app/src/main/scala/veritas/Veritas.scala index 77de1d2..7146cbb 100644 --- a/app/src/main/scala/veritas/Veritas.scala +++ b/app/src/main/scala/veritas/Veritas.scala @@ -16,9 +16,20 @@ import scala.util.{Failure, Success, Try} object Veritas { private val numOfThreads = 10 private val chunkSize = 1 - private val cov = Coverage + private var cov: Coverage.type = _ + private var calculateCoverage = false + /** + * Runs all tests. If the first argument is `coverage`, coverage is calculated and printed. + * + * @param args Command line arguments. + */ def main(args: Array[String]): Unit = { + if (args.isDefinedAt(0) && args.head == "coverage") { + calculateCoverage = true + cov = Coverage + } + var exitCode = 0 time(() => exitCode = RunTests()) @@ -125,7 +136,8 @@ object Veritas { pool.awaitTermination(5, TimeUnit.MINUTES) println(out) println() - cov.CalculateCoverage().foreach(println) + if (calculateCoverage) + cov.CalculateCoverage().foreach(println) // Delete all files created by writeToFile and the tests new File("compiled") @@ -148,7 +160,8 @@ object Veritas { try { val parsed = Parser.parseInput(input) - cov.AddCoverage(parsed) + if (calculateCoverage) + cov.AddCoverage(parsed) this.synchronized(Success(ToAssembly.convertMain(parsed))) } catch { From f877c387ae38d1da80b9c891a41ee7296cbd5de4 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Wed, 30 Mar 2022 12:42:39 +0200 Subject: [PATCH 021/112] Switched to codecov export --- .gitignore | 3 +- app/src/main/scala/veritas/Coverage.scala | 69 ++++++++++++++++++++++- app/src/main/scala/veritas/Veritas.scala | 3 +- 3 files changed, 70 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index d6cdb71..3cb132f 100644 --- a/.gitignore +++ b/.gitignore @@ -168,4 +168,5 @@ gradle-app.setting # JDT-specific (Eclipse Java Development Tools) .classpath -toCompile.txt \ No newline at end of file +toCompile.txt +coverage.json diff --git a/app/src/main/scala/veritas/Coverage.scala b/app/src/main/scala/veritas/Coverage.scala index ee5e385..d688b0f 100644 --- a/app/src/main/scala/veritas/Coverage.scala +++ b/app/src/main/scala/veritas/Coverage.scala @@ -2,6 +2,7 @@ package veritas import org.reflections.Reflections +import java.nio.file.{Files, Path} import scala.collection.convert.ImplicitConversions._ import scala.collection.immutable.ListMap @@ -11,10 +12,10 @@ import scala.collection.immutable.ListMap */ object Coverage { private val reflections = new Reflections("scala") - private var exprs: List[List[(Class[_ <: Expr], String, Int)]] = List() - // Put names of redundant expressions here so they are ignored in the report (such as "Ident") private val redundantExprs: List[String] = List() + private var exprs: List[List[(Class[_ <: Expr], String, Int)]] = List() + private var percentage = -1.0 /** * Calculates the case classes the given expression covers and saves it for later. @@ -72,7 +73,15 @@ object Coverage { .map(el => el._1 -> 0) .filterNot(el => redundantExprs.contains(el._1)) - ListMap.from((res ++ coverages).toSeq.sortBy(_._2)) + percentage = ((coverages.size / GetAllExprCaseClasses().size.toDouble) * 100).round + + println(s"==== Covered $percentage% of Expr case classes ====\n") + + val output = ListMap.from((res ++ coverages).toSeq.sortBy(_._2)) + + CreateCodeCovReport(output) + + output } /** @@ -104,4 +113,58 @@ object Coverage { .distinct .toList } + + /** + * Creates a CodeCov report and exports it to `coverage.json`. + * + * @param cov Calculated coverage from [[CalculateCoverage]]. + */ + private def CreateCodeCovReport(cov: Map[String, Int]): Unit = { + var output: List[ExprCovLine] = List() + val txt = Files.readAllLines(Path.of("src/main/scala/scala/Definitions.scala")) + + // Find where the object starts to avoid mismatching stuff with imports + // This shouldn't happen because of the regex stuff I later added below but + // you never know. + val objStart = txt.indexWhere(_.contains("object Expr")) + + cov.foreach(el => output = output :+ + ExprCovLine(el._1, el._2, + txt + .drop(objStart) + .indexWhere(line => ExtractClassName(line) == el._1) + objStart + 1 + )) + + val start = "{\"coverage\":{\"app/src/main/scala/scala/Definitions.scala\": {" + val end = "}}}" + val json = start + output.map(el => s"\"${el.Line}\": ${el.TimesUsed},").mkString.dropRight(1) + end + + try { + Files.writeString(Path.of("../coverage.json"), json) + println("Coverage report exported successfully!\n") + } catch { + case e: Exception => println(s"Exporting coverage failed with $e") + } + } + + /** + * Extracts the case class name from any `Definitions.scala` line. + * + * @param line The line. + * @return The case class name. + */ + private def ExtractClassName(line: String): String = { + val data = "case class ([a-zA-Z]+)\\(".r.findAllIn(line).matchData.toList + if (data.isEmpty) "" + else data.get(0).group(1) + } + + /** + * Helper class. + * + * @param Expression The [[Expr]] class in question. + * @param TimesUsed The amount of times it was used in the tests. + * @param Line The line number from `Definitions.scala`. + */ + private case class ExprCovLine(Expression: String, TimesUsed: Int, Line: Int) } \ No newline at end of file diff --git a/app/src/main/scala/veritas/Veritas.scala b/app/src/main/scala/veritas/Veritas.scala index 7146cbb..74230fb 100644 --- a/app/src/main/scala/veritas/Veritas.scala +++ b/app/src/main/scala/veritas/Veritas.scala @@ -136,8 +136,9 @@ object Veritas { pool.awaitTermination(5, TimeUnit.MINUTES) println(out) println() + if (calculateCoverage) - cov.CalculateCoverage().foreach(println) + cov.CalculateCoverage() // Delete all files created by writeToFile and the tests new File("compiled") From 1ed2a3e305c353d8a7cf99e7ca7dcab7d206afb8 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Wed, 30 Mar 2022 12:51:10 +0200 Subject: [PATCH 022/112] Adding codecov in the workflow --- .github/workflows/workflow.yml | 4 ++++ codecov.yml | 14 ++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 codecov.yml diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index a64b9b5..990a810 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -40,6 +40,10 @@ jobs: - name: Run tests run: gradle runCoverage -q + - uses: codecov/codecov-action@v2 + with: + files: coverage.json + verbose: true # Ubuntu workflow, something goes wrong in the test, perhaps fix in the future # gradle: diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..66ad11d --- /dev/null +++ b/codecov.yml @@ -0,0 +1,14 @@ +coverage: + precision: 2 + round: down + range: "60...100" + + status: + project: + default: + target: 60 + informational: true + patch: + default: + target: 60 + informational: true \ No newline at end of file From d0efa44190f965a030d77c5e3ec97864b74a62d8 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Wed, 30 Mar 2022 13:09:35 +0200 Subject: [PATCH 023/112] Removed verbose flag --- .github/workflows/workflow.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 990a810..ddb25f7 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -43,7 +43,6 @@ jobs: - uses: codecov/codecov-action@v2 with: files: coverage.json - verbose: true # Ubuntu workflow, something goes wrong in the test, perhaps fix in the future # gradle: From 8f68b214dc4400aea6d4e4d1fb8459f2f55bee97 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Mon, 4 Apr 2022 19:55:25 +0200 Subject: [PATCH 024/112] Added veritas README --- app/src/main/scala/veritas/README.md | 73 ++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 app/src/main/scala/veritas/README.md diff --git a/app/src/main/scala/veritas/README.md b/app/src/main/scala/veritas/README.md new file mode 100644 index 0000000..b9084c9 --- /dev/null +++ b/app/src/main/scala/veritas/README.md @@ -0,0 +1,73 @@ +# Veritas + +Veritas is a testing framework built to help test PoSharp code. + +It is currently targetting windows only as it uses WSL under the hood, this might change in the future. This may also work +for linux or mac locally but do note that there were issues on the remote pipeline with an Ubuntu image. + +## Usage + +### Writing a Test + +Writing tests is pretty straight forward. For a method to be considered a test it must: + +- Be inside the `test` package +- Be inside a class annotated with the `veritas.Veritas.Test` annotation +- Include the word `test` in the method name +- Have a return type of `(Boolean, String)` + +The [`PoSharp.scala`](./PoSharp.scala) class is created to provide an interface as well as some helper methods to aid in +writing tests. Do note that the framework considers the last printed value to be the value to check, this means that each +snippet must have a print statement. + +A simple test might look like this; + +```scala +@Test +class TestExample { + def runTest2(): (Boolean, String) = + """def main(): int { + val a = 5; + print(a); + return 0; + }""" + .ShouldBe("5") + .Run() +} +``` + +Notice how implicit classes are being used to trim down the needed syntax. These were added later on but in case you do +not want to use them, you can rewrite the test to: + +```scala +@Test +class TestExample { + def runTest2(): (Boolean, String) = + PoSharpScript("""def main(): int { + val a = 5; + print(a); + return 0; + }""") + .ShouldBe("5") + .Run() +} +``` + +The [`PoSharp.scala`](./PoSharp.scala) file contains thorough documentation on all the different methods that it provides +which I will be keeping up to date so be sure to read through the JavaDoc. + +### Running the Tests + +Running tests takes quite a bit of time as they need to be compiled and executed on a WSL external process, the framework +leverages multithreading to try and combat this. + +You can run the tests with `gradle runTests` or run tests + print coverage with `gradle runCoverage`. + + +## The Framework Explained + +I explain a lot of the inner workings [here](https://antoniosbarotsis.github.io/Blog/posts/posharp/). + +- `Veritas.scala`: Main file. Detects and executes all tests. +- `Coverage.scala`: Calculates and prints the coverage after the tests are executed, also generates a CodeCov report. +- `PoSharp.scala`: Contains helper classes and assertions for writing the tests. \ No newline at end of file From 6b4f964b99962bf0a894afa043b3ff0acb8032de Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Mon, 4 Apr 2022 20:57:30 +0200 Subject: [PATCH 025/112] Added helper class --- app/src/main/scala/veritas/Coverage.scala | 32 ++++++++++++++--------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/app/src/main/scala/veritas/Coverage.scala b/app/src/main/scala/veritas/Coverage.scala index d688b0f..0d2b4c9 100644 --- a/app/src/main/scala/veritas/Coverage.scala +++ b/app/src/main/scala/veritas/Coverage.scala @@ -14,7 +14,7 @@ object Coverage { private val reflections = new Reflections("scala") // Put names of redundant expressions here so they are ignored in the report (such as "Ident") private val redundantExprs: List[String] = List() - private var exprs: List[List[(Class[_ <: Expr], String, Int)]] = List() + private var exprs: List[List[ExprUsages]] = List() private var percentage = -1.0 /** @@ -32,7 +32,7 @@ object Coverage { * @param expr The expression * @return List[ClassName, Count] */ - private def GetExprs(expr: Expr): List[(Class[_ <: Expr], String, Int)] = { + private def GetExprs(expr: Expr): List[ExprUsages] = { val exprs = GetExprClassNameTuples val tmp = expr.toString @@ -47,7 +47,7 @@ object Coverage { exprs .filter(el => tmp.contains(el._2)) - .map(el => (el._1, el._2, tmp.count(_ == el._2))) + .map(el => ExprUsages(el._2, tmp.count(_ == el._2))) } /** @@ -93,12 +93,12 @@ object Coverage { * only includes used case classes in the return while the other one includes all of them (the rest just * with a count of zero) */ - private def SumCoverages(args: List[List[(Class[_ <: Expr], String, Int)]]): Map[String, Int] = { + private def SumCoverages(args: List[List[ExprUsages]]): Map[String, Int] = { args .flatten - .groupBy(_._2) - .map(el => (el._1, el._2.reduce((op, x) => (op._1, op._2, op._3 + x._3)))) - .map(el => (el._1, el._2._3)) + .map(el => (el.Expression, el.TimesUsed)) + .groupBy(_._1) + .map(el => el._2.reduce((op, x) => (op._1, op._2 + x._2))) } /** @@ -120,7 +120,7 @@ object Coverage { * @param cov Calculated coverage from [[CalculateCoverage]]. */ private def CreateCodeCovReport(cov: Map[String, Int]): Unit = { - var output: List[ExprCovLine] = List() + var output: List[ExprUsagesLine] = List() val txt = Files.readAllLines(Path.of("src/main/scala/scala/Definitions.scala")) // Find where the object starts to avoid mismatching stuff with imports @@ -129,7 +129,7 @@ object Coverage { val objStart = txt.indexWhere(_.contains("object Expr")) cov.foreach(el => output = output :+ - ExprCovLine(el._1, el._2, + ExprUsagesLine(el._1, el._2, txt .drop(objStart) .indexWhere(line => ExtractClassName(line) == el._1) + objStart + 1 @@ -163,8 +163,16 @@ object Coverage { * Helper class. * * @param Expression The [[Expr]] class in question. - * @param TimesUsed The amount of times it was used in the tests. - * @param Line The line number from `Definitions.scala`. + * @param TimesUsed The amount of times it was used in the tests. + * @param Line The line number from `Definitions.scala`. */ - private case class ExprCovLine(Expression: String, TimesUsed: Int, Line: Int) + private case class ExprUsagesLine(Expression: String, TimesUsed: Int, Line: Int) + + /** + * Helper class. + * + * @param Expression The [[Expr]] class in question. + * @param TimesUsed The amount of times it was used in the tests. + */ + private case class ExprUsages(Expression: String, TimesUsed: Int) } \ No newline at end of file From c5478dffc59c51c99ab135b84746349e2c4cde7a Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Tue, 5 Apr 2022 12:26:17 +0200 Subject: [PATCH 026/112] Referenced Veritas --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 514504a..0cea712 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,12 @@ With IntelliJ With sbt * In root directory call `make full` +[//]: # (TODO Does this still work? Probably a good idea to use gradle instead) + +A [testing framework](./app/src/main/scala/veritas) is also included in the project. +To run the language tests do `gradle runTests`. The documentation can be found +[here](./app/src/main/scala/veritas/README.md). + ### Language specification
From d2381979b9fae9a86790e8b6ce55a07762260086 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Tue, 5 Apr 2022 12:27:09 +0200 Subject: [PATCH 027/112] Made JSON coverage export optional --- .github/workflows/workflow.yml | 2 +- app/build.gradle | 9 ++++++++- app/src/main/scala/veritas/Coverage.scala | 6 ++++-- app/src/main/scala/veritas/README.md | 1 + app/src/main/scala/veritas/Veritas.scala | 15 ++++++++++++++- 5 files changed, 28 insertions(+), 5 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index ddb25f7..aa342e9 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -38,7 +38,7 @@ jobs: apt install make nasm gcc -y - name: Run tests - run: gradle runCoverage -q + run: gradle runCoverage -PextraArgs="export" -q - uses: codecov/codecov-action@v2 with: diff --git a/app/build.gradle b/app/build.gradle index 51d6ef4..bbe0163 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -52,5 +52,12 @@ task runTests(type: JavaExec) { task runCoverage(type: JavaExec) { classpath sourceSets.main.runtimeClasspath main = "veritas.Veritas" - args = ["coverage"] + + // If `extraArgs` exists, prepend it with "coverage" and set args to the result + // else just set args to "coverage" + if (project.hasProperty("extraArgs")) { + args(["coverage"] + (extraArgs.split(',') as List).flatten()) + } else { + args("coverage") + } } \ No newline at end of file diff --git a/app/src/main/scala/veritas/Coverage.scala b/app/src/main/scala/veritas/Coverage.scala index 0d2b4c9..7719faf 100644 --- a/app/src/main/scala/veritas/Coverage.scala +++ b/app/src/main/scala/veritas/Coverage.scala @@ -63,9 +63,10 @@ object Coverage { /** * Generates a map between each case class and the amount of times they were used in the tests. * + * @param export True exports a CodeCov JSON report * @return Map[ClassName, TimesUsed] */ - def CalculateCoverage(): Map[String, Int] = { + def CalculateCoverage(export: Boolean = false): Map[String, Int] = { val coverages = SumCoverages(exprs) val res = GetAllExprCaseClasses() @@ -79,7 +80,8 @@ object Coverage { val output = ListMap.from((res ++ coverages).toSeq.sortBy(_._2)) - CreateCodeCovReport(output) + if (export) + CreateCodeCovReport(output) output } diff --git a/app/src/main/scala/veritas/README.md b/app/src/main/scala/veritas/README.md index b9084c9..80f5471 100644 --- a/app/src/main/scala/veritas/README.md +++ b/app/src/main/scala/veritas/README.md @@ -62,6 +62,7 @@ Running tests takes quite a bit of time as they need to be compiled and executed leverages multithreading to try and combat this. You can run the tests with `gradle runTests` or run tests + print coverage with `gradle runCoverage`. +If you want to also generate the CodeCov JSON, run `gradle runCoverage -PextraArgs="export"` ## The Framework Explained diff --git a/app/src/main/scala/veritas/Veritas.scala b/app/src/main/scala/veritas/Veritas.scala index 74230fb..b0a517f 100644 --- a/app/src/main/scala/veritas/Veritas.scala +++ b/app/src/main/scala/veritas/Veritas.scala @@ -18,16 +18,29 @@ object Veritas { private val chunkSize = 1 private var cov: Coverage.type = _ private var calculateCoverage = false + private var exportCoverage = false /** * Runs all tests. If the first argument is `coverage`, coverage is calculated and printed. * + * Command line arguments: + *
    + *
  • [0] - `coverage`: coverage is calculated and printed
  • + *
  • [1] - `export`: coverage is exported in CodeCov JSON format
  • + *
+ * + * Order matters! + * * @param args Command line arguments. */ def main(args: Array[String]): Unit = { + println(args.mkString("Array(", ", ", ")")) if (args.isDefinedAt(0) && args.head == "coverage") { calculateCoverage = true cov = Coverage + + if (args.isDefinedAt(1) && args(1) == "export") + exportCoverage = true } var exitCode = 0 @@ -138,7 +151,7 @@ object Veritas { println() if (calculateCoverage) - cov.CalculateCoverage() + cov.CalculateCoverage(exportCoverage) // Delete all files created by writeToFile and the tests new File("compiled") From 1c76b98c5ba67563ebca25279c73a408b569b4b7 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Tue, 5 Apr 2022 12:31:03 +0200 Subject: [PATCH 028/112] Added workflow dispatch trigger Should allow to manually trigger the pipeline for testing purposes --- .github/workflows/workflow.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index aa342e9..66dc453 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -1,6 +1,7 @@ name: Build and Test on: + workflow_dispatch: push: branches: - "master" From 43ac3b6f4a44d63e6c89a289d0183f8fcfe46a8e Mon Sep 17 00:00:00 2001 From: Pijus Krisiukenas Date: Thu, 7 Apr 2022 03:35:01 +0200 Subject: [PATCH 029/112] makefile works --- Makefile | 5 ++++- build.sh | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 build.sh diff --git a/Makefile b/Makefile index d4a8761..c14a2b4 100644 --- a/Makefile +++ b/Makefile @@ -9,8 +9,11 @@ build: nasm -felf64 $(TARGET_FILE).asm && \ gcc -O0 -ggdb -no-pie $(TARGET_FILE).o -o $(TARGET_FILE) +build_all: + $(bash ./build.sh) + #compile and run asm -run: build +run: build_all compiled/$(TARGET_FILE) #compile Po# using sbt and then run it, also running the generated .asm diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..e27cfbf --- /dev/null +++ b/build.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +mkdir -p compiled && \ +cd compiled/ + +dir_succeeded=$? + +if [ $dir_succeeded -ne 0 ]; +then + exit 1 +fi + +for i in $( cut -d '.' -f 1 <<< "$(ls | grep .asm)" ); +do + nasm -felf64 $i.asm && \ + gcc -O0 -ggdb -no-pie $i.o -o $i +done \ No newline at end of file From 8bda66d76530c755f9467ce05240a8db157df70e Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Fri, 8 Apr 2022 23:23:49 +0200 Subject: [PATCH 030/112] Did gradle refactoring --- .gitignore | 2 +- Makefile | 2 + README.md | 8 +-- app/Makefile | 29 ----------- app/build.gradle | 48 +----------------- app/settings.gradle | 1 + .../{scala => posharp}/Definitions.scala | 4 +- .../main/scala/{scala => posharp}/Main.scala | 2 +- .../scala/{scala => posharp}/Parser.scala | 4 +- .../scala/{scala => posharp}/ToAssembly.scala | 6 +-- .../main/scala/{scala => posharp}/Util.scala | 2 +- build.gradle | 35 +++++++++++++ build.sbt | 15 ------ logo.png => docs/logo.png | Bin tasklist.md => docs/tasklist.md | 0 gradle.properties | 5 ++ settings.gradle | 2 +- veritas/build.gradle | 38 ++++++++++++++ veritas/settings.gradle | 4 ++ .../src/main/scala}/README.md | 8 +-- veritas/src/main/scala/core/Constants.scala | 16 ++++++ .../src/main/scala/core}/Coverage.scala | 10 ++-- .../src/main/scala/core}/PoSharp.scala | 4 +- .../src/main/scala/core}/Veritas.scala | 23 ++++++--- .../src/main/scala/test/TestExample.scala | 8 +-- 25 files changed, 148 insertions(+), 128 deletions(-) delete mode 100644 app/Makefile create mode 100644 app/settings.gradle rename app/src/main/scala/{scala => posharp}/Definitions.scala (99%) rename app/src/main/scala/{scala => posharp}/Main.scala (98%) rename app/src/main/scala/{scala => posharp}/Parser.scala (99%) rename app/src/main/scala/{scala => posharp}/ToAssembly.scala (99%) rename app/src/main/scala/{scala => posharp}/Util.scala (99%) create mode 100644 build.gradle delete mode 100644 build.sbt rename logo.png => docs/logo.png (100%) rename tasklist.md => docs/tasklist.md (100%) create mode 100644 gradle.properties create mode 100644 veritas/build.gradle create mode 100644 veritas/settings.gradle rename {app/src/main/scala/veritas => veritas/src/main/scala}/README.md (88%) create mode 100644 veritas/src/main/scala/core/Constants.scala rename {app/src/main/scala/veritas => veritas/src/main/scala/core}/Coverage.scala (95%) rename {app/src/main/scala/veritas => veritas/src/main/scala/core}/PoSharp.scala (97%) rename {app/src/main/scala/veritas => veritas/src/main/scala/core}/Veritas.scala (93%) rename {app => veritas}/src/main/scala/test/TestExample.scala (96%) diff --git a/.gitignore b/.gitignore index 3cb132f..ccd8784 100644 --- a/.gitignore +++ b/.gitignore @@ -142,7 +142,7 @@ project/plugins/project/ .cache .lib/ -/compiled +**/compiled /.bsp diff --git a/Makefile b/Makefile index d4a8761..08cac95 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,8 @@ build: #compile and run asm run: build + nasm -felf64 compiled/$(TARGET_FILE).asm + gcc -O0 -ggdb -no-pie compiled/$(TARGET_FILE).o -o compiled/$(TARGET_FILE) compiled/$(TARGET_FILE) #compile Po# using sbt and then run it, also running the generated .asm diff --git a/README.md b/README.md index 0cea712..1cf5575 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ -->

- +

[![Contributors][contributors-shield]][contributors-url] @@ -116,9 +116,9 @@ With sbt [//]: # (TODO Does this still work? Probably a good idea to use gradle instead) -A [testing framework](./app/src/main/scala/veritas) is also included in the project. +A [testing framework](./veritas/src/main/scala/core/) is also included in the project. To run the language tests do `gradle runTests`. The documentation can be found -[here](./app/src/main/scala/veritas/README.md). +[here](./veritas/src/main/scala/README.md). ### Language specification
@@ -201,4 +201,4 @@ def fib(n: int): int { [forks-url]: https://github.com/github_username/repo_name/network/members [stars-shield]: https://img.shields.io/github/stars/github_username/repo_name.svg?style=for-the-badge [stars-url]: https://github.com/github_username/repo_name/stargazers -[product-screenshot]: logo.png \ No newline at end of file +[product-screenshot]: docs/logo.png \ No newline at end of file diff --git a/app/Makefile b/app/Makefile deleted file mode 100644 index 08cac95..0000000 --- a/app/Makefile +++ /dev/null @@ -1,29 +0,0 @@ -TARGET_FILE = 'hello' - -all: run - -#assemble hello.asm -build: - mkdir -p compiled && \ - cd compiled/ && \ - nasm -felf64 $(TARGET_FILE).asm && \ - gcc -O0 -ggdb -no-pie $(TARGET_FILE).o -o $(TARGET_FILE) - -#compile and run asm -run: build - nasm -felf64 compiled/$(TARGET_FILE).asm - gcc -O0 -ggdb -no-pie compiled/$(TARGET_FILE).o -o compiled/$(TARGET_FILE) - compiled/$(TARGET_FILE) - -#compile Po# using sbt and then run it, also running the generated .asm -full: sbt run - -#compile Po# compiler -sbt: - sbt --batch -Dsbt.server.forcestart=true run - -# for some example on the internet, the gcc compiler has issues -standalone: - nasm -f elf hello.asm && ld -m elf_i386 hello.o -o hello && ./hello - -#valgrind --leak-check=full --track-origins=yes --dsymutil=yes ./hello diff --git a/app/build.gradle b/app/build.gradle index bbe0163..2da7cd2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,63 +1,17 @@ -/* - * This file was generated by the Gradle 'init' task. - * - * This generated file contains a sample Scala application project to get you started. - * For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle - * User Manual available at https://docs.gradle.org/7.2/userguide/building_java_projects.html - */ - plugins { - // Apply the scala Plugin to add support for Scala. id 'scala' - - // Apply the application plugin to add support for building a CLI application in Java. id 'application' } repositories { - // Use Maven Central for resolving dependencies. mavenCentral() } dependencies { - // Use Scala 2.13 in our library project implementation 'org.scala-lang:scala-library:2.13.6' - - // This dependency is used by the application. - implementation 'com.google.guava:guava:30.1.1-jre' - implementation 'com.lihaoyi:fastparse_2.13:2.3.3' - implementation 'org.reflections:reflections:0.10.2' - implementation 'org.scala-lang:scala-reflect:2.13.8' - - // Use Scalatest for testing our library - testImplementation 'junit:junit:4.13.2' - testImplementation 'org.scalatest:scalatest_2.13:3.2.9' - testImplementation 'org.scalatestplus:junit-4-13_2.13:3.2.2.0' - - // Need scala-xml at test runtime - testRuntimeOnly 'org.scala-lang.modules:scala-xml_2.13:1.2.0' } application { - // Define the main class for the application. - mainClass = 'scala.Main' -} - -task runTests(type: JavaExec) { - classpath sourceSets.main.runtimeClasspath - main = "veritas.Veritas" -} - -task runCoverage(type: JavaExec) { - classpath sourceSets.main.runtimeClasspath - main = "veritas.Veritas" - - // If `extraArgs` exists, prepend it with "coverage" and set args to the result - // else just set args to "coverage" - if (project.hasProperty("extraArgs")) { - args(["coverage"] + (extraArgs.split(',') as List).flatten()) - } else { - args("coverage") - } + mainClass = 'posharp.Main' } \ No newline at end of file diff --git a/app/settings.gradle b/app/settings.gradle new file mode 100644 index 0000000..6780415 --- /dev/null +++ b/app/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'app' \ No newline at end of file diff --git a/app/src/main/scala/scala/Definitions.scala b/app/src/main/scala/posharp/Definitions.scala similarity index 99% rename from app/src/main/scala/scala/Definitions.scala rename to app/src/main/scala/posharp/Definitions.scala index 1ab7152..fb8b254 100644 --- a/app/src/main/scala/scala/Definitions.scala +++ b/app/src/main/scala/posharp/Definitions.scala @@ -1,6 +1,6 @@ -package scala +package posharp -import scala.ToAssembly.FunctionInfo +import ToAssembly.FunctionInfo sealed trait Expr object Expr{ diff --git a/app/src/main/scala/scala/Main.scala b/app/src/main/scala/posharp/Main.scala similarity index 98% rename from app/src/main/scala/scala/Main.scala rename to app/src/main/scala/posharp/Main.scala index f7c7812..6e2ad37 100644 --- a/app/src/main/scala/scala/Main.scala +++ b/app/src/main/scala/posharp/Main.scala @@ -1,4 +1,4 @@ -package scala +package posharp import java.io.{File, FileWriter} import scala.io.Source diff --git a/app/src/main/scala/scala/Parser.scala b/app/src/main/scala/posharp/Parser.scala similarity index 99% rename from app/src/main/scala/scala/Parser.scala rename to app/src/main/scala/posharp/Parser.scala index 466d1be..fc6f80b 100644 --- a/app/src/main/scala/scala/Parser.scala +++ b/app/src/main/scala/posharp/Parser.scala @@ -1,9 +1,9 @@ -package scala +package posharp import fastparse.JavaWhitespace._ import fastparse._ -import scala.Expr.GetProperty +import Expr.GetProperty object Parser { //TODO fix issue when spacing at start of file diff --git a/app/src/main/scala/scala/ToAssembly.scala b/app/src/main/scala/posharp/ToAssembly.scala similarity index 99% rename from app/src/main/scala/scala/ToAssembly.scala rename to app/src/main/scala/posharp/ToAssembly.scala index 696f596..3b0b51e 100644 --- a/app/src/main/scala/scala/ToAssembly.scala +++ b/app/src/main/scala/posharp/ToAssembly.scala @@ -1,7 +1,7 @@ -package scala +package posharp -import scala.Expr.GetProperty -import scala.Type.{UserType, shortS} +import Expr.GetProperty +import Type.{UserType, shortS} import scala.io.AnsiColor object ToAssembly { diff --git a/app/src/main/scala/scala/Util.scala b/app/src/main/scala/posharp/Util.scala similarity index 99% rename from app/src/main/scala/scala/Util.scala rename to app/src/main/scala/posharp/Util.scala index fb62c44..a1ea1bd 100644 --- a/app/src/main/scala/scala/Util.scala +++ b/app/src/main/scala/posharp/Util.scala @@ -1,4 +1,4 @@ -package scala +package posharp object Util { /** diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..6b74800 --- /dev/null +++ b/build.gradle @@ -0,0 +1,35 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * This generated file contains a sample Scala application project to get you started. + * For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle + * User Manual available at https://docs.gradle.org/7.2/userguide/building_java_projects.html + */ + +plugins { + // Apply the scala Plugin to add support for Scala. + id 'scala' + + // Apply the application plugin to add support for building a CLI application in Java. + id 'application' +} + +repositories { + // Use Maven Central for resolving dependencies. + mavenCentral() +} + +dependencies { + // Use Scala 2.13 in our library project + implementation 'org.scala-lang:scala-library:2.13.6' + + // This dependency is used by the application. + implementation 'com.google.guava:guava:30.1.1-jre' + + implementation 'com.lihaoyi:fastparse_2.13:2.3.3' +} + +application { + // Define the main class for the application. + mainClass = 'scala.Main' +} \ No newline at end of file diff --git a/build.sbt b/build.sbt deleted file mode 100644 index b4b7d56..0000000 --- a/build.sbt +++ /dev/null @@ -1,15 +0,0 @@ -ThisBuild / version := "0.1.0-SNAPSHOT" - -ThisBuild / scalaVersion := "2.13.8" - -lazy val root = (project in file(".")) - .settings( - name := "Compiler-asm", - //libraryDependencies += // SBT - ) -ThisBuild / libraryDependencies ++= Seq( - "com.lihaoyi" %% "fastparse" % "2.3.3", - "org.reflections" % "reflections" % "0.10.2", - "org.scala-lang" % "scala-reflect" % scalaVersion.value, - "org.slf4j" % "slf4j-simple" % "1.7.33" % Runtime -) \ No newline at end of file diff --git a/logo.png b/docs/logo.png similarity index 100% rename from logo.png rename to docs/logo.png diff --git a/tasklist.md b/docs/tasklist.md similarity index 100% rename from tasklist.md rename to docs/tasklist.md diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..e69ac06 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,5 @@ +# https://docs.gradle.org/current/userguide/command_line_interface.html#sec:command_line_performance +org.gradle.configureondemand=true +org.gradle.parallel=true +org.gradle.caching=true +org.gradle.daemon=true \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 1633742..cd76dbf 100644 --- a/settings.gradle +++ b/settings.gradle @@ -8,4 +8,4 @@ */ rootProject.name = 'scala' -include('app') +include('app', 'veritas') \ No newline at end of file diff --git a/veritas/build.gradle b/veritas/build.gradle new file mode 100644 index 0000000..f57b5b4 --- /dev/null +++ b/veritas/build.gradle @@ -0,0 +1,38 @@ +plugins { + id 'scala' + id 'application' +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.scala-lang:scala-library:2.13.8' + implementation 'org.reflections:reflections:0.10.2' + implementation 'org.scala-lang:scala-reflect:2.13.8' + implementation project(path: ':app', configuration: 'default') +} + +application { + mainClass = 'core.Veritas' +} + +task runTests { + dependsOn(":app:build") + finalizedBy("run") +} + + +task runCoverage(type: JavaExec) { + classpath sourceSets.main.runtimeClasspath + getMainClass().set("core.Veritas") + + // If `extraArgs` exists, prepend it with "coverage" and set args to the result + // else just set args to "coverage" + if (project.hasProperty("extraArgs")) { + args(["coverage"] + (extraArgs.split(',') as List).flatten()) + } else { + args("coverage") + } +} \ No newline at end of file diff --git a/veritas/settings.gradle b/veritas/settings.gradle new file mode 100644 index 0000000..9743f49 --- /dev/null +++ b/veritas/settings.gradle @@ -0,0 +1,4 @@ +rootProject.name = 'veritas' + +include ":app" +project(":app").projectDir = file("../app") \ No newline at end of file diff --git a/app/src/main/scala/veritas/README.md b/veritas/src/main/scala/README.md similarity index 88% rename from app/src/main/scala/veritas/README.md rename to veritas/src/main/scala/README.md index 80f5471..0a6af7c 100644 --- a/app/src/main/scala/veritas/README.md +++ b/veritas/src/main/scala/README.md @@ -16,7 +16,7 @@ Writing tests is pretty straight forward. For a method to be considered a test i - Include the word `test` in the method name - Have a return type of `(Boolean, String)` -The [`PoSharp.scala`](./PoSharp.scala) class is created to provide an interface as well as some helper methods to aid in +The [`PoSharp.scala`](./core/PoSharp.scala) class is created to provide an interface as well as some helper methods to aid in writing tests. Do note that the framework considers the last printed value to be the value to check, this means that each snippet must have a print statement. @@ -24,7 +24,7 @@ A simple test might look like this; ```scala @Test -class TestExample { +class test.TestExample { def runTest2(): (Boolean, String) = """def main(): int { val a = 5; @@ -41,7 +41,7 @@ not want to use them, you can rewrite the test to: ```scala @Test -class TestExample { +class test.TestExample { def runTest2(): (Boolean, String) = PoSharpScript("""def main(): int { val a = 5; @@ -53,7 +53,7 @@ class TestExample { } ``` -The [`PoSharp.scala`](./PoSharp.scala) file contains thorough documentation on all the different methods that it provides +The [`PoSharp.scala`](./core/PoSharp.scala) file contains thorough documentation on all the different methods that it provides which I will be keeping up to date so be sure to read through the JavaDoc. ### Running the Tests diff --git a/veritas/src/main/scala/core/Constants.scala b/veritas/src/main/scala/core/Constants.scala new file mode 100644 index 0000000..5f870ec --- /dev/null +++ b/veritas/src/main/scala/core/Constants.scala @@ -0,0 +1,16 @@ +package core + +/** + * Holds a few constants that can potentially be referenced in multiple places in the code. + */ +object Constants { + /** + * Package name of the PoSharp implementation. + */ + val POSHARP_PACKAGE = "posharp" + + /** + * Path from the root of the project to the compiler's source code. + */ + val POSHARP_PATH = s"app/src/main/scala/$POSHARP_PACKAGE/" +} diff --git a/app/src/main/scala/veritas/Coverage.scala b/veritas/src/main/scala/core/Coverage.scala similarity index 95% rename from app/src/main/scala/veritas/Coverage.scala rename to veritas/src/main/scala/core/Coverage.scala index 7719faf..daec4c3 100644 --- a/app/src/main/scala/veritas/Coverage.scala +++ b/veritas/src/main/scala/core/Coverage.scala @@ -1,6 +1,7 @@ -package veritas +package core import org.reflections.Reflections +import posharp.Expr import java.nio.file.{Files, Path} import scala.collection.convert.ImplicitConversions._ @@ -11,7 +12,7 @@ import scala.collection.immutable.ListMap * the tests. */ object Coverage { - private val reflections = new Reflections("scala") + private val reflections = new Reflections(Constants.POSHARP_PACKAGE) // Put names of redundant expressions here so they are ignored in the report (such as "Ident") private val redundantExprs: List[String] = List() private var exprs: List[List[ExprUsages]] = List() @@ -123,7 +124,8 @@ object Coverage { */ private def CreateCodeCovReport(cov: Map[String, Int]): Unit = { var output: List[ExprUsagesLine] = List() - val txt = Files.readAllLines(Path.of("src/main/scala/scala/Definitions.scala")) + + val txt = Files.readAllLines(Path.of(s"../${Constants.POSHARP_PATH}Definitions.scala")) // Find where the object starts to avoid mismatching stuff with imports // This shouldn't happen because of the regex stuff I later added below but @@ -137,7 +139,7 @@ object Coverage { .indexWhere(line => ExtractClassName(line) == el._1) + objStart + 1 )) - val start = "{\"coverage\":{\"app/src/main/scala/scala/Definitions.scala\": {" + val start = s"{\"coverage\":{\"${Constants.POSHARP_PATH}Definitions.scala\": {" val end = "}}}" val json = start + output.map(el => s"\"${el.Line}\": ${el.TimesUsed},").mkString.dropRight(1) + end diff --git a/app/src/main/scala/veritas/PoSharp.scala b/veritas/src/main/scala/core/PoSharp.scala similarity index 97% rename from app/src/main/scala/veritas/PoSharp.scala rename to veritas/src/main/scala/core/PoSharp.scala index 856e4f6..f2a2047 100644 --- a/app/src/main/scala/veritas/PoSharp.scala +++ b/veritas/src/main/scala/core/PoSharp.scala @@ -1,7 +1,7 @@ -package veritas +package core import scala.language.implicitConversions -import scala.util.{Success, Failure} +import scala.util.{Failure, Success} protected trait IPoSharp { /** diff --git a/app/src/main/scala/veritas/Veritas.scala b/veritas/src/main/scala/core/Veritas.scala similarity index 93% rename from app/src/main/scala/veritas/Veritas.scala rename to veritas/src/main/scala/core/Veritas.scala index b0a517f..22c9f5c 100644 --- a/app/src/main/scala/veritas/Veritas.scala +++ b/veritas/src/main/scala/core/Veritas.scala @@ -1,13 +1,14 @@ -package veritas +package core import org.reflections.Reflections import org.reflections.scanners.Scanners.TypesAnnotated import org.reflections.util.ConfigurationBuilder +import posharp.Main.writeToFile +import posharp.{Parser, ToAssembly} import java.io.File import java.lang.reflect.Method import java.util.concurrent.{Executors, TimeUnit} -import scala.Main.writeToFile import scala.io.AnsiColor._ import scala.reflect.internal.util.ScalaClassLoader import scala.sys.process.Process @@ -25,8 +26,8 @@ object Veritas { * * Command line arguments: *
    - *
  • [0] - `coverage`: coverage is calculated and printed
  • - *
  • [1] - `export`: coverage is exported in CodeCov JSON format
  • + *
  • [0] - `coverage`: coverage is calculated and printed
  • + *
  • [1] - `export`: coverage is exported in CodeCov JSON format
  • *
* * Order matters! @@ -153,14 +154,20 @@ object Veritas { if (calculateCoverage) cov.CalculateCoverage(exportCoverage) - // Delete all files created by writeToFile and the tests + deleteTestArtifacts() + + exitCode + } + + /** + * Deletes all files created by writeToFile and the tests. + */ + private def deleteTestArtifacts(): Unit = { new File("compiled") .listFiles .filter(_.isFile) .filter(_.getName.contains("test")) .foreach(el => el.delete()) - - exitCode } /** @@ -195,7 +202,7 @@ object Veritas { val tmp = Process(if (IsWindows()) { "wsl " - } + s"make TARGET_FILE=$fileName" else { + } + s"make -f ../Makefile TARGET_FILE=$fileName" else { "" }).!! diff --git a/app/src/main/scala/test/TestExample.scala b/veritas/src/main/scala/test/TestExample.scala similarity index 96% rename from app/src/main/scala/test/TestExample.scala rename to veritas/src/main/scala/test/TestExample.scala index b5fa35e..5460faf 100644 --- a/app/src/main/scala/test/TestExample.scala +++ b/veritas/src/main/scala/test/TestExample.scala @@ -1,9 +1,9 @@ package test -import veritas.Veritas.Test -import veritas.PoSharp._ +import core.PoSharp.PoSharpImplicit +import core.Veritas.Test +import posharp.Parser.ParseException -import scala.Parser.ParseException import scala.language.postfixOps @@ -62,7 +62,7 @@ class TestExample { .ShouldThrow(new ParseException("")) def runTestBig(): (Boolean, String) = { - """object Dynamic { + """object Dynamic { size: int; allocated: int; arr: array[int]; From 97e9d0bda4b94bcc3f60d5445a96b225b5253c06 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Thu, 28 Apr 2022 22:00:51 +0200 Subject: [PATCH 031/112] Switched to kotlin --- app/build.gradle | 17 --------------- app/build.gradle.kts | 17 +++++++++++++++ app/settings.gradle | 1 - app/settings.gradle.kts | 1 + build.gradle | 35 ------------------------------- build.gradle.kts | 18 ++++++++++++++++ settings.gradle | 11 ---------- settings.gradle.kts | 2 ++ veritas/build.gradle | 38 --------------------------------- veritas/build.gradle.kts | 42 +++++++++++++++++++++++++++++++++++++ veritas/settings.gradle | 4 ---- veritas/settings.gradle.kts | 4 ++++ 12 files changed, 84 insertions(+), 106 deletions(-) delete mode 100644 app/build.gradle create mode 100644 app/build.gradle.kts delete mode 100644 app/settings.gradle create mode 100644 app/settings.gradle.kts delete mode 100644 build.gradle create mode 100644 build.gradle.kts delete mode 100644 settings.gradle create mode 100644 settings.gradle.kts delete mode 100644 veritas/build.gradle create mode 100644 veritas/build.gradle.kts delete mode 100644 veritas/settings.gradle create mode 100644 veritas/settings.gradle.kts diff --git a/app/build.gradle b/app/build.gradle deleted file mode 100644 index 2da7cd2..0000000 --- a/app/build.gradle +++ /dev/null @@ -1,17 +0,0 @@ -plugins { - id 'scala' - id 'application' -} - -repositories { - mavenCentral() -} - -dependencies { - implementation 'org.scala-lang:scala-library:2.13.6' - implementation 'com.lihaoyi:fastparse_2.13:2.3.3' -} - -application { - mainClass = 'posharp.Main' -} \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..f5d59cc --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,17 @@ +plugins { + scala + application +} + +repositories { + mavenCentral() +} + +dependencies { + implementation("org.scala-lang:scala-library:2.13.6") + implementation("com.lihaoyi:fastparse_2.13:2.3.3") +} + +application { + mainClass.set("posharp.Main") +} \ No newline at end of file diff --git a/app/settings.gradle b/app/settings.gradle deleted file mode 100644 index 6780415..0000000 --- a/app/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = 'app' \ No newline at end of file diff --git a/app/settings.gradle.kts b/app/settings.gradle.kts new file mode 100644 index 0000000..c203e2a --- /dev/null +++ b/app/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "app" \ No newline at end of file diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 6b74800..0000000 --- a/build.gradle +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file was generated by the Gradle 'init' task. - * - * This generated file contains a sample Scala application project to get you started. - * For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle - * User Manual available at https://docs.gradle.org/7.2/userguide/building_java_projects.html - */ - -plugins { - // Apply the scala Plugin to add support for Scala. - id 'scala' - - // Apply the application plugin to add support for building a CLI application in Java. - id 'application' -} - -repositories { - // Use Maven Central for resolving dependencies. - mavenCentral() -} - -dependencies { - // Use Scala 2.13 in our library project - implementation 'org.scala-lang:scala-library:2.13.6' - - // This dependency is used by the application. - implementation 'com.google.guava:guava:30.1.1-jre' - - implementation 'com.lihaoyi:fastparse_2.13:2.3.3' -} - -application { - // Define the main class for the application. - mainClass = 'scala.Main' -} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..397c436 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,18 @@ +plugins { + scala + application +} + +repositories { + mavenCentral() +} + +dependencies { + implementation("org.scala-lang:scala-library:2.13.6") + implementation("com.google.guava:guava:30.1.1-jre") + implementation("com.lihaoyi:fastparse_2.13:2.3.3") +} + +application { + mainClass.set("scala.Main") +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index cd76dbf..0000000 --- a/settings.gradle +++ /dev/null @@ -1,11 +0,0 @@ -/* - * This file was generated by the Gradle 'init' task. - * - * The settings file is used to specify which projects to include in your build. - * - * Detailed information about configuring a multi-project build in Gradle can be found - * in the user manual at https://docs.gradle.org/7.2/userguide/multi_project_builds.html - */ - -rootProject.name = 'scala' -include('app', 'veritas') \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..f733d66 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,2 @@ +rootProject.name = "scala" +include("app", "veritas") \ No newline at end of file diff --git a/veritas/build.gradle b/veritas/build.gradle deleted file mode 100644 index f57b5b4..0000000 --- a/veritas/build.gradle +++ /dev/null @@ -1,38 +0,0 @@ -plugins { - id 'scala' - id 'application' -} - -repositories { - mavenCentral() -} - -dependencies { - implementation 'org.scala-lang:scala-library:2.13.8' - implementation 'org.reflections:reflections:0.10.2' - implementation 'org.scala-lang:scala-reflect:2.13.8' - implementation project(path: ':app', configuration: 'default') -} - -application { - mainClass = 'core.Veritas' -} - -task runTests { - dependsOn(":app:build") - finalizedBy("run") -} - - -task runCoverage(type: JavaExec) { - classpath sourceSets.main.runtimeClasspath - getMainClass().set("core.Veritas") - - // If `extraArgs` exists, prepend it with "coverage" and set args to the result - // else just set args to "coverage" - if (project.hasProperty("extraArgs")) { - args(["coverage"] + (extraArgs.split(',') as List).flatten()) - } else { - args("coverage") - } -} \ No newline at end of file diff --git a/veritas/build.gradle.kts b/veritas/build.gradle.kts new file mode 100644 index 0000000..8149ed0 --- /dev/null +++ b/veritas/build.gradle.kts @@ -0,0 +1,42 @@ +plugins { + scala + application +} + +repositories { + mavenCentral() +} + +dependencies { + implementation("org.scala-lang:scala-library:2.13.8") + implementation("org.reflections:reflections:0.10.2") + implementation("org.scala-lang:scala-reflect:2.13.8") + implementation(project(":app")) +} + +application { + mainClass.set("core.Veritas") +} + +task("runTests", JavaExec::class) { + mainClass.set("core.Veritas") + classpath = sourceSets["main"].runtimeClasspath + + shouldRunAfter(":app:build") + finalizedBy("run") +} + +task("runCoverage", JavaExec::class) { + mainClass.set("core.Veritas") + classpath = sourceSets["main"].runtimeClasspath + + // If `extraArgs` exists, prepend it with "coverage" and set args to the result + // else just set args to "coverage" + if (project.hasProperty("extraArgs")) { + val tmp = project.properties["extraArgs"].toString().split(",") + + args(listOf("coverage") + tmp) + } else { + args("coverage") + } +} \ No newline at end of file diff --git a/veritas/settings.gradle b/veritas/settings.gradle deleted file mode 100644 index 9743f49..0000000 --- a/veritas/settings.gradle +++ /dev/null @@ -1,4 +0,0 @@ -rootProject.name = 'veritas' - -include ":app" -project(":app").projectDir = file("../app") \ No newline at end of file diff --git a/veritas/settings.gradle.kts b/veritas/settings.gradle.kts new file mode 100644 index 0000000..445f2aa --- /dev/null +++ b/veritas/settings.gradle.kts @@ -0,0 +1,4 @@ +rootProject.name = "veritas" + +include(":app") +project(":app").projectDir = file("../app") \ No newline at end of file From cb6868ab4a474e26eedf13106af4c433dbe14d35 Mon Sep 17 00:00:00 2001 From: Pijus Krisiukenas Date: Sun, 1 May 2022 02:18:50 +0200 Subject: [PATCH 032/112] import functions and objects --- Makefile | 7 +- app/src/main/scala/posharp/Definitions.scala | 4 +- app/src/main/scala/posharp/Main.scala | 34 +++++++- app/src/main/scala/posharp/Parser.scala | 9 ++- app/src/main/scala/posharp/ToAssembly.scala | 83 +++++++++++++++++--- build.sh | 17 ++-- docker-old/Dockerfile | 2 - docker-old/Makefile | 26 ------ docker-old/README.md | 43 ---------- docker-old/argument-printer.asm | 81 ------------------- docs/tasklist.md | 6 +- toCompile.txt => po_src/nice.txt | 38 +++++---- 12 files changed, 156 insertions(+), 194 deletions(-) delete mode 100644 docker-old/Dockerfile delete mode 100644 docker-old/Makefile delete mode 100644 docker-old/README.md delete mode 100644 docker-old/argument-printer.asm rename toCompile.txt => po_src/nice.txt (99%) diff --git a/Makefile b/Makefile index c14a2b4..d5d7026 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,7 @@ TARGET_FILE = 'hello' + + all: run #assemble hello.asm @@ -9,8 +11,11 @@ build: nasm -felf64 $(TARGET_FILE).asm && \ gcc -O0 -ggdb -no-pie $(TARGET_FILE).o -o $(TARGET_FILE) + +#compile all files in directory +.PHONY: build_all build_all: - $(bash ./build.sh) + bash ./build.sh #compile and run asm run: build_all diff --git a/app/src/main/scala/posharp/Definitions.scala b/app/src/main/scala/posharp/Definitions.scala index fb8b254..194fbdc 100644 --- a/app/src/main/scala/posharp/Definitions.scala +++ b/app/src/main/scala/posharp/Definitions.scala @@ -62,7 +62,9 @@ object Expr{ case class Convert(value: Expr, to: Type) extends Expr - case class TopLevel(functions: List[Func], interfaces: List[DefineInterface], enums: List[DefineEnum]) extends Expr + case class TopLevel(functions: List[Func], interfaces: List[DefineInterface], enums: List[DefineEnum], imports: List[Import]) extends Expr + case class Import(toImport: String, file: String) extends Expr + case class ThrowException(errorMsg: String) extends Expr case class Nothing() extends Expr case class Compiled(code: String, raxType: Type) extends Expr diff --git a/app/src/main/scala/posharp/Main.scala b/app/src/main/scala/posharp/Main.scala index 6e2ad37..7f92bbe 100644 --- a/app/src/main/scala/posharp/Main.scala +++ b/app/src/main/scala/posharp/Main.scala @@ -5,10 +5,30 @@ import scala.io.Source object Main extends App { - var inputFile = "toCompile.txt"; + var sourceDir = "po_src" + val fileExtension = ".txt" + //var inputFile = "toCompile.txt"; if (args.length > 0) { - inputFile = args(0) + sourceDir = args(0) } + val files = recursiveListFiles(new File(sourceDir)).toList.filter(x=>x.getName.contains(fileExtension)) + val declarations: Map[String, Expr.TopLevel] = files.map(file => { + val toCompile = readFile(file) + val parsed = Parser.parseInput(toCompile); + val top = parsed match { + case x: Expr.TopLevel => x + case _ => throw new Exception("unexpected type in top level") + } + (file.getName.split(fileExtension)(0) -> top) + }).toMap + declarations.foreach(x => { + val file = x._1 + val code = x._2 + val asm = ToAssembly.convertMain(code, file, declarations.filter(x=>x._1 != file)); + println("") + writeToFile(asm, "compiled/", file+".asm") + }) + /* val toCompile = readFile("", inputFile) val parsed = Parser.parseInput(toCompile); //println(Util.prettyPrint(parsed)); @@ -17,6 +37,7 @@ object Main extends App { //println(asm); writeToFile(asm, "compiled/", "hello.asm") + */ def writeToFile(input: String, directoryPath: String, filename: String): Unit = { val directory = new File(directoryPath); @@ -26,12 +47,17 @@ object Main extends App { fileWriter.write(input) fileWriter.close() } - def readFile(directoryPath: String, filename: String): String = { - val source = Source.fromFile(new File(directoryPath+filename)) + def readFile(src: File): String = { + val source = Source.fromFile(src) val codetxt = source.mkString source.close() codetxt } + def recursiveListFiles(f: File): Array[File] = { + if(f.isFile) return Array(f) + val these = f.listFiles + these ++ these.filter(_.isDirectory).flatMap(recursiveListFiles) + } } diff --git a/app/src/main/scala/posharp/Parser.scala b/app/src/main/scala/posharp/Parser.scala index fc6f80b..935b217 100644 --- a/app/src/main/scala/posharp/Parser.scala +++ b/app/src/main/scala/posharp/Parser.scala @@ -7,16 +7,18 @@ import Expr.GetProperty object Parser { //TODO fix issue when spacing at start of file - def topLevel[_: P]: P[Expr.TopLevel] = P(StringIn(" ").? ~ (function | interfaceDef | enumDef).rep(1)).map(x => { + def topLevel[_: P]: P[Expr.TopLevel] = P(StringIn(" ").? ~ (function | interfaceDef | enumDef | imports).rep(1)).map(x => { var func: List[Expr.Func] = List() var intf: List[Expr.DefineInterface] = List() var enum: List[Expr.DefineEnum] = List() + var imports: List[Expr.Import] = List() x.foreach { case y@Expr.Func(a, b, c, d) => func = func :+ y case y@Expr.DefineInterface(a, b, c) => intf = intf :+ y case y@Expr.DefineEnum(a, b) => enum = enum :+ y + case y@Expr.Import(a, b) => imports = imports :+ y } - Expr.TopLevel(func, intf, enum) + Expr.TopLevel(func, intf, enum, imports) }) def function[_: P]: P[Expr.Func] = P("def " ~/ ident ~ "(" ~/ functionArgs ~ ")" ~/ typeDef.? ~ block).map { @@ -29,6 +31,8 @@ object Parser { } } + def imports[_: P]: P[Expr.Import] = P("import " ~/ ident ~ "from" ~ fileName ~ ";").map(x=> Expr.Import(x._1.name, x._2.s)) + /* def interfaceDef[_: P]: P[Expr.DefineInterface] = P("interface " ~ ident ~/ "{" ~ (ident ~ typeDef).rep(sep = ",") ~ "}").map(props=> Expr.DefineInterface(props._1.name, props._2.toList.map(x=>InputVar(x._1.name, x._2))) @@ -221,6 +225,7 @@ object Parser { }) def str[_: P]: P[Expr.Str] = P("\"" ~~/ CharsWhile(_ != '"', 0).! ~~ "\"").map(x => Expr.Str(x + "\u0000")) + def fileName[_: P]: P[Expr.Str] = P("\"" ~~/ CharsWhile(_ != '"', 0).! ~~ "\"").map(x => Expr.Str(x)) def ident[_: P]: P[Expr.Ident] = P(CharIn("a-zA-Z_") ~~ CharsWhileIn("a-zA-Z0-9_", 0)).!.map((input) => { Expr.Ident(input) diff --git a/app/src/main/scala/posharp/ToAssembly.scala b/app/src/main/scala/posharp/ToAssembly.scala index 3b0b51e..4f94601 100644 --- a/app/src/main/scala/posharp/ToAssembly.scala +++ b/app/src/main/scala/posharp/ToAssembly.scala @@ -6,7 +6,24 @@ import scala.io.AnsiColor object ToAssembly { val defaultReg = List("rax", "rdi", "rsi", "rdx", "rcx", "r8", "r9", "r10", "r11") - def convertMain(input: Expr): String = { + var ifCounter = 0; + var subconditionCounter: Int = 0; + var stringLiterals: List[String] = List() + var functions: List[FunctionInfo] = List(); + var interfaces: List[InterfaceInfo] = List(); + var enums: List[EnumInfo] = List() + var lambdas: List[(Expr.Func, Env)] = List() + var functionScope: FunctionInfo = FunctionInfo("main", List(), Type.Num()); + + def convertMain(input: Expr, currentFile: String, otherFiles: Map[String, Expr.TopLevel]): String = { + ifCounter = 0; + subconditionCounter = 0; + stringLiterals = List() + functions = List(); + interfaces = List(); + enums = List() + lambdas = List() + functionScope = FunctionInfo("main", List(), Type.Num()); /* var converted = """ global main @@ -35,6 +52,8 @@ object ToAssembly { declareFunctions(x); declareInterfaces(x); declareEnums(x) + converted += exportDeclarations(currentFile) + converted += handleImports(x, otherFiles) converted += defineFunctions(x.functions.map(y=>(y, Map())), false); converted += defineFunctions(x.interfaces.flatMap(intf=> intf.functions.map(func=>Expr.Func(intf.name + "_" + func.name, func.argNames, func.retType, func.body))) @@ -56,14 +75,7 @@ object ToAssembly { converted = converted.split("\n").map(x=>if(x.contains(":")) x+"\n" else " "+x+"\n").mkString converted } - var ifCounter = 0; - var subconditionCounter: Int = 0; - var stringLiterals: List[String] = List() - var functions: List[FunctionInfo] = List(); - var interfaces: List[InterfaceInfo] = List(); - var enums: List[EnumInfo] = List() - var lambdas: List[(Expr.Func, Env)] = List() - var functionScope: FunctionInfo = FunctionInfo("main", List(), Type.Num()); + private def convert(input: Expr, reg: List[String], env: Env): (String, Type) = { val conv = (_input: Expr) => convert(_input, reg, env) @@ -349,6 +361,58 @@ object ToAssembly { private def declareEnums(input: Expr.TopLevel): Unit = { enums = input.enums.map(x=>EnumInfo(x.name,x.props)) } + private def handleImports(input: Expr.TopLevel, otherFiles: Map[String, Expr.TopLevel]): String = { + input.imports.map(imp=>{ + if (!otherFiles.contains(imp.file)) throw new Exception(s"file \"${imp.file}\" could not be imported"); + val top = otherFiles(imp.file) + + val funcsForImport = searchFileDeclarations(top, imp) match { + case Expr.Func(name, argnames, retType, code) => { + functions = functions :+ FunctionInfo(name, argnames, retType) + List(fNameSignature(FunctionInfo(name, argnames, retType))) + } + case Expr.DefineInterface(name, props, i_functions) => { + val intf = InterfaceInfo(name, props, i_functions.map(x=>FunctionInfo(x.name,x.argNames,x.retType))) + interfaces = interfaces :+ intf + val funcs = addPrefixToFunctions(intf.name,intf.funcs) + functions = functions ::: funcs + funcs.map(x=>fNameSignature(FunctionInfo(x.name, x.args, x.retType))) + } + } + funcsForImport.map(x=>{ + val label = imp.file + "_" + x + s"extern $label\n" + s"${x}:\njmp $label\n" + }).mkString + /* + + + intf.funcs.map(x=>{ + val label = imp.file + "_" + x.name + s"extern ${label}\n" + s"${x.name}:\njmp ${label}\n" + }).mkString + */ + + }).mkString + } + private def searchFileDeclarations(top: Expr.TopLevel, imp: Expr.Import): Expr = { + top.functions.find(x=>x.name == imp.toImport) + .orElse(top.functions.find(x=>x.name == imp.toImport)) + .orElse(top.interfaces.find(x=>x.name == imp.toImport)) + .orElse(throw new Exception(s"could not import ${imp.toImport} from file \"${imp.file}\"")) + .orNull + } + private def exportDeclarations(file: String): String = { + ( + functions + //interfaces.flatMap(intf=> addPrefixToFunctions(intf.name, intf.funcs)) + ) + .map(info => { + val name = fNameSignature(info) + s"global ${file}_${name}\n" + s"${file}_${name}:\njmp ${name}\n" + }).mkString + + } + def makeUserTypesConcrete(input: Type): Type = input match { case UserType(name) => interfaces.find(x=>x.name == name) match { case Some(n) => Type.Interface(n.args, n.funcs) @@ -374,6 +438,7 @@ object ToAssembly { */ val functionCallReg = List( "rdi", "rsi", "rdx", "rcx", "r8", "r9") + def fNameSignature(info: FunctionInfo): String = fNameSignature(info.name, info.args.map(x=>x.varType)) def fNameSignature(name: String, args: List[Type]):String = name + (if(args.isEmpty) "" else "_") + args.map(x=>shortS(x)).mkString private def defineFunctions(input: List[(Expr.Func, Env)], lambdaMode: Boolean): String = { diff --git a/build.sh b/build.sh index e27cfbf..c2363d8 100644 --- a/build.sh +++ b/build.sh @@ -1,7 +1,7 @@ #!/bin/bash mkdir -p compiled && \ -cd compiled/ +cd compiled/ || exit dir_succeeded=$? @@ -10,8 +10,15 @@ then exit 1 fi -for i in $( cut -d '.' -f 1 <<< "$(ls | grep .asm)" ); +files=$( cut -d '.' -f 1 <<< "$(ls | grep .asm)" ) +files_asm=() + +for i in $files; do - nasm -felf64 $i.asm && \ - gcc -O0 -ggdb -no-pie $i.o -o $i -done \ No newline at end of file + files_asm+=($i.o) + nasm -felf64 $i.asm +done + +files="${files_asm[*]}" + +gcc -O0 -ggdb -no-pie $files -o "hello" \ No newline at end of file diff --git a/docker-old/Dockerfile b/docker-old/Dockerfile deleted file mode 100644 index 8814afd..0000000 --- a/docker-old/Dockerfile +++ /dev/null @@ -1,2 +0,0 @@ -FROM alpine:latest -RUN apk add gdb build-base nasm gcc \ No newline at end of file diff --git a/docker-old/Makefile b/docker-old/Makefile deleted file mode 100644 index aeafbc4..0000000 --- a/docker-old/Makefile +++ /dev/null @@ -1,26 +0,0 @@ -current_dir = $(shell pwd) - -# default assembly code location -ifeq ($(asm),) - asm = argument-printer -endif - -build-image: - docker build -t linux-assembly . - -build: - docker run --rm -v $(current_dir):/app -w /app linux-assembly sh -c "nasm -f elf64 -F dwarf -g $(asm).asm && ld -m elf_x86_64 $(asm).o" - -run: build - docker run --rm -v $(current_dir):/app -w /app linux-assembly sh -c ./a.out - -run-with-args: build - docker run --rm -v $(current_dir):/app -w /app linux-assembly sh -c "./a.out $(args)" - -debug: build - docker run --rm -it --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -v $(current_dir):/app -w /app linux-assembly sh -c "gdb a.out" - -run-container: - docker run --rm -it --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -v $(current_dir):/app -w /app linux-assembly - - diff --git a/docker-old/README.md b/docker-old/README.md deleted file mode 100644 index 2240a99..0000000 --- a/docker-old/README.md +++ /dev/null @@ -1,43 +0,0 @@ -## Summary -Goal started out to Write a trivial program in x86_64 assembly language. - -This program prints out the number of arguments provided to the program and prints them out to the terminal. - -`nasm` is used for compiling the program, `ld` for linking and `gdb` for debugging. Docker is used to containerize the workflow within an alpine-linux image. - -See the wiki with process, learnings and guidance [here](https://github.com/tonyOreglia/unique-word-counter/wiki) - -## Dependencies -* [Docker](https://www.docker.com/get-started) -* [Make](https://www.tutorialspoint.com/unix_commands/make.htm) command line utility - -## Run and Debug on Docker -#### Build Image -```sh -make build-image -``` - -#### Compile and Link -```sh -make build -``` - -#### Build and Run the executable -```sh -make run -``` - -#### Build and Run with Arguments -```sh -make run-with-args args="arg1 arg2 arg3" -``` - -#### Build and Debug -```sh -make debug -``` - -#### Run the Container Attached via Terminal -```sh -make run-container -``` diff --git a/docker-old/argument-printer.asm b/docker-old/argument-printer.asm deleted file mode 100644 index 15e60c5..0000000 --- a/docker-old/argument-printer.asm +++ /dev/null @@ -1,81 +0,0 @@ -section .data - -section .text - global _start -_start: - call .getNumberOfArgs ; expects return value in $rax - mov rdi, rax - mov rbx, rdi ; rbx is preserved reg - call .printNumberOfArgs ; expects value to be in 1st argument, i.e. $rdi - call .printAllArgs ; expect rdi to have number of args - call .exit - -.getNumberOfArgs: - pop r10 ; this is the address of the calling fxn. Remove it from the stack for a moment so I can get to the argc - pop rax ; get argc from stack - push r10 ; return address of calling fxn to stack - ret - -; expects value to be in 1st argument, i.e. $rdi -.printNumberOfArgs: - add rdi, 48 ; convert number of args to ascii (only works if < 10) - push rdi ; push the ascii converted argc to stack - mov rsi, rsp ; store value of rsp, i.e. pointer to ascii argc to param2 of sys_write fxn - mov rdx, 8 ; param3 of sys_write fxn is the number of bits to print - call .print ; prints top of the stack - pop rdi ; clean the stack - ret - -; expects * char array in $rdi -.strlen: - mov rax, 1 ; initialize strlen counter -.loop: - add rdi, 1 ; increment char * to next character - add rax, 1 ; increment strlen counter - cmp byte [rdi], 0x00 ; if value at [rdi] is 0x00 return - jne .loop ; loop if not at end of string - ret - - -.printAllArgs: - call .printNewline ; fxn prints newline - pop r11 ; pop address of the calling fxn. Remove temporarily - mov rsi, [rsp] ; stack pointer memory address. Holding argument to print. - mov rdi, rsi - call .strlen ; expect strlen to be in $rax after funtion returns - mov rdx, rax ; how long is the message. TO DO: calculate argument length - push r11 ; push return address back onto the stack - call .print - pop r11 ; pop return address - pop rcx ; this is the already printed arg - push r11 ; push return address back onto the stack - sub rbx, 1 ; rbx is the argument count. Iterate down 1 - cmp rbx, 0 ; are there zero args left to print? - jne .printAllArgs ; if more args to print, loop again - call .printNewline ; otherwise print Newline and return - ret - - -.printNewline: - pop r11 ; this is the address of the calling fxn. Remove it from the stack for a moment so I can get to the argc - push 10 ; ascii newline character - mov rsi, rsp ; rsp points to top of stack. Newline has been pushed to top of stack. rsi is where 2nd param of sys_write is stored - push r11 ; return the address of the calling fxn to top of stack. - mov rdx, 2 - call .print - ; clean up the newline character pushed onto the stack. Retaining the return address currently on top of stack - pop r11 - pop rcx - push r11 - ret - -.print: ; print expects the calling location to be at the top of the stack - mov rax, 1 - mov rdi, 1 - syscall - ret ; return to location pointed to at top of stack - -.exit: - mov rax, 60 - mov rdi, 0 - syscall diff --git a/docs/tasklist.md b/docs/tasklist.md index ee593ce..1c074ae 100644 --- a/docs/tasklist.md +++ b/docs/tasklist.md @@ -1,15 +1,15 @@ ##To do - +* file name in error messages +* line numbers in compiler * make functions use stack instead of registers * static variables * add prinf -* infer self in object functions * Prevent array deferencing (point to a pointer that points to array) +* infer self in object functions * Add array copy function * add method to get all interface arguments/names * function reflection(do this by declaring labels in file with strings and var names) -* line numbers in compiler * parser error reporting * nested arrays * add runtime index checking for arrays diff --git a/toCompile.txt b/po_src/nice.txt similarity index 99% rename from toCompile.txt rename to po_src/nice.txt index 311f736..3068577 100644 --- a/toCompile.txt +++ b/po_src/nice.txt @@ -1,15 +1,3 @@ -/* -def apply(a: int, b: int, f: func[(int, int) => int]): int { - return f(a, b); -} -def main(): int { - val c = 5; - val a = lambda (x: int, y: int): int => (x + y + c); - val b = apply(5,6,a); - print(b); -} -*/ - object Dynamic { size: int; allocated: int; @@ -30,7 +18,7 @@ object Dynamic { }; } - + def expand(self: Dynamic, req: int) { val total = (req + self.size); if(total >= self.allocated) { @@ -57,12 +45,12 @@ object Dynamic { def push(self: Dynamic, value: array[int]) { val oldSize = self.size; self.expand(value.size); - + for(val i = 0; i < value.size; i+= 1;) { self.arr[(i + oldSize)] = value[i]; }; } - + def push(self: Dynamic, value: Dynamic) { val oldSize = self.size; self.expand(value.size); @@ -71,7 +59,7 @@ object Dynamic { self.arr[(i + oldSize)] = value.arr[i]; }; } - + def concat(self: Dynamic, other: Dynamic): Dynamic { val ret = self.copy(); ret.push(other); @@ -105,6 +93,22 @@ object Dynamic { return same; } } + + +/* +def apply(a: int, b: int, f: func[(int, int) => int]): int { + return f(a, b); +} +def main(): int { + val c = 5; + val a = lambda (x: int, y: int): int => (x + y + c); + val b = apply(5,6,a); + print(b); +} +*/ + +/* + def main(): int { val a = new Dynamic(); @@ -116,7 +120,7 @@ def main(): int { .print_arr(); } - +*/ /* def main(): int { From c0c3875e0d7652536cc7a59c6037fc9a09631d0c Mon Sep 17 00:00:00 2001 From: Pijus Krisiukenas Date: Sat, 14 May 2022 01:19:35 +0200 Subject: [PATCH 033/112] file path support --- app/src/main/scala/posharp/Main.scala | 26 +++++++++++++++------ app/src/main/scala/posharp/ToAssembly.scala | 14 +++++++++-- po_src/{ => lib}/nice.txt | 0 3 files changed, 31 insertions(+), 9 deletions(-) rename po_src/{ => lib}/nice.txt (100%) diff --git a/app/src/main/scala/posharp/Main.scala b/app/src/main/scala/posharp/Main.scala index 7f92bbe..ae0bb91 100644 --- a/app/src/main/scala/posharp/Main.scala +++ b/app/src/main/scala/posharp/Main.scala @@ -1,17 +1,21 @@ package posharp - import java.io.{File, FileWriter} +import java.nio.file.Paths import scala.io.Source +package object Constants { + val FileExtension = ".txt" +} + object Main extends App { var sourceDir = "po_src" - val fileExtension = ".txt" - //var inputFile = "toCompile.txt"; + if (args.length > 0) { sourceDir = args(0) } - val files = recursiveListFiles(new File(sourceDir)).toList.filter(x=>x.getName.contains(fileExtension)) + val files = recursiveListFiles(new File(sourceDir)).toList.filter(x=>x.getName.contains(Constants.FileExtension)) + val sourceDirPath = Paths.get(sourceDir) val declarations: Map[String, Expr.TopLevel] = files.map(file => { val toCompile = readFile(file) val parsed = Parser.parseInput(toCompile); @@ -19,14 +23,17 @@ object Main extends App { case x: Expr.TopLevel => x case _ => throw new Exception("unexpected type in top level") } - (file.getName.split(fileExtension)(0) -> top) + var relative_name = sourceDirPath.relativize(file.toPath).toFile.getPath.split(Constants.FileExtension)(0) + relative_name = relative_name.replace("\\", "/") + (relative_name -> top) }).toMap declarations.foreach(x => { val file = x._1 + println(file) val code = x._2 val asm = ToAssembly.convertMain(code, file, declarations.filter(x=>x._1 != file)); println("") - writeToFile(asm, "compiled/", file+".asm") + writeCompiled(asm, "compiled/", file) }) /* val toCompile = readFile("", inputFile) @@ -39,6 +46,11 @@ object Main extends App { writeToFile(asm, "compiled/", "hello.asm") */ + def writeCompiled(asm: String, directoryPath: String, file: String): Unit = { + val flatFile = file.split("/").last + ".asm" + writeToFile(asm, directoryPath, flatFile) + } + def writeToFile(input: String, directoryPath: String, filename: String): Unit = { val directory = new File(directoryPath); if (!directory.exists()) directory.mkdir(); @@ -56,7 +68,7 @@ object Main extends App { def recursiveListFiles(f: File): Array[File] = { if(f.isFile) return Array(f) val these = f.listFiles - these ++ these.filter(_.isDirectory).flatMap(recursiveListFiles) + these ++ these.filter(x=>x.isDirectory).flatMap(x=>recursiveListFiles(x)) } } diff --git a/app/src/main/scala/posharp/ToAssembly.scala b/app/src/main/scala/posharp/ToAssembly.scala index 4f94601..3773934 100644 --- a/app/src/main/scala/posharp/ToAssembly.scala +++ b/app/src/main/scala/posharp/ToAssembly.scala @@ -380,7 +380,7 @@ object ToAssembly { } } funcsForImport.map(x=>{ - val label = imp.file + "_" + x + val label = formatFName(imp.file) + "_" + x s"extern $label\n" + s"${x}:\njmp $label\n" }).mkString /* @@ -407,12 +407,22 @@ object ToAssembly { //interfaces.flatMap(intf=> addPrefixToFunctions(intf.name, intf.funcs)) ) .map(info => { + val formatFile = formatFName(file) val name = fNameSignature(info) - s"global ${file}_${name}\n" + s"${file}_${name}:\njmp ${name}\n" + s"global ${formatFile}_${name}\n" + s"${formatFile}_${name}:\njmp ${name}\n" }).mkString } + /*** + * Formats file name in a format assembly can understand + * @param file name + * @return + */ + def formatFName(file: String): String = { + file.replace("/", "_") + } + def makeUserTypesConcrete(input: Type): Type = input match { case UserType(name) => interfaces.find(x=>x.name == name) match { case Some(n) => Type.Interface(n.args, n.funcs) diff --git a/po_src/nice.txt b/po_src/lib/nice.txt similarity index 100% rename from po_src/nice.txt rename to po_src/lib/nice.txt From a2cf1f4adbe06a082e102a52b78d3c4a5e246789 Mon Sep 17 00:00:00 2001 From: Pijus Krisiukenas Date: Mon, 16 May 2022 21:25:55 +0200 Subject: [PATCH 034/112] static methods --- .gitignore | 3 +- app/src/main/scala/posharp/Definitions.scala | 1 + app/src/main/scala/posharp/Main.scala | 15 ++++- app/src/main/scala/posharp/ToAssembly.scala | 58 +++++++++++++------- po_src/lib/nice.txt | 3 + 5 files changed, 56 insertions(+), 24 deletions(-) diff --git a/.gitignore b/.gitignore index ccd8784..4513b3f 100644 --- a/.gitignore +++ b/.gitignore @@ -168,5 +168,6 @@ gradle-app.setting # JDT-specific (Eclipse Java Development Tools) .classpath -toCompile.txt +!po_src/lib/ +po_src coverage.json diff --git a/app/src/main/scala/posharp/Definitions.scala b/app/src/main/scala/posharp/Definitions.scala index 194fbdc..c9d807c 100644 --- a/app/src/main/scala/posharp/Definitions.scala +++ b/app/src/main/scala/posharp/Definitions.scala @@ -81,6 +81,7 @@ object Type { case class Bool() extends Type //case class Interface(properties: List[InputVar]) extends Type case class Interface(properties: List[InputVar], functions: List[FunctionInfo]) extends Type + case class StaticInterface(properties: List[InputVar], functions: List[FunctionInfo]) extends Type case class Function(args: List[Type], retType: Type) extends Type case class T1() extends Type case class Enum(el: List[String]) extends Type diff --git a/app/src/main/scala/posharp/Main.scala b/app/src/main/scala/posharp/Main.scala index ae0bb91..f1b7315 100644 --- a/app/src/main/scala/posharp/Main.scala +++ b/app/src/main/scala/posharp/Main.scala @@ -1,7 +1,7 @@ package posharp import java.io.{File, FileWriter} import java.nio.file.Paths -import scala.io.Source +import scala.io.{AnsiColor, Source} package object Constants { val FileExtension = ".txt" @@ -29,9 +29,18 @@ object Main extends App { }).toMap declarations.foreach(x => { val file = x._1 - println(file) val code = x._2 - val asm = ToAssembly.convertMain(code, file, declarations.filter(x=>x._1 != file)); + var asm = ""; + + try { + asm = ToAssembly.convertMain(code, file, declarations.filter(x => x._1 != file)); + } + catch { + case x: Exception => { + println( AnsiColor.RED + s"Compilation exception in \"$file\": ${x.getMessage}" + AnsiColor.RESET); + sys.exit(1); + } + } println("") writeCompiled(asm, "compiled/", file) }) diff --git a/app/src/main/scala/posharp/ToAssembly.scala b/app/src/main/scala/posharp/ToAssembly.scala index 3773934..885e0c4 100644 --- a/app/src/main/scala/posharp/ToAssembly.scala +++ b/app/src/main/scala/posharp/ToAssembly.scala @@ -100,16 +100,26 @@ object ToAssembly { case ((code, Type.Num()), Type.Character()) => (code, valType) case ((code, l), r) => throw new Exception(s"cant convert from type ${l} to type $r") } - case Expr.Ident(name) => { - val isStatic = enums.find(x=>x.name == name) - if(isStatic.nonEmpty) { + /* + case Expr.Ident(name) => name match { + case _ if enums.exists(x => x.name == name) => { val enumInfo = isStatic.get ("", Type.Enum(enumInfo.el)) } - else { + case _ if interfaces.exists(x=> ) + case _ => { val look = lookup(name, env) (s"mov ${reg.head}, ${look._1}\n", look._2.varType) } + } + */ + case Expr.Ident(name) => { + enums.find(x => x.name == name).orElse(interfaces.find(x=>x.name == name)).orElse(Some(lookup(name, env))) match { + case Some(EnumInfo(_, el)) => ("", Type.Enum(el)) + case Some(InterfaceInfo(_, props, funcs)) => ("", Type.Interface(props, funcs)) + case Some((code: String, variable: Variable)) => (s"mov ${reg.head}, ${code}\n", variable.varType) + case _ => throw new Exception(s"unrecognised identifier $name") + } } case Expr.Block(lines) => convertBlock(lines, reg, env); case Expr.DefineArray(size, elemType, defaultValues) => conv(size) match { @@ -153,27 +163,14 @@ object ToAssembly { } case None => throw new Exception(s"interface ${interfaces.find(x=>x.args == props).get.name} does not have a property ${prop}") } + //case (code, Type.StaticInterface(props, funcs)) => case (code, Type.Enum(el)) => (s"mov ${reg.head}, 0${el.indexOf(prop)}d\n", Type.Num()) case (code, Type.Array(a)) if prop == "size" => conv(Expr.ArraySize(obj)) case (x, valType) => throw new Exception(s"expected a interface, got ${valType}") } case Expr.CallObjFunc(obj, func) => conv(obj) match { - case(code, t@Type.Interface(props, funcs)) => funcs.find(x=>x.name == func.name) match { - case Some(n) => { - var args = func.args - //TODO fails if interface has same attributes/functions but different name - val intfName = interfaces.find(x=>x.args == props && x.funcs == funcs).get.name - if(n.args.nonEmpty && n.args.head.name == "self") args = obj +: args - interpFunction(intfName+"_"+func.name, args, reg, env) - } - case None => props.find(x=>x.name == func.name) match { - case Some(InputVar(_, Type.Function(_,_))) => { - //callLambda(Expr.GetProperty(Expr.Compiled(code, t), func.name), func.args, reg, env) - callLambda(Expr.GetProperty(obj, func.name), func.args, reg, env) - } - case None => throw new Exception(s"object has no property or function named $func") - } - } + case(code, t@Type.Interface(props, funcs)) => callObjFunction(obj, func, props, funcs, isStatic = false, reg, env) + case(code, Type.StaticInterface(props, funcs)) => callObjFunction(obj, func, props, funcs, isStatic = true, reg, env) } case Expr.GetArray(name, index) => getArray(name, index, reg, env); case Expr.ArraySize(name) => getArraySize(name, reg, env); @@ -506,6 +503,27 @@ object ToAssembly { } case (_, x) => throw new Exception(s"Can not call variable of type $x"); } + def callObjFunction(obj: Expr, func: Expr.CallF, props: List[InputVar], funcs: List[FunctionInfo], isStatic: Boolean, reg: List[String], env: Env): (String, Type) = { + funcs.find(x=>x.name == func.name) match { + case Some(n) => { + var args = func.args + //TODO fails if interface has same attributes/functions but different name + val intfName = interfaces.find(x=>x.args == props && x.funcs == funcs).get.name + if(n.args.nonEmpty && n.args.head.name == "self") { + if(isStatic) throw new Exception(s"can not call method $func staticly") + else args = obj +: args + } + interpFunction(intfName+"_"+func.name, args, reg, env) + } + case None => props.find(x=>x.name == func.name) match { + case Some(InputVar(_, Type.Function(_,_))) => { + //callLambda(Expr.GetProperty(Expr.Compiled(code, t), func.name), func.args, reg, env) + callLambda(Expr.GetProperty(obj, func.name), func.args, reg, env) + } + case None => throw new Exception(s"object has no property or function named $func") + } + } + } def interpFunction(name: String, args: List[Expr], reg: List[String], env: Env ): (String, Type) = { val usedReg = defaultReg.filter(x => !reg.contains(x)); var ret = usedReg.map(x=>s"push $x\n").mkString diff --git a/po_src/lib/nice.txt b/po_src/lib/nice.txt index 3068577..a1744f1 100644 --- a/po_src/lib/nice.txt +++ b/po_src/lib/nice.txt @@ -92,6 +92,9 @@ object Dynamic { }; return same; } + def not_useful() { + print("not useful, you're dumb"); + } } From 1e1f67ab9b28dada6305bfc246cd37dde3bd80c7 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Wed, 15 Jun 2022 14:44:47 +0200 Subject: [PATCH 035/112] Removed random run --- veritas/build.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/veritas/build.gradle.kts b/veritas/build.gradle.kts index 8149ed0..41a37a7 100644 --- a/veritas/build.gradle.kts +++ b/veritas/build.gradle.kts @@ -23,7 +23,6 @@ task("runTests", JavaExec::class) { classpath = sourceSets["main"].runtimeClasspath shouldRunAfter(":app:build") - finalizedBy("run") } task("runCoverage", JavaExec::class) { From 6899823d02d4231efa0333a41ccd7030385cd8b7 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Wed, 15 Jun 2022 14:45:00 +0200 Subject: [PATCH 036/112] Fixed linux bug --- veritas/src/main/scala/core/Veritas.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/veritas/src/main/scala/core/Veritas.scala b/veritas/src/main/scala/core/Veritas.scala index 22c9f5c..50a945c 100644 --- a/veritas/src/main/scala/core/Veritas.scala +++ b/veritas/src/main/scala/core/Veritas.scala @@ -202,9 +202,9 @@ object Veritas { val tmp = Process(if (IsWindows()) { "wsl " - } + s"make -f ../Makefile TARGET_FILE=$fileName" else { + } else { "" - }).!! + } + s"make -f ../Makefile TARGET_FILE=$fileName").!! tmp.split("\n").last.trim } From b9accc8c3fef42a67ae0b9031c5cf26f0c5b63db Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Wed, 15 Jun 2022 14:46:34 +0200 Subject: [PATCH 037/112] Created toast workflow --- toast.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 toast.yml diff --git a/toast.yml b/toast.yml new file mode 100644 index 0000000..e470d4a --- /dev/null +++ b/toast.yml @@ -0,0 +1,23 @@ +image: gradle:7.4.2-jdk17 +command_prefix: set -e # Make Bash fail fast. +tasks: + + build: + input_paths: + - . + command: gradle build + + install_deps: + dependencies: + - build + cache: true + command: | + apt-get update + apt-get install make nasm gcc -y + + test: + environment: + ARGS: '' + dependencies: + - install_deps + command: gradle runCoverage $ARGS From 561abbaf83314ad7fc04461731b8d56c77c36950 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Wed, 15 Jun 2022 14:46:42 +0200 Subject: [PATCH 038/112] Updated github workflow --- .github/workflows/workflow.yml | 74 +++++----------------------------- 1 file changed, 10 insertions(+), 64 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 66dc453..58dbbfa 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -1,5 +1,4 @@ name: Build and Test - on: workflow_dispatch: push: @@ -8,66 +7,13 @@ on: pull_request: jobs: - gradle: - runs-on: windows-2022 - steps: - - uses: actions/checkout@v2 - - - name: Set up WSL - uses: Vampire/setup-wsl@v1 - with: - distribution: Ubuntu-20.04 - - - uses: actions/setup-java@v2 - with: - distribution: adopt-hotspot - java-version: 17 - cache: 'gradle' - - - name: Setup Gradle - uses: gradle/gradle-build-action@v2 - with: - gradle-version: 7.4 - - - name: Execute Gradle build - run: gradle build - - - name: Install dependencies - shell: wsl-bash {0} - run: | - apt update - apt install make nasm gcc -y - - - name: Run tests - run: gradle runCoverage -PextraArgs="export" -q - - - uses: codecov/codecov-action@v2 - with: - files: coverage.json - - # Ubuntu workflow, something goes wrong in the test, perhaps fix in the future - # gradle: - # runs-on: ubuntu-20.04 - # steps: - # - uses: actions/checkout@v2 - - # - uses: actions/setup-java@v2 - # with: - # distribution: temurin - # java-version: 17 - - # - name: Setup Gradle - # uses: gradle/gradle-build-action@v2 - # with: - # gradle-version: 7.3 - - # - name: Execute Gradle build - # run: gradle build - - # - name: Install dependencies - # run: | - # sudo apt update - # sudo apt install make nasm gcc -y - - # - name: Run tests - # run: gradle runTests \ No newline at end of file + ci: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: stepchowfun/toast/.github/actions/toast@main + with: + ARGS: -PextraArgs="export" -q + - uses: codecov/codecov-action@v2 + with: + files: coverage.json From 9fb18361f354a6882b265a02fed580a2c8f8a527 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Wed, 15 Jun 2022 14:59:55 +0200 Subject: [PATCH 039/112] Removed failing test --- veritas/src/main/scala/test/TestExample.scala | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/veritas/src/main/scala/test/TestExample.scala b/veritas/src/main/scala/test/TestExample.scala index 5460faf..155a99f 100644 --- a/veritas/src/main/scala/test/TestExample.scala +++ b/veritas/src/main/scala/test/TestExample.scala @@ -43,15 +43,15 @@ class TestExample { .ShouldBe("-1") .Run() - def runTest5(): (Boolean, String) = - """def main(): int { - val a = array(1,2,3); - a[0] = 5; - print(a[0]); - return 0; - }""" - .ShouldBe("5") - .Run() +// def runTest5(): (Boolean, String) = +// """def main(): int { +// val a = array(1,2,3); +// a[0] = 5; +// print(a[0]); +// return 0; +// }""" +// .ShouldBe("5") +// .Run() def runTest6(): (Boolean, String) = "def main(): int { val a = 10; print(a[0]); return 0;}" From b75d3238f8bef839f036e82e70215066baf9eed6 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Wed, 15 Jun 2022 15:00:07 +0200 Subject: [PATCH 040/112] Fixed env? --- .github/workflows/workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 58dbbfa..cd52844 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -12,7 +12,7 @@ jobs: steps: - uses: actions/checkout@v2 - uses: stepchowfun/toast/.github/actions/toast@main - with: + env: ARGS: -PextraArgs="export" -q - uses: codecov/codecov-action@v2 with: From cd1f24c7b3329785e36bda9db85842c3cbc79d1f Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Wed, 15 Jun 2022 15:09:42 +0200 Subject: [PATCH 041/112] Trying to add coverage report --- toast.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/toast.yml b/toast.yml index e470d4a..9714642 100644 --- a/toast.yml +++ b/toast.yml @@ -20,4 +20,6 @@ tasks: ARGS: '' dependencies: - install_deps + output_paths: + - coverage.json command: gradle runCoverage $ARGS From a01955258f918db8fa5826b44e7be573bf0ed3d5 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Wed, 15 Jun 2022 15:17:05 +0200 Subject: [PATCH 042/112] Debugging env --- toast.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/toast.yml b/toast.yml index 9714642..8747a78 100644 --- a/toast.yml +++ b/toast.yml @@ -23,3 +23,8 @@ tasks: output_paths: - coverage.json command: gradle runCoverage $ARGS + + print: + dependencies: + - test + command: echo $ARGS \ No newline at end of file From 1a437164815a01a94d2391dd8c1ceaabc452887c Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Wed, 15 Jun 2022 15:54:08 +0200 Subject: [PATCH 043/112] Never using inline if elses ever again --- veritas/src/main/scala/core/Veritas.scala | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/veritas/src/main/scala/core/Veritas.scala b/veritas/src/main/scala/core/Veritas.scala index 50a945c..379b4a8 100644 --- a/veritas/src/main/scala/core/Veritas.scala +++ b/veritas/src/main/scala/core/Veritas.scala @@ -200,11 +200,8 @@ object Veritas { def GetOutput(asm: String, fileName: String): String = { writeToFile(asm, "compiled/", s"$fileName.asm") - val tmp = Process(if (IsWindows()) { - "wsl " - } else { - "" - } + s"make -f ../Makefile TARGET_FILE=$fileName").!! + val prefix = if (IsWindows()) {"wsl "} else {""} + val tmp = Process(prefix + s"make -f ../Makefile TARGET_FILE=$fileName").!! tmp.split("\n").last.trim } From 181da4396b7de29e661499cc073a48411ae787f6 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Wed, 15 Jun 2022 15:58:50 +0200 Subject: [PATCH 044/112] work ffs --- .github/workflows/workflow.yml | 2 +- toast.yml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index cd52844..e6af8cb 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v2 - uses: stepchowfun/toast/.github/actions/toast@main env: - ARGS: -PextraArgs="export" -q + args: -PextraArgs=export -q - uses: codecov/codecov-action@v2 with: files: coverage.json diff --git a/toast.yml b/toast.yml index 8747a78..25b6fd8 100644 --- a/toast.yml +++ b/toast.yml @@ -17,14 +17,14 @@ tasks: test: environment: - ARGS: '' + args: '' dependencies: - install_deps output_paths: - coverage.json - command: gradle runCoverage $ARGS + command: gradle runCoverage $args print: - dependencies: - - test - command: echo $ARGS \ No newline at end of file + environment: + args: '' + command: echo $args From cf9ac690eedfe4ef18af4e49895aa8a42b64798f Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Wed, 15 Jun 2022 16:24:32 +0200 Subject: [PATCH 045/112] Removed debugging stage --- toast.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/toast.yml b/toast.yml index 25b6fd8..1a03fb7 100644 --- a/toast.yml +++ b/toast.yml @@ -22,9 +22,4 @@ tasks: - install_deps output_paths: - coverage.json - command: gradle runCoverage $args - - print: - environment: - args: '' - command: echo $args + command: gradle runCoverage $args \ No newline at end of file From 940f155e067fe0e94f9d2e2a8043a4131594e449 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Wed, 15 Jun 2022 17:24:40 +0200 Subject: [PATCH 046/112] TRUEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE --- veritas/src/main/scala/test/TestExample.scala | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/veritas/src/main/scala/test/TestExample.scala b/veritas/src/main/scala/test/TestExample.scala index 155a99f..5460faf 100644 --- a/veritas/src/main/scala/test/TestExample.scala +++ b/veritas/src/main/scala/test/TestExample.scala @@ -43,15 +43,15 @@ class TestExample { .ShouldBe("-1") .Run() -// def runTest5(): (Boolean, String) = -// """def main(): int { -// val a = array(1,2,3); -// a[0] = 5; -// print(a[0]); -// return 0; -// }""" -// .ShouldBe("5") -// .Run() + def runTest5(): (Boolean, String) = + """def main(): int { + val a = array(1,2,3); + a[0] = 5; + print(a[0]); + return 0; + }""" + .ShouldBe("5") + .Run() def runTest6(): (Boolean, String) = "def main(): int { val a = 10; print(a[0]); return 0;}" From c0b1a62507b1f4267e43340d990ea515635bf12a Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Thu, 7 Jul 2022 22:45:09 +0200 Subject: [PATCH 047/112] Hoping this works, didnt really test it --- toast.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/toast.yml b/toast.yml index 1a03fb7..f45d72b 100644 --- a/toast.yml +++ b/toast.yml @@ -1,7 +1,7 @@ image: gradle:7.4.2-jdk17 command_prefix: set -e # Make Bash fail fast. -tasks: +tasks: build: input_paths: - . @@ -10,10 +10,14 @@ tasks: install_deps: dependencies: - build - cache: true command: | apt-get update apt-get install make nasm gcc -y + + # Dev dependencies (not for CI runner) + if [[ -z "${CI}" ]]; then + apt-get install micro -y + fi test: environment: From 80d26d4a47becbd4802a737fc1f8b87a1ae909b4 Mon Sep 17 00:00:00 2001 From: pijuskri Date: Wed, 20 Jul 2022 01:51:59 +0300 Subject: [PATCH 048/112] fix veritas --- project/build.properties | 1 - veritas/src/main/scala/core/Veritas.scala | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) delete mode 100644 project/build.properties diff --git a/project/build.properties b/project/build.properties deleted file mode 100644 index dd4ff43..0000000 --- a/project/build.properties +++ /dev/null @@ -1 +0,0 @@ -sbt.version = 1.6.1 diff --git a/veritas/src/main/scala/core/Veritas.scala b/veritas/src/main/scala/core/Veritas.scala index 22c9f5c..918d68f 100644 --- a/veritas/src/main/scala/core/Veritas.scala +++ b/veritas/src/main/scala/core/Veritas.scala @@ -4,7 +4,7 @@ import org.reflections.Reflections import org.reflections.scanners.Scanners.TypesAnnotated import org.reflections.util.ConfigurationBuilder import posharp.Main.writeToFile -import posharp.{Parser, ToAssembly} +import posharp.{Expr, Parser, ToAssembly} import java.io.File import java.lang.reflect.Method @@ -184,7 +184,7 @@ object Veritas { if (calculateCoverage) cov.AddCoverage(parsed) - this.synchronized(Success(ToAssembly.convertMain(parsed))) + this.synchronized(Success(ToAssembly.convertMain(parsed, "", Map[String, Expr.TopLevel]()))) } catch { case e: Exception => Failure(e) } From 7f417b9140618f46e5260214cb5c934c01785851 Mon Sep 17 00:00:00 2001 From: pijuskri Date: Wed, 20 Jul 2022 15:32:41 +0300 Subject: [PATCH 049/112] very basic start --- Makefile | 22 ++-- app/src/main/scala/posharp/Main.scala | 2 +- app/src/main/scala/posharp/ToAssembly.scala | 127 +++++--------------- build-llvm.sh | 24 ++++ {po_src/lib => lib-code}/nice.txt | 0 5 files changed, 59 insertions(+), 116 deletions(-) create mode 100644 build-llvm.sh rename {po_src/lib => lib-code}/nice.txt (100%) diff --git a/Makefile b/Makefile index d5d7026..9653a5c 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,6 @@ TARGET_FILE = 'hello' - - -all: run +all: run_llvm #assemble hello.asm build: @@ -11,25 +9,19 @@ build: nasm -felf64 $(TARGET_FILE).asm && \ gcc -O0 -ggdb -no-pie $(TARGET_FILE).o -o $(TARGET_FILE) - #compile all files in directory .PHONY: build_all build_all: bash ./build.sh +build_all_llvm: + bash ./build-llvm.sh + #compile and run asm -run: build_all +run_llvm: build_all_llvm compiled/$(TARGET_FILE) -#compile Po# using sbt and then run it, also running the generated .asm -full: sbt run - -#compile Po# compiler -sbt: - sbt --batch -Dsbt.server.forcestart=true run - -# for some example on the internet, the gcc compiler has issues -standalone: - nasm -f elf hello.asm && ld -m elf_i386 hello.o -o hello && ./hello +run_asm: build_all + compiled/$(TARGET_FILE) #valgrind --leak-check=full --track-origins=yes --dsymutil=yes ./hello diff --git a/app/src/main/scala/posharp/Main.scala b/app/src/main/scala/posharp/Main.scala index f1b7315..9c01295 100644 --- a/app/src/main/scala/posharp/Main.scala +++ b/app/src/main/scala/posharp/Main.scala @@ -56,7 +56,7 @@ object Main extends App { */ def writeCompiled(asm: String, directoryPath: String, file: String): Unit = { - val flatFile = file.split("/").last + ".asm" + val flatFile = file.split("/").last + ".ll" writeToFile(asm, directoryPath, flatFile) } diff --git a/app/src/main/scala/posharp/ToAssembly.scala b/app/src/main/scala/posharp/ToAssembly.scala index 885e0c4..70b8bf7 100644 --- a/app/src/main/scala/posharp/ToAssembly.scala +++ b/app/src/main/scala/posharp/ToAssembly.scala @@ -1,7 +1,7 @@ package posharp -import Expr.GetProperty -import Type.{UserType, shortS} +import posharp.Type.{UserType, shortS} + import scala.io.AnsiColor object ToAssembly { @@ -24,55 +24,30 @@ object ToAssembly { enums = List() lambdas = List() functionScope = FunctionInfo("main", List(), Type.Num()); - /* - var converted = - """ global main - | extern printf - | extern calloc - | extern free - | section .text - |main: - | sub rsp, 256 - |""".stripMargin; - converted += convert(input, defaultReg, Map() )._1; - converted += "add rsp, 256\n" - converted += " mov rax, 0\n ret\n" - converted += "format_num:\n db \"%d\", 10, 0" - converted - */ - var converted = - """ global main - | extern printf - | extern calloc - | extern free - | extern exit - | section .text - |""".stripMargin; + + var converted = ""; + println(input); input match { case x: Expr.TopLevel => { declareFunctions(x); + /* declareInterfaces(x); declareEnums(x) converted += exportDeclarations(currentFile) converted += handleImports(x, otherFiles) + */ converted += defineFunctions(x.functions.map(y=>(y, Map())), false); + /* converted += defineFunctions(x.interfaces.flatMap(intf=> intf.functions.map(func=>Expr.Func(intf.name + "_" + func.name, func.argNames, func.retType, func.body))) .map(y=>(y, Map())), false ); + */ }} - converted += defineFunctions(lambdas, true); - converted += "exception:\nmov rdi, 1\ncall exit\n" - converted += "format_num:\n db \"%d\", 10, 0\n" - converted += "format_float:\n db \"%f\", 10, 0\n" - converted += "format_string:\n db \"%s\", 10, 0\n" - converted += "format_char:\n db \"%c\", 10, 0\n" - converted += "format_true:\n db \"true\", 10, 0\n" - converted += "format_false:\n db \"false\", 10, 0\n" - //converted += "section .data\nmain_rbp DQ 0\nmain_rsp DQ 0\n" - converted += stringLiterals.mkString - //converted = converted.split("\n").zipWithIndex.foldLeft("")((acc, v)=> acc +s"\nline${v._2}:\n"+ v._1) - converted = converted.split("\n").map(x=>if(x.contains(":")) x+"\n" else " "+x+"\n").mkString + //converted += defineFunctions(lambdas, true); + //converted += "exception:\nmov rdi, 1\ncall exit\n" + //converted += stringLiterals.mkString + //converted = converted.split("\n").map(x=>if(x.contains(":")) x+"\n" else " "+x+"\n").mkString converted } @@ -84,13 +59,14 @@ object ToAssembly { case (_code, received_type) => throw new Exception(s"got type $received_type, expected ${_type}") } val ret = input match { - case Expr.Num(value) => (s"mov ${reg.head}, 0${value}d\n", Type.Num()) + case Expr.Num(value) => (s"$value", Type.Num()) case Expr.NumFloat(value) => { (s"mov ${reg.head}, __float64__(${value.toString})\n", Type.NumFloat()) } case Expr.True() => (s"mov ${reg.head}, 1\n", Type.Bool()) case Expr.False() => (s"mov ${reg.head}, 0\n", Type.Bool()) - case Expr.Plus(left, right) => aritTemplate(left, right, "add", "addsd", reg, env) + case Expr.Plus(left, right) => (s"%result = add i32 ${conv(left)._1}, ${conv(right)._1}\n", Type.Num()) + //case Expr.Plus(left, right) => aritTemplate(left, right, "add", "addsd", reg, env) case Expr.Minus(left, right) => aritTemplate(left, right, "sub", "subsd", reg, env) case Expr.Mult(left, right) => aritTemplate(left, right, "mul", "mulsd", reg, env) case Expr.Div(left, right) => aritTemplate(left, right, "div", "divsd", reg, env) @@ -126,22 +102,6 @@ object ToAssembly { case (code, Type.Num()) => defineArray(code, elemType, defaultValues, env) case (code, x) => throw new Exception(s"not number when defining array size, got input of type $x") } - /* - case Expr.InstantiateInterface(name, values) => interfaces.find(x=>x.name == name) match { - case Some(intf) => { - var ret = values.zipWithIndex.map{case (entry, index) => { - val converted = convert(entry, defaultReg, env); - if(converted._2 != intf.args(index).varType) throw new Exception(s"expected type ${intf.args(index).varType}" + - s" for interface element ${intf.args(index).name}, but got ${converted._2}") - converted._1 + setArrayDirect("[rsp]", index, 8); - }}.mkString; - val array_def = s"mov rdi, ${intf.args.length}\n" + s"mov rsi, 8\n" + "call calloc\n" + "push rax\n" - ret = array_def + ret + "pop rax\n"; - (ret, Type.Interface(intf.args)) - } - case None => throw new Exception(s"no such interface defined") - } - */ case Expr.InstantiateInterface(name, values) => interfaces.find(x=>x.name == name) match { case Some(intf) => { val array_def = s"mov rdi, ${intf.args.length}\n" + s"mov rsi, 8\n" + "call calloc\n" + "push rax\n" @@ -238,10 +198,11 @@ object ToAssembly { var extendLines = lines; var defstring: String = lines.head match { case Expr.SetVal(Expr.Ident(name), value) => { - val converted = convert(value, reg, env); - val modified = setval(name, converted._2, env) - newenv = modified._2 - converted._1 + modified._1 + convert(value, reg, env)._1 + //val converted = convert(value, reg, env); + //val modified = setval(name, converted._2, env) + //newenv = modified._2 + //converted._1 + modified._1 }; case Expr.SetArray(expr, index, value) => { val converted = convert(value, reg, env); @@ -291,6 +252,8 @@ object ToAssembly { ret } case Expr.Return(in) => { + "ret i32 0" + /* val defInside = (env.keys.toSet diff functionScope.args.map(x=>x.name).toSet); //TODO fix issue when 2 variables reference same location val free = "" //freeMemory(env.filter(x=>defInside.contains(x._1))) @@ -302,7 +265,7 @@ object ToAssembly { converted._1 + free + "leave\nret\n" } case None => free + "leave\nret\n"; - } + }*/ } case Expr.ThrowException(err) => { @@ -427,23 +390,7 @@ object ToAssembly { } case x => x } - //TODO avoid traversing the same interfaces by creating a list of marking which interfaces are concretely defined - /* - def traverseTypeTree(input: Type): Type = input match { - case UserType(name) => interfaces.find(x=>x.name == name) match { - case Some(n) => traverseTypeTree(Type.Interface(n.args, n.funcs)) match { - case t@Type.Interface(newargs,f) => - interfaces = interfaces.map(x=>if(x == n) InterfaceInfo(x.name, newargs, x.funcs) else x) - t - } - case _ => throw new Exception (s"no interface of name $name"); - } - case Type.Interface(args,f) => Type.Interface(args.map(x=>InputVar(x.name, traverseTypeTree(x.varType))), f) - case Type.Array(valType) => traverseTypeTree(valType) - case x => x - } - */ val functionCallReg = List( "rdi", "rsi", "rdx", "rcx", "r8", "r9") def fNameSignature(info: FunctionInfo): String = fNameSignature(info.name, info.args.map(x=>x.varType)) def fNameSignature(name: String, args: List[Type]):String = name + (if(args.isEmpty) "" else "_") + args.map(x=>shortS(x)).mkString @@ -452,29 +399,9 @@ object ToAssembly { input.map{ case (function, upperScope) => { val info = functions.find(x=>x.name == function.name && x.args==function.argNames).get; functionScope = info; - val label = if(lambdaMode) info.name else fNameSignature(info.name, info.args.map(x=>x.varType)) - var ret = "\n" + s"${label}:\n" - //if(info.name == "main") ret += "mov [main_rbp], rbp\nmov [main_rsp], rsp\n" - ret += - """ push rbp - | mov rbp, rsp - | sub rsp, 256 - |""".stripMargin; - var env: Env = Map() - var regArgs = functionCallReg; - ret += function.argNames.map(arg => { - env = newVar(arg.name, arg.varType, env) - val moveVar = s"mov qword ${lookup(arg.name, env)._1}, ${regArgs.head}\n" - regArgs = regArgs.tail; - moveVar - }).mkString - ret += convert(function.body, defaultReg, shiftEnvLocations(upperScope) ++ env)._1 - if(info.retType == Type.Undefined()) ret += "leave\nret\n"; - if(info.name == "main") { - //ret += "exception:\nmov rdi, 1\nmov rbp, [main_rbp]\nmov rsp, [main_rsp]\nmov rdx, [rsp-8]\njmp rdx\n" - ret += "mov rax, 0\nleave\nret\n"; - } - + var ret = s"define i32 @${info.name}() {\n" + ret += convert(function.body, defaultReg, Map())._1 + ret += "\n}" ret }}.mkString } diff --git a/build-llvm.sh b/build-llvm.sh new file mode 100644 index 0000000..029086c --- /dev/null +++ b/build-llvm.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +mkdir -p compiled && \ +cd compiled/ || exit + +dir_succeeded=$? + +if [ $dir_succeeded -ne 0 ]; +then + exit 1 +fi + +files=$( cut -d '.' -f 1 <<< "$(ls | grep .ll)" ) +files_asm=() + +for i in $files; +do + files_asm+=($i.s) + llc $i.ll +done + +files="${files_asm[*]}" + +gcc -O0 -ggdb -no-pie $files -o "hello" \ No newline at end of file diff --git a/po_src/lib/nice.txt b/lib-code/nice.txt similarity index 100% rename from po_src/lib/nice.txt rename to lib-code/nice.txt From 12e69d8feeb43aed2a5016ee3ddcae01c4dcb5a0 Mon Sep 17 00:00:00 2001 From: pijuskri Date: Fri, 22 Jul 2022 00:34:56 +0300 Subject: [PATCH 050/112] working variables --- app/src/main/scala/posharp/Definitions.scala | 8 + app/src/main/scala/posharp/ToAssembly.scala | 252 ++++++++++--------- build-llvm.sh | 3 +- 3 files changed, 145 insertions(+), 118 deletions(-) diff --git a/app/src/main/scala/posharp/Definitions.scala b/app/src/main/scala/posharp/Definitions.scala index c9d807c..5f57ea6 100644 --- a/app/src/main/scala/posharp/Definitions.scala +++ b/app/src/main/scala/posharp/Definitions.scala @@ -117,6 +117,14 @@ object Type { case Character() => Expr.Character('\u0000'); case _ => Expr.Nothing(); } + def toLLVM(valType: Type): String = valType match { + case Num() => "i32"; + case NumFloat() => "double"; + case Character() => "i8" + case Bool() => "i1" + //case Array(inner) => "[40 x i32]" + case Undefined() => "void" + } } case class InputVar(name: String, varType: Type) case class ObjVal(name: String, varType: Type, defaultValue: Expr) diff --git a/app/src/main/scala/posharp/ToAssembly.scala b/app/src/main/scala/posharp/ToAssembly.scala index 70b8bf7..a1b9917 100644 --- a/app/src/main/scala/posharp/ToAssembly.scala +++ b/app/src/main/scala/posharp/ToAssembly.scala @@ -2,10 +2,26 @@ package posharp import posharp.Type.{UserType, shortS} -import scala.io.AnsiColor +class Counter { + private var counter = 1; + private var counterExtra = 0; + def next(): String = { + val cur = counter; + counter += 1; + s"%$cur"; + } + def extra(): Int = { + counterExtra += 1; + counterExtra - 1; + } + def last(): String = s"%${counter-1}"; + def reset(): Unit = { + counter = 1; + } +} object ToAssembly { - val defaultReg = List("rax", "rdi", "rsi", "rdx", "rcx", "r8", "r9", "r10", "r11") + var varc: Counter = new Counter(); var ifCounter = 0; var subconditionCounter: Int = 0; var stringLiterals: List[String] = List() @@ -25,7 +41,11 @@ object ToAssembly { lambdas = List() functionScope = FunctionInfo("main", List(), Type.Num()); - var converted = ""; + var converted = + """ + | declare i32 @printf(i8*, ...) + | @format_num = private constant [2 x i8] c"%d" + |""".stripMargin; println(input); input match { case x: Expr.TopLevel => { declareFunctions(x); @@ -52,52 +72,51 @@ object ToAssembly { } - private def convert(input: Expr, reg: List[String], env: Env): (String, Type) = { - val conv = (_input: Expr) => convert(_input, reg, env) - val convt = (_input: Expr, _type: Type) => convert(_input, reg, env) match { + private def convert(input: Expr, env: Env): (String, Type) = { + val conv = (_input: Expr) => convert(_input, env) + val convt = (_input: Expr, _type: Type) => convert(_input, env) match { case (_code, received_type) if received_type == _type => _code case (_code, received_type) => throw new Exception(s"got type $received_type, expected ${_type}") } val ret = input match { - case Expr.Num(value) => (s"$value", Type.Num()) + case Expr.Num(value) => (s"${varc.next()} = add i32 $value, 0\n", Type.Num()) + case Expr.Plus(left, right) => aritTemplate(left, right, "add", env) + case Expr.Minus(left, right) => aritTemplate(left, right, "sub", env) + case Expr.Mult(left, right) => aritTemplate(left, right, "mul", env) + case Expr.Div(left, right) => aritTemplate(left, right, "sdiv", env) + case Expr.Ident(name) => { + enums.find(x => x.name == name).orElse(interfaces.find(x=>x.name == name)).orElse(Some(lookup(name, env))) match { + case Some(EnumInfo(_, el)) => ("", Type.Enum(el)) + case Some(InterfaceInfo(_, props, funcs)) => ("", Type.Interface(props, funcs)) + case Some((code: String, variable: Variable)) => (code, variable.varType) + case _ => throw new Exception(s"unrecognised identifier $name") + } + } + case Expr.True() => (s"${varc.next()} = and i1 1, 1\n", Type.Bool()) + case Expr.False() => (s"${varc.next()} = and i1 0, 0\n", Type.Bool()) + case Expr.Equals(left, right) => (compareExpr(left, right, true, "eq", env), Type.Bool()) + case Expr.LessThan(left, right) => (compareExpr(left, right, true, "slt", env), Type.Bool()) + case Expr.MoreThan(left, right) => (compareExpr(left, right, true, "sgt", env), Type.Bool()) + case Expr.Not(left) => { + val converted = convt(left, Type.Bool()) + val loc = varc.last() + val ret = converted + s"${varc.next()} = xor i1 $loc, 1\n" + (ret, Type.Bool()) + } + + /* case Expr.NumFloat(value) => { (s"mov ${reg.head}, __float64__(${value.toString})\n", Type.NumFloat()) } - case Expr.True() => (s"mov ${reg.head}, 1\n", Type.Bool()) - case Expr.False() => (s"mov ${reg.head}, 0\n", Type.Bool()) - case Expr.Plus(left, right) => (s"%result = add i32 ${conv(left)._1}, ${conv(right)._1}\n", Type.Num()) - //case Expr.Plus(left, right) => aritTemplate(left, right, "add", "addsd", reg, env) - case Expr.Minus(left, right) => aritTemplate(left, right, "sub", "subsd", reg, env) - case Expr.Mult(left, right) => aritTemplate(left, right, "mul", "mulsd", reg, env) - case Expr.Div(left, right) => aritTemplate(left, right, "div", "divsd", reg, env) + case Expr.Convert(value, valType: Type) => (convert(value, reg, env), valType) match { case ((code, Type.Num()), Type.NumFloat()) => (code + convertToFloat(reg.head), valType) case ((code, Type.NumFloat()), Type.Num()) => (code + convertToInt(reg.head), valType) case ((code, Type.Num()), Type.Character()) => (code, valType) case ((code, l), r) => throw new Exception(s"cant convert from type ${l} to type $r") } - /* - case Expr.Ident(name) => name match { - case _ if enums.exists(x => x.name == name) => { - val enumInfo = isStatic.get - ("", Type.Enum(enumInfo.el)) - } - case _ if interfaces.exists(x=> ) - case _ => { - val look = lookup(name, env) - (s"mov ${reg.head}, ${look._1}\n", look._2.varType) - } - } - */ - case Expr.Ident(name) => { - enums.find(x => x.name == name).orElse(interfaces.find(x=>x.name == name)).orElse(Some(lookup(name, env))) match { - case Some(EnumInfo(_, el)) => ("", Type.Enum(el)) - case Some(InterfaceInfo(_, props, funcs)) => ("", Type.Interface(props, funcs)) - case Some((code: String, variable: Variable)) => (s"mov ${reg.head}, ${code}\n", variable.varType) - case _ => throw new Exception(s"unrecognised identifier $name") - } - } - case Expr.Block(lines) => convertBlock(lines, reg, env); + + case Expr.DefineArray(size, elemType, defaultValues) => conv(size) match { case (code, Type.Num()) => defineArray(code, elemType, defaultValues, env) case (code, x) => throw new Exception(s"not number when defining array size, got input of type $x") @@ -149,22 +168,7 @@ object ToAssembly { lambdas = lambdas :+ (Expr.Func(label, args, ret, body), env) (s"mov ${reg.head}, $label\n", Type.Function(args.map(x=>x.varType), ret)) } - case Expr.Equals(left, right) => { - val ret = compareExpr(left, right, false, reg, env) + s"sete ${sizeToReg(1, reg.head)}\n" - (ret, Type.Bool()) - } - case Expr.LessThan(left, right) => { - val ret = compareExpr(left, right, true, reg, env) + s"setl ${sizeToReg(1, reg.head)}\n" - (ret, Type.Bool()) - } - case Expr.MoreThan(left, right) => { - val ret = compareExpr(left, right, true, reg, env) + s"setg ${sizeToReg(1, reg.head)}\n" - (ret, Type.Bool()) - } - case Expr.Not(left) => { - val ret = convt(left, Type.Bool()) + s"xor ${reg.head}, 1\n" - (ret, Type.Bool()) - } + case Expr.And(l) => { val reg1 = sizeToReg(1, reg.head) val reg2 = sizeToReg(1, reg.tail.head) @@ -186,24 +190,32 @@ object ToAssembly { case Expr.Nothing() => ("", Type.Undefined()); case Expr.Compiled(code, retType) => (code, retType); + */ + case Expr.Block(lines) => convertBlock(lines, env); case x => throw new Exception (s"$x is not interpreted yet :("); } (ret._1, makeUserTypesConcrete(ret._2)) } //TODO add line awareness for error reporting - private def convertBlock(lines: List[Expr], reg: List[String], env: Env): (String, Type) = { - val conv = (_input: Expr) => convert(_input, reg, env) + private def convertBlock(lines: List[Expr], env: Env): (String, Type) = { + val conv = (_input: Expr) => convert(_input, env) if(lines.isEmpty) return ("", Type.Undefined()); var newenv = env; var extendLines = lines; var defstring: String = lines.head match { case Expr.SetVal(Expr.Ident(name), value) => { - convert(value, reg, env)._1 - //val converted = convert(value, reg, env); - //val modified = setval(name, converted._2, env) - //newenv = modified._2 - //converted._1 + modified._1 + //val look = lookup(name, env) + val converted = convertLoc(value, env); + //if(look._2.varType != converted._2) throw new Exception(s"mismatch when assigning value" + + // s" to variable $name, expected ${look._2.varType}, but got ${converted._2}") + val set = s"store ${Type.toLLVM(converted._2)} ${converted._3}, ${Type.toLLVM(converted._2)}* %$name\n" + converted._1 + set; }; + case Expr.DefVal(Expr.Ident(name), varType) => { + newenv = newVar(name, varType, newenv); + s"%$name = alloca ${Type.toLLVM(varType)}\n" + } + /* case Expr.SetArray(expr, index, value) => { val converted = convert(value, reg, env); val arr = setArray(expr, index, converted._2, env) @@ -220,8 +232,7 @@ object ToAssembly { } case (x, valType) => throw new Exception(s"expected a interface, got ${valType}") } - case Expr.DefVal(Expr.Ident(name), varType) => newenv = newVar(name, varType, newenv); "" - case Expr.Print(toPrint) => printInterp(toPrint, env); + case Expr.If(condition, ifTrue, ifFalse) => { def compare(left: Expr, right: Expr, numeric: Boolean): String = compareExpr(left, right, numeric, reg, env) val trueLabel = s"if_${ifCounter}_true" @@ -251,6 +262,15 @@ object ToAssembly { ifCounter += 1; ret } + case Expr.ThrowException(err) => { + val msg = AnsiColor.RED + "RuntimeException: " + err + AnsiColor.RESET + val name = s"exception_print_${stringLiterals.length}" + stringLiterals = stringLiterals :+ s"$name:\n db \"${msg}\", 10, 0\n" + s"mov rax, $name\n" + printTemplate("format_string") + "jmp exception\n" + } + case x@Expr.CallF(n, a) => convert(x, reg, env)._1; + case x@Expr.CallObjFunc(obj, func) => convert(x, reg, env)._1; + */ case Expr.Return(in) => { "ret i32 0" /* @@ -268,28 +288,21 @@ object ToAssembly { }*/ } - case Expr.ThrowException(err) => { - val msg = AnsiColor.RED + "RuntimeException: " + err + AnsiColor.RESET - val name = s"exception_print_${stringLiterals.length}" - stringLiterals = stringLiterals :+ s"$name:\n db \"${msg}\", 10, 0\n" - s"mov rax, $name\n" + printTemplate("format_string") + "jmp exception\n" - } - case x@Expr.CallF(n, a) => convert(x, reg, env)._1; - case x@Expr.Block(n) => convert(x, reg, env)._1; - case x@Expr.CallObjFunc(obj, func) => convert(x, reg, env)._1; + case Expr.Print(toPrint) => printInterp(toPrint, env); + case x@Expr.Block(n) => convert(x, env)._1; case Expr.ExtendBlock(n) => extendLines = extendLines.head +: n ::: extendLines.tail;"" case _ => throw new Exception(lines.head.toString + " should not be in block lines") } if(extendLines.tail.nonEmpty) { - defstring += convertBlock(extendLines.tail, defaultReg, newenv)._1 + defstring += convertBlock(extendLines.tail, newenv)._1 } - //defstring += freeMemory((newenv.toSet diff env.toSet).toMap) + (defstring, Type.Undefined()); } - def compareExpr(left: Expr, right: Expr, numeric: Boolean, reg: List[String], env: Env): String = { - val leftout = convert(left, reg, env); - val rightout = convert(right, reg.tail, env); + def compareExpr(left: Expr, right: Expr, numeric: Boolean, comp: String, env: Env): String = { + val leftout = convertLoc(left, env); + val rightout = convertLoc(right, env); (leftout._2, rightout._2) match { case (Type.Bool(), Type.Bool()) if !numeric => ; case (Type.Num(), Type.Num()) => ; @@ -297,8 +310,9 @@ object ToAssembly { case (Type.Character(), Type.Character()) => ; case (t1, t2) => throw new Exception(s"can not compare types of $t1 and $t2") } - leftout._1 + rightout._1 + s"cmp ${reg.head}, ${reg.tail.head}\n" + leftout._1 + rightout._1 + s"${varc.next()} = icmp $comp ${Type.toLLVM(leftout._2)} ${leftout._3}, ${rightout._3}\n" } + private def declareFunctions(input: Expr.TopLevel): Unit = { functions = input.functions.map(x=> FunctionInfo(x.name, x.argNames, x.retType)) } @@ -391,7 +405,6 @@ object ToAssembly { case x => x } - val functionCallReg = List( "rdi", "rsi", "rdx", "rcx", "r8", "r9") def fNameSignature(info: FunctionInfo): String = fNameSignature(info.name, info.args.map(x=>x.varType)) def fNameSignature(name: String, args: List[Type]):String = name + (if(args.isEmpty) "" else "_") + args.map(x=>shortS(x)).mkString @@ -399,12 +412,13 @@ object ToAssembly { input.map{ case (function, upperScope) => { val info = functions.find(x=>x.name == function.name && x.args==function.argNames).get; functionScope = info; - var ret = s"define i32 @${info.name}() {\n" - ret += convert(function.body, defaultReg, Map())._1 + var ret = s"define ${Type.toLLVM(info.retType)} @${info.name}() {\n" + ret += convert(function.body, Map())._1 ret += "\n}" ret }}.mkString } + /* def shiftEnvLocations(env: Env): Env = { env.map(x=> (x._1, Variable(x._2.pointer - 256 - 16 , x._2.varType) @@ -490,7 +504,9 @@ object ToAssembly { case None => {println(functions.find(x=>x.name == name).map(x=>x.args).mkString);throw new Exception(s"no overload of function $name matches argument list $argInputTypes")}; } } + */ //Maybe remove type assingmed after fact(causes issues when type is unknown in compile time) + /* def setval(name: String, raxType: Type, env: Env): (String, Env) = { val look = lookup(name, env); var newenv = env; @@ -503,21 +519,8 @@ object ToAssembly { (s"mov qword ${look._1}, rax\n", newenv) } - //TODO add runtime index checking - /* - def setArray(name: String, index: Expr, raxType: Type, env: Env): (String, Env) = (lookup(name, env), convert(index, defaultReg, env)) match { - case ((varLoc, Variable(loc, Type.Array(elemType))), (indexCode, indexType)) => { - var newenv = env; - if(elemType != raxType) { - if(elemType == Type.Undefined()) newenv = env.map(x=>if(x._1==name) (x._1, Variable(x._2.pointer, Type.Array(raxType))) else x) - else throw new Exception(s"trying to set array element of type ${elemType} to $raxType") - } - if(indexType != Type.Num()) throw new Exception(s"wrong index for array, got $indexType") - ("push rax\n" + indexCode + s"mov rdi, ${varLoc}\n" + "pop rsi\n" + s"mov [rdi+8+rax*8], rsi\n", newenv) - } - } - */ + /* def setArray(arr: Expr, index: Expr, raxType: Type, env: Env): String = (convert(arr, defaultReg, env), convert(index, defaultReg, env)) match { case ((arrCode, Type.Array(elemType)), (indexCode, indexType)) => { if(elemType != raxType) throw new Exception(s"trying to set array element of type ${elemType} to $raxType") @@ -591,10 +594,10 @@ object ToAssembly { s"mov ${reg.head}, $label\n" } */ - + */ def lookup(tofind: String, env: Env): (String, Variable) = { val ret = lookupOffset(tofind, env) - (s"[rbp-${ret.pointer}]", ret) + (s"${varc.next()} = load ${Type.toLLVM(ret.varType)}, ${Type.toLLVM(ret.varType)}* %$tofind\n", ret) } def lookupOffset(tofind: String, env: Env): Variable = env.get(tofind) match { case Some(v) => v @@ -602,10 +605,10 @@ object ToAssembly { } def newVar(name: String, varType: Type, env: Env) : Env = { if(env.contains(name)) throw new Exception(s"variable \"${name}\" already defined") - val newOffset: Int = if(env.isEmpty) 0 else env.values.toList.map(x=>x.pointer).max - env + (name -> Variable(newOffset + 8, varType)) + env + (name -> Variable(varType)) } + /* def floatTemplate (codeLeft: String, codeRight: String, command: String, reg: List[String]): (String, Type) = { val ret = codeLeft + codeRight + s"movq xmm0, ${reg.head}\n" + s"movq xmm1, ${reg.tail.head}\n" + s"${command} xmm0, xmm1\n" + s"movq ${reg.head}, xmm0\n" @@ -624,63 +627,78 @@ object ToAssembly { leftout + rightout + s"${command} ${reg.tail.head}\n"; } */ - def intBinOpTemplate(codeLeft: String, codeRight: String, command: String, reg: List[String]): (String, Type) = { - (codeLeft + codeRight + s"${command} ${reg.head}, ${reg.tail.head}\n", Type.Num()); + */ + def convertLoc(input: Expr, env: Env): (String, Type, String) = { + val ret = convert(input, env) + (ret._1, ret._2, varc.last()) + } + def intBinOpTemplate(codeLeft: String, vLeft: String, codeRight: String, vRight: String, command: String): (String, Type) = { + (codeLeft + codeRight + s"${varc.next()} = $command i32 $vLeft, $vRight\n", Type.Num()); } - def intmulTemplate(codeLeft: String, codeRight: String, command: String, reg: List[String]): (String, Type) = { + def floatBinOpTemplate(codeLeft: String, vLeft: String, codeRight: String, vRight: String, command: String): (String, Type) = { + (codeLeft + codeRight + s"${varc.next()} = f$command double $vLeft, $vRight\n", Type.Num()); + } + /* + def intmulTemplate(codeLeft: String, codeRight: String, command: String): (String, Type) = { (codeLeft + codeRight + s"${command} ${reg.tail.head}\n", Type.Num()); } - def aritTemplate(left: Expr, right: Expr, commandInt: String, commandFloat: String, reg: List[String], env: Env): (String, Type) = { - (convert(left, reg, env), convert(right, reg.tail, env)) match { - case ((codeLeft, Type.Num()), (codeRight, Type.Num())) => - if(commandInt == "add" || commandInt == "sub") intBinOpTemplate(codeLeft, codeRight, commandInt, reg) - else intmulTemplate(codeLeft, codeRight, commandInt, reg) - case ((codeLeft, Type.NumFloat()), (codeRight, Type.NumFloat())) => floatTemplate(codeLeft, codeRight, commandFloat, reg) - case ((codeLeft, Type.Num()), (codeRight, Type.NumFloat())) => floatTemplate(codeLeft + convertToFloat(reg.head), codeRight, commandFloat, reg) - case ((codeLeft, Type.NumFloat()), (codeRight, Type.Num())) => floatTemplate(codeLeft, codeRight + convertToFloat(reg.tail.head), commandFloat, reg) - case ((codeLeft, typeLeft), (codeRight, typeRight)) => throw new Exception(s"can't perform arithmetic on operands of types ${typeLeft} and ${typeRight}"); + */ + def aritTemplate(left: Expr, right: Expr, command: String, env: Env): (String, Type) = { + (convertLoc(left, env), convertLoc(right, env)) match { + case ((codeLeft, Type.Num(), vLeft), (codeRight, Type.Num(), vRight)) => + intBinOpTemplate(codeLeft, vLeft, codeRight, vRight, command) + case ((codeLeft, Type.Num(), vLeft), (codeRight, Type.Num(), vRight)) => + if(command == "sdiv") intBinOpTemplate(codeLeft, vLeft, codeRight, vRight, "fdiv") + else intBinOpTemplate(codeLeft, vLeft, codeRight, vRight, command) + //case ((codeLeft, Type.Num()), (codeRight, Type.NumFloat())) => floatTemplate(codeLeft + convertToFloat(reg.head), codeRight, commandFloat, reg) + //case ((codeLeft, Type.NumFloat()), (codeRight, Type.Num())) => floatTemplate(codeLeft, codeRight + convertToFloat(reg.tail.head), commandFloat, reg) + case ((codeLeft, typeLeft, x), (codeRight, typeRight, y)) => throw new Exception(s"can't perform arithmetic on operands of types ${typeLeft} and ${typeRight}"); } } + /* def convertToFloat(reg: String): String = { s"cvtsi2sd xmm0, ${reg}\n" + s"movq ${reg}, xmm0\n" } def convertToInt(reg: String): String = { s"movq xmm0, ${reg}\n" + s"cvtsd2si ${reg}, xmm0\n" } + */ + def printTemplate(format: String): String = { - "mov rdi, " + format + - """ - |mov rsi, rax - |xor rax, rax - |call printf - |""".stripMargin + s"%call.${varc.extra()} = call i32 (i8*, ...) @printf(i8* getelementptr inbounds" + + s" ([2 x i8], [2 x i8]* @$format, i32 0, i32 0), i32 ${varc.last()})\n" } def printInterp(toPrint: Expr, env: Env): String = { - val converted = convert(toPrint, defaultReg, env) + val converted = convert(toPrint, env) ifCounter+=1 converted._2 match { case Type.Num() => converted._1 + printTemplate("format_num"); + /* case Type.NumFloat() => converted._1 + "movq xmm0, rax\n" + "mov rdi, format_float\n" + "mov rax, 1\n" + "call printf\n" //case (Type.Str) => converted._1 + printTemplate("format_string"); case Type.Bool() => converted._1 + s"cmp rax, 0\nje bool_${ifCounter}\n" + printTemplate("format_true") + s"jmp boole_${ifCounter}\nbool_${ifCounter}:\n" + printTemplate("format_false") + s"boole_${ifCounter}:\n"; case Type.Character() => converted._1 + printTemplate("format_char"); case Type.Array(Type.Character()) => converted._1 + "add rax, 8\n" + printTemplate("format_string"); + */ case _ => throw new Exception(s"input of type ${converted._2} not recognized in print") } } //TODO runtime memory garbage collection, currently only pointers on the stack are handled + /* def freeMemory(env: Env): String = env.foldLeft("")((acc, entry) => entry._2.varType match { case Type.Array(arrType) => acc + s"mov rdi, [rbp-${entry._2.pointer}]\n" + "call free\n"; //case Type.Interface(args) => acc + s"mov rdi, [rbp-${entry._2.pointer}]\n" + "call free\n"; case _ => acc }) + */ + type Env = Map[String, Variable] case class FunctionInfo(name: String, args: List[InputVar], retType: Type) //case class InterfaceInfo(name: String, args: List[InputVar]) case class InterfaceInfo(name: String, args: List[InputVar], funcs: List[FunctionInfo]) case class EnumInfo(name: String, el: List[String]) - case class Variable(pointer: Int, varType: Type) + case class Variable(varType: Type) } diff --git a/build-llvm.sh b/build-llvm.sh index 029086c..560e6cb 100644 --- a/build-llvm.sh +++ b/build-llvm.sh @@ -10,7 +10,8 @@ then exit 1 fi -files=$( cut -d '.' -f 1 <<< "$(ls | grep .ll)" ) +files=$( cut -d '.' -f 1 <<< "$(ls | grep "\.ll")" ) + files_asm=() for i in $files; From 09935e8327d4a3921946dd453d2533bf633b6aed Mon Sep 17 00:00:00 2001 From: pijuskri Date: Fri, 22 Jul 2022 01:23:53 +0300 Subject: [PATCH 051/112] ifs --- app/src/main/scala/posharp/ToAssembly.scala | 73 ++++++++++++++------- 1 file changed, 49 insertions(+), 24 deletions(-) diff --git a/app/src/main/scala/posharp/ToAssembly.scala b/app/src/main/scala/posharp/ToAssembly.scala index a1b9917..0d9a577 100644 --- a/app/src/main/scala/posharp/ToAssembly.scala +++ b/app/src/main/scala/posharp/ToAssembly.scala @@ -5,9 +5,10 @@ import posharp.Type.{UserType, shortS} class Counter { private var counter = 1; private var counterExtra = 0; + private var paused = false; def next(): String = { val cur = counter; - counter += 1; + if(!paused) counter += 1; s"%$cur"; } def extra(): Int = { @@ -18,6 +19,9 @@ class Counter { def reset(): Unit = { counter = 1; } + def pauseToggle(): Unit = { + paused = !paused; + } } object ToAssembly { @@ -41,10 +45,23 @@ object ToAssembly { lambdas = List() functionScope = FunctionInfo("main", List(), Type.Num()); + /* + converted += "format_num:\n db \"%d\", 10, 0\n" + converted += "format_float:\n db \"%f\", 10, 0\n" + converted += "format_string:\n db \"%s\", 10, 0\n" + converted += "format_char:\n db \"%c\", 10, 0\n" + converted += "format_true:\n db \"true\", 10, 0\n" + converted += "format_false:\n db \"false\", 10, 0\n" + */ var converted = """ | declare i32 @printf(i8*, ...) - | @format_num = private constant [2 x i8] c"%d" + | @format_num = private constant [3 x i8] c"%d\00" + | @format_float = private constant [3 x i8] c"%f\00" + | @format_string = private constant [3 x i8] c"%s\00" + | @format_char = private constant [3 x i8] c"%c\00" + | @format_false = private constant [7 x i8] c"false\0A\00" + | @format_true = private constant [7 x i8] c"true\0A\00\00" |""".stripMargin; println(input); input match { case x: Expr.TopLevel => { @@ -204,10 +221,10 @@ object ToAssembly { var extendLines = lines; var defstring: String = lines.head match { case Expr.SetVal(Expr.Ident(name), value) => { - //val look = lookup(name, env) + val look = lookupOffset(name, env) val converted = convertLoc(value, env); - //if(look._2.varType != converted._2) throw new Exception(s"mismatch when assigning value" + - // s" to variable $name, expected ${look._2.varType}, but got ${converted._2}") + if(look.varType != converted._2) throw new Exception(s"mismatch when assigning value" + + s" to variable $name, expected ${look.varType}, but got ${converted._2}") val set = s"store ${Type.toLLVM(converted._2)} ${converted._3}, ${Type.toLLVM(converted._2)}* %$name\n" converted._1 + set; }; @@ -215,6 +232,21 @@ object ToAssembly { newenv = newVar(name, varType, newenv); s"%$name = alloca ${Type.toLLVM(varType)}\n" } + case Expr.If(condition, ifTrue, ifFalse) => { + def compare(left: Expr, right: Expr, numeric: Boolean): String = compareExpr(left, right, numeric, "eq", env) + val trueLabel = s"if_${ifCounter}_true" + val falseLabel = s"if_${ifCounter}_false" + val endLabel = s"if_${ifCounter}_end" + ifCounter += 1; + val cond = convertType(condition, env) match { + case Type.Bool() => compare(condition, Expr.True(), false) + case t => throw new Exception(s"got type $t inside condition, expected bool") + } + val ret = cond + s"br i1 ${varc.last()}, label %$trueLabel, label %$falseLabel\n" + + s"${trueLabel}:\n" + convert(ifTrue, env)._1 + s"br label %$endLabel\n" + s"${falseLabel}:\n" + + convert(ifFalse, env)._1 + s"br label %$endLabel\n" + s"$endLabel:\n" + ret + } /* case Expr.SetArray(expr, index, value) => { val converted = convert(value, reg, env); @@ -233,21 +265,7 @@ object ToAssembly { case (x, valType) => throw new Exception(s"expected a interface, got ${valType}") } - case Expr.If(condition, ifTrue, ifFalse) => { - def compare(left: Expr, right: Expr, numeric: Boolean): String = compareExpr(left, right, numeric, reg, env) - val trueLabel = s"if_${ifCounter}_true" - val falseLabel = s"if_${ifCounter}_false" - val endLabel = s"if_${ifCounter}_end" - ifCounter += 1; - val cond = convert(condition, reg, env) match { - case (code, Type.Bool()) => compare(condition, Expr.True(), false) + s"jne ${falseLabel}\n" - case (_, t) => throw new Exception(s"got type $t inside condition, expected bool") - } - //convertCondition(condition, reg, env, orMode = false, trueLabel, falseLabel) - val ret = cond + s"${trueLabel}:\n" + convert(ifTrue, reg, env)._1 + - s"jmp ${endLabel}\n" + s"${falseLabel}:\n" + convert(ifFalse, reg, env)._1 + s"${endLabel}:\n" - ret - } + case Expr.While(condition, execute) => { def compare(left: Expr, right: Expr, numeric: Boolean): String = compareExpr(left, right, numeric, reg, env) val startLabel = s"while_${ifCounter}_start" @@ -632,6 +650,12 @@ object ToAssembly { val ret = convert(input, env) (ret._1, ret._2, varc.last()) } + def convertType(input: Expr, env: Env): Type = { + varc.pauseToggle() + val ret = convert(input, env)._2 + varc.pauseToggle() + ret + } def intBinOpTemplate(codeLeft: String, vLeft: String, codeRight: String, vRight: String, command: String): (String, Type) = { (codeLeft + codeRight + s"${varc.next()} = $command i32 $vLeft, $vRight\n", Type.Num()); } @@ -664,17 +688,18 @@ object ToAssembly { } */ - def printTemplate(format: String): String = { + def printTemplate(format: String, ty: String): String = { s"%call.${varc.extra()} = call i32 (i8*, ...) @printf(i8* getelementptr inbounds" + - s" ([2 x i8], [2 x i8]* @$format, i32 0, i32 0), i32 ${varc.last()})\n" + s" ([3 x i8], [3 x i8]* @$format, i32 0, i32 0), $ty ${varc.last()})\n" } def printInterp(toPrint: Expr, env: Env): String = { val converted = convert(toPrint, env) ifCounter+=1 converted._2 match { - case Type.Num() => converted._1 + printTemplate("format_num"); + case Type.Num() => converted._1 + printTemplate("format_num", "i32"); + case Type.Bool() => converted._1 + printTemplate("format_num", "i1"); + case Type.NumFloat() => converted._1 + printTemplate("format_float", "double"); /* - case Type.NumFloat() => converted._1 + "movq xmm0, rax\n" + "mov rdi, format_float\n" + "mov rax, 1\n" + "call printf\n" //case (Type.Str) => converted._1 + printTemplate("format_string"); case Type.Bool() => converted._1 + s"cmp rax, 0\nje bool_${ifCounter}\n" + printTemplate("format_true") + s"jmp boole_${ifCounter}\nbool_${ifCounter}:\n" + printTemplate("format_false") + s"boole_${ifCounter}:\n"; From ab068657662bdd3dbafcbf74f7fc892febce4733 Mon Sep 17 00:00:00 2001 From: pijuskri Date: Sat, 23 Jul 2022 02:01:43 +0300 Subject: [PATCH 052/112] arrays, and, or, while --- app/src/main/scala/posharp/Definitions.scala | 3 +- app/src/main/scala/posharp/ToAssembly.scala | 318 +++++++++++-------- 2 files changed, 182 insertions(+), 139 deletions(-) diff --git a/app/src/main/scala/posharp/Definitions.scala b/app/src/main/scala/posharp/Definitions.scala index 5f57ea6..8fdd564 100644 --- a/app/src/main/scala/posharp/Definitions.scala +++ b/app/src/main/scala/posharp/Definitions.scala @@ -122,8 +122,9 @@ object Type { case NumFloat() => "double"; case Character() => "i8" case Bool() => "i1" - //case Array(inner) => "[40 x i32]" + case Array(inner) => s"%Type.array.${toLLVM(inner)}*" case Undefined() => "void" + case _ => throw new Exception(s"$valType unrecognised"); } } case class InputVar(name: String, varType: Type) diff --git a/app/src/main/scala/posharp/ToAssembly.scala b/app/src/main/scala/posharp/ToAssembly.scala index 0d9a577..67b5087 100644 --- a/app/src/main/scala/posharp/ToAssembly.scala +++ b/app/src/main/scala/posharp/ToAssembly.scala @@ -16,6 +16,7 @@ class Counter { counterExtra - 1; } def last(): String = s"%${counter-1}"; + def secondLast(): String = s"%${counter-2}"; def reset(): Unit = { counter = 1; } @@ -56,14 +57,17 @@ object ToAssembly { var converted = """ | declare i32 @printf(i8*, ...) + | declare i64* @calloc(i32, i32) | @format_num = private constant [3 x i8] c"%d\00" | @format_float = private constant [3 x i8] c"%f\00" | @format_string = private constant [3 x i8] c"%s\00" | @format_char = private constant [3 x i8] c"%c\00" | @format_false = private constant [7 x i8] c"false\0A\00" | @format_true = private constant [7 x i8] c"true\0A\00\00" + | %Type.array.double = type {i32, double*} + | %Type.array.i32 = type {i32, i32*} + | %Type.array.i8 = type {i32, i8*} |""".stripMargin; - println(input); input match { case x: Expr.TopLevel => { declareFunctions(x); /* @@ -97,6 +101,7 @@ object ToAssembly { } val ret = input match { case Expr.Num(value) => (s"${varc.next()} = add i32 $value, 0\n", Type.Num()) + case Expr.NumFloat(value) => (s"${varc.next()} = fadd double $value, 0.0\n", Type.NumFloat()) case Expr.Plus(left, right) => aritTemplate(left, right, "add", env) case Expr.Minus(left, right) => aritTemplate(left, right, "sub", env) case Expr.Mult(left, right) => aritTemplate(left, right, "mul", env) @@ -111,9 +116,9 @@ object ToAssembly { } case Expr.True() => (s"${varc.next()} = and i1 1, 1\n", Type.Bool()) case Expr.False() => (s"${varc.next()} = and i1 0, 0\n", Type.Bool()) - case Expr.Equals(left, right) => (compareExpr(left, right, true, "eq", env), Type.Bool()) - case Expr.LessThan(left, right) => (compareExpr(left, right, true, "slt", env), Type.Bool()) - case Expr.MoreThan(left, right) => (compareExpr(left, right, true, "sgt", env), Type.Bool()) + case Expr.Equals(left, right) => (compareExpr(left, right, true, "eq", "oeq", env), Type.Bool()) + case Expr.LessThan(left, right) => (compareExpr(left, right, true, "slt", "olt", env), Type.Bool()) + case Expr.MoreThan(left, right) => (compareExpr(left, right, true, "sgt", "ogt", env), Type.Bool()) case Expr.Not(left) => { val converted = convt(left, Type.Bool()) val loc = varc.last() @@ -121,11 +126,53 @@ object ToAssembly { (ret, Type.Bool()) } - /* - case Expr.NumFloat(value) => { - (s"mov ${reg.head}, __float64__(${value.toString})\n", Type.NumFloat()) + case Expr.And(l) => { + var andLoc = varc.next(); + val ret = l.foldLeft(s"$andLoc = and i1 1, 1\n")((acc, v) => convertLoc(v, env) match { + case (code, Type.Bool(), loc) =>{ + val ret = acc + code + s"${varc.next()} = and i1 $andLoc, $loc\n" + andLoc = varc.last() + ret + } + case (_, t, _) => throw new Exception(s"expected bool in and, got $t") + }) + (ret, Type.Bool()) + } + case Expr.Or(l) => { + var andLoc = varc.next(); + val ret = l.foldLeft(s"$andLoc = or i1 0, 0\n")((acc, v) => convertLoc(v, env) match { + case (code, Type.Bool(), loc) =>{ + val ret = acc + code + s"${varc.next()} = or i1 $andLoc, $loc\n" + andLoc = varc.last() + ret + } + case (_, t, _) => throw new Exception(s"expected bool in and, got $t") + }) + (ret, Type.Bool()) } + case Expr.DefineArray(size, elemType, defaultValues) => conv(size) match { + case (code, Type.Num()) => defineArray(code, elemType, defaultValues, env) + case (_, x) => throw new Exception(s"not number when defining array size, got input of type $x") + } + case Expr.GetArray(name, index) => getArray(name, index, env); + case Expr.ArraySize(name) => getArraySize(name, env); + case Expr.GetProperty(obj, prop) => convertType(obj, env) match { + /* + case(code, Type.Interface(props, funcs)) => props.find(x=>x.name == prop) match { + case Some(n) => { + val ret = code + getArrayDirect(reg.head, props.indexOf(n), 8, reg) + (ret, n.varType) + } + case None => throw new Exception(s"interface ${interfaces.find(x=>x.args == props).get.name} does not have a property ${prop}") + } + //case (code, Type.StaticInterface(props, funcs)) => + case (code, Type.Enum(el)) => (s"mov ${reg.head}, 0${el.indexOf(prop)}d\n", Type.Num()) + */ + case Type.Array(a) if prop == "size" => conv(Expr.ArraySize(obj)) + case valType => throw new Exception(s"expected a interface, got ${valType}") + } + /* case Expr.Convert(value, valType: Type) => (convert(value, reg, env), valType) match { case ((code, Type.Num()), Type.NumFloat()) => (code + convertToFloat(reg.head), valType) case ((code, Type.NumFloat()), Type.Num()) => (code + convertToInt(reg.head), valType) @@ -134,10 +181,7 @@ object ToAssembly { } - case Expr.DefineArray(size, elemType, defaultValues) => conv(size) match { - case (code, Type.Num()) => defineArray(code, elemType, defaultValues, env) - case (code, x) => throw new Exception(s"not number when defining array size, got input of type $x") - } + case Expr.InstantiateInterface(name, values) => interfaces.find(x=>x.name == name) match { case Some(intf) => { val array_def = s"mov rdi, ${intf.args.length}\n" + s"mov rsi, 8\n" + "call calloc\n" + "push rax\n" @@ -151,25 +195,13 @@ object ToAssembly { } case None => throw new Exception(s"no such interface defined") } - case Expr.GetProperty(obj, prop) => conv(obj) match { - case(code, Type.Interface(props, funcs)) => props.find(x=>x.name == prop) match { - case Some(n) => { - val ret = code + getArrayDirect(reg.head, props.indexOf(n), 8, reg) - (ret, n.varType) - } - case None => throw new Exception(s"interface ${interfaces.find(x=>x.args == props).get.name} does not have a property ${prop}") - } - //case (code, Type.StaticInterface(props, funcs)) => - case (code, Type.Enum(el)) => (s"mov ${reg.head}, 0${el.indexOf(prop)}d\n", Type.Num()) - case (code, Type.Array(a)) if prop == "size" => conv(Expr.ArraySize(obj)) - case (x, valType) => throw new Exception(s"expected a interface, got ${valType}") - } + case Expr.CallObjFunc(obj, func) => conv(obj) match { case(code, t@Type.Interface(props, funcs)) => callObjFunction(obj, func, props, funcs, isStatic = false, reg, env) case(code, Type.StaticInterface(props, funcs)) => callObjFunction(obj, func, props, funcs, isStatic = true, reg, env) } - case Expr.GetArray(name, index) => getArray(name, index, reg, env); - case Expr.ArraySize(name) => getArraySize(name, reg, env); + + case Expr.CallF(name, args) => { if(functions.exists(x=>x.name == name)) interpFunction(name, args, reg, env) else if(env.contains(name)) callLambda(Expr.Ident(name), args, reg, env) @@ -185,29 +217,9 @@ object ToAssembly { lambdas = lambdas :+ (Expr.Func(label, args, ret, body), env) (s"mov ${reg.head}, $label\n", Type.Function(args.map(x=>x.varType), ret)) } - - case Expr.And(l) => { - val reg1 = sizeToReg(1, reg.head) - val reg2 = sizeToReg(1, reg.tail.head) - val ret = l.foldLeft(s"mov ${reg.head}, 1\n")((acc, v) => convert(v, reg.tail, env) match { - case (code, Type.Bool()) => acc + code + s"and ${reg1}, ${reg2}\n" //cmp ${reg1}, 1\nsete $reg1\n - case (_, t) => throw new Exception(s"expected bool in and, got $t") - }) - (ret, Type.Bool()) - } - case Expr.Or(l) => { - val reg1 = sizeToReg(1, reg.head) - val reg2 = sizeToReg(1, reg.tail.head) - val ret = l.foldLeft(s"mov ${reg.head}, 0\n")((acc, v) => convert(v, reg.tail, env) match { - case (code, Type.Bool()) => acc + code + s"or ${reg1}, ${reg2}\n" - case (_, t) => throw new Exception(s"expected bool in and, got $t") - }) - (ret, Type.Bool()) - } - + */ case Expr.Nothing() => ("", Type.Undefined()); case Expr.Compiled(code, retType) => (code, retType); - */ case Expr.Block(lines) => convertBlock(lines, env); case x => throw new Exception (s"$x is not interpreted yet :("); } @@ -233,7 +245,8 @@ object ToAssembly { s"%$name = alloca ${Type.toLLVM(varType)}\n" } case Expr.If(condition, ifTrue, ifFalse) => { - def compare(left: Expr, right: Expr, numeric: Boolean): String = compareExpr(left, right, numeric, "eq", env) + def compare(left: Expr, right: Expr, numeric: Boolean): String = + compareExpr(left, right, numeric, "eq", "oeq", env) val trueLabel = s"if_${ifCounter}_true" val falseLabel = s"if_${ifCounter}_false" val endLabel = s"if_${ifCounter}_end" @@ -247,12 +260,25 @@ object ToAssembly { convert(ifFalse, env)._1 + s"br label %$endLabel\n" + s"$endLabel:\n" ret } + case Expr.While(condition, execute) => { + def compare(left: Expr, right: Expr, numeric: Boolean): String = + compareExpr(left, right, numeric, "eq", "oeq", env) + val startLabel = s"while_${ifCounter}_start" + val trueLabel = s"while_${ifCounter}_true" + val endLabel = s"while_${ifCounter}_end" + val cond = convertType(condition, env) match { + case Type.Bool() => compare(condition, Expr.True(), false) + + s"br i1 ${varc.last()}, label %${trueLabel}, label %${endLabel}\n" + case t => throw new Exception(s"got type $t inside condition, expected bool") + } + val ret = s"br label %${startLabel}\n" + s"${startLabel}:\n" + cond + s"${trueLabel}:\n" + + convert(execute, env)._1 + s"br label %${startLabel}\n" + s"${endLabel}:\n" + ifCounter += 1; + ret + } + case Expr.SetArray(expr, index, value) => setArray(expr, index, value, env) /* - case Expr.SetArray(expr, index, value) => { - val converted = convert(value, reg, env); - val arr = setArray(expr, index, converted._2, env) - converted._1 + arr - }; + case Expr.SetInterfaceProp(intf, prop, valueRaw) => convert(intf, reg.tail, env) match { case(code, Type.Interface(props,f)) => props.find(x=>x.name == prop) match { case Some(n) => conv(valueRaw) match { @@ -264,22 +290,6 @@ object ToAssembly { } case (x, valType) => throw new Exception(s"expected a interface, got ${valType}") } - - - case Expr.While(condition, execute) => { - def compare(left: Expr, right: Expr, numeric: Boolean): String = compareExpr(left, right, numeric, reg, env) - val startLabel = s"while_${ifCounter}_start" - val trueLabel = s"while_${ifCounter}_true" - val endLabel = s"while_${ifCounter}_end" - val cond = convert(condition, reg, env) match { - case (code, Type.Bool()) => compare(condition, Expr.True(), false) + s"jne ${endLabel}\n" - case (_, t) => throw new Exception(s"got type $t inside condition, expected bool") - } - val ret = s"${startLabel}:\n" + cond + s"${trueLabel}:\n" + convert(execute, reg, env)._1 + - s"jmp ${startLabel}\n" + s"${endLabel}:\n" - ifCounter += 1; - ret - } case Expr.ThrowException(err) => { val msg = AnsiColor.RED + "RuntimeException: " + err + AnsiColor.RESET val name = s"exception_print_${stringLiterals.length}" @@ -318,17 +328,20 @@ object ToAssembly { (defstring, Type.Undefined()); } - def compareExpr(left: Expr, right: Expr, numeric: Boolean, comp: String, env: Env): String = { + def compareExpr(left: Expr, right: Expr, numeric: Boolean, comp: String, fcomp: String, env: Env): String = { val leftout = convertLoc(left, env); val rightout = convertLoc(right, env); + var isFloat = false; (leftout._2, rightout._2) match { case (Type.Bool(), Type.Bool()) if !numeric => ; case (Type.Num(), Type.Num()) => ; - case (Type.NumFloat(), Type.NumFloat()) => ; + case (Type.NumFloat(), Type.NumFloat()) => isFloat = true; case (Type.Character(), Type.Character()) => ; case (t1, t2) => throw new Exception(s"can not compare types of $t1 and $t2") } - leftout._1 + rightout._1 + s"${varc.next()} = icmp $comp ${Type.toLLVM(leftout._2)} ${leftout._3}, ${rightout._3}\n" + val cmp = if (isFloat) "fcmp" else "icmp" + val compKey = if (isFloat) fcomp else comp; + leftout._1 + rightout._1 + s"${varc.next()} = $cmp $compKey ${Type.toLLVM(leftout._2)} ${leftout._3}, ${rightout._3}\n" } private def declareFunctions(input: Expr.TopLevel): Unit = { @@ -539,28 +552,7 @@ object ToAssembly { } */ /* - def setArray(arr: Expr, index: Expr, raxType: Type, env: Env): String = (convert(arr, defaultReg, env), convert(index, defaultReg, env)) match { - case ((arrCode, Type.Array(elemType)), (indexCode, indexType)) => { - if(elemType != raxType) throw new Exception(s"trying to set array element of type ${elemType} to $raxType") - if(indexType != Type.Num()) throw new Exception(s"wrong index for array, got $indexType") - "push rax\n" + arrCode + "push rax\n" + indexCode + "pop rdi\n" + "pop rsi\n" + s"mov [rdi+8+rax*8], rsi\n" - } - } - def getArray(arr: Expr, index: Expr, reg: List[String], env: Env): (String, Type) = (convert(arr, reg, env), convert(index, reg, env)) match { - case ((code, Type.Array( arrType)), (indexCode, indexType)) => { - if(indexType != Type.Num()) throw new Exception(s"wrong index for array, got $indexType") - val size = arraySizeFromType(arrType); - (code + s"push ${reg.head}\n" + indexCode + s"mov ${reg.tail.head}, ${reg.head}\n" + - s"pop ${reg.head}\n" + s"mov ${sizeToReg(size, reg.head)}, [${reg.head}+8+${reg.tail.head}*$size]\n", arrType) - } - case ((code, varType), l) => throw new Exception(s"trying to access variable ${arr} as an array, has type $varType") - } - def getArraySize(arr: Expr, reg: List[String], env: Env): (String, Type) = convert(arr, reg, env) match { - case (code, Type.Array(arrType)) => { - (code + getArrayDirect(reg.head, 0, 8, reg), Type.Num()) - } - case (code, varType) => throw new Exception(s"trying to access variable ${arr} as an array, has type $varType") - } + //TODO not safe when default values use rdi def setArrayDirect(code: String, index: Int, size: Int): String = { s"mov rdi, ${code}\n" + s"mov [rdi+${index*size}], ${sizeToReg(size, "rax")}\n" @@ -587,21 +579,7 @@ object ToAssembly { defineArray(s"mov rax, 0${size}d\n", setElemType, defaultValues, env) } //TODO use available registers or save, not rax - def defineArray(size: String, setElemType:Type, defaultValues: List[Expr], env: Env): (String, Type) = { - var elemType: Type = setElemType; - var ret = defaultValues.zipWithIndex.map{case (entry, index) => { - val converted = convert(entry, defaultReg, env) - if(elemType == Type.Undefined()) elemType = converted._2; - else if(converted._2 != elemType) throw new Exception(s"array elements are of different types") - converted._1 + setArrayDirect("[rsp]", skipArrSize(index, arraySizeFromType(elemType)), arraySizeFromType(elemType)); - }}.mkString; - val array_elem_size = arraySizeFromType(elemType); - val array_def = size + "push rax\n" + "add rax, 1\n" + s"mov rdi, rax\n" + s"mov rsi, ${array_elem_size}\n" + "call calloc\n" + "push rax\n" - ret = array_def + ret; - ret += "pop r9\npop rax\n" + setArrayDirect("[rsp-16]", 0, 8) - ret += "mov rax, r9\n" - (ret, Type.Array(elemType)) - } + def skipArrSize(index: Int, size: Int): Int = index + (8/size) /* def defineString(value: String, reg: List[String]): String = { @@ -613,6 +591,94 @@ object ToAssembly { } */ */ + def getArrayPointerIndex(arrType: Type, arrLoc: String, indexLoc: String): String = { + val Tsig = Type.toLLVM(arrType) + val arrTC = s"%Type.array.$Tsig" + + var ret = ""; + ret += s"${varc.next()} = getelementptr $arrTC, $arrTC* $arrLoc, i32 0, i32 1\n" + val arrStructLoc = varc.last() + ret += s"${varc.next()} = getelementptr inbounds $Tsig*, $Tsig** ${arrStructLoc}, i32 $indexLoc\n" + val arrElemLoc = varc.last() + ret += s"${varc.next()} = load $Tsig*, $Tsig** ${arrElemLoc}\n" + ret + } + def getArray(arr: Expr, index: Expr, env: Env): (String, Type) = (convertLoc(arr, env), convertLoc(index, env)) match { + case ((code, Type.Array(arrType), arrLoc), (indexCode, indexType, indexLoc)) => { + if(indexType != Type.Num()) throw new Exception(s"wrong index for array, got $indexType"); + val Tsig = Type.toLLVM(arrType) + + var ret = code + indexCode; + ret += getArrayPointerIndex(arrType, arrLoc, indexLoc) + ret += s"${varc.next()} = load $Tsig, $Tsig* ${varc.secondLast()}\n" + (ret, arrType) + } + case ((_, varType, _), _) => throw new Exception(s"trying to access variable ${arr} as an array, has type $varType") + } + def setArray(arr: Expr, index: Expr, newVal: Expr, env: Env): String = + (convertLoc(arr, env), convertLoc(index, env), convertLoc(newVal, env)) match { + case ((code, Type.Array(arrType), arrLoc), (indexCode, indexType, indexLoc), (valCode, valType, valLoc)) => { + if(arrType != valType) throw new Exception(s"trying to set array element of type ${arrType} to $valType"); + if(indexType != Type.Num()) throw new Exception(s"wrong index for array, got $indexType"); + val Tsig = Type.toLLVM(arrType) + + var ret = code + indexCode + valCode; + ret += getArrayPointerIndex(arrType, arrLoc, indexLoc) + ret += s"store $Tsig $valLoc, $Tsig* ${varc.last()}\n" + ret + } + } + def getArraySize(arr: Expr, env: Env): (String, Type) = convertLoc(arr, env) match { + case (code, Type.Array(arrType), loc) => { + val Tsig = Type.toLLVM(arrType) + val arrTC = s"%Type.array.$Tsig" + + val ret = code + + s"${varc.next()} = getelementptr $arrTC, $arrTC* $loc, i32 0, i32 0\n" + + s"${varc.next()} = load $Tsig, $Tsig* ${varc.secondLast()}\n" + (ret, Type.Num()) + } + case (_, varType, _) => throw new Exception(s"trying to access variable ${arr} as an array, has type $varType") + } + + def defineArray(size: String, setElemType:Type, defaultValues: List[Expr], env: Env): (String, Type) = { + var elemType: Type = setElemType; + val array_elem_size = arraySizeFromType(elemType); + val Tsig = Type.toLLVM(elemType) + val arrTC = s"%Type.array.$Tsig" + var ret = size; + val sizeLoc = varc.last() + ret += s"${varc.next()} = call i64* (i32, i32) @calloc(i32 $sizeLoc, i32 $array_elem_size)\n"; + ret += s"${varc.next()} = bitcast i64* ${varc.secondLast()} to $Tsig*" + val arrLoc = varc.last() + val sizeStructPointer = s"%arr.size.${varc.extra()}" + val arrStructPointer = s"%arr.arr.${varc.extra()}" + ret += s"${varc.next()} = alloca $arrTC\n" + val structLoc = varc.last() + + ret += s"${sizeStructPointer} = getelementptr $arrTC, $arrTC* $structLoc, i32 0, i32 0\n" + ret += s"store i32 $sizeLoc, i32* ${sizeStructPointer}\n" + ret += s"${arrStructPointer} = getelementptr $arrTC, $arrTC* $structLoc, i32 0, i32 1\n" + ret += s"store $Tsig* $arrLoc, $Tsig** ${arrStructPointer}\n" + /* + var ret = defaultValues.zipWithIndex.map{case (entry, index) => { + val converted = convert(entry, defaultReg, env) + if(elemType == Type.Undefined()) elemType = converted._2; + else if(converted._2 != elemType) throw new Exception(s"array elements are of different types") + converted._1 + setArrayDirect("[rsp]", skipArrSize(index, arraySizeFromType(elemType)), arraySizeFromType(elemType)); + }}.mkString; + */ + (ret, Type.Array(elemType)) + } + def arraySizeFromType(valtype: Type): Int = valtype match { + case Type.Undefined() => 8 + case Type.Character() => 1 + case Type.Num() => 8 + case Type.NumFloat() => 8 + case Type.Function(_,_) => 8 + case Type.T1() => 8 + } + def lookup(tofind: String, env: Env): (String, Variable) = { val ret = lookupOffset(tofind, env) (s"${varc.next()} = load ${Type.toLLVM(ret.varType)}, ${Type.toLLVM(ret.varType)}* %$tofind\n", ret) @@ -626,26 +692,6 @@ object ToAssembly { env + (name -> Variable(varType)) } - /* - def floatTemplate (codeLeft: String, codeRight: String, command: String, reg: List[String]): (String, Type) = { - val ret = codeLeft + codeRight + s"movq xmm0, ${reg.head}\n" + s"movq xmm1, ${reg.tail.head}\n" + - s"${command} xmm0, xmm1\n" + s"movq ${reg.head}, xmm0\n" - (ret, Type.NumFloat()); - } - - def binOpTemplate(left: Expr, right: Expr, command: String, reg: List[String], env: Env): String = { - val leftout = convert(left, reg, env)._1; - val rightout = convert(right, reg.tail, env)._1; - leftout + rightout + s"${command} ${reg.head}, ${reg.tail.head}\n"; - } - /* - def mulTemplate(left: Expr, right: Expr, command: String, reg: List[String], env: Env): String = { - val leftout = convert(left, reg, env)._1; - val rightout = convert(right, reg.tail, env)._1; - leftout + rightout + s"${command} ${reg.tail.head}\n"; - } - */ - */ def convertLoc(input: Expr, env: Env): (String, Type, String) = { val ret = convert(input, env) (ret._1, ret._2, varc.last()) @@ -660,20 +706,16 @@ object ToAssembly { (codeLeft + codeRight + s"${varc.next()} = $command i32 $vLeft, $vRight\n", Type.Num()); } def floatBinOpTemplate(codeLeft: String, vLeft: String, codeRight: String, vRight: String, command: String): (String, Type) = { - (codeLeft + codeRight + s"${varc.next()} = f$command double $vLeft, $vRight\n", Type.Num()); - } - /* - def intmulTemplate(codeLeft: String, codeRight: String, command: String): (String, Type) = { - (codeLeft + codeRight + s"${command} ${reg.tail.head}\n", Type.Num()); + (codeLeft + codeRight + s"${varc.next()} = f$command double $vLeft, $vRight\n", Type.NumFloat()); } - */ + def aritTemplate(left: Expr, right: Expr, command: String, env: Env): (String, Type) = { (convertLoc(left, env), convertLoc(right, env)) match { case ((codeLeft, Type.Num(), vLeft), (codeRight, Type.Num(), vRight)) => intBinOpTemplate(codeLeft, vLeft, codeRight, vRight, command) - case ((codeLeft, Type.Num(), vLeft), (codeRight, Type.Num(), vRight)) => - if(command == "sdiv") intBinOpTemplate(codeLeft, vLeft, codeRight, vRight, "fdiv") - else intBinOpTemplate(codeLeft, vLeft, codeRight, vRight, command) + case ((codeLeft, Type.NumFloat(), vLeft), (codeRight, Type.NumFloat(), vRight)) => + if(command == "sdiv") floatBinOpTemplate(codeLeft, vLeft, codeRight, vRight, "div") + else floatBinOpTemplate(codeLeft, vLeft, codeRight, vRight, command) //case ((codeLeft, Type.Num()), (codeRight, Type.NumFloat())) => floatTemplate(codeLeft + convertToFloat(reg.head), codeRight, commandFloat, reg) //case ((codeLeft, Type.NumFloat()), (codeRight, Type.Num())) => floatTemplate(codeLeft, codeRight + convertToFloat(reg.tail.head), commandFloat, reg) case ((codeLeft, typeLeft, x), (codeRight, typeRight, y)) => throw new Exception(s"can't perform arithmetic on operands of types ${typeLeft} and ${typeRight}"); From b8dbd410d18df85681d27c21bc9064f53ec45e88 Mon Sep 17 00:00:00 2001 From: pijuskri Date: Sat, 23 Jul 2022 16:56:43 +0300 Subject: [PATCH 053/112] function calls --- app/src/main/scala/posharp/Main.scala | 10 -- app/src/main/scala/posharp/ToAssembly.scala | 166 ++++++-------------- 2 files changed, 52 insertions(+), 124 deletions(-) diff --git a/app/src/main/scala/posharp/Main.scala b/app/src/main/scala/posharp/Main.scala index 9c01295..a2b57cc 100644 --- a/app/src/main/scala/posharp/Main.scala +++ b/app/src/main/scala/posharp/Main.scala @@ -44,16 +44,6 @@ object Main extends App { println("") writeCompiled(asm, "compiled/", file) }) - /* - val toCompile = readFile("", inputFile) - val parsed = Parser.parseInput(toCompile); - //println(Util.prettyPrint(parsed)); - println("") - val asm = ToAssembly.convertMain(parsed); - //println(asm); - - writeToFile(asm, "compiled/", "hello.asm") - */ def writeCompiled(asm: String, directoryPath: String, file: String): Unit = { val flatFile = file.split("/").last + ".ll" diff --git a/app/src/main/scala/posharp/ToAssembly.scala b/app/src/main/scala/posharp/ToAssembly.scala index 67b5087..4fbf773 100644 --- a/app/src/main/scala/posharp/ToAssembly.scala +++ b/app/src/main/scala/posharp/ToAssembly.scala @@ -67,6 +67,8 @@ object ToAssembly { | %Type.array.double = type {i32, double*} | %Type.array.i32 = type {i32, i32*} | %Type.array.i8 = type {i32, i8*} + | %Type.array.i1 = type {i32, i1*} + | |""".stripMargin; input match { case x: Expr.TopLevel => { declareFunctions(x); @@ -172,6 +174,11 @@ object ToAssembly { case Type.Array(a) if prop == "size" => conv(Expr.ArraySize(obj)) case valType => throw new Exception(s"expected a interface, got ${valType}") } + case Expr.CallF(name, args) => { + if(functions.exists(x=>x.name == name)) interpFunction(name, args, env) + //else if(env.contains(name)) callLambda(Expr.Ident(name), args, reg, env) + else throw new Exception(s"unknown identifier $name") + } /* case Expr.Convert(value, valType: Type) => (convert(value, reg, env), valType) match { case ((code, Type.Num()), Type.NumFloat()) => (code + convertToFloat(reg.head), valType) @@ -179,9 +186,6 @@ object ToAssembly { case ((code, Type.Num()), Type.Character()) => (code, valType) case ((code, l), r) => throw new Exception(s"cant convert from type ${l} to type $r") } - - - case Expr.InstantiateInterface(name, values) => interfaces.find(x=>x.name == name) match { case Some(intf) => { val array_def = s"mov rdi, ${intf.args.length}\n" + s"mov rsi, 8\n" + "call calloc\n" + "push rax\n" @@ -202,11 +206,7 @@ object ToAssembly { } - case Expr.CallF(name, args) => { - if(functions.exists(x=>x.name == name)) interpFunction(name, args, reg, env) - else if(env.contains(name)) callLambda(Expr.Ident(name), args, reg, env) - else throw new Exception(s"unknow identifier $name") - } + //case Expr.Str(value) => (defineString(value, reg), Type.Str()) case Expr.Str(value) => (defineArrayKnown(value.length, Type.Character(), value.map(x=>Expr.Character(x)).toList, env)._1, Type.Array(Type.Character())) case Expr.Character(value) => (s"mov ${reg.head}, ${value.toInt}\n", Type.Character()) @@ -296,24 +296,25 @@ object ToAssembly { stringLiterals = stringLiterals :+ s"$name:\n db \"${msg}\", 10, 0\n" s"mov rax, $name\n" + printTemplate("format_string") + "jmp exception\n" } - case x@Expr.CallF(n, a) => convert(x, reg, env)._1; + case x@Expr.CallObjFunc(obj, func) => convert(x, reg, env)._1; */ + case x@Expr.CallF(n, a) => convert(x, env)._1; case Expr.Return(in) => { - "ret i32 0" - /* - val defInside = (env.keys.toSet diff functionScope.args.map(x=>x.name).toSet); - //TODO fix issue when 2 variables reference same location - val free = "" //freeMemory(env.filter(x=>defInside.contains(x._1))) in match { case Some(value) => { - val converted = convert(value, defaultReg, env) + val converted = convert(value, env) if (makeUserTypesConcrete(functionScope.retType) != converted._2) throw new Exception(s"Wrong return argument: function ${functionScope.name} expects ${functionScope.retType}, got ${converted._2}") - converted._1 + free + "leave\nret\n" + converted._1 + s"ret ${Type.toLLVM(converted._2)} ${varc.last()}\n" } - case None => free + "leave\nret\n"; - }*/ + case None => "ret void\n"; + } + /* + val defInside = (env.keys.toSet diff functionScope.args.map(x=>x.name).toSet); + //TODO fix issue when 2 variables reference same location + val free = "" //freeMemory(env.filter(x=>defInside.contains(x._1))) + */ } case Expr.Print(toPrint) => printInterp(toPrint, env); @@ -443,18 +444,42 @@ object ToAssembly { input.map{ case (function, upperScope) => { val info = functions.find(x=>x.name == function.name && x.args==function.argNames).get; functionScope = info; - var ret = s"define ${Type.toLLVM(info.retType)} @${info.name}() {\n" - ret += convert(function.body, Map())._1 - ret += "\n}" + val args = info.args.map(x=>s"${Type.toLLVM(x.varType)} %Input.${x.name}").mkString(", ") + var ret = s"define ${Type.toLLVM(info.retType)} @${info.name}($args) {\n" + val newEnv = upperScope ++ info.args.map(x=> (x.name, Variable(x.varType))).toMap + var body = info.args.map(x=> + s"%${x.name} = alloca ${Type.toLLVM(x.varType)}\n" + + s"store ${Type.toLLVM(x.varType)} %Input.${x.name}, ${Type.toLLVM(x.varType)}* %${x.name}\n").mkString + + body += convert(function.body, newEnv)._1 + varc.reset() + ret += body.split("\n").map(x=>"\t"+x).mkString("\n") + ret += "\n}\n" ret }}.mkString } - /* - def shiftEnvLocations(env: Env): Env = { - env.map(x=> (x._1, - Variable(x._2.pointer - 256 - 16 , x._2.varType) - )) + def interpFunction(name: String, args: List[Expr], env: Env ): (String, Type) = { + val argRet = args.map (arg => convertLoc(arg, env)) + val argInputTypes = argRet.map(x => x._2) + var ret = argRet.map(x=>x._1).mkString + val argsString = argRet.map(x=>s"${Type.toLLVM(x._2)} ${x._3}").mkString(", ") + + functions.find(x=>x.name == name) match { + case Some(x) => ; case None => throw new Exception(s"function of name $name undefined"); + } + functions.find(x=>x.name == name && Type.compare(argInputTypes, x.args.map(x=>makeUserTypesConcrete(x.varType)))) match { + case Some(FunctionInfo(p, argTypes, retType)) => { + val argTypeString = argTypes.map(x=>Type.toLLVM(x.varType)).mkString(", ") + ret += s"${varc.next()} = call ${Type.toLLVM(retType)} ($argTypeString) @$name($argsString)\n" + (ret, retType) + } + case None => { + println(functions.find(x=>x.name == name).map(x=>x.args).mkString); + throw new Exception(s"no overload of function $name matches argument list $argInputTypes") + }; + } } + /* def callLambda(input: Expr, args: List[Expr], reg: List[String], env: Env): (String, Type) = convert(input, reg, env) match { case (code, Type.Function(argTypes, retType)) => { val usedReg = defaultReg.filter(x => !reg.contains(x)); @@ -496,92 +521,6 @@ object ToAssembly { } } } - def interpFunction(name: String, args: List[Expr], reg: List[String], env: Env ): (String, Type) = { - val usedReg = defaultReg.filter(x => !reg.contains(x)); - var ret = usedReg.map(x=>s"push $x\n").mkString - val argRet = args.zipWithIndex.map{case (arg, index) => { - val converted = convert(arg, reg, env) - (converted._1 + s"push ${reg.head}\n", converted._2) - }} - val argInputTypes = argRet.map(x=>x._2) - ret += argRet.map(x=>x._1).mkString - ret += args.zipWithIndex.reverse.map{case (arg, index) => s"pop ${functionCallReg(index)}\n"}.mkString - - //if(converted._2 != argTypes(index)) throw new Exception (s"wrong argument type: expected ${argTypes(index)}, got ${converted._2}") - functions.find(x=>x.name == name) match { - case Some(x) => ; case None => throw new Exception(s"function of name $name undefined"); - } - functions.find(x=>x.name == name && Type.compare(argInputTypes, x.args.map(x=>makeUserTypesConcrete(x.varType)))) match { - case Some(FunctionInfo(p, argTypes, retType)) => { - //if(argTypes.length != args.length) throw new Exception (s"wrong number of arguments: expected ${argTypes.length}, got ${args.length}") - //TODO add errors for unexpected behaviour - def eqT(x: Type): Boolean = x == Type.T1() || x == Type.Array(Type.T1()) - val template1Type = argTypes.map(x=>x.varType).zipWithIndex.find(x=> eqT(x._1)) match { - case Some(n) => argInputTypes(n._2) - case None => Type.Undefined() - } - argTypes.foreach(x=> { - if (eqT(x.varType) && !Type.compare(x.varType, template1Type)) throw new Exception(s"Generic type inputs were different; ${x.varType} did not equal ${template1Type}"); - }) - val retTypeTemplated = retType match { - case Type.T1() | Type.Array(Type.T1()) if(template1Type != Type.Undefined()) => template1Type - case _ => retType - } - ret += s"call ${fNameSignature(name, argTypes.map(x=>x.varType))}\n" - ret += s"mov ${reg.head}, rax\n" - ret += usedReg.reverse.map(x=>s"pop $x\n").mkString - (ret, retTypeTemplated) - } - case None => {println(functions.find(x=>x.name == name).map(x=>x.args).mkString);throw new Exception(s"no overload of function $name matches argument list $argInputTypes")}; - } - } - */ - //Maybe remove type assingmed after fact(causes issues when type is unknown in compile time) - /* - def setval(name: String, raxType: Type, env: Env): (String, Env) = { - val look = lookup(name, env); - var newenv = env; - (look._2.varType, raxType) match { - case (Type.Undefined(), Type.Undefined()) => {} - case (Type.Undefined(), ass) => newenv = env.map(x=>if(x._1==name) (x._1, Variable(x._2.pointer, raxType)) else x) - case (x,y) if x == y => {} - case (x,y) => throw new Exception(s"trying to set variable of type ${look._2.varType} to $raxType") - } - - (s"mov qword ${look._1}, rax\n", newenv) - } - */ - /* - - //TODO not safe when default values use rdi - def setArrayDirect(code: String, index: Int, size: Int): String = { - s"mov rdi, ${code}\n" + s"mov [rdi+${index*size}], ${sizeToReg(size, "rax")}\n" - } - def getArrayDirect(code: String, index: Int, size: Int, reg: List[String]): String = { - s"mov ${reg.tail.head}, ${code}\n" + s"mov ${reg.head}, [${reg.tail.head}+${index*size}]\n" - } - //List("rax", "rdi", "rsi", "rdx", "rcx", "r8", "r9", "r10", "r11") - val fullToByteReg: Map[String, String] = Map(("rax", "al"), ("rdi", "dil"), ("rsi", "sil"), ("rdx","dl")) - def sizeToReg(size: Int, reg: String): String = size match { - case 8 => reg - case 1 => fullToByteReg.getOrElse(reg, reg) - } - def arraySizeFromType(valtype: Type): Int = valtype match { - case Type.Undefined() => 8 - case Type.Character() => 1 - case Type.Num() => 8 - case Type.NumFloat() => 8 - case Type.Function(_,_) => 8 - case Type.T1() => 8 - } - - def defineArrayKnown(size: Int, setElemType:Type, defaultValues: List[Expr], env: Env): (String, Type) = { - defineArray(s"mov rax, 0${size}d\n", setElemType, defaultValues, env) - } - //TODO use available registers or save, not rax - - def skipArrSize(index: Int, size: Int): Int = index + (8/size) - /* def defineString(value: String, reg: List[String]): String = { //TODO Make definitions dynamic also //var ret = s"mov rdi, ${value.length+1}\n" + s"mov rsi, 8\n" + "call calloc\n" + "push rax\n" + "mov r9, rax\n"; @@ -590,7 +529,6 @@ object ToAssembly { s"mov ${reg.head}, $label\n" } */ - */ def getArrayPointerIndex(arrType: Type, arrLoc: String, indexLoc: String): String = { val Tsig = Type.toLLVM(arrType) val arrTC = s"%Type.array.$Tsig" @@ -731,7 +669,7 @@ object ToAssembly { */ def printTemplate(format: String, ty: String): String = { - s"%call.${varc.extra()} = call i32 (i8*, ...) @printf(i8* getelementptr inbounds" + + s"%ret.${varc.extra()} = call i32 (i8*, ...) @printf(i8* getelementptr inbounds" + s" ([3 x i8], [3 x i8]* @$format, i32 0, i32 0), $ty ${varc.last()})\n" } def printInterp(toPrint: Expr, env: Env): String = { From 7a0234987a2d0bbed0d13b55e4d52f77c63ee5fa Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Mon, 25 Jul 2022 19:47:06 +0300 Subject: [PATCH 054/112] Enabled config caching --- gradle.properties | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index e69ac06..14e9409 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,4 +2,5 @@ org.gradle.configureondemand=true org.gradle.parallel=true org.gradle.caching=true -org.gradle.daemon=true \ No newline at end of file +org.gradle.daemon=true +org.gradle.unsafe.configuration-cache=true From 84ab10520515752282630024423615e8ff03d377 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Mon, 25 Jul 2022 19:47:20 +0300 Subject: [PATCH 055/112] Moved dependencies to the beginning --- toast.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/toast.yml b/toast.yml index f45d72b..28f810a 100644 --- a/toast.yml +++ b/toast.yml @@ -2,14 +2,7 @@ image: gradle:7.4.2-jdk17 command_prefix: set -e # Make Bash fail fast. tasks: - build: - input_paths: - - . - command: gradle build - install_deps: - dependencies: - - build command: | apt-get update apt-get install make nasm gcc -y @@ -19,11 +12,18 @@ tasks: apt-get install micro -y fi + build: + dependencies: + - install_deps + input_paths: + - . + command: gradle build + test: environment: args: '' dependencies: - - install_deps + - build output_paths: - coverage.json command: gradle runCoverage $args \ No newline at end of file From 71fb93226e388d311622255ea3f67ec40475ab1b Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Mon, 25 Jul 2022 19:47:30 +0300 Subject: [PATCH 056/112] Fixed some warning --- veritas/src/main/scala/core/Veritas.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/veritas/src/main/scala/core/Veritas.scala b/veritas/src/main/scala/core/Veritas.scala index 379b4a8..5a364ef 100644 --- a/veritas/src/main/scala/core/Veritas.scala +++ b/veritas/src/main/scala/core/Veritas.scala @@ -9,6 +9,7 @@ import posharp.{Parser, ToAssembly} import java.io.File import java.lang.reflect.Method import java.util.concurrent.{Executors, TimeUnit} +import scala.collection.mutable import scala.io.AnsiColor._ import scala.reflect.internal.util.ScalaClassLoader import scala.sys.process.Process @@ -73,7 +74,7 @@ object Veritas { val pool = Executors.newFixedThreadPool(numOfThreads) var exitCode = 0 - val out = new StringBuilder + val out = new mutable.StringBuilder out.append('\n') // reflection stuff @@ -99,7 +100,7 @@ object Veritas { def runTest(instance: AnyRef, tests: Array[Method]): Unit = { // Put output here until all tests are done to avoid using synchronized - val chunkedOut = new StringBuilder + val chunkedOut = new mutable.StringBuilder // Catches invalid tests (say main is missing from the code snippet) tests.foreach(el => { From 689ee96a17403fbb34cf850fa5e86e63b2dcce6a Mon Sep 17 00:00:00 2001 From: pijuskri Date: Tue, 26 Jul 2022 19:34:38 +0300 Subject: [PATCH 057/112] done with objects --- app/src/main/scala/posharp/Definitions.scala | 15 +- app/src/main/scala/posharp/ToAssembly.scala | 152 +++++++++---------- docs/tasklist.md | 13 +- 3 files changed, 98 insertions(+), 82 deletions(-) diff --git a/app/src/main/scala/posharp/Definitions.scala b/app/src/main/scala/posharp/Definitions.scala index 8fdd564..130ea06 100644 --- a/app/src/main/scala/posharp/Definitions.scala +++ b/app/src/main/scala/posharp/Definitions.scala @@ -80,7 +80,7 @@ object Type { case class Array(elemType: Type) extends Type case class Bool() extends Type //case class Interface(properties: List[InputVar]) extends Type - case class Interface(properties: List[InputVar], functions: List[FunctionInfo]) extends Type + case class Interface(name: String, properties: List[InputVar], functions: List[FunctionInfo]) extends Type case class StaticInterface(properties: List[InputVar], functions: List[FunctionInfo]) extends Type case class Function(args: List[Type], retType: Type) extends Type case class T1() extends Type @@ -96,7 +96,7 @@ object Type { case Bool() => "b" case Array(inner) => "arr_"+shortS(inner)+"_" //case Interface(inner) => "itf_"+inner.map(x=>shortS(x.varType))+"_" - case Interface(inner, innerf) => "itf_"+inner.map(x=>shortS(x.varType)).mkString+"_"+innerf.map(x=>x.name)+"_" + case Interface(_, inner, innerf) => "itf_"+inner.map(x=>shortS(x.varType)).mkString+"_"+innerf.map(x=>x.name)+"_" case Function(args, retType) => "func_"+args.map(x=>shortS(x)).mkString+"_"+shortS(retType) case UserType(name) => name case T1() => "T1" @@ -124,7 +124,16 @@ object Type { case Bool() => "i1" case Array(inner) => s"%Type.array.${toLLVM(inner)}*" case Undefined() => "void" - case _ => throw new Exception(s"$valType unrecognised"); + //should be avoided, as usertype could be not a class + case UserType(name) => s"%Class.$name*" + case Interface(name, _, _) => s"%Class.$name*" + /* + case Interface(vars, funcs) => { + val argS = vars.map(x=>toLLVM(x.varType)).mkString(", ") + s"{ $argS }" + } + */ + case _ => throw new Exception(s"$valType unrecognised for LLVM"); } } case class InputVar(name: String, varType: Type) diff --git a/app/src/main/scala/posharp/ToAssembly.scala b/app/src/main/scala/posharp/ToAssembly.scala index 4fbf773..d2d1dcf 100644 --- a/app/src/main/scala/posharp/ToAssembly.scala +++ b/app/src/main/scala/posharp/ToAssembly.scala @@ -72,25 +72,22 @@ object ToAssembly { |""".stripMargin; input match { case x: Expr.TopLevel => { declareFunctions(x); + converted += declareInterfaces(x) + "\n"; /* - declareInterfaces(x); declareEnums(x) converted += exportDeclarations(currentFile) converted += handleImports(x, otherFiles) */ converted += defineFunctions(x.functions.map(y=>(y, Map())), false); - /* converted += defineFunctions(x.interfaces.flatMap(intf=> intf.functions.map(func=>Expr.Func(intf.name + "_" + func.name, func.argNames, func.retType, func.body))) .map(y=>(y, Map())), false ); - */ }} //converted += defineFunctions(lambdas, true); //converted += "exception:\nmov rdi, 1\ncall exit\n" //converted += stringLiterals.mkString - //converted = converted.split("\n").map(x=>if(x.contains(":")) x+"\n" else " "+x+"\n").mkString converted } @@ -111,7 +108,7 @@ object ToAssembly { case Expr.Ident(name) => { enums.find(x => x.name == name).orElse(interfaces.find(x=>x.name == name)).orElse(Some(lookup(name, env))) match { case Some(EnumInfo(_, el)) => ("", Type.Enum(el)) - case Some(InterfaceInfo(_, props, funcs)) => ("", Type.Interface(props, funcs)) + case Some(InterfaceInfo(itfName, props, funcs)) => ("", Type.Interface(itfName, props, funcs)) case Some((code: String, variable: Variable)) => (code, variable.varType) case _ => throw new Exception(s"unrecognised identifier $name") } @@ -159,19 +156,22 @@ object ToAssembly { case Expr.GetArray(name, index) => getArray(name, index, env); case Expr.ArraySize(name) => getArraySize(name, env); - case Expr.GetProperty(obj, prop) => convertType(obj, env) match { - /* - case(code, Type.Interface(props, funcs)) => props.find(x=>x.name == prop) match { + case Expr.GetProperty(obj, prop) => convert(obj, env) match { + case(code, Type.Interface(name, props, funcs)) => props.find(x=>x.name == prop) match { case Some(n) => { - val ret = code + getArrayDirect(reg.head, props.indexOf(n), 8, reg) + val intfDec = s"%Class.$name" + val idx = props.indexOf(n); + val ret = code + s"${varc.next()} = getelementptr $intfDec, $intfDec* ${varc.secondLast()}, i32 0, i32 $idx\n" + + s"${varc.next()} = load ${Type.toLLVM(n.varType)}, ${Type.toLLVM(n.varType)}* ${varc.secondLast()}\n" (ret, n.varType) } case None => throw new Exception(s"interface ${interfaces.find(x=>x.args == props).get.name} does not have a property ${prop}") } + /* //case (code, Type.StaticInterface(props, funcs)) => case (code, Type.Enum(el)) => (s"mov ${reg.head}, 0${el.indexOf(prop)}d\n", Type.Num()) */ - case Type.Array(a) if prop == "size" => conv(Expr.ArraySize(obj)) + case (code, Type.Array(a)) if prop == "size" => getArraySize(Expr.Compiled(code, Type.Array(a)), env)//conv(Expr.ArraySize(obj)) case valType => throw new Exception(s"expected a interface, got ${valType}") } case Expr.CallF(name, args) => { @@ -179,34 +179,28 @@ object ToAssembly { //else if(env.contains(name)) callLambda(Expr.Ident(name), args, reg, env) else throw new Exception(s"unknown identifier $name") } - /* - case Expr.Convert(value, valType: Type) => (convert(value, reg, env), valType) match { - case ((code, Type.Num()), Type.NumFloat()) => (code + convertToFloat(reg.head), valType) - case ((code, Type.NumFloat()), Type.Num()) => (code + convertToInt(reg.head), valType) - case ((code, Type.Num()), Type.Character()) => (code, valType) - case ((code, l), r) => throw new Exception(s"cant convert from type ${l} to type $r") + case Expr.CallObjFunc(obj, func) => convertType(obj, env) match { + case Type.Interface(_, props, funcs) => callObjFunction(obj, func, props, funcs, isStatic = false, env) + case Type.StaticInterface(props, funcs) => callObjFunction(obj, func, props, funcs, isStatic = true, env) } case Expr.InstantiateInterface(name, values) => interfaces.find(x=>x.name == name) match { case Some(intf) => { - val array_def = s"mov rdi, ${intf.args.length}\n" + s"mov rsi, 8\n" + "call calloc\n" + "push rax\n" - //val newenv = newVar("self", UserType(name), env) - //val func_code = interpFunction(name+"_"+name, Expr.Ident("self") +: values, reg, newenv)._1 - //val ret = array_def + setval("self", UserType(name), newenv)._1 + func_code + "pop rax\n"; - - val func_code = interpFunction(name+"_"+name, Expr.Compiled(array_def, UserType(name)) +: values, reg, env)._1 - val ret = func_code + "pop rax\n"; - (ret, Type.Interface(intf.args, intf.funcs)) + val struct_def = s"${varc.next()} = alloca %Class.$name\n" + + val func_code = interpFunction(name+"_"+name, Expr.Compiled(struct_def, UserType(name)) +: values, env)._1 + val ret = func_code; + (ret, Type.Interface(name, intf.args, intf.funcs)) } case None => throw new Exception(s"no such interface defined") } - - case Expr.CallObjFunc(obj, func) => conv(obj) match { - case(code, t@Type.Interface(props, funcs)) => callObjFunction(obj, func, props, funcs, isStatic = false, reg, env) - case(code, Type.StaticInterface(props, funcs)) => callObjFunction(obj, func, props, funcs, isStatic = true, reg, env) + /* + case Expr.Convert(value, valType: Type) => (convert(value, reg, env), valType) match { + case ((code, Type.Num()), Type.NumFloat()) => (code + convertToFloat(reg.head), valType) + case ((code, Type.NumFloat()), Type.Num()) => (code + convertToInt(reg.head), valType) + case ((code, Type.Num()), Type.Character()) => (code, valType) + case ((code, l), r) => throw new Exception(s"cant convert from type ${l} to type $r") } - - //case Expr.Str(value) => (defineString(value, reg), Type.Str()) case Expr.Str(value) => (defineArrayKnown(value.length, Type.Character(), value.map(x=>Expr.Character(x)).toList, env)._1, Type.Array(Type.Character())) case Expr.Character(value) => (s"mov ${reg.head}, ${value.toInt}\n", Type.Character()) @@ -235,7 +229,7 @@ object ToAssembly { case Expr.SetVal(Expr.Ident(name), value) => { val look = lookupOffset(name, env) val converted = convertLoc(value, env); - if(look.varType != converted._2) throw new Exception(s"mismatch when assigning value" + + if(makeUserTypesConcrete(look.varType) != converted._2) throw new Exception(s"mismatch when assigning value" + s" to variable $name, expected ${look.varType}, but got ${converted._2}") val set = s"store ${Type.toLLVM(converted._2)} ${converted._3}, ${Type.toLLVM(converted._2)}* %$name\n" converted._1 + set; @@ -277,28 +271,32 @@ object ToAssembly { ret } case Expr.SetArray(expr, index, value) => setArray(expr, index, value, env) - /* - - case Expr.SetInterfaceProp(intf, prop, valueRaw) => convert(intf, reg.tail, env) match { - case(code, Type.Interface(props,f)) => props.find(x=>x.name == prop) match { - case Some(n) => conv(valueRaw) match { - case (valCode, valType) if(valType == n.varType) => - valCode + code + setArrayDirect(s"${reg.tail.head}", props.indexOf(n), 8) - case (_, valType) => throw new Exception(s"trying to property ${n.name} of type ${n.varType} to incompatible type ${valType}") + case Expr.SetInterfaceProp(intf, prop, valueRaw) => convertLoc(intf, env) match { + case(code, Type.Interface(name, props,f), intfLoc) => props.find(x=>x.name == prop) match { + case Some(n) => convertLoc(valueRaw, env) match { + case (valCode, valType, valueLoc) if(valType == n.varType) => { + val intfDec = s"%Class.$name" + val idx = props.indexOf(n); + var ret = code + valCode + ret += s"${varc.next()} = getelementptr $intfDec, $intfDec* $intfLoc, i32 0, i32 $idx\n" + ret += s"store ${Type.toLLVM(valType)} $valueLoc, ${Type.toLLVM(n.varType)}* ${varc.last()}\n" + ret + } + case (_, valType, _) => throw new Exception(s"trying to property ${n.name} of type ${n.varType} to incompatible type ${valType}") } case None => throw new Exception(s"interface ${interfaces.find(x=>x.args == props).get.name} does not have a property ${prop}") } - case (x, valType) => throw new Exception(s"expected a interface, got ${valType}") + case (_, valType, _) => throw new Exception(s"expected an interface, got ${valType}") } + /* case Expr.ThrowException(err) => { val msg = AnsiColor.RED + "RuntimeException: " + err + AnsiColor.RESET val name = s"exception_print_${stringLiterals.length}" stringLiterals = stringLiterals :+ s"$name:\n db \"${msg}\", 10, 0\n" s"mov rax, $name\n" + printTemplate("format_string") + "jmp exception\n" } - - case x@Expr.CallObjFunc(obj, func) => convert(x, reg, env)._1; */ + case x@Expr.CallObjFunc(obj, func) => convert(x, env)._1; case x@Expr.CallF(n, a) => convert(x, env)._1; case Expr.Return(in) => { in match { @@ -348,20 +346,13 @@ object ToAssembly { private def declareFunctions(input: Expr.TopLevel): Unit = { functions = input.functions.map(x=> FunctionInfo(x.name, x.argNames, x.retType)) } - private def declareInterfaces(input: Expr.TopLevel): Unit = { + private def declareInterfaces(input: Expr.TopLevel): String = { interfaces = input.interfaces.map(x=> InterfaceInfo(x.name, x.props, x.functions.map(x=>FunctionInfo(x.name,x.argNames,x.retType)))) - /* - interfaces = interfaces.map(x=> - InterfaceInfo( x.name, x.args.map(y=> - InputVar(y.name, traverseTypeTree(y.varType)) - ), x.funcs.map(y => - FunctionInfo(y.name, - y.args.map(z=>InputVar(z.name, traverseTypeTree(z.varType))), - traverseTypeTree(y.retType)) - )) - ) - */ functions = functions ::: interfaces.flatMap(x=>addPrefixToFunctions(x.name,x.funcs)) + interfaces.map(intf => { + val types = intf.args.map(x=>Type.toLLVM(x.varType)).mkString(", ") + s"%Class.${intf.name} = type {$types}\n" + }).mkString } private def addPrefixToFunctions(prefix: String, funcs: List[FunctionInfo]): List[FunctionInfo] = funcs.map(y=>FunctionInfo(prefix+"_"+y.name, y.args, y.retType)) private def declareEnums(input: Expr.TopLevel): Unit = { @@ -431,7 +422,7 @@ object ToAssembly { def makeUserTypesConcrete(input: Type): Type = input match { case UserType(name) => interfaces.find(x=>x.name == name) match { - case Some(n) => Type.Interface(n.args, n.funcs) + case Some(n) => Type.Interface(name, n.args, n.funcs) case _ => throw new Exception (s"no interface of name $name"); } case x => x @@ -452,7 +443,11 @@ object ToAssembly { s"store ${Type.toLLVM(x.varType)} %Input.${x.name}, ${Type.toLLVM(x.varType)}* %${x.name}\n").mkString body += convert(function.body, newEnv)._1 + + if(info.retType == Type.Undefined()) body += "ret void\n" + if(info.name == "main") body += "ret i32 0\n" varc.reset() + ret += body.split("\n").map(x=>"\t"+x).mkString("\n") ret += "\n}\n" ret @@ -470,7 +465,8 @@ object ToAssembly { functions.find(x=>x.name == name && Type.compare(argInputTypes, x.args.map(x=>makeUserTypesConcrete(x.varType)))) match { case Some(FunctionInfo(p, argTypes, retType)) => { val argTypeString = argTypes.map(x=>Type.toLLVM(x.varType)).mkString(", ") - ret += s"${varc.next()} = call ${Type.toLLVM(retType)} ($argTypeString) @$name($argsString)\n" + if (retType != Type.Undefined()) ret += s"${varc.next()} = "; + ret += s"call ${Type.toLLVM(retType)} ($argTypeString) @$name($argsString)\n" (ret, retType) } case None => { @@ -479,6 +475,28 @@ object ToAssembly { }; } } + + def callObjFunction(obj: Expr, func: Expr.CallF, props: List[InputVar], funcs: List[FunctionInfo], isStatic: Boolean, env: Env): (String, Type) = { + funcs.find(x=>x.name == func.name) match { + case Some(n) => { + var args = func.args + //TODO fails if interface has same attributes/functions but different name + val intfName = interfaces.find(x=>x.args == props && x.funcs == funcs).get.name + if(n.args.nonEmpty && n.args.head.name == "self") { + if(isStatic) throw new Exception(s"can not call method $func staticly") + else args = obj +: args + } + interpFunction(intfName+"_"+func.name, args, env) + } + case None => props.find(x=>x.name == func.name) match { + case Some(InputVar(_, Type.Function(_,_))) => { + //callLambda(Expr.GetProperty(obj, func.name), func.args, reg, env) + ("",Type.Undefined()) + } + case None => throw new Exception(s"object has no property or function named $func") + } + } + } /* def callLambda(input: Expr, args: List[Expr], reg: List[String], env: Env): (String, Type) = convert(input, reg, env) match { case (code, Type.Function(argTypes, retType)) => { @@ -500,27 +518,7 @@ object ToAssembly { } case (_, x) => throw new Exception(s"Can not call variable of type $x"); } - def callObjFunction(obj: Expr, func: Expr.CallF, props: List[InputVar], funcs: List[FunctionInfo], isStatic: Boolean, reg: List[String], env: Env): (String, Type) = { - funcs.find(x=>x.name == func.name) match { - case Some(n) => { - var args = func.args - //TODO fails if interface has same attributes/functions but different name - val intfName = interfaces.find(x=>x.args == props && x.funcs == funcs).get.name - if(n.args.nonEmpty && n.args.head.name == "self") { - if(isStatic) throw new Exception(s"can not call method $func staticly") - else args = obj +: args - } - interpFunction(intfName+"_"+func.name, args, reg, env) - } - case None => props.find(x=>x.name == func.name) match { - case Some(InputVar(_, Type.Function(_,_))) => { - //callLambda(Expr.GetProperty(Expr.Compiled(code, t), func.name), func.args, reg, env) - callLambda(Expr.GetProperty(obj, func.name), func.args, reg, env) - } - case None => throw new Exception(s"object has no property or function named $func") - } - } - } + def defineString(value: String, reg: List[String]): String = { //TODO Make definitions dynamic also //var ret = s"mov rdi, ${value.length+1}\n" + s"mov rsi, 8\n" + "call calloc\n" + "push rax\n" + "mov r9, rax\n"; diff --git a/docs/tasklist.md b/docs/tasklist.md index 1c074ae..3613ef4 100644 --- a/docs/tasklist.md +++ b/docs/tasklist.md @@ -1,8 +1,17 @@ -##To do +## LLVM migration + +* fix overloading +* allow for no type declaration +* strings +* conversions +* lambdas +* exceptions +* imports + +## To do * file name in error messages * line numbers in compiler -* make functions use stack instead of registers * static variables * add prinf * Prevent array deferencing (point to a pointer that points to array) From ef8d8962e801e5dfa7e6a708ae7ce5c34a729b40 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Thu, 28 Jul 2022 16:58:36 +0300 Subject: [PATCH 058/112] Merged LLVM --- .gitignore | 3 +- Makefile | 26 +- app/src/main/scala/posharp/Definitions.scala | 27 +- app/src/main/scala/posharp/Main.scala | 63 +- app/src/main/scala/posharp/Parser.scala | 9 +- app/src/main/scala/posharp/ToAssembly.scala | 927 ++++++++++--------- build-llvm.sh | 25 + build.sh | 24 + docker-old/Dockerfile | 2 - docker-old/Makefile | 26 - docker-old/README.md | 43 - docker-old/argument-printer.asm | 81 -- docs/tasklist.md | 17 +- toCompile.txt => lib-code/nice.txt | 41 +- project/build.properties | 1 - veritas/src/main/scala/core/Veritas.scala | 4 +- 16 files changed, 667 insertions(+), 652 deletions(-) create mode 100644 build-llvm.sh create mode 100644 build.sh delete mode 100644 docker-old/Dockerfile delete mode 100644 docker-old/Makefile delete mode 100644 docker-old/README.md delete mode 100644 docker-old/argument-printer.asm rename toCompile.txt => lib-code/nice.txt (97%) delete mode 100644 project/build.properties diff --git a/.gitignore b/.gitignore index ccd8784..4513b3f 100644 --- a/.gitignore +++ b/.gitignore @@ -168,5 +168,6 @@ gradle-app.setting # JDT-specific (Eclipse Java Development Tools) .classpath -toCompile.txt +!po_src/lib/ +po_src coverage.json diff --git a/Makefile b/Makefile index 08cac95..9653a5c 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ TARGET_FILE = 'hello' -all: run +all: run_llvm #assemble hello.asm build: @@ -9,21 +9,19 @@ build: nasm -felf64 $(TARGET_FILE).asm && \ gcc -O0 -ggdb -no-pie $(TARGET_FILE).o -o $(TARGET_FILE) -#compile and run asm -run: build - nasm -felf64 compiled/$(TARGET_FILE).asm - gcc -O0 -ggdb -no-pie compiled/$(TARGET_FILE).o -o compiled/$(TARGET_FILE) - compiled/$(TARGET_FILE) +#compile all files in directory +.PHONY: build_all +build_all: + bash ./build.sh -#compile Po# using sbt and then run it, also running the generated .asm -full: sbt run +build_all_llvm: + bash ./build-llvm.sh -#compile Po# compiler -sbt: - sbt --batch -Dsbt.server.forcestart=true run +#compile and run asm +run_llvm: build_all_llvm + compiled/$(TARGET_FILE) -# for some example on the internet, the gcc compiler has issues -standalone: - nasm -f elf hello.asm && ld -m elf_i386 hello.o -o hello && ./hello +run_asm: build_all + compiled/$(TARGET_FILE) #valgrind --leak-check=full --track-origins=yes --dsymutil=yes ./hello diff --git a/app/src/main/scala/posharp/Definitions.scala b/app/src/main/scala/posharp/Definitions.scala index fb8b254..130ea06 100644 --- a/app/src/main/scala/posharp/Definitions.scala +++ b/app/src/main/scala/posharp/Definitions.scala @@ -62,7 +62,9 @@ object Expr{ case class Convert(value: Expr, to: Type) extends Expr - case class TopLevel(functions: List[Func], interfaces: List[DefineInterface], enums: List[DefineEnum]) extends Expr + case class TopLevel(functions: List[Func], interfaces: List[DefineInterface], enums: List[DefineEnum], imports: List[Import]) extends Expr + case class Import(toImport: String, file: String) extends Expr + case class ThrowException(errorMsg: String) extends Expr case class Nothing() extends Expr case class Compiled(code: String, raxType: Type) extends Expr @@ -78,7 +80,8 @@ object Type { case class Array(elemType: Type) extends Type case class Bool() extends Type //case class Interface(properties: List[InputVar]) extends Type - case class Interface(properties: List[InputVar], functions: List[FunctionInfo]) extends Type + case class Interface(name: String, properties: List[InputVar], functions: List[FunctionInfo]) extends Type + case class StaticInterface(properties: List[InputVar], functions: List[FunctionInfo]) extends Type case class Function(args: List[Type], retType: Type) extends Type case class T1() extends Type case class Enum(el: List[String]) extends Type @@ -93,7 +96,7 @@ object Type { case Bool() => "b" case Array(inner) => "arr_"+shortS(inner)+"_" //case Interface(inner) => "itf_"+inner.map(x=>shortS(x.varType))+"_" - case Interface(inner, innerf) => "itf_"+inner.map(x=>shortS(x.varType)).mkString+"_"+innerf.map(x=>x.name)+"_" + case Interface(_, inner, innerf) => "itf_"+inner.map(x=>shortS(x.varType)).mkString+"_"+innerf.map(x=>x.name)+"_" case Function(args, retType) => "func_"+args.map(x=>shortS(x)).mkString+"_"+shortS(retType) case UserType(name) => name case T1() => "T1" @@ -114,6 +117,24 @@ object Type { case Character() => Expr.Character('\u0000'); case _ => Expr.Nothing(); } + def toLLVM(valType: Type): String = valType match { + case Num() => "i32"; + case NumFloat() => "double"; + case Character() => "i8" + case Bool() => "i1" + case Array(inner) => s"%Type.array.${toLLVM(inner)}*" + case Undefined() => "void" + //should be avoided, as usertype could be not a class + case UserType(name) => s"%Class.$name*" + case Interface(name, _, _) => s"%Class.$name*" + /* + case Interface(vars, funcs) => { + val argS = vars.map(x=>toLLVM(x.varType)).mkString(", ") + s"{ $argS }" + } + */ + case _ => throw new Exception(s"$valType unrecognised for LLVM"); + } } case class InputVar(name: String, varType: Type) case class ObjVal(name: String, varType: Type, defaultValue: Expr) diff --git a/app/src/main/scala/posharp/Main.scala b/app/src/main/scala/posharp/Main.scala index 6e2ad37..a2b57cc 100644 --- a/app/src/main/scala/posharp/Main.scala +++ b/app/src/main/scala/posharp/Main.scala @@ -1,22 +1,54 @@ package posharp - import java.io.{File, FileWriter} -import scala.io.Source +import java.nio.file.Paths +import scala.io.{AnsiColor, Source} + +package object Constants { + val FileExtension = ".txt" +} object Main extends App { - var inputFile = "toCompile.txt"; + var sourceDir = "po_src" + if (args.length > 0) { - inputFile = args(0) + sourceDir = args(0) } - val toCompile = readFile("", inputFile) - val parsed = Parser.parseInput(toCompile); - //println(Util.prettyPrint(parsed)); - println("") - val asm = ToAssembly.convertMain(parsed); - //println(asm); + val files = recursiveListFiles(new File(sourceDir)).toList.filter(x=>x.getName.contains(Constants.FileExtension)) + val sourceDirPath = Paths.get(sourceDir) + val declarations: Map[String, Expr.TopLevel] = files.map(file => { + val toCompile = readFile(file) + val parsed = Parser.parseInput(toCompile); + val top = parsed match { + case x: Expr.TopLevel => x + case _ => throw new Exception("unexpected type in top level") + } + var relative_name = sourceDirPath.relativize(file.toPath).toFile.getPath.split(Constants.FileExtension)(0) + relative_name = relative_name.replace("\\", "/") + (relative_name -> top) + }).toMap + declarations.foreach(x => { + val file = x._1 + val code = x._2 + var asm = ""; - writeToFile(asm, "compiled/", "hello.asm") + try { + asm = ToAssembly.convertMain(code, file, declarations.filter(x => x._1 != file)); + } + catch { + case x: Exception => { + println( AnsiColor.RED + s"Compilation exception in \"$file\": ${x.getMessage}" + AnsiColor.RESET); + sys.exit(1); + } + } + println("") + writeCompiled(asm, "compiled/", file) + }) + + def writeCompiled(asm: String, directoryPath: String, file: String): Unit = { + val flatFile = file.split("/").last + ".ll" + writeToFile(asm, directoryPath, flatFile) + } def writeToFile(input: String, directoryPath: String, filename: String): Unit = { val directory = new File(directoryPath); @@ -26,12 +58,17 @@ object Main extends App { fileWriter.write(input) fileWriter.close() } - def readFile(directoryPath: String, filename: String): String = { - val source = Source.fromFile(new File(directoryPath+filename)) + def readFile(src: File): String = { + val source = Source.fromFile(src) val codetxt = source.mkString source.close() codetxt } + def recursiveListFiles(f: File): Array[File] = { + if(f.isFile) return Array(f) + val these = f.listFiles + these ++ these.filter(x=>x.isDirectory).flatMap(x=>recursiveListFiles(x)) + } } diff --git a/app/src/main/scala/posharp/Parser.scala b/app/src/main/scala/posharp/Parser.scala index fc6f80b..935b217 100644 --- a/app/src/main/scala/posharp/Parser.scala +++ b/app/src/main/scala/posharp/Parser.scala @@ -7,16 +7,18 @@ import Expr.GetProperty object Parser { //TODO fix issue when spacing at start of file - def topLevel[_: P]: P[Expr.TopLevel] = P(StringIn(" ").? ~ (function | interfaceDef | enumDef).rep(1)).map(x => { + def topLevel[_: P]: P[Expr.TopLevel] = P(StringIn(" ").? ~ (function | interfaceDef | enumDef | imports).rep(1)).map(x => { var func: List[Expr.Func] = List() var intf: List[Expr.DefineInterface] = List() var enum: List[Expr.DefineEnum] = List() + var imports: List[Expr.Import] = List() x.foreach { case y@Expr.Func(a, b, c, d) => func = func :+ y case y@Expr.DefineInterface(a, b, c) => intf = intf :+ y case y@Expr.DefineEnum(a, b) => enum = enum :+ y + case y@Expr.Import(a, b) => imports = imports :+ y } - Expr.TopLevel(func, intf, enum) + Expr.TopLevel(func, intf, enum, imports) }) def function[_: P]: P[Expr.Func] = P("def " ~/ ident ~ "(" ~/ functionArgs ~ ")" ~/ typeDef.? ~ block).map { @@ -29,6 +31,8 @@ object Parser { } } + def imports[_: P]: P[Expr.Import] = P("import " ~/ ident ~ "from" ~ fileName ~ ";").map(x=> Expr.Import(x._1.name, x._2.s)) + /* def interfaceDef[_: P]: P[Expr.DefineInterface] = P("interface " ~ ident ~/ "{" ~ (ident ~ typeDef).rep(sep = ",") ~ "}").map(props=> Expr.DefineInterface(props._1.name, props._2.toList.map(x=>InputVar(x._1.name, x._2))) @@ -221,6 +225,7 @@ object Parser { }) def str[_: P]: P[Expr.Str] = P("\"" ~~/ CharsWhile(_ != '"', 0).! ~~ "\"").map(x => Expr.Str(x + "\u0000")) + def fileName[_: P]: P[Expr.Str] = P("\"" ~~/ CharsWhile(_ != '"', 0).! ~~ "\"").map(x => Expr.Str(x)) def ident[_: P]: P[Expr.Ident] = P(CharIn("a-zA-Z_") ~~ CharsWhileIn("a-zA-Z0-9_", 0)).!.map((input) => { Expr.Ident(input) diff --git a/app/src/main/scala/posharp/ToAssembly.scala b/app/src/main/scala/posharp/ToAssembly.scala index 3b0b51e..d2d1dcf 100644 --- a/app/src/main/scala/posharp/ToAssembly.scala +++ b/app/src/main/scala/posharp/ToAssembly.scala @@ -1,40 +1,83 @@ package posharp -import Expr.GetProperty -import Type.{UserType, shortS} -import scala.io.AnsiColor +import posharp.Type.{UserType, shortS} + +class Counter { + private var counter = 1; + private var counterExtra = 0; + private var paused = false; + def next(): String = { + val cur = counter; + if(!paused) counter += 1; + s"%$cur"; + } + def extra(): Int = { + counterExtra += 1; + counterExtra - 1; + } + def last(): String = s"%${counter-1}"; + def secondLast(): String = s"%${counter-2}"; + def reset(): Unit = { + counter = 1; + } + def pauseToggle(): Unit = { + paused = !paused; + } +} object ToAssembly { - val defaultReg = List("rax", "rdi", "rsi", "rdx", "rcx", "r8", "r9", "r10", "r11") - def convertMain(input: Expr): String = { + var varc: Counter = new Counter(); + var ifCounter = 0; + var subconditionCounter: Int = 0; + var stringLiterals: List[String] = List() + var functions: List[FunctionInfo] = List(); + var interfaces: List[InterfaceInfo] = List(); + var enums: List[EnumInfo] = List() + var lambdas: List[(Expr.Func, Env)] = List() + var functionScope: FunctionInfo = FunctionInfo("main", List(), Type.Num()); + + def convertMain(input: Expr, currentFile: String, otherFiles: Map[String, Expr.TopLevel]): String = { + ifCounter = 0; + subconditionCounter = 0; + stringLiterals = List() + functions = List(); + interfaces = List(); + enums = List() + lambdas = List() + functionScope = FunctionInfo("main", List(), Type.Num()); + /* - var converted = - """ global main - | extern printf - | extern calloc - | extern free - | section .text - |main: - | sub rsp, 256 - |""".stripMargin; - converted += convert(input, defaultReg, Map() )._1; - converted += "add rsp, 256\n" - converted += " mov rax, 0\n ret\n" - converted += "format_num:\n db \"%d\", 10, 0" - converted + converted += "format_num:\n db \"%d\", 10, 0\n" + converted += "format_float:\n db \"%f\", 10, 0\n" + converted += "format_string:\n db \"%s\", 10, 0\n" + converted += "format_char:\n db \"%c\", 10, 0\n" + converted += "format_true:\n db \"true\", 10, 0\n" + converted += "format_false:\n db \"false\", 10, 0\n" */ var converted = - """ global main - | extern printf - | extern calloc - | extern free - | extern exit - | section .text + """ + | declare i32 @printf(i8*, ...) + | declare i64* @calloc(i32, i32) + | @format_num = private constant [3 x i8] c"%d\00" + | @format_float = private constant [3 x i8] c"%f\00" + | @format_string = private constant [3 x i8] c"%s\00" + | @format_char = private constant [3 x i8] c"%c\00" + | @format_false = private constant [7 x i8] c"false\0A\00" + | @format_true = private constant [7 x i8] c"true\0A\00\00" + | %Type.array.double = type {i32, double*} + | %Type.array.i32 = type {i32, i32*} + | %Type.array.i8 = type {i32, i8*} + | %Type.array.i1 = type {i32, i1*} + | |""".stripMargin; input match { case x: Expr.TopLevel => { declareFunctions(x); - declareInterfaces(x); + converted += declareInterfaces(x) + "\n"; + /* declareEnums(x) + converted += exportDeclarations(currentFile) + converted += handleImports(x, otherFiles) + */ converted += defineFunctions(x.functions.map(y=>(y, Map())), false); converted += defineFunctions(x.interfaces.flatMap(intf=> intf.functions.map(func=>Expr.Func(intf.name + "_" + func.name, func.argNames, func.retType, func.body))) @@ -42,134 +85,122 @@ object ToAssembly { false ); }} - converted += defineFunctions(lambdas, true); - converted += "exception:\nmov rdi, 1\ncall exit\n" - converted += "format_num:\n db \"%d\", 10, 0\n" - converted += "format_float:\n db \"%f\", 10, 0\n" - converted += "format_string:\n db \"%s\", 10, 0\n" - converted += "format_char:\n db \"%c\", 10, 0\n" - converted += "format_true:\n db \"true\", 10, 0\n" - converted += "format_false:\n db \"false\", 10, 0\n" - //converted += "section .data\nmain_rbp DQ 0\nmain_rsp DQ 0\n" - converted += stringLiterals.mkString - //converted = converted.split("\n").zipWithIndex.foldLeft("")((acc, v)=> acc +s"\nline${v._2}:\n"+ v._1) - converted = converted.split("\n").map(x=>if(x.contains(":")) x+"\n" else " "+x+"\n").mkString + //converted += defineFunctions(lambdas, true); + //converted += "exception:\nmov rdi, 1\ncall exit\n" + //converted += stringLiterals.mkString converted } - var ifCounter = 0; - var subconditionCounter: Int = 0; - var stringLiterals: List[String] = List() - var functions: List[FunctionInfo] = List(); - var interfaces: List[InterfaceInfo] = List(); - var enums: List[EnumInfo] = List() - var lambdas: List[(Expr.Func, Env)] = List() - var functionScope: FunctionInfo = FunctionInfo("main", List(), Type.Num()); - private def convert(input: Expr, reg: List[String], env: Env): (String, Type) = { - val conv = (_input: Expr) => convert(_input, reg, env) - val convt = (_input: Expr, _type: Type) => convert(_input, reg, env) match { + + private def convert(input: Expr, env: Env): (String, Type) = { + val conv = (_input: Expr) => convert(_input, env) + val convt = (_input: Expr, _type: Type) => convert(_input, env) match { case (_code, received_type) if received_type == _type => _code case (_code, received_type) => throw new Exception(s"got type $received_type, expected ${_type}") } val ret = input match { - case Expr.Num(value) => (s"mov ${reg.head}, 0${value}d\n", Type.Num()) - case Expr.NumFloat(value) => { - (s"mov ${reg.head}, __float64__(${value.toString})\n", Type.NumFloat()) - } - case Expr.True() => (s"mov ${reg.head}, 1\n", Type.Bool()) - case Expr.False() => (s"mov ${reg.head}, 0\n", Type.Bool()) - case Expr.Plus(left, right) => aritTemplate(left, right, "add", "addsd", reg, env) - case Expr.Minus(left, right) => aritTemplate(left, right, "sub", "subsd", reg, env) - case Expr.Mult(left, right) => aritTemplate(left, right, "mul", "mulsd", reg, env) - case Expr.Div(left, right) => aritTemplate(left, right, "div", "divsd", reg, env) - case Expr.Convert(value, valType: Type) => (convert(value, reg, env), valType) match { - case ((code, Type.Num()), Type.NumFloat()) => (code + convertToFloat(reg.head), valType) - case ((code, Type.NumFloat()), Type.Num()) => (code + convertToInt(reg.head), valType) - case ((code, Type.Num()), Type.Character()) => (code, valType) - case ((code, l), r) => throw new Exception(s"cant convert from type ${l} to type $r") - } + case Expr.Num(value) => (s"${varc.next()} = add i32 $value, 0\n", Type.Num()) + case Expr.NumFloat(value) => (s"${varc.next()} = fadd double $value, 0.0\n", Type.NumFloat()) + case Expr.Plus(left, right) => aritTemplate(left, right, "add", env) + case Expr.Minus(left, right) => aritTemplate(left, right, "sub", env) + case Expr.Mult(left, right) => aritTemplate(left, right, "mul", env) + case Expr.Div(left, right) => aritTemplate(left, right, "sdiv", env) case Expr.Ident(name) => { - val isStatic = enums.find(x=>x.name == name) - if(isStatic.nonEmpty) { - val enumInfo = isStatic.get - ("", Type.Enum(enumInfo.el)) - } - else { - val look = lookup(name, env) - (s"mov ${reg.head}, ${look._1}\n", look._2.varType) + enums.find(x => x.name == name).orElse(interfaces.find(x=>x.name == name)).orElse(Some(lookup(name, env))) match { + case Some(EnumInfo(_, el)) => ("", Type.Enum(el)) + case Some(InterfaceInfo(itfName, props, funcs)) => ("", Type.Interface(itfName, props, funcs)) + case Some((code: String, variable: Variable)) => (code, variable.varType) + case _ => throw new Exception(s"unrecognised identifier $name") } } - case Expr.Block(lines) => convertBlock(lines, reg, env); - case Expr.DefineArray(size, elemType, defaultValues) => conv(size) match { - case (code, Type.Num()) => defineArray(code, elemType, defaultValues, env) - case (code, x) => throw new Exception(s"not number when defining array size, got input of type $x") + case Expr.True() => (s"${varc.next()} = and i1 1, 1\n", Type.Bool()) + case Expr.False() => (s"${varc.next()} = and i1 0, 0\n", Type.Bool()) + case Expr.Equals(left, right) => (compareExpr(left, right, true, "eq", "oeq", env), Type.Bool()) + case Expr.LessThan(left, right) => (compareExpr(left, right, true, "slt", "olt", env), Type.Bool()) + case Expr.MoreThan(left, right) => (compareExpr(left, right, true, "sgt", "ogt", env), Type.Bool()) + case Expr.Not(left) => { + val converted = convt(left, Type.Bool()) + val loc = varc.last() + val ret = converted + s"${varc.next()} = xor i1 $loc, 1\n" + (ret, Type.Bool()) } - /* - case Expr.InstantiateInterface(name, values) => interfaces.find(x=>x.name == name) match { - case Some(intf) => { - var ret = values.zipWithIndex.map{case (entry, index) => { - val converted = convert(entry, defaultReg, env); - if(converted._2 != intf.args(index).varType) throw new Exception(s"expected type ${intf.args(index).varType}" + - s" for interface element ${intf.args(index).name}, but got ${converted._2}") - converted._1 + setArrayDirect("[rsp]", index, 8); - }}.mkString; - val array_def = s"mov rdi, ${intf.args.length}\n" + s"mov rsi, 8\n" + "call calloc\n" + "push rax\n" - ret = array_def + ret + "pop rax\n"; - (ret, Type.Interface(intf.args)) - } - case None => throw new Exception(s"no such interface defined") + + case Expr.And(l) => { + var andLoc = varc.next(); + val ret = l.foldLeft(s"$andLoc = and i1 1, 1\n")((acc, v) => convertLoc(v, env) match { + case (code, Type.Bool(), loc) =>{ + val ret = acc + code + s"${varc.next()} = and i1 $andLoc, $loc\n" + andLoc = varc.last() + ret + } + case (_, t, _) => throw new Exception(s"expected bool in and, got $t") + }) + (ret, Type.Bool()) } - */ - case Expr.InstantiateInterface(name, values) => interfaces.find(x=>x.name == name) match { - case Some(intf) => { - val array_def = s"mov rdi, ${intf.args.length}\n" + s"mov rsi, 8\n" + "call calloc\n" + "push rax\n" - //val newenv = newVar("self", UserType(name), env) - //val func_code = interpFunction(name+"_"+name, Expr.Ident("self") +: values, reg, newenv)._1 - //val ret = array_def + setval("self", UserType(name), newenv)._1 + func_code + "pop rax\n"; - - val func_code = interpFunction(name+"_"+name, Expr.Compiled(array_def, UserType(name)) +: values, reg, env)._1 - val ret = func_code + "pop rax\n"; - (ret, Type.Interface(intf.args, intf.funcs)) - } - case None => throw new Exception(s"no such interface defined") + case Expr.Or(l) => { + var andLoc = varc.next(); + val ret = l.foldLeft(s"$andLoc = or i1 0, 0\n")((acc, v) => convertLoc(v, env) match { + case (code, Type.Bool(), loc) =>{ + val ret = acc + code + s"${varc.next()} = or i1 $andLoc, $loc\n" + andLoc = varc.last() + ret + } + case (_, t, _) => throw new Exception(s"expected bool in and, got $t") + }) + (ret, Type.Bool()) } - case Expr.GetProperty(obj, prop) => conv(obj) match { - case(code, Type.Interface(props, funcs)) => props.find(x=>x.name == prop) match { + case Expr.DefineArray(size, elemType, defaultValues) => conv(size) match { + case (code, Type.Num()) => defineArray(code, elemType, defaultValues, env) + case (_, x) => throw new Exception(s"not number when defining array size, got input of type $x") + } + case Expr.GetArray(name, index) => getArray(name, index, env); + case Expr.ArraySize(name) => getArraySize(name, env); + + case Expr.GetProperty(obj, prop) => convert(obj, env) match { + case(code, Type.Interface(name, props, funcs)) => props.find(x=>x.name == prop) match { case Some(n) => { - val ret = code + getArrayDirect(reg.head, props.indexOf(n), 8, reg) + val intfDec = s"%Class.$name" + val idx = props.indexOf(n); + val ret = code + s"${varc.next()} = getelementptr $intfDec, $intfDec* ${varc.secondLast()}, i32 0, i32 $idx\n" + + s"${varc.next()} = load ${Type.toLLVM(n.varType)}, ${Type.toLLVM(n.varType)}* ${varc.secondLast()}\n" (ret, n.varType) } case None => throw new Exception(s"interface ${interfaces.find(x=>x.args == props).get.name} does not have a property ${prop}") } + /* + //case (code, Type.StaticInterface(props, funcs)) => case (code, Type.Enum(el)) => (s"mov ${reg.head}, 0${el.indexOf(prop)}d\n", Type.Num()) - case (code, Type.Array(a)) if prop == "size" => conv(Expr.ArraySize(obj)) - case (x, valType) => throw new Exception(s"expected a interface, got ${valType}") + */ + case (code, Type.Array(a)) if prop == "size" => getArraySize(Expr.Compiled(code, Type.Array(a)), env)//conv(Expr.ArraySize(obj)) + case valType => throw new Exception(s"expected a interface, got ${valType}") } - case Expr.CallObjFunc(obj, func) => conv(obj) match { - case(code, t@Type.Interface(props, funcs)) => funcs.find(x=>x.name == func.name) match { - case Some(n) => { - var args = func.args - //TODO fails if interface has same attributes/functions but different name - val intfName = interfaces.find(x=>x.args == props && x.funcs == funcs).get.name - if(n.args.nonEmpty && n.args.head.name == "self") args = obj +: args - interpFunction(intfName+"_"+func.name, args, reg, env) - } - case None => props.find(x=>x.name == func.name) match { - case Some(InputVar(_, Type.Function(_,_))) => { - //callLambda(Expr.GetProperty(Expr.Compiled(code, t), func.name), func.args, reg, env) - callLambda(Expr.GetProperty(obj, func.name), func.args, reg, env) - } - case None => throw new Exception(s"object has no property or function named $func") - } + case Expr.CallF(name, args) => { + if(functions.exists(x=>x.name == name)) interpFunction(name, args, env) + //else if(env.contains(name)) callLambda(Expr.Ident(name), args, reg, env) + else throw new Exception(s"unknown identifier $name") + } + case Expr.CallObjFunc(obj, func) => convertType(obj, env) match { + case Type.Interface(_, props, funcs) => callObjFunction(obj, func, props, funcs, isStatic = false, env) + case Type.StaticInterface(props, funcs) => callObjFunction(obj, func, props, funcs, isStatic = true, env) + } + case Expr.InstantiateInterface(name, values) => interfaces.find(x=>x.name == name) match { + case Some(intf) => { + val struct_def = s"${varc.next()} = alloca %Class.$name\n" + + val func_code = interpFunction(name+"_"+name, Expr.Compiled(struct_def, UserType(name)) +: values, env)._1 + val ret = func_code; + (ret, Type.Interface(name, intf.args, intf.funcs)) } + case None => throw new Exception(s"no such interface defined") } - case Expr.GetArray(name, index) => getArray(name, index, reg, env); - case Expr.ArraySize(name) => getArraySize(name, reg, env); - case Expr.CallF(name, args) => { - if(functions.exists(x=>x.name == name)) interpFunction(name, args, reg, env) - else if(env.contains(name)) callLambda(Expr.Ident(name), args, reg, env) - else throw new Exception(s"unknow identifier $name") + /* + case Expr.Convert(value, valType: Type) => (convert(value, reg, env), valType) match { + case ((code, Type.Num()), Type.NumFloat()) => (code + convertToFloat(reg.head), valType) + case ((code, Type.NumFloat()), Type.Num()) => (code + convertToInt(reg.head), valType) + case ((code, Type.Num()), Type.Character()) => (code, valType) + case ((code, l), r) => throw new Exception(s"cant convert from type ${l} to type $r") } + //case Expr.Str(value) => (defineString(value, reg), Type.Str()) case Expr.Str(value) => (defineArrayKnown(value.length, Type.Character(), value.map(x=>Expr.Character(x)).toList, env)._1, Type.Array(Type.Character())) case Expr.Character(value) => (s"mov ${reg.head}, ${value.toInt}\n", Type.Character()) @@ -180,237 +211,293 @@ object ToAssembly { lambdas = lambdas :+ (Expr.Func(label, args, ret, body), env) (s"mov ${reg.head}, $label\n", Type.Function(args.map(x=>x.varType), ret)) } - case Expr.Equals(left, right) => { - val ret = compareExpr(left, right, false, reg, env) + s"sete ${sizeToReg(1, reg.head)}\n" - (ret, Type.Bool()) - } - case Expr.LessThan(left, right) => { - val ret = compareExpr(left, right, true, reg, env) + s"setl ${sizeToReg(1, reg.head)}\n" - (ret, Type.Bool()) - } - case Expr.MoreThan(left, right) => { - val ret = compareExpr(left, right, true, reg, env) + s"setg ${sizeToReg(1, reg.head)}\n" - (ret, Type.Bool()) - } - case Expr.Not(left) => { - val ret = convt(left, Type.Bool()) + s"xor ${reg.head}, 1\n" - (ret, Type.Bool()) - } - case Expr.And(l) => { - val reg1 = sizeToReg(1, reg.head) - val reg2 = sizeToReg(1, reg.tail.head) - val ret = l.foldLeft(s"mov ${reg.head}, 1\n")((acc, v) => convert(v, reg.tail, env) match { - case (code, Type.Bool()) => acc + code + s"and ${reg1}, ${reg2}\n" //cmp ${reg1}, 1\nsete $reg1\n - case (_, t) => throw new Exception(s"expected bool in and, got $t") - }) - (ret, Type.Bool()) - } - case Expr.Or(l) => { - val reg1 = sizeToReg(1, reg.head) - val reg2 = sizeToReg(1, reg.tail.head) - val ret = l.foldLeft(s"mov ${reg.head}, 0\n")((acc, v) => convert(v, reg.tail, env) match { - case (code, Type.Bool()) => acc + code + s"or ${reg1}, ${reg2}\n" - case (_, t) => throw new Exception(s"expected bool in and, got $t") - }) - (ret, Type.Bool()) - } - + */ case Expr.Nothing() => ("", Type.Undefined()); case Expr.Compiled(code, retType) => (code, retType); + case Expr.Block(lines) => convertBlock(lines, env); case x => throw new Exception (s"$x is not interpreted yet :("); } (ret._1, makeUserTypesConcrete(ret._2)) } //TODO add line awareness for error reporting - private def convertBlock(lines: List[Expr], reg: List[String], env: Env): (String, Type) = { - val conv = (_input: Expr) => convert(_input, reg, env) + private def convertBlock(lines: List[Expr], env: Env): (String, Type) = { + val conv = (_input: Expr) => convert(_input, env) if(lines.isEmpty) return ("", Type.Undefined()); var newenv = env; var extendLines = lines; var defstring: String = lines.head match { case Expr.SetVal(Expr.Ident(name), value) => { - val converted = convert(value, reg, env); - val modified = setval(name, converted._2, env) - newenv = modified._2 - converted._1 + modified._1 + val look = lookupOffset(name, env) + val converted = convertLoc(value, env); + if(makeUserTypesConcrete(look.varType) != converted._2) throw new Exception(s"mismatch when assigning value" + + s" to variable $name, expected ${look.varType}, but got ${converted._2}") + val set = s"store ${Type.toLLVM(converted._2)} ${converted._3}, ${Type.toLLVM(converted._2)}* %$name\n" + converted._1 + set; }; - case Expr.SetArray(expr, index, value) => { - val converted = convert(value, reg, env); - val arr = setArray(expr, index, converted._2, env) - converted._1 + arr - }; - case Expr.SetInterfaceProp(intf, prop, valueRaw) => convert(intf, reg.tail, env) match { - case(code, Type.Interface(props,f)) => props.find(x=>x.name == prop) match { - case Some(n) => conv(valueRaw) match { - case (valCode, valType) if(valType == n.varType) => - valCode + code + setArrayDirect(s"${reg.tail.head}", props.indexOf(n), 8) - case (_, valType) => throw new Exception(s"trying to property ${n.name} of type ${n.varType} to incompatible type ${valType}") - } - case None => throw new Exception(s"interface ${interfaces.find(x=>x.args == props).get.name} does not have a property ${prop}") - } - case (x, valType) => throw new Exception(s"expected a interface, got ${valType}") + case Expr.DefVal(Expr.Ident(name), varType) => { + newenv = newVar(name, varType, newenv); + s"%$name = alloca ${Type.toLLVM(varType)}\n" } - case Expr.DefVal(Expr.Ident(name), varType) => newenv = newVar(name, varType, newenv); "" - case Expr.Print(toPrint) => printInterp(toPrint, env); case Expr.If(condition, ifTrue, ifFalse) => { - def compare(left: Expr, right: Expr, numeric: Boolean): String = compareExpr(left, right, numeric, reg, env) + def compare(left: Expr, right: Expr, numeric: Boolean): String = + compareExpr(left, right, numeric, "eq", "oeq", env) val trueLabel = s"if_${ifCounter}_true" val falseLabel = s"if_${ifCounter}_false" val endLabel = s"if_${ifCounter}_end" ifCounter += 1; - val cond = convert(condition, reg, env) match { - case (code, Type.Bool()) => compare(condition, Expr.True(), false) + s"jne ${falseLabel}\n" - case (_, t) => throw new Exception(s"got type $t inside condition, expected bool") + val cond = convertType(condition, env) match { + case Type.Bool() => compare(condition, Expr.True(), false) + case t => throw new Exception(s"got type $t inside condition, expected bool") } - //convertCondition(condition, reg, env, orMode = false, trueLabel, falseLabel) - val ret = cond + s"${trueLabel}:\n" + convert(ifTrue, reg, env)._1 + - s"jmp ${endLabel}\n" + s"${falseLabel}:\n" + convert(ifFalse, reg, env)._1 + s"${endLabel}:\n" + val ret = cond + s"br i1 ${varc.last()}, label %$trueLabel, label %$falseLabel\n" + + s"${trueLabel}:\n" + convert(ifTrue, env)._1 + s"br label %$endLabel\n" + s"${falseLabel}:\n" + + convert(ifFalse, env)._1 + s"br label %$endLabel\n" + s"$endLabel:\n" ret } case Expr.While(condition, execute) => { - def compare(left: Expr, right: Expr, numeric: Boolean): String = compareExpr(left, right, numeric, reg, env) + def compare(left: Expr, right: Expr, numeric: Boolean): String = + compareExpr(left, right, numeric, "eq", "oeq", env) val startLabel = s"while_${ifCounter}_start" val trueLabel = s"while_${ifCounter}_true" val endLabel = s"while_${ifCounter}_end" - val cond = convert(condition, reg, env) match { - case (code, Type.Bool()) => compare(condition, Expr.True(), false) + s"jne ${endLabel}\n" - case (_, t) => throw new Exception(s"got type $t inside condition, expected bool") + val cond = convertType(condition, env) match { + case Type.Bool() => compare(condition, Expr.True(), false) + + s"br i1 ${varc.last()}, label %${trueLabel}, label %${endLabel}\n" + case t => throw new Exception(s"got type $t inside condition, expected bool") } - val ret = s"${startLabel}:\n" + cond + s"${trueLabel}:\n" + convert(execute, reg, env)._1 + - s"jmp ${startLabel}\n" + s"${endLabel}:\n" + val ret = s"br label %${startLabel}\n" + s"${startLabel}:\n" + cond + s"${trueLabel}:\n" + + convert(execute, env)._1 + s"br label %${startLabel}\n" + s"${endLabel}:\n" ifCounter += 1; ret } - case Expr.Return(in) => { - val defInside = (env.keys.toSet diff functionScope.args.map(x=>x.name).toSet); - //TODO fix issue when 2 variables reference same location - val free = "" //freeMemory(env.filter(x=>defInside.contains(x._1))) - in match { - case Some(value) => { - val converted = convert(value, defaultReg, env) - if (makeUserTypesConcrete(functionScope.retType) != converted._2) - throw new Exception(s"Wrong return argument: function ${functionScope.name} expects ${functionScope.retType}, got ${converted._2}") - converted._1 + free + "leave\nret\n" + case Expr.SetArray(expr, index, value) => setArray(expr, index, value, env) + case Expr.SetInterfaceProp(intf, prop, valueRaw) => convertLoc(intf, env) match { + case(code, Type.Interface(name, props,f), intfLoc) => props.find(x=>x.name == prop) match { + case Some(n) => convertLoc(valueRaw, env) match { + case (valCode, valType, valueLoc) if(valType == n.varType) => { + val intfDec = s"%Class.$name" + val idx = props.indexOf(n); + var ret = code + valCode + ret += s"${varc.next()} = getelementptr $intfDec, $intfDec* $intfLoc, i32 0, i32 $idx\n" + ret += s"store ${Type.toLLVM(valType)} $valueLoc, ${Type.toLLVM(n.varType)}* ${varc.last()}\n" + ret + } + case (_, valType, _) => throw new Exception(s"trying to property ${n.name} of type ${n.varType} to incompatible type ${valType}") } - case None => free + "leave\nret\n"; + case None => throw new Exception(s"interface ${interfaces.find(x=>x.args == props).get.name} does not have a property ${prop}") } - + case (_, valType, _) => throw new Exception(s"expected an interface, got ${valType}") } + /* case Expr.ThrowException(err) => { val msg = AnsiColor.RED + "RuntimeException: " + err + AnsiColor.RESET val name = s"exception_print_${stringLiterals.length}" stringLiterals = stringLiterals :+ s"$name:\n db \"${msg}\", 10, 0\n" s"mov rax, $name\n" + printTemplate("format_string") + "jmp exception\n" } - case x@Expr.CallF(n, a) => convert(x, reg, env)._1; - case x@Expr.Block(n) => convert(x, reg, env)._1; - case x@Expr.CallObjFunc(obj, func) => convert(x, reg, env)._1; + */ + case x@Expr.CallObjFunc(obj, func) => convert(x, env)._1; + case x@Expr.CallF(n, a) => convert(x, env)._1; + case Expr.Return(in) => { + in match { + case Some(value) => { + val converted = convert(value, env) + if (makeUserTypesConcrete(functionScope.retType) != converted._2) + throw new Exception(s"Wrong return argument: function ${functionScope.name} expects ${functionScope.retType}, got ${converted._2}") + converted._1 + s"ret ${Type.toLLVM(converted._2)} ${varc.last()}\n" + } + case None => "ret void\n"; + } + /* + val defInside = (env.keys.toSet diff functionScope.args.map(x=>x.name).toSet); + //TODO fix issue when 2 variables reference same location + val free = "" //freeMemory(env.filter(x=>defInside.contains(x._1))) + */ + + } + case Expr.Print(toPrint) => printInterp(toPrint, env); + case x@Expr.Block(n) => convert(x, env)._1; case Expr.ExtendBlock(n) => extendLines = extendLines.head +: n ::: extendLines.tail;"" case _ => throw new Exception(lines.head.toString + " should not be in block lines") } if(extendLines.tail.nonEmpty) { - defstring += convertBlock(extendLines.tail, defaultReg, newenv)._1 + defstring += convertBlock(extendLines.tail, newenv)._1 } - //defstring += freeMemory((newenv.toSet diff env.toSet).toMap) + (defstring, Type.Undefined()); } - def compareExpr(left: Expr, right: Expr, numeric: Boolean, reg: List[String], env: Env): String = { - val leftout = convert(left, reg, env); - val rightout = convert(right, reg.tail, env); + def compareExpr(left: Expr, right: Expr, numeric: Boolean, comp: String, fcomp: String, env: Env): String = { + val leftout = convertLoc(left, env); + val rightout = convertLoc(right, env); + var isFloat = false; (leftout._2, rightout._2) match { case (Type.Bool(), Type.Bool()) if !numeric => ; case (Type.Num(), Type.Num()) => ; - case (Type.NumFloat(), Type.NumFloat()) => ; + case (Type.NumFloat(), Type.NumFloat()) => isFloat = true; case (Type.Character(), Type.Character()) => ; case (t1, t2) => throw new Exception(s"can not compare types of $t1 and $t2") } - leftout._1 + rightout._1 + s"cmp ${reg.head}, ${reg.tail.head}\n" + val cmp = if (isFloat) "fcmp" else "icmp" + val compKey = if (isFloat) fcomp else comp; + leftout._1 + rightout._1 + s"${varc.next()} = $cmp $compKey ${Type.toLLVM(leftout._2)} ${leftout._3}, ${rightout._3}\n" } + private def declareFunctions(input: Expr.TopLevel): Unit = { functions = input.functions.map(x=> FunctionInfo(x.name, x.argNames, x.retType)) } - private def declareInterfaces(input: Expr.TopLevel): Unit = { + private def declareInterfaces(input: Expr.TopLevel): String = { interfaces = input.interfaces.map(x=> InterfaceInfo(x.name, x.props, x.functions.map(x=>FunctionInfo(x.name,x.argNames,x.retType)))) - /* - interfaces = interfaces.map(x=> - InterfaceInfo( x.name, x.args.map(y=> - InputVar(y.name, traverseTypeTree(y.varType)) - ), x.funcs.map(y => - FunctionInfo(y.name, - y.args.map(z=>InputVar(z.name, traverseTypeTree(z.varType))), - traverseTypeTree(y.retType)) - )) - ) - */ functions = functions ::: interfaces.flatMap(x=>addPrefixToFunctions(x.name,x.funcs)) + interfaces.map(intf => { + val types = intf.args.map(x=>Type.toLLVM(x.varType)).mkString(", ") + s"%Class.${intf.name} = type {$types}\n" + }).mkString } private def addPrefixToFunctions(prefix: String, funcs: List[FunctionInfo]): List[FunctionInfo] = funcs.map(y=>FunctionInfo(prefix+"_"+y.name, y.args, y.retType)) private def declareEnums(input: Expr.TopLevel): Unit = { enums = input.enums.map(x=>EnumInfo(x.name,x.props)) } - def makeUserTypesConcrete(input: Type): Type = input match { - case UserType(name) => interfaces.find(x=>x.name == name) match { - case Some(n) => Type.Interface(n.args, n.funcs) - case _ => throw new Exception (s"no interface of name $name"); - } - case x => x + private def handleImports(input: Expr.TopLevel, otherFiles: Map[String, Expr.TopLevel]): String = { + input.imports.map(imp=>{ + if (!otherFiles.contains(imp.file)) throw new Exception(s"file \"${imp.file}\" could not be imported"); + val top = otherFiles(imp.file) + + val funcsForImport = searchFileDeclarations(top, imp) match { + case Expr.Func(name, argnames, retType, code) => { + functions = functions :+ FunctionInfo(name, argnames, retType) + List(fNameSignature(FunctionInfo(name, argnames, retType))) + } + case Expr.DefineInterface(name, props, i_functions) => { + val intf = InterfaceInfo(name, props, i_functions.map(x=>FunctionInfo(x.name,x.argNames,x.retType))) + interfaces = interfaces :+ intf + val funcs = addPrefixToFunctions(intf.name,intf.funcs) + functions = functions ::: funcs + funcs.map(x=>fNameSignature(FunctionInfo(x.name, x.args, x.retType))) + } + } + funcsForImport.map(x=>{ + val label = formatFName(imp.file) + "_" + x + s"extern $label\n" + s"${x}:\njmp $label\n" + }).mkString + /* + + + intf.funcs.map(x=>{ + val label = imp.file + "_" + x.name + s"extern ${label}\n" + s"${x.name}:\njmp ${label}\n" + }).mkString + */ + + }).mkString + } + private def searchFileDeclarations(top: Expr.TopLevel, imp: Expr.Import): Expr = { + top.functions.find(x=>x.name == imp.toImport) + .orElse(top.functions.find(x=>x.name == imp.toImport)) + .orElse(top.interfaces.find(x=>x.name == imp.toImport)) + .orElse(throw new Exception(s"could not import ${imp.toImport} from file \"${imp.file}\"")) + .orNull + } + private def exportDeclarations(file: String): String = { + ( + functions + //interfaces.flatMap(intf=> addPrefixToFunctions(intf.name, intf.funcs)) + ) + .map(info => { + val formatFile = formatFName(file) + val name = fNameSignature(info) + s"global ${formatFile}_${name}\n" + s"${formatFile}_${name}:\njmp ${name}\n" + }).mkString + } - //TODO avoid traversing the same interfaces by creating a list of marking which interfaces are concretely defined - /* - def traverseTypeTree(input: Type): Type = input match { + + /*** + * Formats file name in a format assembly can understand + * @param file name + * @return + */ + def formatFName(file: String): String = { + file.replace("/", "_") + } + + def makeUserTypesConcrete(input: Type): Type = input match { case UserType(name) => interfaces.find(x=>x.name == name) match { - case Some(n) => traverseTypeTree(Type.Interface(n.args, n.funcs)) match { - case t@Type.Interface(newargs,f) => - interfaces = interfaces.map(x=>if(x == n) InterfaceInfo(x.name, newargs, x.funcs) else x) - t - } + case Some(n) => Type.Interface(name, n.args, n.funcs) case _ => throw new Exception (s"no interface of name $name"); } - case Type.Interface(args,f) => Type.Interface(args.map(x=>InputVar(x.name, traverseTypeTree(x.varType))), f) - case Type.Array(valType) => traverseTypeTree(valType) case x => x } - */ - val functionCallReg = List( "rdi", "rsi", "rdx", "rcx", "r8", "r9") + def fNameSignature(info: FunctionInfo): String = fNameSignature(info.name, info.args.map(x=>x.varType)) def fNameSignature(name: String, args: List[Type]):String = name + (if(args.isEmpty) "" else "_") + args.map(x=>shortS(x)).mkString private def defineFunctions(input: List[(Expr.Func, Env)], lambdaMode: Boolean): String = { input.map{ case (function, upperScope) => { val info = functions.find(x=>x.name == function.name && x.args==function.argNames).get; functionScope = info; - val label = if(lambdaMode) info.name else fNameSignature(info.name, info.args.map(x=>x.varType)) - var ret = "\n" + s"${label}:\n" - //if(info.name == "main") ret += "mov [main_rbp], rbp\nmov [main_rsp], rsp\n" - ret += - """ push rbp - | mov rbp, rsp - | sub rsp, 256 - |""".stripMargin; - var env: Env = Map() - var regArgs = functionCallReg; - ret += function.argNames.map(arg => { - env = newVar(arg.name, arg.varType, env) - val moveVar = s"mov qword ${lookup(arg.name, env)._1}, ${regArgs.head}\n" - regArgs = regArgs.tail; - moveVar - }).mkString - ret += convert(function.body, defaultReg, shiftEnvLocations(upperScope) ++ env)._1 - if(info.retType == Type.Undefined()) ret += "leave\nret\n"; - if(info.name == "main") { - //ret += "exception:\nmov rdi, 1\nmov rbp, [main_rbp]\nmov rsp, [main_rsp]\nmov rdx, [rsp-8]\njmp rdx\n" - ret += "mov rax, 0\nleave\nret\n"; - } + val args = info.args.map(x=>s"${Type.toLLVM(x.varType)} %Input.${x.name}").mkString(", ") + var ret = s"define ${Type.toLLVM(info.retType)} @${info.name}($args) {\n" + val newEnv = upperScope ++ info.args.map(x=> (x.name, Variable(x.varType))).toMap + var body = info.args.map(x=> + s"%${x.name} = alloca ${Type.toLLVM(x.varType)}\n" + + s"store ${Type.toLLVM(x.varType)} %Input.${x.name}, ${Type.toLLVM(x.varType)}* %${x.name}\n").mkString + + body += convert(function.body, newEnv)._1 + if(info.retType == Type.Undefined()) body += "ret void\n" + if(info.name == "main") body += "ret i32 0\n" + varc.reset() + + ret += body.split("\n").map(x=>"\t"+x).mkString("\n") + ret += "\n}\n" ret }}.mkString } - def shiftEnvLocations(env: Env): Env = { - env.map(x=> (x._1, - Variable(x._2.pointer - 256 - 16 , x._2.varType) - )) + def interpFunction(name: String, args: List[Expr], env: Env ): (String, Type) = { + val argRet = args.map (arg => convertLoc(arg, env)) + val argInputTypes = argRet.map(x => x._2) + var ret = argRet.map(x=>x._1).mkString + val argsString = argRet.map(x=>s"${Type.toLLVM(x._2)} ${x._3}").mkString(", ") + + functions.find(x=>x.name == name) match { + case Some(x) => ; case None => throw new Exception(s"function of name $name undefined"); + } + functions.find(x=>x.name == name && Type.compare(argInputTypes, x.args.map(x=>makeUserTypesConcrete(x.varType)))) match { + case Some(FunctionInfo(p, argTypes, retType)) => { + val argTypeString = argTypes.map(x=>Type.toLLVM(x.varType)).mkString(", ") + if (retType != Type.Undefined()) ret += s"${varc.next()} = "; + ret += s"call ${Type.toLLVM(retType)} ($argTypeString) @$name($argsString)\n" + (ret, retType) + } + case None => { + println(functions.find(x=>x.name == name).map(x=>x.args).mkString); + throw new Exception(s"no overload of function $name matches argument list $argInputTypes") + }; + } } + + def callObjFunction(obj: Expr, func: Expr.CallF, props: List[InputVar], funcs: List[FunctionInfo], isStatic: Boolean, env: Env): (String, Type) = { + funcs.find(x=>x.name == func.name) match { + case Some(n) => { + var args = func.args + //TODO fails if interface has same attributes/functions but different name + val intfName = interfaces.find(x=>x.args == props && x.funcs == funcs).get.name + if(n.args.nonEmpty && n.args.head.name == "self") { + if(isStatic) throw new Exception(s"can not call method $func staticly") + else args = obj +: args + } + interpFunction(intfName+"_"+func.name, args, env) + } + case None => props.find(x=>x.name == func.name) match { + case Some(InputVar(_, Type.Function(_,_))) => { + //callLambda(Expr.GetProperty(obj, func.name), func.args, reg, env) + ("",Type.Undefined()) + } + case None => throw new Exception(s"object has no property or function named $func") + } + } + } + /* def callLambda(input: Expr, args: List[Expr], reg: List[String], env: Env): (String, Type) = convert(input, reg, env) match { case (code, Type.Function(argTypes, retType)) => { val usedReg = defaultReg.filter(x => !reg.contains(x)); @@ -431,150 +518,106 @@ object ToAssembly { } case (_, x) => throw new Exception(s"Can not call variable of type $x"); } - def interpFunction(name: String, args: List[Expr], reg: List[String], env: Env ): (String, Type) = { - val usedReg = defaultReg.filter(x => !reg.contains(x)); - var ret = usedReg.map(x=>s"push $x\n").mkString - val argRet = args.zipWithIndex.map{case (arg, index) => { - val converted = convert(arg, reg, env) - (converted._1 + s"push ${reg.head}\n", converted._2) - }} - val argInputTypes = argRet.map(x=>x._2) - ret += argRet.map(x=>x._1).mkString - ret += args.zipWithIndex.reverse.map{case (arg, index) => s"pop ${functionCallReg(index)}\n"}.mkString - //if(converted._2 != argTypes(index)) throw new Exception (s"wrong argument type: expected ${argTypes(index)}, got ${converted._2}") - functions.find(x=>x.name == name) match { - case Some(x) => ; case None => throw new Exception(s"function of name $name undefined"); - } - functions.find(x=>x.name == name && Type.compare(argInputTypes, x.args.map(x=>makeUserTypesConcrete(x.varType)))) match { - case Some(FunctionInfo(p, argTypes, retType)) => { - //if(argTypes.length != args.length) throw new Exception (s"wrong number of arguments: expected ${argTypes.length}, got ${args.length}") - //TODO add errors for unexpected behaviour - def eqT(x: Type): Boolean = x == Type.T1() || x == Type.Array(Type.T1()) - val template1Type = argTypes.map(x=>x.varType).zipWithIndex.find(x=> eqT(x._1)) match { - case Some(n) => argInputTypes(n._2) - case None => Type.Undefined() - } - argTypes.foreach(x=> { - if (eqT(x.varType) && !Type.compare(x.varType, template1Type)) throw new Exception(s"Generic type inputs were different; ${x.varType} did not equal ${template1Type}"); - }) - val retTypeTemplated = retType match { - case Type.T1() | Type.Array(Type.T1()) if(template1Type != Type.Undefined()) => template1Type - case _ => retType - } - ret += s"call ${fNameSignature(name, argTypes.map(x=>x.varType))}\n" - ret += s"mov ${reg.head}, rax\n" - ret += usedReg.reverse.map(x=>s"pop $x\n").mkString - (ret, retTypeTemplated) - } - case None => {println(functions.find(x=>x.name == name).map(x=>x.args).mkString);throw new Exception(s"no overload of function $name matches argument list $argInputTypes")}; - } + def defineString(value: String, reg: List[String]): String = { + //TODO Make definitions dynamic also + //var ret = s"mov rdi, ${value.length+1}\n" + s"mov rsi, 8\n" + "call calloc\n" + "push rax\n" + "mov r9, rax\n"; + val label = s"string_${stringLiterals.length}"; + stringLiterals = stringLiterals :+ s"$label:\n db \"${value}\", 10, 0\n" + s"mov ${reg.head}, $label\n" } - //Maybe remove type assingmed after fact(causes issues when type is unknown in compile time) - def setval(name: String, raxType: Type, env: Env): (String, Env) = { - val look = lookup(name, env); - var newenv = env; - (look._2.varType, raxType) match { - case (Type.Undefined(), Type.Undefined()) => {} - case (Type.Undefined(), ass) => newenv = env.map(x=>if(x._1==name) (x._1, Variable(x._2.pointer, raxType)) else x) - case (x,y) if x == y => {} - case (x,y) => throw new Exception(s"trying to set variable of type ${look._2.varType} to $raxType") - } + */ + def getArrayPointerIndex(arrType: Type, arrLoc: String, indexLoc: String): String = { + val Tsig = Type.toLLVM(arrType) + val arrTC = s"%Type.array.$Tsig" - (s"mov qword ${look._1}, rax\n", newenv) - } - //TODO add runtime index checking - /* - def setArray(name: String, index: Expr, raxType: Type, env: Env): (String, Env) = (lookup(name, env), convert(index, defaultReg, env)) match { - case ((varLoc, Variable(loc, Type.Array(elemType))), (indexCode, indexType)) => { - var newenv = env; - if(elemType != raxType) { - if(elemType == Type.Undefined()) newenv = env.map(x=>if(x._1==name) (x._1, Variable(x._2.pointer, Type.Array(raxType))) else x) - else throw new Exception(s"trying to set array element of type ${elemType} to $raxType") - } - if(indexType != Type.Num()) throw new Exception(s"wrong index for array, got $indexType") - ("push rax\n" + indexCode + s"mov rdi, ${varLoc}\n" + "pop rsi\n" + s"mov [rdi+8+rax*8], rsi\n", newenv) - } - } + var ret = ""; + ret += s"${varc.next()} = getelementptr $arrTC, $arrTC* $arrLoc, i32 0, i32 1\n" + val arrStructLoc = varc.last() + ret += s"${varc.next()} = getelementptr inbounds $Tsig*, $Tsig** ${arrStructLoc}, i32 $indexLoc\n" + val arrElemLoc = varc.last() + ret += s"${varc.next()} = load $Tsig*, $Tsig** ${arrElemLoc}\n" + ret + } + def getArray(arr: Expr, index: Expr, env: Env): (String, Type) = (convertLoc(arr, env), convertLoc(index, env)) match { + case ((code, Type.Array(arrType), arrLoc), (indexCode, indexType, indexLoc)) => { + if(indexType != Type.Num()) throw new Exception(s"wrong index for array, got $indexType"); + val Tsig = Type.toLLVM(arrType) - */ - def setArray(arr: Expr, index: Expr, raxType: Type, env: Env): String = (convert(arr, defaultReg, env), convert(index, defaultReg, env)) match { - case ((arrCode, Type.Array(elemType)), (indexCode, indexType)) => { - if(elemType != raxType) throw new Exception(s"trying to set array element of type ${elemType} to $raxType") - if(indexType != Type.Num()) throw new Exception(s"wrong index for array, got $indexType") - "push rax\n" + arrCode + "push rax\n" + indexCode + "pop rdi\n" + "pop rsi\n" + s"mov [rdi+8+rax*8], rsi\n" + var ret = code + indexCode; + ret += getArrayPointerIndex(arrType, arrLoc, indexLoc) + ret += s"${varc.next()} = load $Tsig, $Tsig* ${varc.secondLast()}\n" + (ret, arrType) } - } - def getArray(arr: Expr, index: Expr, reg: List[String], env: Env): (String, Type) = (convert(arr, reg, env), convert(index, reg, env)) match { - case ((code, Type.Array( arrType)), (indexCode, indexType)) => { - if(indexType != Type.Num()) throw new Exception(s"wrong index for array, got $indexType") - val size = arraySizeFromType(arrType); - (code + s"push ${reg.head}\n" + indexCode + s"mov ${reg.tail.head}, ${reg.head}\n" + - s"pop ${reg.head}\n" + s"mov ${sizeToReg(size, reg.head)}, [${reg.head}+8+${reg.tail.head}*$size]\n", arrType) + case ((_, varType, _), _) => throw new Exception(s"trying to access variable ${arr} as an array, has type $varType") + } + def setArray(arr: Expr, index: Expr, newVal: Expr, env: Env): String = + (convertLoc(arr, env), convertLoc(index, env), convertLoc(newVal, env)) match { + case ((code, Type.Array(arrType), arrLoc), (indexCode, indexType, indexLoc), (valCode, valType, valLoc)) => { + if(arrType != valType) throw new Exception(s"trying to set array element of type ${arrType} to $valType"); + if(indexType != Type.Num()) throw new Exception(s"wrong index for array, got $indexType"); + val Tsig = Type.toLLVM(arrType) + + var ret = code + indexCode + valCode; + ret += getArrayPointerIndex(arrType, arrLoc, indexLoc) + ret += s"store $Tsig $valLoc, $Tsig* ${varc.last()}\n" + ret } - case ((code, varType), l) => throw new Exception(s"trying to access variable ${arr} as an array, has type $varType") } - def getArraySize(arr: Expr, reg: List[String], env: Env): (String, Type) = convert(arr, reg, env) match { - case (code, Type.Array(arrType)) => { - (code + getArrayDirect(reg.head, 0, 8, reg), Type.Num()) + def getArraySize(arr: Expr, env: Env): (String, Type) = convertLoc(arr, env) match { + case (code, Type.Array(arrType), loc) => { + val Tsig = Type.toLLVM(arrType) + val arrTC = s"%Type.array.$Tsig" + + val ret = code + + s"${varc.next()} = getelementptr $arrTC, $arrTC* $loc, i32 0, i32 0\n" + + s"${varc.next()} = load $Tsig, $Tsig* ${varc.secondLast()}\n" + (ret, Type.Num()) } - case (code, varType) => throw new Exception(s"trying to access variable ${arr} as an array, has type $varType") - } - //TODO not safe when default values use rdi - def setArrayDirect(code: String, index: Int, size: Int): String = { - s"mov rdi, ${code}\n" + s"mov [rdi+${index*size}], ${sizeToReg(size, "rax")}\n" - } - def getArrayDirect(code: String, index: Int, size: Int, reg: List[String]): String = { - s"mov ${reg.tail.head}, ${code}\n" + s"mov ${reg.head}, [${reg.tail.head}+${index*size}]\n" - } - //List("rax", "rdi", "rsi", "rdx", "rcx", "r8", "r9", "r10", "r11") - val fullToByteReg: Map[String, String] = Map(("rax", "al"), ("rdi", "dil"), ("rsi", "sil"), ("rdx","dl")) - def sizeToReg(size: Int, reg: String): String = size match { - case 8 => reg - case 1 => fullToByteReg.getOrElse(reg, reg) - } - def arraySizeFromType(valtype: Type): Int = valtype match { - case Type.Undefined() => 8 - case Type.Character() => 1 - case Type.Num() => 8 - case Type.NumFloat() => 8 - case Type.Function(_,_) => 8 - case Type.T1() => 8 + case (_, varType, _) => throw new Exception(s"trying to access variable ${arr} as an array, has type $varType") } - def defineArrayKnown(size: Int, setElemType:Type, defaultValues: List[Expr], env: Env): (String, Type) = { - defineArray(s"mov rax, 0${size}d\n", setElemType, defaultValues, env) - } - //TODO use available registers or save, not rax def defineArray(size: String, setElemType:Type, defaultValues: List[Expr], env: Env): (String, Type) = { var elemType: Type = setElemType; + val array_elem_size = arraySizeFromType(elemType); + val Tsig = Type.toLLVM(elemType) + val arrTC = s"%Type.array.$Tsig" + var ret = size; + val sizeLoc = varc.last() + ret += s"${varc.next()} = call i64* (i32, i32) @calloc(i32 $sizeLoc, i32 $array_elem_size)\n"; + ret += s"${varc.next()} = bitcast i64* ${varc.secondLast()} to $Tsig*" + val arrLoc = varc.last() + val sizeStructPointer = s"%arr.size.${varc.extra()}" + val arrStructPointer = s"%arr.arr.${varc.extra()}" + ret += s"${varc.next()} = alloca $arrTC\n" + val structLoc = varc.last() + + ret += s"${sizeStructPointer} = getelementptr $arrTC, $arrTC* $structLoc, i32 0, i32 0\n" + ret += s"store i32 $sizeLoc, i32* ${sizeStructPointer}\n" + ret += s"${arrStructPointer} = getelementptr $arrTC, $arrTC* $structLoc, i32 0, i32 1\n" + ret += s"store $Tsig* $arrLoc, $Tsig** ${arrStructPointer}\n" + /* var ret = defaultValues.zipWithIndex.map{case (entry, index) => { val converted = convert(entry, defaultReg, env) if(elemType == Type.Undefined()) elemType = converted._2; else if(converted._2 != elemType) throw new Exception(s"array elements are of different types") converted._1 + setArrayDirect("[rsp]", skipArrSize(index, arraySizeFromType(elemType)), arraySizeFromType(elemType)); }}.mkString; - val array_elem_size = arraySizeFromType(elemType); - val array_def = size + "push rax\n" + "add rax, 1\n" + s"mov rdi, rax\n" + s"mov rsi, ${array_elem_size}\n" + "call calloc\n" + "push rax\n" - ret = array_def + ret; - ret += "pop r9\npop rax\n" + setArrayDirect("[rsp-16]", 0, 8) - ret += "mov rax, r9\n" + */ (ret, Type.Array(elemType)) } - def skipArrSize(index: Int, size: Int): Int = index + (8/size) - /* - def defineString(value: String, reg: List[String]): String = { - //TODO Make definitions dynamic also - //var ret = s"mov rdi, ${value.length+1}\n" + s"mov rsi, 8\n" + "call calloc\n" + "push rax\n" + "mov r9, rax\n"; - val label = s"string_${stringLiterals.length}"; - stringLiterals = stringLiterals :+ s"$label:\n db \"${value}\", 10, 0\n" - s"mov ${reg.head}, $label\n" + def arraySizeFromType(valtype: Type): Int = valtype match { + case Type.Undefined() => 8 + case Type.Character() => 1 + case Type.Num() => 8 + case Type.NumFloat() => 8 + case Type.Function(_,_) => 8 + case Type.T1() => 8 } - */ def lookup(tofind: String, env: Env): (String, Variable) = { val ret = lookupOffset(tofind, env) - (s"[rbp-${ret.pointer}]", ret) + (s"${varc.next()} = load ${Type.toLLVM(ret.varType)}, ${Type.toLLVM(ret.varType)}* %$tofind\n", ret) } def lookupOffset(tofind: String, env: Env): Variable = env.get(tofind) match { case Some(v) => v @@ -582,85 +625,83 @@ object ToAssembly { } def newVar(name: String, varType: Type, env: Env) : Env = { if(env.contains(name)) throw new Exception(s"variable \"${name}\" already defined") - val newOffset: Int = if(env.isEmpty) 0 else env.values.toList.map(x=>x.pointer).max - env + (name -> Variable(newOffset + 8, varType)) + env + (name -> Variable(varType)) } - def floatTemplate (codeLeft: String, codeRight: String, command: String, reg: List[String]): (String, Type) = { - val ret = codeLeft + codeRight + s"movq xmm0, ${reg.head}\n" + s"movq xmm1, ${reg.tail.head}\n" + - s"${command} xmm0, xmm1\n" + s"movq ${reg.head}, xmm0\n" - (ret, Type.NumFloat()); + def convertLoc(input: Expr, env: Env): (String, Type, String) = { + val ret = convert(input, env) + (ret._1, ret._2, varc.last()) } - - def binOpTemplate(left: Expr, right: Expr, command: String, reg: List[String], env: Env): String = { - val leftout = convert(left, reg, env)._1; - val rightout = convert(right, reg.tail, env)._1; - leftout + rightout + s"${command} ${reg.head}, ${reg.tail.head}\n"; + def convertType(input: Expr, env: Env): Type = { + varc.pauseToggle() + val ret = convert(input, env)._2 + varc.pauseToggle() + ret } - /* - def mulTemplate(left: Expr, right: Expr, command: String, reg: List[String], env: Env): String = { - val leftout = convert(left, reg, env)._1; - val rightout = convert(right, reg.tail, env)._1; - leftout + rightout + s"${command} ${reg.tail.head}\n"; + def intBinOpTemplate(codeLeft: String, vLeft: String, codeRight: String, vRight: String, command: String): (String, Type) = { + (codeLeft + codeRight + s"${varc.next()} = $command i32 $vLeft, $vRight\n", Type.Num()); } - */ - def intBinOpTemplate(codeLeft: String, codeRight: String, command: String, reg: List[String]): (String, Type) = { - (codeLeft + codeRight + s"${command} ${reg.head}, ${reg.tail.head}\n", Type.Num()); - } - def intmulTemplate(codeLeft: String, codeRight: String, command: String, reg: List[String]): (String, Type) = { - (codeLeft + codeRight + s"${command} ${reg.tail.head}\n", Type.Num()); - } - def aritTemplate(left: Expr, right: Expr, commandInt: String, commandFloat: String, reg: List[String], env: Env): (String, Type) = { - (convert(left, reg, env), convert(right, reg.tail, env)) match { - case ((codeLeft, Type.Num()), (codeRight, Type.Num())) => - if(commandInt == "add" || commandInt == "sub") intBinOpTemplate(codeLeft, codeRight, commandInt, reg) - else intmulTemplate(codeLeft, codeRight, commandInt, reg) - case ((codeLeft, Type.NumFloat()), (codeRight, Type.NumFloat())) => floatTemplate(codeLeft, codeRight, commandFloat, reg) - case ((codeLeft, Type.Num()), (codeRight, Type.NumFloat())) => floatTemplate(codeLeft + convertToFloat(reg.head), codeRight, commandFloat, reg) - case ((codeLeft, Type.NumFloat()), (codeRight, Type.Num())) => floatTemplate(codeLeft, codeRight + convertToFloat(reg.tail.head), commandFloat, reg) - case ((codeLeft, typeLeft), (codeRight, typeRight)) => throw new Exception(s"can't perform arithmetic on operands of types ${typeLeft} and ${typeRight}"); + def floatBinOpTemplate(codeLeft: String, vLeft: String, codeRight: String, vRight: String, command: String): (String, Type) = { + (codeLeft + codeRight + s"${varc.next()} = f$command double $vLeft, $vRight\n", Type.NumFloat()); + } + + def aritTemplate(left: Expr, right: Expr, command: String, env: Env): (String, Type) = { + (convertLoc(left, env), convertLoc(right, env)) match { + case ((codeLeft, Type.Num(), vLeft), (codeRight, Type.Num(), vRight)) => + intBinOpTemplate(codeLeft, vLeft, codeRight, vRight, command) + case ((codeLeft, Type.NumFloat(), vLeft), (codeRight, Type.NumFloat(), vRight)) => + if(command == "sdiv") floatBinOpTemplate(codeLeft, vLeft, codeRight, vRight, "div") + else floatBinOpTemplate(codeLeft, vLeft, codeRight, vRight, command) + //case ((codeLeft, Type.Num()), (codeRight, Type.NumFloat())) => floatTemplate(codeLeft + convertToFloat(reg.head), codeRight, commandFloat, reg) + //case ((codeLeft, Type.NumFloat()), (codeRight, Type.Num())) => floatTemplate(codeLeft, codeRight + convertToFloat(reg.tail.head), commandFloat, reg) + case ((codeLeft, typeLeft, x), (codeRight, typeRight, y)) => throw new Exception(s"can't perform arithmetic on operands of types ${typeLeft} and ${typeRight}"); } } + /* def convertToFloat(reg: String): String = { s"cvtsi2sd xmm0, ${reg}\n" + s"movq ${reg}, xmm0\n" } def convertToInt(reg: String): String = { s"movq xmm0, ${reg}\n" + s"cvtsd2si ${reg}, xmm0\n" } - def printTemplate(format: String): String = { - "mov rdi, " + format + - """ - |mov rsi, rax - |xor rax, rax - |call printf - |""".stripMargin + */ + + def printTemplate(format: String, ty: String): String = { + s"%ret.${varc.extra()} = call i32 (i8*, ...) @printf(i8* getelementptr inbounds" + + s" ([3 x i8], [3 x i8]* @$format, i32 0, i32 0), $ty ${varc.last()})\n" } def printInterp(toPrint: Expr, env: Env): String = { - val converted = convert(toPrint, defaultReg, env) + val converted = convert(toPrint, env) ifCounter+=1 converted._2 match { - case Type.Num() => converted._1 + printTemplate("format_num"); - case Type.NumFloat() => converted._1 + "movq xmm0, rax\n" + "mov rdi, format_float\n" + "mov rax, 1\n" + "call printf\n" + case Type.Num() => converted._1 + printTemplate("format_num", "i32"); + case Type.Bool() => converted._1 + printTemplate("format_num", "i1"); + case Type.NumFloat() => converted._1 + printTemplate("format_float", "double"); + /* //case (Type.Str) => converted._1 + printTemplate("format_string"); case Type.Bool() => converted._1 + s"cmp rax, 0\nje bool_${ifCounter}\n" + printTemplate("format_true") + s"jmp boole_${ifCounter}\nbool_${ifCounter}:\n" + printTemplate("format_false") + s"boole_${ifCounter}:\n"; case Type.Character() => converted._1 + printTemplate("format_char"); case Type.Array(Type.Character()) => converted._1 + "add rax, 8\n" + printTemplate("format_string"); + */ case _ => throw new Exception(s"input of type ${converted._2} not recognized in print") } } //TODO runtime memory garbage collection, currently only pointers on the stack are handled + /* def freeMemory(env: Env): String = env.foldLeft("")((acc, entry) => entry._2.varType match { case Type.Array(arrType) => acc + s"mov rdi, [rbp-${entry._2.pointer}]\n" + "call free\n"; //case Type.Interface(args) => acc + s"mov rdi, [rbp-${entry._2.pointer}]\n" + "call free\n"; case _ => acc }) + */ + type Env = Map[String, Variable] case class FunctionInfo(name: String, args: List[InputVar], retType: Type) //case class InterfaceInfo(name: String, args: List[InputVar]) case class InterfaceInfo(name: String, args: List[InputVar], funcs: List[FunctionInfo]) case class EnumInfo(name: String, el: List[String]) - case class Variable(pointer: Int, varType: Type) + case class Variable(varType: Type) } diff --git a/build-llvm.sh b/build-llvm.sh new file mode 100644 index 0000000..994b68b --- /dev/null +++ b/build-llvm.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +mkdir -p compiled && \ +cd compiled/ || exit + +dir_succeeded=$? + +if [ $dir_succeeded -ne 0 ]; +then + exit 1 +fi + +files=$( cut -d '.' -f 1 <<< "$(ls -- *.ll)" ) + +files_asm=() + +for i in $files; +do + files_asm+=("$i".s) + llc "$i".ll +done + +files="${files_asm[*]}" + +gcc -O0 -ggdb -no-pie "$files" -o "hello" \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..c2363d8 --- /dev/null +++ b/build.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +mkdir -p compiled && \ +cd compiled/ || exit + +dir_succeeded=$? + +if [ $dir_succeeded -ne 0 ]; +then + exit 1 +fi + +files=$( cut -d '.' -f 1 <<< "$(ls | grep .asm)" ) +files_asm=() + +for i in $files; +do + files_asm+=($i.o) + nasm -felf64 $i.asm +done + +files="${files_asm[*]}" + +gcc -O0 -ggdb -no-pie $files -o "hello" \ No newline at end of file diff --git a/docker-old/Dockerfile b/docker-old/Dockerfile deleted file mode 100644 index 8814afd..0000000 --- a/docker-old/Dockerfile +++ /dev/null @@ -1,2 +0,0 @@ -FROM alpine:latest -RUN apk add gdb build-base nasm gcc \ No newline at end of file diff --git a/docker-old/Makefile b/docker-old/Makefile deleted file mode 100644 index aeafbc4..0000000 --- a/docker-old/Makefile +++ /dev/null @@ -1,26 +0,0 @@ -current_dir = $(shell pwd) - -# default assembly code location -ifeq ($(asm),) - asm = argument-printer -endif - -build-image: - docker build -t linux-assembly . - -build: - docker run --rm -v $(current_dir):/app -w /app linux-assembly sh -c "nasm -f elf64 -F dwarf -g $(asm).asm && ld -m elf_x86_64 $(asm).o" - -run: build - docker run --rm -v $(current_dir):/app -w /app linux-assembly sh -c ./a.out - -run-with-args: build - docker run --rm -v $(current_dir):/app -w /app linux-assembly sh -c "./a.out $(args)" - -debug: build - docker run --rm -it --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -v $(current_dir):/app -w /app linux-assembly sh -c "gdb a.out" - -run-container: - docker run --rm -it --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -v $(current_dir):/app -w /app linux-assembly - - diff --git a/docker-old/README.md b/docker-old/README.md deleted file mode 100644 index 2240a99..0000000 --- a/docker-old/README.md +++ /dev/null @@ -1,43 +0,0 @@ -## Summary -Goal started out to Write a trivial program in x86_64 assembly language. - -This program prints out the number of arguments provided to the program and prints them out to the terminal. - -`nasm` is used for compiling the program, `ld` for linking and `gdb` for debugging. Docker is used to containerize the workflow within an alpine-linux image. - -See the wiki with process, learnings and guidance [here](https://github.com/tonyOreglia/unique-word-counter/wiki) - -## Dependencies -* [Docker](https://www.docker.com/get-started) -* [Make](https://www.tutorialspoint.com/unix_commands/make.htm) command line utility - -## Run and Debug on Docker -#### Build Image -```sh -make build-image -``` - -#### Compile and Link -```sh -make build -``` - -#### Build and Run the executable -```sh -make run -``` - -#### Build and Run with Arguments -```sh -make run-with-args args="arg1 arg2 arg3" -``` - -#### Build and Debug -```sh -make debug -``` - -#### Run the Container Attached via Terminal -```sh -make run-container -``` diff --git a/docker-old/argument-printer.asm b/docker-old/argument-printer.asm deleted file mode 100644 index 15e60c5..0000000 --- a/docker-old/argument-printer.asm +++ /dev/null @@ -1,81 +0,0 @@ -section .data - -section .text - global _start -_start: - call .getNumberOfArgs ; expects return value in $rax - mov rdi, rax - mov rbx, rdi ; rbx is preserved reg - call .printNumberOfArgs ; expects value to be in 1st argument, i.e. $rdi - call .printAllArgs ; expect rdi to have number of args - call .exit - -.getNumberOfArgs: - pop r10 ; this is the address of the calling fxn. Remove it from the stack for a moment so I can get to the argc - pop rax ; get argc from stack - push r10 ; return address of calling fxn to stack - ret - -; expects value to be in 1st argument, i.e. $rdi -.printNumberOfArgs: - add rdi, 48 ; convert number of args to ascii (only works if < 10) - push rdi ; push the ascii converted argc to stack - mov rsi, rsp ; store value of rsp, i.e. pointer to ascii argc to param2 of sys_write fxn - mov rdx, 8 ; param3 of sys_write fxn is the number of bits to print - call .print ; prints top of the stack - pop rdi ; clean the stack - ret - -; expects * char array in $rdi -.strlen: - mov rax, 1 ; initialize strlen counter -.loop: - add rdi, 1 ; increment char * to next character - add rax, 1 ; increment strlen counter - cmp byte [rdi], 0x00 ; if value at [rdi] is 0x00 return - jne .loop ; loop if not at end of string - ret - - -.printAllArgs: - call .printNewline ; fxn prints newline - pop r11 ; pop address of the calling fxn. Remove temporarily - mov rsi, [rsp] ; stack pointer memory address. Holding argument to print. - mov rdi, rsi - call .strlen ; expect strlen to be in $rax after funtion returns - mov rdx, rax ; how long is the message. TO DO: calculate argument length - push r11 ; push return address back onto the stack - call .print - pop r11 ; pop return address - pop rcx ; this is the already printed arg - push r11 ; push return address back onto the stack - sub rbx, 1 ; rbx is the argument count. Iterate down 1 - cmp rbx, 0 ; are there zero args left to print? - jne .printAllArgs ; if more args to print, loop again - call .printNewline ; otherwise print Newline and return - ret - - -.printNewline: - pop r11 ; this is the address of the calling fxn. Remove it from the stack for a moment so I can get to the argc - push 10 ; ascii newline character - mov rsi, rsp ; rsp points to top of stack. Newline has been pushed to top of stack. rsi is where 2nd param of sys_write is stored - push r11 ; return the address of the calling fxn to top of stack. - mov rdx, 2 - call .print - ; clean up the newline character pushed onto the stack. Retaining the return address currently on top of stack - pop r11 - pop rcx - push r11 - ret - -.print: ; print expects the calling location to be at the top of the stack - mov rax, 1 - mov rdi, 1 - syscall - ret ; return to location pointed to at top of stack - -.exit: - mov rax, 60 - mov rdi, 0 - syscall diff --git a/docs/tasklist.md b/docs/tasklist.md index ee593ce..3613ef4 100644 --- a/docs/tasklist.md +++ b/docs/tasklist.md @@ -1,15 +1,24 @@ -##To do +## LLVM migration +* fix overloading +* allow for no type declaration +* strings +* conversions +* lambdas +* exceptions +* imports -* make functions use stack instead of registers +## To do + +* file name in error messages +* line numbers in compiler * static variables * add prinf -* infer self in object functions * Prevent array deferencing (point to a pointer that points to array) +* infer self in object functions * Add array copy function * add method to get all interface arguments/names * function reflection(do this by declaring labels in file with strings and var names) -* line numbers in compiler * parser error reporting * nested arrays * add runtime index checking for arrays diff --git a/toCompile.txt b/lib-code/nice.txt similarity index 97% rename from toCompile.txt rename to lib-code/nice.txt index 311f736..a1744f1 100644 --- a/toCompile.txt +++ b/lib-code/nice.txt @@ -1,15 +1,3 @@ -/* -def apply(a: int, b: int, f: func[(int, int) => int]): int { - return f(a, b); -} -def main(): int { - val c = 5; - val a = lambda (x: int, y: int): int => (x + y + c); - val b = apply(5,6,a); - print(b); -} -*/ - object Dynamic { size: int; allocated: int; @@ -30,7 +18,7 @@ object Dynamic { }; } - + def expand(self: Dynamic, req: int) { val total = (req + self.size); if(total >= self.allocated) { @@ -57,12 +45,12 @@ object Dynamic { def push(self: Dynamic, value: array[int]) { val oldSize = self.size; self.expand(value.size); - + for(val i = 0; i < value.size; i+= 1;) { self.arr[(i + oldSize)] = value[i]; }; } - + def push(self: Dynamic, value: Dynamic) { val oldSize = self.size; self.expand(value.size); @@ -71,7 +59,7 @@ object Dynamic { self.arr[(i + oldSize)] = value.arr[i]; }; } - + def concat(self: Dynamic, other: Dynamic): Dynamic { val ret = self.copy(); ret.push(other); @@ -104,7 +92,26 @@ object Dynamic { }; return same; } + def not_useful() { + print("not useful, you're dumb"); + } +} + + +/* +def apply(a: int, b: int, f: func[(int, int) => int]): int { + return f(a, b); } +def main(): int { + val c = 5; + val a = lambda (x: int, y: int): int => (x + y + c); + val b = apply(5,6,a); + print(b); +} +*/ + +/* + def main(): int { val a = new Dynamic(); @@ -116,7 +123,7 @@ def main(): int { .print_arr(); } - +*/ /* def main(): int { diff --git a/project/build.properties b/project/build.properties deleted file mode 100644 index dd4ff43..0000000 --- a/project/build.properties +++ /dev/null @@ -1 +0,0 @@ -sbt.version = 1.6.1 diff --git a/veritas/src/main/scala/core/Veritas.scala b/veritas/src/main/scala/core/Veritas.scala index 5a364ef..8b558ec 100644 --- a/veritas/src/main/scala/core/Veritas.scala +++ b/veritas/src/main/scala/core/Veritas.scala @@ -4,7 +4,7 @@ import org.reflections.Reflections import org.reflections.scanners.Scanners.TypesAnnotated import org.reflections.util.ConfigurationBuilder import posharp.Main.writeToFile -import posharp.{Parser, ToAssembly} +import posharp.{Expr, Parser, ToAssembly} import java.io.File import java.lang.reflect.Method @@ -185,7 +185,7 @@ object Veritas { if (calculateCoverage) cov.AddCoverage(parsed) - this.synchronized(Success(ToAssembly.convertMain(parsed))) + this.synchronized(Success(ToAssembly.convertMain(parsed, "", Map[String, Expr.TopLevel]()))) } catch { case e: Exception => Failure(e) } From 4b95674a525b52da394cb8d1a0995c817bb48e5c Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Thu, 28 Jul 2022 17:27:35 +0300 Subject: [PATCH 059/112] Fixed tests ? --- Makefile | 5 +++-- veritas/src/main/scala/core/Veritas.scala | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 9653a5c..55ea418 100644 --- a/Makefile +++ b/Makefile @@ -6,8 +6,9 @@ all: run_llvm build: mkdir -p compiled && \ cd compiled/ && \ - nasm -felf64 $(TARGET_FILE).asm && \ - gcc -O0 -ggdb -no-pie $(TARGET_FILE).o -o $(TARGET_FILE) + llc $(TARGET_FILE).ll && \ + gcc -O0 -ggdb -no-pie $(TARGET_FILE).s -o $(TARGET_FILE) + compiled/$(TARGET_FILE) #compile all files in directory .PHONY: build_all diff --git a/veritas/src/main/scala/core/Veritas.scala b/veritas/src/main/scala/core/Veritas.scala index 8b558ec..da50ba6 100644 --- a/veritas/src/main/scala/core/Veritas.scala +++ b/veritas/src/main/scala/core/Veritas.scala @@ -164,7 +164,7 @@ object Veritas { * Deletes all files created by writeToFile and the tests. */ private def deleteTestArtifacts(): Unit = { - new File("compiled") + new File("../compiled") .listFiles .filter(_.isFile) .filter(_.getName.contains("test")) @@ -199,10 +199,10 @@ object Veritas { * @return The last thing printed by the code */ def GetOutput(asm: String, fileName: String): String = { - writeToFile(asm, "compiled/", s"$fileName.asm") + writeToFile(asm, "../compiled/", s"$fileName.ll") val prefix = if (IsWindows()) {"wsl "} else {""} - val tmp = Process(prefix + s"make -f ../Makefile TARGET_FILE=$fileName").!! + val tmp = Process(prefix + s"cd .. && make build TARGET_FILE=\"$fileName\"").!! tmp.split("\n").last.trim } From 90da93627c156e27b2ab6fb25a0a019374b2865c Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Sat, 30 Jul 2022 16:55:04 +0300 Subject: [PATCH 060/112] Updated some gradle stuff --- gradle.properties | 1 - gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 14e9409..ee158f2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,4 +3,3 @@ org.gradle.configureondemand=true org.gradle.parallel=true org.gradle.caching=true org.gradle.daemon=true -org.gradle.unsafe.configuration-cache=true diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 69a9715..8049c68 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 3fb413f55e97ddb55612cd2982d071a540d02abc Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Sat, 30 Jul 2022 16:57:26 +0300 Subject: [PATCH 061/112] Moved some code around and fixed pipeline bug --- veritas/src/main/scala/core/FileHelpers.scala | 59 +++++++++++++++++++ veritas/src/main/scala/core/PoSharp.scala | 4 +- veritas/src/main/scala/core/Veritas.scala | 47 +-------------- 3 files changed, 62 insertions(+), 48 deletions(-) create mode 100644 veritas/src/main/scala/core/FileHelpers.scala diff --git a/veritas/src/main/scala/core/FileHelpers.scala b/veritas/src/main/scala/core/FileHelpers.scala new file mode 100644 index 0000000..db2e9da --- /dev/null +++ b/veritas/src/main/scala/core/FileHelpers.scala @@ -0,0 +1,59 @@ +package core + +import posharp.Main.writeToFile + +import java.io.File +import scala.sys.process._ + +object FileHelpers { + + /** + * Writes the code to a file, executes it and returns the output. + * + * @param asm Assembly + * @param fileName The filename + * @return The last thing printed by the code + */ + def GetOutput(asm: String, fileName: String): String = { + writeToFile(asm, "../compiled/", s"$fileName.ll") + + val prefix = if (IsWindows()) { + "wsl " + } else { + "" + } + + val cmd = s"$prefix make build -B TARGET_FILE=$fileName" + + val tmp = Process.apply(cmd, Option(new File("../"))).!! + + tmp.split("\n").last.trim + } + + private def IsWindows(): Boolean = System.getProperty("os.name").toLowerCase().contains("windows") + + /** + * Creates a unique-enoughâ„¢ filename for the current test by concatenating the class name the test comes from with + * the test name itself. + * + * @return The test name + */ + def getTestName: String = { + val stackTrace = new Throwable().getStackTrace()(2) + val className = stackTrace.getClassName + val methodName = stackTrace.getMethodName + + s"$className.$methodName" + } + + /** + * Deletes all files created by writeToFile and the tests. + */ + def deleteTestArtifacts(): Unit = { + new File("../compiled") + .listFiles + .filter(_.isFile) + .filter(_.getName.contains("test")) + .foreach(el => el.delete()) + } +} diff --git a/veritas/src/main/scala/core/PoSharp.scala b/veritas/src/main/scala/core/PoSharp.scala index f2a2047..2addade 100644 --- a/veritas/src/main/scala/core/PoSharp.scala +++ b/veritas/src/main/scala/core/PoSharp.scala @@ -54,7 +54,7 @@ object PoSharp { def Run(): (Boolean, String) = { Veritas.Compile(code) match { case Success(value) => - val output = Veritas.GetOutput(value, Veritas.getTestName) + val output = FileHelpers.GetOutput(value, FileHelpers.getTestName) if (expected == output) { (true, expected) @@ -63,7 +63,7 @@ object PoSharp { } case Failure(exception) => println(s"Compilation failed with $exception") - throw exception + throw exception } } diff --git a/veritas/src/main/scala/core/Veritas.scala b/veritas/src/main/scala/core/Veritas.scala index da50ba6..47b0d58 100644 --- a/veritas/src/main/scala/core/Veritas.scala +++ b/veritas/src/main/scala/core/Veritas.scala @@ -1,18 +1,16 @@ package core +import core.FileHelpers.deleteTestArtifacts import org.reflections.Reflections import org.reflections.scanners.Scanners.TypesAnnotated import org.reflections.util.ConfigurationBuilder -import posharp.Main.writeToFile import posharp.{Expr, Parser, ToAssembly} -import java.io.File import java.lang.reflect.Method import java.util.concurrent.{Executors, TimeUnit} import scala.collection.mutable import scala.io.AnsiColor._ import scala.reflect.internal.util.ScalaClassLoader -import scala.sys.process.Process import scala.util.{Failure, Success, Try} object Veritas { @@ -160,17 +158,6 @@ object Veritas { exitCode } - /** - * Deletes all files created by writeToFile and the tests. - */ - private def deleteTestArtifacts(): Unit = { - new File("../compiled") - .listFiles - .filter(_.isFile) - .filter(_.getName.contains("test")) - .foreach(el => el.delete()) - } - /** * Parses and compiles the code to asm * @@ -191,38 +178,6 @@ object Veritas { } } - /** - * Writes the code to a file, executes it and returns the output. - * - * @param asm Assembly - * @param fileName The filename - * @return The last thing printed by the code - */ - def GetOutput(asm: String, fileName: String): String = { - writeToFile(asm, "../compiled/", s"$fileName.ll") - - val prefix = if (IsWindows()) {"wsl "} else {""} - val tmp = Process(prefix + s"cd .. && make build TARGET_FILE=\"$fileName\"").!! - - tmp.split("\n").last.trim - } - - private def IsWindows(): Boolean = System.getProperty("os.name").toLowerCase().contains("windows") - - /** - * Creates a unique-enoughâ„¢ filename for the current test by concatenating the class name the test comes from with - * the test name itself. - * - * @return The test name - */ - def getTestName: String = { - val stackTrace = new Throwable().getStackTrace()(2) - val className = stackTrace.getClassName - val methodName = stackTrace.getMethodName - - s"$className.$methodName" - } - class Test extends scala.annotation.ConstantAnnotation {} case class InvalidReturnTypeException(msg: String) extends RuntimeException(msg) From b2ed0887dc757c8610a17db077f4d18a94e56c1d Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Sat, 30 Jul 2022 16:58:52 +0300 Subject: [PATCH 062/112] Created docker image for running the tests --- .dockerignore | 2 ++ Dockerfile | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..1cb8379 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +bin/ +build/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..70b1e0d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,20 @@ +# antoniosbarotsis/posharp-veritas +# Packages all dependencies needed to run the tests + +FROM openjdk:17-jdk-slim + +RUN apt-get update && apt-get install -y \ + make \ + llvm \ + gcc \ + curl \ + nano \ + && apt-get clean \ + && rm -rf /var/cache/apt/archives /var/lib/apt/lists/* + +# Cache dependencies hopefully +COPY *.gradle gradle.* gradlew ./ +COPY gradle/ ./gradle/ + +# Make the build fail so the dependencies get resolved +RUN ./gradlew clean build --no-daemon 2>&1 || true From e8b317fb4eaa70ef2e69a1c1703130bc6394a833 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Sat, 30 Jul 2022 17:26:18 +0300 Subject: [PATCH 063/112] Updated toast file --- toast.yml | 49 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/toast.yml b/toast.yml index 28f810a..a3ae25b 100644 --- a/toast.yml +++ b/toast.yml @@ -1,29 +1,48 @@ -image: gradle:7.4.2-jdk17 +image: antoniosbarotsis/posharp-veritas command_prefix: set -e # Make Bash fail fast. tasks: - install_deps: + build_dependencies: command: | - apt-get update - apt-get install make nasm gcc -y - - # Dev dependencies (not for CI runner) - if [[ -z "${CI}" ]]; then - apt-get install micro -y - fi + ./gradlew clean build --no-daemon 2>&1 || true + input_paths: + - gradlew + - .gradle + - gradle + - build.gradle.kts + - gradle.properties + - settings.gradle.kts + - app/build.gradle.kts + - app/settings.gradle.kts + - veritas/build.gradle.kts + - veritas/settings.gradle.kts build: + command: | + # Skip separate build stage locally to save time + if [[ -z "${CI}" ]]; then + echo "Not in CI environment, skipping build stage..." + else + ./gradlew clean build --no-daemon + fi dependencies: - - install_deps + - build_dependencies + excluded_input_paths: + - 'bin' + - 'build' input_paths: - - . - command: gradle build + - '.' test: - environment: - args: '' + cache: false + command: | + ./gradlew runCoverage $args --no-daemon dependencies: + - build_dependencies - build + environment: + args: '' + input_paths: + - '.' output_paths: - coverage.json - command: gradle runCoverage $args \ No newline at end of file From 3cf7441ba2619bd28758980b6e317bc25519c53b Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Sat, 30 Jul 2022 18:40:05 +0300 Subject: [PATCH 064/112] Fixed missing file issue --- toast.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/toast.yml b/toast.yml index a3ae25b..f46cbff 100644 --- a/toast.yml +++ b/toast.yml @@ -5,9 +5,9 @@ tasks: build_dependencies: command: | ./gradlew clean build --no-daemon 2>&1 || true + description: "Builds and caches all the used libraries to speedup subsequent steps." input_paths: - gradlew - - .gradle - gradle - build.gradle.kts - gradle.properties @@ -27,6 +27,7 @@ tasks: fi dependencies: - build_dependencies + description: "Performs a `gradle build`. Only runs in the CI environment to save time." excluded_input_paths: - 'bin' - 'build' @@ -40,6 +41,7 @@ tasks: dependencies: - build_dependencies - build + description: "Runs the tests and generates a coverage report." environment: args: '' input_paths: From 13dae047565f2ed02eef5246f343e40e143d373c Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Sat, 30 Jul 2022 18:42:24 +0300 Subject: [PATCH 065/112] Made the gradle wrapper executable --- toast.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/toast.yml b/toast.yml index f46cbff..60b4d0f 100644 --- a/toast.yml +++ b/toast.yml @@ -19,6 +19,9 @@ tasks: build: command: | + # Make the wrapper executable + chmod +x gradlew + # Skip separate build stage locally to save time if [[ -z "${CI}" ]]; then echo "Not in CI environment, skipping build stage..." From 88ffd3ad23d432839739180ee4635817d785a72c Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Sat, 30 Jul 2022 18:43:40 +0300 Subject: [PATCH 066/112] Made the gradle wrapper executable? --- toast.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toast.yml b/toast.yml index 60b4d0f..2dd5b68 100644 --- a/toast.yml +++ b/toast.yml @@ -20,7 +20,7 @@ tasks: build: command: | # Make the wrapper executable - chmod +x gradlew + chmod +x ./gradlew # Skip separate build stage locally to save time if [[ -z "${CI}" ]]; then From 222644937a83270a8a2c95b6ba6b40adbdecb31b Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Sat, 30 Jul 2022 18:44:58 +0300 Subject: [PATCH 067/112] Made the gradle wrapper executable! --- gradlew | 0 toast.yml | 3 --- 2 files changed, 3 deletions(-) mode change 100644 => 100755 gradlew diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/toast.yml b/toast.yml index 2dd5b68..f46cbff 100644 --- a/toast.yml +++ b/toast.yml @@ -19,9 +19,6 @@ tasks: build: command: | - # Make the wrapper executable - chmod +x ./gradlew - # Skip separate build stage locally to save time if [[ -z "${CI}" ]]; then echo "Not in CI environment, skipping build stage..." From e39368d7a3250ab98baa416b47084e191fd12186 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Sat, 30 Jul 2022 18:49:24 +0300 Subject: [PATCH 068/112] Fixed env variable --- toast.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/toast.yml b/toast.yml index f46cbff..5d389ba 100644 --- a/toast.yml +++ b/toast.yml @@ -28,6 +28,8 @@ tasks: dependencies: - build_dependencies description: "Performs a `gradle build`. Only runs in the CI environment to save time." + environment: + CI: '' excluded_input_paths: - 'bin' - 'build' From da72aec87da7ca4932737fad252d845822d193ba Mon Sep 17 00:00:00 2001 From: pijuskri Date: Mon, 12 Sep 2022 19:44:47 +0200 Subject: [PATCH 069/112] overloading and type inference --- app/src/main/scala/posharp/Definitions.scala | 5 +- app/src/main/scala/posharp/Parser.scala | 19 +------ app/src/main/scala/posharp/ToAssembly.scala | 58 ++++++++++++++------ docs/tasklist.md | 2 - 4 files changed, 47 insertions(+), 37 deletions(-) diff --git a/app/src/main/scala/posharp/Definitions.scala b/app/src/main/scala/posharp/Definitions.scala index 130ea06..4ae4760 100644 --- a/app/src/main/scala/posharp/Definitions.scala +++ b/app/src/main/scala/posharp/Definitions.scala @@ -28,7 +28,8 @@ object Expr{ case class Print(value: Expr) extends Expr case class SetVal(variable: Expr, value: Expr) extends Expr - case class DefVal(variable: Expr, varType: Type) extends Expr + case class DefVal(variable: String, varType: Type) extends Expr + case class DefValWithValue(variable: String, varType: Type, value: Expr) extends Expr case class Block(lines: List[Expr]) extends Expr case class ExtendBlock(lines: List[Expr]) extends Expr case class While(condition: Expr, execute: Expr.Block) extends Expr @@ -79,6 +80,7 @@ object Type { case class Character() extends Type case class Array(elemType: Type) extends Type case class Bool() extends Type + case class ConstantString() extends Type //case class Interface(properties: List[InputVar]) extends Type case class Interface(name: String, properties: List[InputVar], functions: List[FunctionInfo]) extends Type case class StaticInterface(properties: List[InputVar], functions: List[FunctionInfo]) extends Type @@ -124,6 +126,7 @@ object Type { case Bool() => "i1" case Array(inner) => s"%Type.array.${toLLVM(inner)}*" case Undefined() => "void" + case ConstantString() => "i8*" //should be avoided, as usertype could be not a class case UserType(name) => s"%Class.$name*" case Interface(name, _, _) => s"%Class.$name*" diff --git a/app/src/main/scala/posharp/Parser.scala b/app/src/main/scala/posharp/Parser.scala index 935b217..89a4a51 100644 --- a/app/src/main/scala/posharp/Parser.scala +++ b/app/src/main/scala/posharp/Parser.scala @@ -67,8 +67,8 @@ object Parser { numberFloat | number | ident | constant | str | char | trueC | falseC) def defVal[_: P]: P[Expr.DefVal] = P("val " ~/ ident ~ typeDef.?).map { - case (ident, Some(varType)) => Expr.DefVal(ident, varType) - case (ident, None) => Expr.DefVal(ident, Type.Undefined()) + case (ident, Some(varType)) => Expr.DefVal(ident.name, varType) + case (ident, None) => Expr.DefVal(ident.name, Type.Undefined()) } def accessVar[_: P]: P[Expr] = P(ident ~ ((".".! ~ ident ~ "(".! ~ prefixExpr.rep(sep = ",") ~ ")") | (".".! ~ ident) | ("[".! ~/ prefixExpr ~ "]")).rep).map { case (start, acs) => @@ -104,7 +104,7 @@ object Parser { } } - def defAndSetVal[_: P] = P(defVal ~ "=" ~ prefixExpr).map(x => Expr.ExtendBlock(List(x._1, Expr.SetVal(x._1.variable, x._2)))) + def defAndSetVal[_: P] = P(defVal ~ "=" ~/ prefixExpr).map(x => Expr.DefValWithValue(x._1.variable, x._1.varType, x._2)) def defineLambda[_: P]: P[Expr.Lambda] = P("lambda" ~/ "(" ~ (ident ~ typeDef).rep(sep=",") ~ ")" ~ typeDef ~ "=>" ~/ (block | prefixExpr) ).map{ case (args, ret, body) => { val argsf = args.map(x=>InputVar(x._1.name, x._2)).toList @@ -155,19 +155,6 @@ object Parser { case (value, "toChar") => Expr.Convert(value, Type.Character()) } - /* - def binOp[_: P] = P(prefixExpr ~ StringIn("+", "-", "*", "/", "==", ">", "<").! ~/ prefixExpr).map({ - case (l, "+", r) => Expr.Plus(l, r) - case (l, "-", r) => Expr.Minus(l, r) - case (l, "*", r) => Expr.Mult(l, r) - case (l, "/", r) => Expr.Div(l, r) - case (l, "==", r) => Expr.Equals(l,r) - case (l, "<", r) => Expr.LessThan(l,r) - case (l, ">", r) => Expr.MoreThan(l,r) - case _ => throw new ParseException("not bin p[") - }) - */ - def callFunction[_: P]: P[Expr.CallF] = P(ident ~ "(" ~/ prefixExpr.rep(sep = ",") ~/ ")").map { case (name, args) => Expr.CallF(name.name, args.toList); }//.filter((x) => !reservedKeywords.contains(x.name)) diff --git a/app/src/main/scala/posharp/ToAssembly.scala b/app/src/main/scala/posharp/ToAssembly.scala index d2d1dcf..9a8b46c 100644 --- a/app/src/main/scala/posharp/ToAssembly.scala +++ b/app/src/main/scala/posharp/ToAssembly.scala @@ -2,6 +2,8 @@ package posharp import posharp.Type.{UserType, shortS} +import scala.io.AnsiColor + class Counter { private var counter = 1; private var counterExtra = 0; @@ -23,6 +25,12 @@ class Counter { def pauseToggle(): Unit = { paused = !paused; } + def pause(): Unit = { + paused = true; + } + def unpause(): Unit = { + paused = false; + } } object ToAssembly { @@ -193,6 +201,7 @@ object ToAssembly { } case None => throw new Exception(s"no such interface defined") } + //case Expr.Str(value) => (defineString(value), Type.Str()) /* case Expr.Convert(value, valType: Type) => (convert(value, reg, env), valType) match { case ((code, Type.Num()), Type.NumFloat()) => (code + convertToFloat(reg.head), valType) @@ -201,7 +210,7 @@ object ToAssembly { case ((code, l), r) => throw new Exception(s"cant convert from type ${l} to type $r") } - //case Expr.Str(value) => (defineString(value, reg), Type.Str()) + // case Expr.Str(value) => (defineArrayKnown(value.length, Type.Character(), value.map(x=>Expr.Character(x)).toList, env)._1, Type.Array(Type.Character())) case Expr.Character(value) => (s"mov ${reg.head}, ${value.toInt}\n", Type.Character()) @@ -234,10 +243,20 @@ object ToAssembly { val set = s"store ${Type.toLLVM(converted._2)} ${converted._3}, ${Type.toLLVM(converted._2)}* %$name\n" converted._1 + set; }; - case Expr.DefVal(Expr.Ident(name), varType) => { + case Expr.DefVal(name, varType) => { newenv = newVar(name, varType, newenv); s"%$name = alloca ${Type.toLLVM(varType)}\n" } + case Expr.DefValWithValue(variable, varType, value) => { + var newType = varType + val converted = convertLoc(value, env); + if(newType == Type.Undefined()) newType = converted._2; + if(makeUserTypesConcrete(newType) != converted._2) throw new Exception(s"mismatch when assigning value" + + s" to variable $variable, expected ${newType}, but got ${converted._2}") + val set = s"store ${Type.toLLVM(converted._2)} ${converted._3}, ${Type.toLLVM(converted._2)}* %$variable\n"; + newenv = newVar(variable, newType, newenv); + s"%$variable = alloca ${Type.toLLVM(newType)}\n" + converted._1 + set; + } case Expr.If(condition, ifTrue, ifFalse) => { def compare(left: Expr, right: Expr, numeric: Boolean): String = compareExpr(left, right, numeric, "eq", "oeq", env) @@ -288,14 +307,14 @@ object ToAssembly { } case (_, valType, _) => throw new Exception(s"expected an interface, got ${valType}") } - /* case Expr.ThrowException(err) => { val msg = AnsiColor.RED + "RuntimeException: " + err + AnsiColor.RESET - val name = s"exception_print_${stringLiterals.length}" - stringLiterals = stringLiterals :+ s"$name:\n db \"${msg}\", 10, 0\n" - s"mov rax, $name\n" + printTemplate("format_string") + "jmp exception\n" + val label = s"string.${stringLiterals.length}"; + val n = msg.length+1; + stringLiterals = stringLiterals :+ s"@$label = internal constant [$n x i8] c\"$msg\"" + val name = s"exception_print.${stringLiterals.length}" + s"mov rax, $name\n" + printTemplate("format_string", "i8*") + "jmp exception\n" } - */ case x@Expr.CallObjFunc(obj, func) => convert(x, env)._1; case x@Expr.CallF(n, a) => convert(x, env)._1; case Expr.Return(in) => { @@ -429,14 +448,15 @@ object ToAssembly { } def fNameSignature(info: FunctionInfo): String = fNameSignature(info.name, info.args.map(x=>x.varType)) - def fNameSignature(name: String, args: List[Type]):String = name + (if(args.isEmpty) "" else "_") + args.map(x=>shortS(x)).mkString + def fNameSignature(name: String, args: List[Type]):String = name + (if(args.isEmpty) "" else ".") + args.map(x=>shortS(x)).mkString private def defineFunctions(input: List[(Expr.Func, Env)], lambdaMode: Boolean): String = { input.map{ case (function, upperScope) => { val info = functions.find(x=>x.name == function.name && x.args==function.argNames).get; functionScope = info; + val fname = fNameSignature(info) val args = info.args.map(x=>s"${Type.toLLVM(x.varType)} %Input.${x.name}").mkString(", ") - var ret = s"define ${Type.toLLVM(info.retType)} @${info.name}($args) {\n" + var ret = s"define ${Type.toLLVM(info.retType)} @${fname}($args) {\n" val newEnv = upperScope ++ info.args.map(x=> (x.name, Variable(x.varType))).toMap var body = info.args.map(x=> s"%${x.name} = alloca ${Type.toLLVM(x.varType)}\n" + @@ -463,10 +483,11 @@ object ToAssembly { case Some(x) => ; case None => throw new Exception(s"function of name $name undefined"); } functions.find(x=>x.name == name && Type.compare(argInputTypes, x.args.map(x=>makeUserTypesConcrete(x.varType)))) match { - case Some(FunctionInfo(p, argTypes, retType)) => { + case Some(info@FunctionInfo(p, argTypes, retType)) => { + val tName = fNameSignature(info) val argTypeString = argTypes.map(x=>Type.toLLVM(x.varType)).mkString(", ") if (retType != Type.Undefined()) ret += s"${varc.next()} = "; - ret += s"call ${Type.toLLVM(retType)} ($argTypeString) @$name($argsString)\n" + ret += s"call ${Type.toLLVM(retType)} ($argTypeString) @$tName($argsString)\n" (ret, retType) } case None => { @@ -518,15 +539,16 @@ object ToAssembly { } case (_, x) => throw new Exception(s"Can not call variable of type $x"); } - + */ def defineString(value: String, reg: List[String]): String = { //TODO Make definitions dynamic also //var ret = s"mov rdi, ${value.length+1}\n" + s"mov rsi, 8\n" + "call calloc\n" + "push rax\n" + "mov r9, rax\n"; - val label = s"string_${stringLiterals.length}"; - stringLiterals = stringLiterals :+ s"$label:\n db \"${value}\", 10, 0\n" - s"mov ${reg.head}, $label\n" + val label = s"string.${stringLiterals.length}"; + val n = value.length+1; + stringLiterals = stringLiterals :+ s"@$label = internal constant [$n x i8] c\"$value\"" + s"${varc.next()} = getelementptr inbounds ([$n x i8]* @$label, i32 0, i32 0)" } - */ + def getArrayPointerIndex(arrType: Type, arrLoc: String, indexLoc: String): String = { val Tsig = Type.toLLVM(arrType) val arrTC = s"%Type.array.$Tsig" @@ -633,9 +655,9 @@ object ToAssembly { (ret._1, ret._2, varc.last()) } def convertType(input: Expr, env: Env): Type = { - varc.pauseToggle() + varc.pause() val ret = convert(input, env)._2 - varc.pauseToggle() + varc.unpause() ret } def intBinOpTemplate(codeLeft: String, vLeft: String, codeRight: String, vRight: String, command: String): (String, Type) = { diff --git a/docs/tasklist.md b/docs/tasklist.md index 3613ef4..64786ae 100644 --- a/docs/tasklist.md +++ b/docs/tasklist.md @@ -1,7 +1,5 @@ ## LLVM migration -* fix overloading -* allow for no type declaration * strings * conversions * lambdas From 4d9a5fa460125b542441ef0fd7aca209a2931d48 Mon Sep 17 00:00:00 2001 From: Pijus Krisiukenas Date: Mon, 12 Sep 2022 21:31:03 +0200 Subject: [PATCH 070/112] strings, exceptions --- app/src/main/scala/posharp/Definitions.scala | 4 +- app/src/main/scala/posharp/Main.scala | 107 ++++++++++++++++++- app/src/main/scala/posharp/Parser.scala | 2 +- app/src/main/scala/posharp/ToAssembly.scala | 44 ++++---- docs/tasklist.md | 2 - 5 files changed, 129 insertions(+), 30 deletions(-) diff --git a/app/src/main/scala/posharp/Definitions.scala b/app/src/main/scala/posharp/Definitions.scala index 4ae4760..f363452 100644 --- a/app/src/main/scala/posharp/Definitions.scala +++ b/app/src/main/scala/posharp/Definitions.scala @@ -80,7 +80,7 @@ object Type { case class Character() extends Type case class Array(elemType: Type) extends Type case class Bool() extends Type - case class ConstantString() extends Type + case class Str() extends Type //case class Interface(properties: List[InputVar]) extends Type case class Interface(name: String, properties: List[InputVar], functions: List[FunctionInfo]) extends Type case class StaticInterface(properties: List[InputVar], functions: List[FunctionInfo]) extends Type @@ -126,7 +126,7 @@ object Type { case Bool() => "i1" case Array(inner) => s"%Type.array.${toLLVM(inner)}*" case Undefined() => "void" - case ConstantString() => "i8*" + case Str() => "i8*" //should be avoided, as usertype could be not a class case UserType(name) => s"%Class.$name*" case Interface(name, _, _) => s"%Class.$name*" diff --git a/app/src/main/scala/posharp/Main.scala b/app/src/main/scala/posharp/Main.scala index a2b57cc..fd329ff 100644 --- a/app/src/main/scala/posharp/Main.scala +++ b/app/src/main/scala/posharp/Main.scala @@ -7,7 +7,6 @@ package object Constants { val FileExtension = ".txt" } - object Main extends App { var sourceDir = "po_src" @@ -34,6 +33,7 @@ object Main extends App { try { asm = ToAssembly.convertMain(code, file, declarations.filter(x => x._1 != file)); + asm += StringCode.stringCode; } catch { case x: Exception => { @@ -69,7 +69,112 @@ object Main extends App { val these = f.listFiles these ++ these.filter(x=>x.isDirectory).flatMap(x=>recursiveListFiles(x)) } + } +object StringCode { + val stringCode: String = """; The actual type definition for our 'String' type. + |%String = type { + | i8*, ; 0: buffer; pointer to the character buffer + | i32, ; 1: length; the number of chars in the buffer + | i32, ; 2: maxlen; the maximum number of chars in the buffer + | i32 ; 3: factor; the number of chars to preallocate when growing + |} + | + |define fastcc void @String_Create_Default(%String* %this) nounwind { + | ; Initialize 'buffer'. + | %1 = getelementptr %String, %String* %this, i32 0, i32 0 + | store i8* null, i8** %1 + | + | ; Initialize 'length'. + | %2 = getelementptr %String, %String* %this, i32 0, i32 1 + | store i32 0, i32* %2 + | + | ; Initialize 'maxlen'. + | %3 = getelementptr %String, %String* %this, i32 0, i32 2 + | store i32 0, i32* %3 + | + | ; Initialize 'factor'. + | %4 = getelementptr %String, %String* %this, i32 0, i32 3 + | store i32 16, i32* %4 + | + | ret void + |} + | + | + |define fastcc void @String_Delete(%String* %this) nounwind { + | ; Check if we need to call 'free'. + | %1 = getelementptr %String, %String* %this, i32 0, i32 0 + | %2 = load i8*, i8** %1 + | %3 = icmp ne i8* %2, null + | br i1 %3, label %free_begin, label %free_close + | + |free_begin: + | call void @free(i8* %2) + | br label %free_close + | + |free_close: + | ret void + |} + | + |define fastcc void @String_Resize(%String* %this, i32 %value) { + | ; %output = malloc(%value) + | %output = call i8* @malloc(i32 %value) + | + | ; todo: check return value + | + | ; %buffer = this->buffer + | %1 = getelementptr %String, %String* %this, i32 0, i32 0 + | %buffer = load i8*, i8** %1 + | + | ; %length = this->length + | %2 = getelementptr %String, %String* %this, i32 0, i32 1 + | %length = load i32, i32* %2 + | + | ; memcpy(%output, %buffer, %length) + | %3 = call i8* @memcpy(i8* %output, i8* %buffer, i32 %length) + | + | ; free(%buffer) + | call void @free(i8* %buffer) + | + | ; this->buffer = %output + | store i8* %output, i8** %1 + | + | ;this->maxlen = %value (value that was passed into @malloc is the new maxlen) + | %4 = getelementptr %String, %String* %this, i32 0, i32 2 + | store i32 %value, i32* %4 + | ret void + |} + | + |define fastcc void @String_Add_Char(%String* %this, i8 %value) { + | ; Check if we need to grow the string. + | %1 = getelementptr %String, %String* %this, i32 0, i32 1 + | %length = load i32, i32* %1 + | %2 = getelementptr %String, %String* %this, i32 0, i32 2 + | %maxlen = load i32, i32* %2 + | ; if length == maxlen: + | %3 = icmp eq i32 %length, %maxlen + | br i1 %3, label %grow_begin, label %grow_close + | + |grow_begin: + | %4 = getelementptr %String, %String* %this, i32 0, i32 3 + | %factor = load i32, i32* %4 + | %5 = add i32 %maxlen, %factor + | call void @String_Resize(%String* %this, i32 %5) + | br label %grow_close + | + |grow_close: + | %6 = getelementptr %String, %String* %this, i32 0, i32 0 + | %buffer = load i8*, i8** %6 + | %7 = getelementptr i8, i8* %buffer, i32 %length + | store i8 %value, i8* %7 + | %8 = add i32 %length, 1 + | store i32 %8, i32* %1 + | + | ret void + |}""".stripMargin +} + + diff --git a/app/src/main/scala/posharp/Parser.scala b/app/src/main/scala/posharp/Parser.scala index 89a4a51..62876d3 100644 --- a/app/src/main/scala/posharp/Parser.scala +++ b/app/src/main/scala/posharp/Parser.scala @@ -142,7 +142,7 @@ object Parser { case "char" => Type.Character(); case "float" => Type.NumFloat(); case "bool" => Type.Bool(); - case "string" => Type.Array(Type.Character()); + case "string" => Type.Str(); case "void" => Type.Undefined(); case "T1" => Type.T1(); } diff --git a/app/src/main/scala/posharp/ToAssembly.scala b/app/src/main/scala/posharp/ToAssembly.scala index 9a8b46c..4251c31 100644 --- a/app/src/main/scala/posharp/ToAssembly.scala +++ b/app/src/main/scala/posharp/ToAssembly.scala @@ -54,18 +54,14 @@ object ToAssembly { lambdas = List() functionScope = FunctionInfo("main", List(), Type.Num()); - /* - converted += "format_num:\n db \"%d\", 10, 0\n" - converted += "format_float:\n db \"%f\", 10, 0\n" - converted += "format_string:\n db \"%s\", 10, 0\n" - converted += "format_char:\n db \"%c\", 10, 0\n" - converted += "format_true:\n db \"true\", 10, 0\n" - converted += "format_false:\n db \"false\", 10, 0\n" - */ var converted = """ | declare i32 @printf(i8*, ...) | declare i64* @calloc(i32, i32) + | declare i8* @malloc(i32) + | declare void @free(i8*) + | declare i8* @memcpy(i8*, i8*, i32) + | declare void @exit(i32) | @format_num = private constant [3 x i8] c"%d\00" | @format_float = private constant [3 x i8] c"%f\00" | @format_string = private constant [3 x i8] c"%s\00" @@ -94,8 +90,8 @@ object ToAssembly { ); }} //converted += defineFunctions(lambdas, true); - //converted += "exception:\nmov rdi, 1\ncall exit\n" - //converted += stringLiterals.mkString + //converted += "define void @exception() {\ncall void @exit(i32 1)\n}\n" + converted += stringLiterals.mkString converted } @@ -201,7 +197,7 @@ object ToAssembly { } case None => throw new Exception(s"no such interface defined") } - //case Expr.Str(value) => (defineString(value), Type.Str()) + case Expr.Str(value) => (defineString(value), Type.Str()) /* case Expr.Convert(value, valType: Type) => (convert(value, reg, env), valType) match { case ((code, Type.Num()), Type.NumFloat()) => (code + convertToFloat(reg.head), valType) @@ -308,12 +304,8 @@ object ToAssembly { case (_, valType, _) => throw new Exception(s"expected an interface, got ${valType}") } case Expr.ThrowException(err) => { - val msg = AnsiColor.RED + "RuntimeException: " + err + AnsiColor.RESET - val label = s"string.${stringLiterals.length}"; - val n = msg.length+1; - stringLiterals = stringLiterals :+ s"@$label = internal constant [$n x i8] c\"$msg\"" - val name = s"exception_print.${stringLiterals.length}" - s"mov rax, $name\n" + printTemplate("format_string", "i8*") + "jmp exception\n" + val msg = AnsiColor.RED + "RuntimeException: " + err + AnsiColor.RESET + "\n" + defineString(msg) + printTemplate("format_string", "i8*") + "call void @exit(i32 1)\n" // "br label %exception\n" } case x@Expr.CallObjFunc(obj, func) => convert(x, env)._1; case x@Expr.CallF(n, a) => convert(x, env)._1; @@ -540,13 +532,16 @@ object ToAssembly { case (_, x) => throw new Exception(s"Can not call variable of type $x"); } */ - def defineString(value: String, reg: List[String]): String = { - //TODO Make definitions dynamic also - //var ret = s"mov rdi, ${value.length+1}\n" + s"mov rsi, 8\n" + "call calloc\n" + "push rax\n" + "mov r9, rax\n"; + def defineString(value: String): String = { val label = s"string.${stringLiterals.length}"; - val n = value.length+1; - stringLiterals = stringLiterals :+ s"@$label = internal constant [$n x i8] c\"$value\"" - s"${varc.next()} = getelementptr inbounds ([$n x i8]* @$label, i32 0, i32 0)" + val n = value.length; + //%foo = bitcast i8* %1 to %Foo* + stringLiterals = stringLiterals :+ s"@$label = internal constant [$n x i8] c\"$value\"" // + //s"${varc.next()} = getelementptr inbounds i8*, i8** @$label, i32 0, i32 0" + s"${varc.next()} = alloca i8*\n" + + s"${varc.next()} = bitcast [$n x i8]* @$label to i8*\n" + //s"store i8* @$label, i8** ${varc.last()}\n" + + //s"${varc.next()} = load i8*, i8** ${varc.secondLast()}\n" } def getArrayPointerIndex(arrType: Type, arrLoc: String, indexLoc: String): String = { @@ -699,8 +694,9 @@ object ToAssembly { case Type.Num() => converted._1 + printTemplate("format_num", "i32"); case Type.Bool() => converted._1 + printTemplate("format_num", "i1"); case Type.NumFloat() => converted._1 + printTemplate("format_float", "double"); + case Type.Str() => converted._1 + printTemplate("format_string", "i8*"); /* - //case (Type.Str) => converted._1 + printTemplate("format_string"); + // case Type.Bool() => converted._1 + s"cmp rax, 0\nje bool_${ifCounter}\n" + printTemplate("format_true") + s"jmp boole_${ifCounter}\nbool_${ifCounter}:\n" + printTemplate("format_false") + s"boole_${ifCounter}:\n"; case Type.Character() => converted._1 + printTemplate("format_char"); diff --git a/docs/tasklist.md b/docs/tasklist.md index 64786ae..63cddda 100644 --- a/docs/tasklist.md +++ b/docs/tasklist.md @@ -1,9 +1,7 @@ ## LLVM migration -* strings * conversions * lambdas -* exceptions * imports ## To do From 3a6b07b93448285311926eb663e2ba5298ef2fa1 Mon Sep 17 00:00:00 2001 From: Pijus Krisiukenas Date: Sun, 18 Sep 2022 20:12:26 +0200 Subject: [PATCH 071/112] imports, arrays with default --- app/src/main/scala/posharp/Definitions.scala | 2 +- app/src/main/scala/posharp/Main.scala | 8 +- app/src/main/scala/posharp/ToAssembly.scala | 135 ++++++++++++------- build-llvm.sh | 2 +- docs/tasklist.md | 2 +- 5 files changed, 93 insertions(+), 56 deletions(-) diff --git a/app/src/main/scala/posharp/Definitions.scala b/app/src/main/scala/posharp/Definitions.scala index f363452..6aa97c0 100644 --- a/app/src/main/scala/posharp/Definitions.scala +++ b/app/src/main/scala/posharp/Definitions.scala @@ -68,7 +68,7 @@ object Expr{ case class ThrowException(errorMsg: String) extends Expr case class Nothing() extends Expr - case class Compiled(code: String, raxType: Type) extends Expr + case class Compiled(code: String, raxType: Type, loc: String) extends Expr case class RawReference() extends Expr } diff --git a/app/src/main/scala/posharp/Main.scala b/app/src/main/scala/posharp/Main.scala index fd329ff..fefaa7b 100644 --- a/app/src/main/scala/posharp/Main.scala +++ b/app/src/main/scala/posharp/Main.scala @@ -81,7 +81,7 @@ object StringCode { | i32 ; 3: factor; the number of chars to preallocate when growing |} | - |define fastcc void @String_Create_Default(%String* %this) nounwind { + |define private fastcc void @String_Create_Default(%String* %this) nounwind { | ; Initialize 'buffer'. | %1 = getelementptr %String, %String* %this, i32 0, i32 0 | store i8* null, i8** %1 @@ -102,7 +102,7 @@ object StringCode { |} | | - |define fastcc void @String_Delete(%String* %this) nounwind { + |define private fastcc void @String_Delete(%String* %this) nounwind { | ; Check if we need to call 'free'. | %1 = getelementptr %String, %String* %this, i32 0, i32 0 | %2 = load i8*, i8** %1 @@ -117,7 +117,7 @@ object StringCode { | ret void |} | - |define fastcc void @String_Resize(%String* %this, i32 %value) { + |define private fastcc void @String_Resize(%String* %this, i32 %value) { | ; %output = malloc(%value) | %output = call i8* @malloc(i32 %value) | @@ -146,7 +146,7 @@ object StringCode { | ret void |} | - |define fastcc void @String_Add_Char(%String* %this, i8 %value) { + |define private fastcc void @String_Add_Char(%String* %this, i8 %value) { | ; Check if we need to grow the string. | %1 = getelementptr %String, %String* %this, i32 0, i32 1 | %length = load i32, i32* %1 diff --git a/app/src/main/scala/posharp/ToAssembly.scala b/app/src/main/scala/posharp/ToAssembly.scala index 4251c31..d101e9f 100644 --- a/app/src/main/scala/posharp/ToAssembly.scala +++ b/app/src/main/scala/posharp/ToAssembly.scala @@ -1,11 +1,12 @@ package posharp -import posharp.Type.{UserType, shortS} +import posharp.Type.{UserType, shortS, toLLVM} import scala.io.AnsiColor class Counter { - private var counter = 1; + var counter = 1; + private var pausedCounter = 1; private var counterExtra = 0; private var paused = false; def next(): String = { @@ -22,14 +23,13 @@ class Counter { def reset(): Unit = { counter = 1; } - def pauseToggle(): Unit = { - paused = !paused; - } def pause(): Unit = { paused = true; + pausedCounter = counter; } def unpause(): Unit = { paused = false; + counter = pausedCounter; } } @@ -77,11 +77,9 @@ object ToAssembly { input match { case x: Expr.TopLevel => { declareFunctions(x); converted += declareInterfaces(x) + "\n"; - /* - declareEnums(x) - converted += exportDeclarations(currentFile) - converted += handleImports(x, otherFiles) - */ + //declareEnums(x) + converted += exportDeclarations(currentFile) + "\n" + converted += handleImports(x, otherFiles) + "\n" converted += defineFunctions(x.functions.map(y=>(y, Map())), false); converted += defineFunctions(x.interfaces.flatMap(intf=> intf.functions.map(func=>Expr.Func(intf.name + "_" + func.name, func.argNames, func.retType, func.body))) @@ -90,7 +88,6 @@ object ToAssembly { ); }} //converted += defineFunctions(lambdas, true); - //converted += "define void @exception() {\ncall void @exit(i32 1)\n}\n" converted += stringLiterals.mkString converted } @@ -153,15 +150,18 @@ object ToAssembly { }) (ret, Type.Bool()) } - case Expr.DefineArray(size, elemType, defaultValues) => conv(size) match { - case (code, Type.Num()) => defineArray(code, elemType, defaultValues, env) - case (_, x) => throw new Exception(s"not number when defining array size, got input of type $x") + case Expr.DefineArray(size, elemType, defaultValues) => convertLoc(size, env) match { + case (code, Type.Num(), loc) => { + val r = defineArray(loc, elemType, defaultValues, env) + (code + r._1, r._2) + } + case (_, x, _) => throw new Exception(s"not number when defining array size, got input of type $x") } case Expr.GetArray(name, index) => getArray(name, index, env); case Expr.ArraySize(name) => getArraySize(name, env); - case Expr.GetProperty(obj, prop) => convert(obj, env) match { - case(code, Type.Interface(name, props, funcs)) => props.find(x=>x.name == prop) match { + case Expr.GetProperty(obj, prop) => convertLoc(obj, env) match { + case(code, Type.Interface(name, props, funcs), loc) => props.find(x=>x.name == prop) match { case Some(n) => { val intfDec = s"%Class.$name" val idx = props.indexOf(n); @@ -175,8 +175,8 @@ object ToAssembly { //case (code, Type.StaticInterface(props, funcs)) => case (code, Type.Enum(el)) => (s"mov ${reg.head}, 0${el.indexOf(prop)}d\n", Type.Num()) */ - case (code, Type.Array(a)) if prop == "size" => getArraySize(Expr.Compiled(code, Type.Array(a)), env)//conv(Expr.ArraySize(obj)) - case valType => throw new Exception(s"expected a interface, got ${valType}") + case (code, Type.Array(a), loc) if prop == "size" => getArraySize(Expr.Compiled(code, Type.Array(a), loc), env)//conv(Expr.ArraySize(obj)) + case (_, valType, _) => throw new Exception(s"expected a interface, got ${valType}") } case Expr.CallF(name, args) => { if(functions.exists(x=>x.name == name)) interpFunction(name, args, env) @@ -191,11 +191,11 @@ object ToAssembly { case Some(intf) => { val struct_def = s"${varc.next()} = alloca %Class.$name\n" - val func_code = interpFunction(name+"_"+name, Expr.Compiled(struct_def, UserType(name)) +: values, env)._1 + val func_code = interpFunction(name+"_"+name, Expr.Compiled(struct_def, UserType(name), varc.last()) +: values, env)._1 val ret = func_code; (ret, Type.Interface(name, intf.args, intf.funcs)) } - case None => throw new Exception(s"no such interface defined") + case None => throw new Exception(s"no interface with name \"$name\" defined") } case Expr.Str(value) => (defineString(value), Type.Str()) /* @@ -218,7 +218,7 @@ object ToAssembly { } */ case Expr.Nothing() => ("", Type.Undefined()); - case Expr.Compiled(code, retType) => (code, retType); + case Expr.Compiled(code, retType, _) => (code, retType); case Expr.Block(lines) => convertBlock(lines, env); case x => throw new Exception (s"$x is not interpreted yet :("); } @@ -260,10 +260,11 @@ object ToAssembly { val falseLabel = s"if_${ifCounter}_false" val endLabel = s"if_${ifCounter}_end" ifCounter += 1; - val cond = convertType(condition, env) match { - case Type.Bool() => compare(condition, Expr.True(), false) + val cond = convertLoc(condition, env) match { + case (code, Type.Bool(), loc) => compare(Expr.Compiled(code, Type.Bool(), loc), Expr.True(), false) case t => throw new Exception(s"got type $t inside condition, expected bool") } + //val cond = compare(condition, Expr.True(), false) val ret = cond + s"br i1 ${varc.last()}, label %$trueLabel, label %$falseLabel\n" + s"${trueLabel}:\n" + convert(ifTrue, env)._1 + s"br label %$endLabel\n" + s"${falseLabel}:\n" + convert(ifFalse, env)._1 + s"br label %$endLabel\n" + s"$endLabel:\n" @@ -373,27 +374,33 @@ object ToAssembly { input.imports.map(imp=>{ if (!otherFiles.contains(imp.file)) throw new Exception(s"file \"${imp.file}\" could not be imported"); val top = otherFiles(imp.file) + var ret = "" val funcsForImport = searchFileDeclarations(top, imp) match { case Expr.Func(name, argnames, retType, code) => { functions = functions :+ FunctionInfo(name, argnames, retType) - List(fNameSignature(FunctionInfo(name, argnames, retType))) + List(FunctionInfo(name, argnames, retType)) } case Expr.DefineInterface(name, props, i_functions) => { val intf = InterfaceInfo(name, props, i_functions.map(x=>FunctionInfo(x.name,x.argNames,x.retType))) interfaces = interfaces :+ intf + + val types = intf.args.map(x=>Type.toLLVM(x.varType)).mkString(", ") + ret += s"%Class.${intf.name} = type {$types}\n" + val funcs = addPrefixToFunctions(intf.name,intf.funcs) functions = functions ::: funcs - funcs.map(x=>fNameSignature(FunctionInfo(x.name, x.args, x.retType))) + funcs.map(x=>FunctionInfo(x.name, x.args, x.retType)) } } - funcsForImport.map(x=>{ - val label = formatFName(imp.file) + "_" + x - s"extern $label\n" + s"${x}:\njmp $label\n" + ret + funcsForImport.map(info=>{ + val name = fNameSignature(info) + val importName = formatFName(imp.file) + "_" + name + val args = info.args.map(x=>s"${Type.toLLVM(x.varType)}").mkString(", ") + s"declare ${Type.toLLVM(info.retType)} @${importName}($args)\n" + + s"@$name = ifunc ${toLLVM(info)}, ${toLLVM(info)}* @${importName}\n" }).mkString /* - - intf.funcs.map(x=>{ val label = imp.file + "_" + x.name s"extern ${label}\n" + s"${x.name}:\njmp ${label}\n" @@ -417,7 +424,7 @@ object ToAssembly { .map(info => { val formatFile = formatFName(file) val name = fNameSignature(info) - s"global ${formatFile}_${name}\n" + s"${formatFile}_${name}:\njmp ${name}\n" + s"@${formatFile}_${name} = external alias ${toLLVM(info)}, ${toLLVM(info)}* @${name}\n" }).mkString } @@ -441,9 +448,14 @@ object ToAssembly { def fNameSignature(info: FunctionInfo): String = fNameSignature(info.name, info.args.map(x=>x.varType)) def fNameSignature(name: String, args: List[Type]):String = name + (if(args.isEmpty) "" else ".") + args.map(x=>shortS(x)).mkString + def toLLVM(info: FunctionInfo): String = { + val args = info.args.map(x=>s"${Type.toLLVM(x.varType)}").mkString(", ") + s"${Type.toLLVM(info.retType)} ($args)" + } private def defineFunctions(input: List[(Expr.Func, Env)], lambdaMode: Boolean): String = { input.map{ case (function, upperScope) => { + val info = functions.find(x=>x.name == function.name && x.args==function.argNames).get; functionScope = info; val fname = fNameSignature(info) @@ -467,7 +479,7 @@ object ToAssembly { } def interpFunction(name: String, args: List[Expr], env: Env ): (String, Type) = { val argRet = args.map (arg => convertLoc(arg, env)) - val argInputTypes = argRet.map(x => x._2) + val argInputTypes = argRet.map(x => makeUserTypesConcrete(x._2)) var ret = argRet.map(x=>x._1).mkString val argsString = argRet.map(x=>s"${Type.toLLVM(x._2)} ${x._3}").mkString(", ") @@ -483,7 +495,8 @@ object ToAssembly { (ret, retType) } case None => { - println(functions.find(x=>x.name == name).map(x=>x.args).mkString); + //println(Util.prettyPrint(functions.find(x=>x.name == name).map(x=>x.args.map(y=>InputVar(y.name,makeUserTypesConcrete(y.varType)))).get)); + println(Util.prettyPrint(argInputTypes.last)); throw new Exception(s"no overload of function $name matches argument list $argInputTypes") }; } @@ -551,9 +564,16 @@ object ToAssembly { var ret = ""; ret += s"${varc.next()} = getelementptr $arrTC, $arrTC* $arrLoc, i32 0, i32 1\n" val arrStructLoc = varc.last() - ret += s"${varc.next()} = getelementptr inbounds $Tsig*, $Tsig** ${arrStructLoc}, i32 $indexLoc\n" + /* + ret += s"${varc.next()} = getelementptr $Tsig*, $Tsig** ${arrStructLoc}, i32 $indexLoc\n" val arrElemLoc = varc.last() ret += s"${varc.next()} = load $Tsig*, $Tsig** ${arrElemLoc}\n" + + */ + ret += s"${varc.next()} = load $Tsig*, $Tsig** ${arrStructLoc}\n" + ret += s"${varc.next()} = getelementptr $Tsig, $Tsig* ${varc.secondLast()}, i32 $indexLoc\n" + val arrElemLoc = varc.last() + ret } def getArray(arr: Expr, index: Expr, env: Env): (String, Type) = (convertLoc(arr, env), convertLoc(index, env)) match { @@ -577,6 +597,7 @@ object ToAssembly { var ret = code + indexCode + valCode; ret += getArrayPointerIndex(arrType, arrLoc, indexLoc) + ret += s"store $Tsig $valLoc, $Tsig* ${varc.last()}\n" ret } @@ -594,15 +615,21 @@ object ToAssembly { case (_, varType, _) => throw new Exception(s"trying to access variable ${arr} as an array, has type $varType") } - def defineArray(size: String, setElemType:Type, defaultValues: List[Expr], env: Env): (String, Type) = { + def defineArray(sizeLoc: String, setElemType:Type, defaultValues: List[Expr], env: Env): (String, Type) = { var elemType: Type = setElemType; + var valuesConverted = defaultValues.map(entry => { + val converted = convertLoc(entry, env) + if(elemType == Type.Undefined()) elemType = converted._2; + else if(converted._2 != elemType) throw new Exception(s"array elements are of different types") + converted + }) val array_elem_size = arraySizeFromType(elemType); val Tsig = Type.toLLVM(elemType) val arrTC = s"%Type.array.$Tsig" - var ret = size; - val sizeLoc = varc.last() + var ret = ""; + ret += valuesConverted.map(x=>x._1).mkString ret += s"${varc.next()} = call i64* (i32, i32) @calloc(i32 $sizeLoc, i32 $array_elem_size)\n"; - ret += s"${varc.next()} = bitcast i64* ${varc.secondLast()} to $Tsig*" + ret += s"${varc.next()} = bitcast i64* ${varc.secondLast()} to $Tsig*\n" val arrLoc = varc.last() val sizeStructPointer = s"%arr.size.${varc.extra()}" val arrStructPointer = s"%arr.arr.${varc.extra()}" @@ -613,14 +640,16 @@ object ToAssembly { ret += s"store i32 $sizeLoc, i32* ${sizeStructPointer}\n" ret += s"${arrStructPointer} = getelementptr $arrTC, $arrTC* $structLoc, i32 0, i32 1\n" ret += s"store $Tsig* $arrLoc, $Tsig** ${arrStructPointer}\n" - /* - var ret = defaultValues.zipWithIndex.map{case (entry, index) => { - val converted = convert(entry, defaultReg, env) - if(elemType == Type.Undefined()) elemType = converted._2; - else if(converted._2 != elemType) throw new Exception(s"array elements are of different types") - converted._1 + setArrayDirect("[rsp]", skipArrSize(index, arraySizeFromType(elemType)), arraySizeFromType(elemType)); + + ret += valuesConverted.zipWithIndex.map{case ((code, retTy, loc), index) => { + //val arrcode = convertLoc(Expr.Ident("a"), env) + //val idxcode = convertLoc(Expr.Num(0), env) + //Expr.Compiled("", Type.Array(elemType), structLoc) + //Expr.Compiled(idxcode._1, idxcode._2, idxcode._3) + //Expr.Compiled("", retTy, loc) + setArray(Expr.Compiled("", Type.Array(elemType), structLoc), Expr.Num(index), Expr.Compiled("", retTy, loc), env) }}.mkString; - */ + ret += s"${varc.next()} = bitcast $arrTC* ${structLoc} to $arrTC*\n" (ret, Type.Array(elemType)) } def arraySizeFromType(valtype: Type): Int = valtype match { @@ -646,13 +675,21 @@ object ToAssembly { } def convertLoc(input: Expr, env: Env): (String, Type, String) = { - val ret = convert(input, env) - (ret._1, ret._2, varc.last()) + input match { + case Expr.Compiled(code, ret, loc) => (code, ret, loc) + case _ => { + val ret = convert(input, env) + (ret._1, ret._2, varc.last()) + } + } + } def convertType(input: Expr, env: Env): Type = { - varc.pause() + varc.pause(); + val c = varc.counter val ret = convert(input, env)._2 - varc.unpause() + varc.unpause(); + varc.counter = c; ret } def intBinOpTemplate(codeLeft: String, vLeft: String, codeRight: String, vRight: String, command: String): (String, Type) = { diff --git a/build-llvm.sh b/build-llvm.sh index 6d5173b..fa91f4b 100644 --- a/build-llvm.sh +++ b/build-llvm.sh @@ -22,4 +22,4 @@ done files="${files_asm[*]}" -gcc -O0 -ggdb -no-pie "$files" -o "hello" +gcc -O0 -ggdb -no-pie $files -o "hello" diff --git a/docs/tasklist.md b/docs/tasklist.md index 63cddda..78122de 100644 --- a/docs/tasklist.md +++ b/docs/tasklist.md @@ -2,7 +2,7 @@ * conversions * lambdas -* imports +* arrays with default value ## To do From 7672cae034bb5bf78b005251b5081243a9cf2228 Mon Sep 17 00:00:00 2001 From: Pijus Krisiukenas Date: Mon, 17 Oct 2022 19:42:47 +0200 Subject: [PATCH 072/112] dumb --- app/src/main/scala/posharp/Main.scala | 2 +- app/src/main/scala/posharp/Parser.scala | 9 +- app/src/main/scala/posharp/ToAssembly.scala | 118 ++++++++++++-------- build-llvm.sh | 2 +- 4 files changed, 83 insertions(+), 48 deletions(-) diff --git a/app/src/main/scala/posharp/Main.scala b/app/src/main/scala/posharp/Main.scala index fefaa7b..b275f05 100644 --- a/app/src/main/scala/posharp/Main.scala +++ b/app/src/main/scala/posharp/Main.scala @@ -33,7 +33,7 @@ object Main extends App { try { asm = ToAssembly.convertMain(code, file, declarations.filter(x => x._1 != file)); - asm += StringCode.stringCode; + //asm += StringCode.stringCode; } catch { case x: Exception => { diff --git a/app/src/main/scala/posharp/Parser.scala b/app/src/main/scala/posharp/Parser.scala index 62876d3..1bd593a 100644 --- a/app/src/main/scala/posharp/Parser.scala +++ b/app/src/main/scala/posharp/Parser.scala @@ -4,6 +4,7 @@ import fastparse.JavaWhitespace._ import fastparse._ import Expr.GetProperty +import scala.compat.Platform.EOL object Parser { //TODO fix issue when spacing at start of file @@ -211,7 +212,7 @@ object Parser { Expr.Block(List(input._1, Expr.While(input._2, Expr.Block(input._4.lines :+ input._3)))); }) - def str[_: P]: P[Expr.Str] = P("\"" ~~/ CharsWhile(_ != '"', 0).! ~~ "\"").map(x => Expr.Str(x + "\u0000")) + def str[_: P]: P[Expr.Str] = P("\"" ~~/ CharsWhile(_ != '"', 0).! ~~ "\"").map(x => Expr.Str(x.replace("\\n", ""+10.toChar) + "\u0000")) def fileName[_: P]: P[Expr.Str] = P("\"" ~~/ CharsWhile(_ != '"', 0).! ~~ "\"").map(x => Expr.Str(x)) def ident[_: P]: P[Expr.Ident] = P(CharIn("a-zA-Z_") ~~ CharsWhileIn("a-zA-Z0-9_", 0)).!.map((input) => { @@ -222,7 +223,11 @@ object Parser { def numberFloat[_: P]: P[Expr.NumFloat] = P("-".? ~~ CharsWhileIn("0-9", 1) ~~ "." ~~ CharsWhileIn("0-9", 1)).!.map(x => Expr.NumFloat(x.toFloat)) - def char[_: P]: P[Expr.Character] = P("'" ~/ AnyChar.! ~/ "'").map(x => Expr.Character(x.charAt(0))); + def char[_: P]: P[Expr.Character] = P("'" ~/ !"'" ~ (AnyChar.! | "\\n".!) ~/ "'").map(x => { + var c = x.charAt(0); + if(x.length == 2 && x=="\n") c = 10; + Expr.Character(c) + }); def constant[_: P]: P[Expr] = P(trueC | falseC) diff --git a/app/src/main/scala/posharp/ToAssembly.scala b/app/src/main/scala/posharp/ToAssembly.scala index d101e9f..0880e74 100644 --- a/app/src/main/scala/posharp/ToAssembly.scala +++ b/app/src/main/scala/posharp/ToAssembly.scala @@ -56,24 +56,27 @@ object ToAssembly { var converted = """ + | target triple = "x86_64-pc-linux-gnu" + | target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" + | | declare i32 @printf(i8*, ...) | declare i64* @calloc(i32, i32) | declare i8* @malloc(i32) | declare void @free(i8*) | declare i8* @memcpy(i8*, i8*, i32) | declare void @exit(i32) - | @format_num = private constant [3 x i8] c"%d\00" - | @format_float = private constant [3 x i8] c"%f\00" - | @format_string = private constant [3 x i8] c"%s\00" - | @format_char = private constant [3 x i8] c"%c\00" - | @format_false = private constant [7 x i8] c"false\0A\00" - | @format_true = private constant [7 x i8] c"true\0A\00\00" + | @format_num = private constant [3 x i8] c"%d\00", align 1 + | @format_float = private constant [3 x i8] c"%f\00", align 1 + | @format_string = private constant [3 x i8] c"%s\00", align 1 + | @format_char = private constant [3 x i8] c"%c\00", align 1 + | @format_false = private constant [7 x i8] c"false\0A\00", align 1 + | @format_true = private constant [7 x i8] c"true\0A\00\00", align 1 | %Type.array.double = type {i32, double*} | %Type.array.i32 = type {i32, i32*} | %Type.array.i8 = type {i32, i8*} | %Type.array.i1 = type {i32, i1*} | - |""".stripMargin; + |""".stripMargin; //[0 x i32] input match { case x: Expr.TopLevel => { declareFunctions(x); converted += declareInterfaces(x) + "\n"; @@ -89,6 +92,9 @@ object ToAssembly { }} //converted += defineFunctions(lambdas, true); converted += stringLiterals.mkString + converted += """attributes #0 = { mustprogress noinline norecurse optnone uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } + |attributes #1 = { nocallback nofree nosync nounwind readnone speculatable willreturn } + |attributes #2 = { "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }""".stripMargin converted } @@ -166,7 +172,7 @@ object ToAssembly { val intfDec = s"%Class.$name" val idx = props.indexOf(n); val ret = code + s"${varc.next()} = getelementptr $intfDec, $intfDec* ${varc.secondLast()}, i32 0, i32 $idx\n" + - s"${varc.next()} = load ${Type.toLLVM(n.varType)}, ${Type.toLLVM(n.varType)}* ${varc.secondLast()}\n" + s"${varc.next()} = load ${Type.toLLVM(n.varType)}, ${Type.toLLVM(n.varType)}* ${varc.secondLast()}, align 128\n" (ret, n.varType) } case None => throw new Exception(s"interface ${interfaces.find(x=>x.args == props).get.name} does not have a property ${prop}") @@ -183,13 +189,13 @@ object ToAssembly { //else if(env.contains(name)) callLambda(Expr.Ident(name), args, reg, env) else throw new Exception(s"unknown identifier $name") } - case Expr.CallObjFunc(obj, func) => convertType(obj, env) match { - case Type.Interface(_, props, funcs) => callObjFunction(obj, func, props, funcs, isStatic = false, env) - case Type.StaticInterface(props, funcs) => callObjFunction(obj, func, props, funcs, isStatic = true, env) + case Expr.CallObjFunc(obj, func) => convertLoc(obj, env) match { + case (code, t@Type.Interface(_, props, funcs), loc) => callObjFunction(Expr.Compiled(code, t, loc), func, props, funcs, isStatic = false, env) + case (code, t@Type.StaticInterface(props, funcs), loc) => callObjFunction(Expr.Compiled(code, t, loc), func, props, funcs, isStatic = true, env) } case Expr.InstantiateInterface(name, values) => interfaces.find(x=>x.name == name) match { case Some(intf) => { - val struct_def = s"${varc.next()} = alloca %Class.$name\n" + val struct_def = s"${varc.next()} = alloca %Class.$name, align 128\n" val func_code = interpFunction(name+"_"+name, Expr.Compiled(struct_def, UserType(name), varc.last()) +: values, env)._1 val ret = func_code; @@ -236,12 +242,12 @@ object ToAssembly { val converted = convertLoc(value, env); if(makeUserTypesConcrete(look.varType) != converted._2) throw new Exception(s"mismatch when assigning value" + s" to variable $name, expected ${look.varType}, but got ${converted._2}") - val set = s"store ${Type.toLLVM(converted._2)} ${converted._3}, ${Type.toLLVM(converted._2)}* %$name\n" + val set = s"store ${Type.toLLVM(converted._2)} ${converted._3}, ${Type.toLLVM(converted._2)}* %$name, align 128\n" converted._1 + set; }; case Expr.DefVal(name, varType) => { newenv = newVar(name, varType, newenv); - s"%$name = alloca ${Type.toLLVM(varType)}\n" + s"%$name = alloca ${Type.toLLVM(varType)}, align 128\n" } case Expr.DefValWithValue(variable, varType, value) => { var newType = varType @@ -249,9 +255,9 @@ object ToAssembly { if(newType == Type.Undefined()) newType = converted._2; if(makeUserTypesConcrete(newType) != converted._2) throw new Exception(s"mismatch when assigning value" + s" to variable $variable, expected ${newType}, but got ${converted._2}") - val set = s"store ${Type.toLLVM(converted._2)} ${converted._3}, ${Type.toLLVM(converted._2)}* %$variable\n"; + val set = s"store ${Type.toLLVM(converted._2)} ${converted._3}, ${Type.toLLVM(converted._2)}* %$variable, align 128\n"; newenv = newVar(variable, newType, newenv); - s"%$variable = alloca ${Type.toLLVM(newType)}\n" + converted._1 + set; + s"%$variable = alloca ${Type.toLLVM(newType)}, align 128\n" + converted._1 + set; } case Expr.If(condition, ifTrue, ifFalse) => { def compare(left: Expr, right: Expr, numeric: Boolean): String = @@ -295,7 +301,7 @@ object ToAssembly { val idx = props.indexOf(n); var ret = code + valCode ret += s"${varc.next()} = getelementptr $intfDec, $intfDec* $intfLoc, i32 0, i32 $idx\n" - ret += s"store ${Type.toLLVM(valType)} $valueLoc, ${Type.toLLVM(n.varType)}* ${varc.last()}\n" + ret += s"store ${Type.toLLVM(valType)} $valueLoc, ${Type.toLLVM(n.varType)}* ${varc.last()}, align 128\n" ret } case (_, valType, _) => throw new Exception(s"trying to property ${n.name} of type ${n.varType} to incompatible type ${valType}") @@ -463,8 +469,8 @@ object ToAssembly { var ret = s"define ${Type.toLLVM(info.retType)} @${fname}($args) {\n" val newEnv = upperScope ++ info.args.map(x=> (x.name, Variable(x.varType))).toMap var body = info.args.map(x=> - s"%${x.name} = alloca ${Type.toLLVM(x.varType)}\n" + - s"store ${Type.toLLVM(x.varType)} %Input.${x.name}, ${Type.toLLVM(x.varType)}* %${x.name}\n").mkString + s"%${x.name} = alloca ${Type.toLLVM(x.varType)}, align 128\n" + + s"store ${Type.toLLVM(x.varType)} %Input.${x.name}, ${Type.toLLVM(x.varType)}* %${x.name}, align 128\n").mkString body += convert(function.body, newEnv)._1 @@ -478,7 +484,7 @@ object ToAssembly { }}.mkString } def interpFunction(name: String, args: List[Expr], env: Env ): (String, Type) = { - val argRet = args.map (arg => convertLoc(arg, env)) + val argRet = args.map(arg => convertLoc(arg, env)) val argInputTypes = argRet.map(x => makeUserTypesConcrete(x._2)) var ret = argRet.map(x=>x._1).mkString val argsString = argRet.map(x=>s"${Type.toLLVM(x._2)} ${x._3}").mkString(", ") @@ -547,11 +553,13 @@ object ToAssembly { */ def defineString(value: String): String = { val label = s"string.${stringLiterals.length}"; - val n = value.length; + val n = value.length + 1; + var toString = value.replace("\n", "\\0A") + "\\00" + toString = toString.replace("\u0000", "\\00") //%foo = bitcast i8* %1 to %Foo* - stringLiterals = stringLiterals :+ s"@$label = internal constant [$n x i8] c\"$value\"" // + stringLiterals = stringLiterals :+ s"@$label = internal constant [$n x i8] c\"$toString\"\n" // //s"${varc.next()} = getelementptr inbounds i8*, i8** @$label, i32 0, i32 0" - s"${varc.next()} = alloca i8*\n" + + s"${varc.next()} = alloca i8*, align 128\n" + s"${varc.next()} = bitcast [$n x i8]* @$label to i8*\n" //s"store i8* @$label, i8** ${varc.last()}\n" + //s"${varc.next()} = load i8*, i8** ${varc.secondLast()}\n" @@ -561,21 +569,46 @@ object ToAssembly { val Tsig = Type.toLLVM(arrType) val arrTC = s"%Type.array.$Tsig" + /* var ret = ""; ret += s"${varc.next()} = getelementptr $arrTC, $arrTC* $arrLoc, i32 0, i32 1\n" val arrStructLoc = varc.last() + ret += s"${varc.next()} = load $Tsig*, $Tsig** ${arrStructLoc}\n" + ret += s"${varc.next()} = getelementptr $Tsig, $Tsig* ${varc.secondLast()}, i32 $indexLoc\n" + + */ + /* - ret += s"${varc.next()} = getelementptr $Tsig*, $Tsig** ${arrStructLoc}, i32 $indexLoc\n" + var ret = ""; + ret += s"${varc.next()} = getelementptr $arrTC, $arrTC* $arrLoc, i32 0, i32 1\n" + val arrStructLoc = varc.last() + ret += s"${varc.next()} = getelementptr inbounds $Tsig*, $Tsig** ${arrStructLoc}, i32 $indexLoc\n" val arrElemLoc = varc.last() ret += s"${varc.next()} = load $Tsig*, $Tsig** ${arrElemLoc}\n" - */ - ret += s"${varc.next()} = load $Tsig*, $Tsig** ${arrStructLoc}\n" + + /* + var ret = ""; + ret += s"${varc.next()} = getelementptr $arrTC, $arrTC* $arrLoc, i32 0, i32 1\n" + val arrStructLoc = varc.last() + ret += s"${varc.next()} = load $Tsig*, $Tsig** ${arrStructLoc}, align 128\n" ret += s"${varc.next()} = getelementptr $Tsig, $Tsig* ${varc.secondLast()}, i32 $indexLoc\n" - val arrElemLoc = varc.last() + */ + var ret = ""; + ret += s"${varc.next()} = getelementptr $arrTC, ptr $arrLoc, i32 0, i32 1\n" + val arrStructLoc = varc.last() + ret += s"${varc.next()} = load ptr, ptr ${arrStructLoc}, align 128\n" + ret += s"${varc.next()} = getelementptr inbounds $Tsig, ptr ${varc.secondLast()}, i32 $indexLoc\n" ret } + /* + %9 = getelementptr inbounds %struct.arr, ptr %2, i32 0, i32 1 + %10 = load ptr, ptr %9, align 8 + %11 = getelementptr inbounds i32, ptr %10, i64 0 + %12 = load i32, ptr %11, align 4 + store i32 %12, ptr %4, align 4 + */ def getArray(arr: Expr, index: Expr, env: Env): (String, Type) = (convertLoc(arr, env), convertLoc(index, env)) match { case ((code, Type.Array(arrType), arrLoc), (indexCode, indexType, indexLoc)) => { if(indexType != Type.Num()) throw new Exception(s"wrong index for array, got $indexType"); @@ -583,7 +616,7 @@ object ToAssembly { var ret = code + indexCode; ret += getArrayPointerIndex(arrType, arrLoc, indexLoc) - ret += s"${varc.next()} = load $Tsig, $Tsig* ${varc.secondLast()}\n" + ret += s"${varc.next()} = load $Tsig, $Tsig* ${varc.secondLast()}, align 128\n" (ret, arrType) } case ((_, varType, _), _) => throw new Exception(s"trying to access variable ${arr} as an array, has type $varType") @@ -598,7 +631,7 @@ object ToAssembly { var ret = code + indexCode + valCode; ret += getArrayPointerIndex(arrType, arrLoc, indexLoc) - ret += s"store $Tsig $valLoc, $Tsig* ${varc.last()}\n" + ret += s"store $Tsig $valLoc, $Tsig* ${varc.last()}, align 128\n" ret } } @@ -609,7 +642,7 @@ object ToAssembly { val ret = code + s"${varc.next()} = getelementptr $arrTC, $arrTC* $loc, i32 0, i32 0\n" + - s"${varc.next()} = load $Tsig, $Tsig* ${varc.secondLast()}\n" + s"${varc.next()} = load $Tsig, $Tsig* ${varc.secondLast()}, align 128\n" (ret, Type.Num()) } case (_, varType, _) => throw new Exception(s"trying to access variable ${arr} as an array, has type $varType") @@ -617,7 +650,7 @@ object ToAssembly { def defineArray(sizeLoc: String, setElemType:Type, defaultValues: List[Expr], env: Env): (String, Type) = { var elemType: Type = setElemType; - var valuesConverted = defaultValues.map(entry => { + val valuesConverted = defaultValues.map(entry => { val converted = convertLoc(entry, env) if(elemType == Type.Undefined()) elemType = converted._2; else if(converted._2 != elemType) throw new Exception(s"array elements are of different types") @@ -633,20 +666,15 @@ object ToAssembly { val arrLoc = varc.last() val sizeStructPointer = s"%arr.size.${varc.extra()}" val arrStructPointer = s"%arr.arr.${varc.extra()}" - ret += s"${varc.next()} = alloca $arrTC\n" + ret += s"${varc.next()} = alloca $arrTC, align 128\n" val structLoc = varc.last() ret += s"${sizeStructPointer} = getelementptr $arrTC, $arrTC* $structLoc, i32 0, i32 0\n" - ret += s"store i32 $sizeLoc, i32* ${sizeStructPointer}\n" + ret += s"store i32 $sizeLoc, i32* ${sizeStructPointer}, align 128\n" ret += s"${arrStructPointer} = getelementptr $arrTC, $arrTC* $structLoc, i32 0, i32 1\n" - ret += s"store $Tsig* $arrLoc, $Tsig** ${arrStructPointer}\n" + ret += s"store $Tsig* $arrLoc, $Tsig** ${arrStructPointer}, align 128\n" ret += valuesConverted.zipWithIndex.map{case ((code, retTy, loc), index) => { - //val arrcode = convertLoc(Expr.Ident("a"), env) - //val idxcode = convertLoc(Expr.Num(0), env) - //Expr.Compiled("", Type.Array(elemType), structLoc) - //Expr.Compiled(idxcode._1, idxcode._2, idxcode._3) - //Expr.Compiled("", retTy, loc) setArray(Expr.Compiled("", Type.Array(elemType), structLoc), Expr.Num(index), Expr.Compiled("", retTy, loc), env) }}.mkString; ret += s"${varc.next()} = bitcast $arrTC* ${structLoc} to $arrTC*\n" @@ -663,7 +691,7 @@ object ToAssembly { def lookup(tofind: String, env: Env): (String, Variable) = { val ret = lookupOffset(tofind, env) - (s"${varc.next()} = load ${Type.toLLVM(ret.varType)}, ${Type.toLLVM(ret.varType)}* %$tofind\n", ret) + (s"${varc.next()} = load ${Type.toLLVM(ret.varType)}, ${Type.toLLVM(ret.varType)}* %$tofind, align 128\n", ret) } def lookupOffset(tofind: String, env: Env): Variable = env.get(tofind) match { case Some(v) => v @@ -708,6 +736,8 @@ object ToAssembly { else floatBinOpTemplate(codeLeft, vLeft, codeRight, vRight, command) //case ((codeLeft, Type.Num()), (codeRight, Type.NumFloat())) => floatTemplate(codeLeft + convertToFloat(reg.head), codeRight, commandFloat, reg) //case ((codeLeft, Type.NumFloat()), (codeRight, Type.Num())) => floatTemplate(codeLeft, codeRight + convertToFloat(reg.tail.head), commandFloat, reg) + case ((codeLeft, tyLeft: Type.Interface, vLeft), (codeRight, tyRight: Type.Interface, vRight)) if (tyLeft == tyRight && command == "add") => + convert(Expr.CallObjFunc(Expr.Compiled(codeLeft, tyLeft, vLeft), Expr.CallF("__add__", List(Expr.Compiled(codeRight, tyRight, vRight)))), env) case ((codeLeft, typeLeft, x), (codeRight, typeRight, y)) => throw new Exception(s"can't perform arithmetic on operands of types ${typeLeft} and ${typeRight}"); } } @@ -725,18 +755,18 @@ object ToAssembly { s" ([3 x i8], [3 x i8]* @$format, i32 0, i32 0), $ty ${varc.last()})\n" } def printInterp(toPrint: Expr, env: Env): String = { - val converted = convert(toPrint, env) - ifCounter+=1 + val converted = convertLoc(toPrint, env) converted._2 match { case Type.Num() => converted._1 + printTemplate("format_num", "i32"); case Type.Bool() => converted._1 + printTemplate("format_num", "i1"); case Type.NumFloat() => converted._1 + printTemplate("format_float", "double"); case Type.Str() => converted._1 + printTemplate("format_string", "i8*"); + case Type.Character() => converted._1 + printTemplate("format_char", "i8"); + case Type.Interface(a,b,c) => convert(Expr.CallObjFunc(Expr.Compiled(converted._1, converted._2, converted._3), Expr.CallF("__print__", List())), env)._1 /* - // case Type.Bool() => converted._1 + s"cmp rax, 0\nje bool_${ifCounter}\n" + printTemplate("format_true") + s"jmp boole_${ifCounter}\nbool_${ifCounter}:\n" + printTemplate("format_false") + s"boole_${ifCounter}:\n"; - case Type.Character() => converted._1 + printTemplate("format_char"); + case Type.Array(Type.Character()) => converted._1 + "add rax, 8\n" + printTemplate("format_string"); */ case _ => throw new Exception(s"input of type ${converted._2} not recognized in print") diff --git a/build-llvm.sh b/build-llvm.sh index fa91f4b..d8608b3 100644 --- a/build-llvm.sh +++ b/build-llvm.sh @@ -17,7 +17,7 @@ files_asm=() for i in $files; do files_asm+=("$i".s) - llc "$i".ll + llc-15 "$i".ll -opaque-pointers done files="${files_asm[*]}" From ddcfe6e0eb807870602ed46fb321edc20a0eb3a6 Mon Sep 17 00:00:00 2001 From: Pijus Krisiukenas Date: Thu, 20 Oct 2022 00:59:54 +0200 Subject: [PATCH 073/112] no more segment issue remaining within push --- app/src/main/scala/posharp/ToAssembly.scala | 94 ++++++++++++--------- build-llvm.sh | 6 +- docs/tasklist.md | 3 +- 3 files changed, 58 insertions(+), 45 deletions(-) diff --git a/app/src/main/scala/posharp/ToAssembly.scala b/app/src/main/scala/posharp/ToAssembly.scala index 0880e74..e6f1afb 100644 --- a/app/src/main/scala/posharp/ToAssembly.scala +++ b/app/src/main/scala/posharp/ToAssembly.scala @@ -12,14 +12,14 @@ class Counter { def next(): String = { val cur = counter; if(!paused) counter += 1; - s"%$cur"; + s"%l$cur"; } def extra(): Int = { counterExtra += 1; counterExtra - 1; } - def last(): String = s"%${counter-1}"; - def secondLast(): String = s"%${counter-2}"; + def last(): String = s"%l${counter-1}"; + def secondLast(): String = s"%l${counter-2}"; def reset(): Unit = { counter = 1; } @@ -64,13 +64,15 @@ object ToAssembly { | declare i8* @malloc(i32) | declare void @free(i8*) | declare i8* @memcpy(i8*, i8*, i32) + | declare void @setbuf(ptr noundef, ptr noundef) | declare void @exit(i32) - | @format_num = private constant [3 x i8] c"%d\00", align 1 - | @format_float = private constant [3 x i8] c"%f\00", align 1 - | @format_string = private constant [3 x i8] c"%s\00", align 1 - | @format_char = private constant [3 x i8] c"%c\00", align 1 - | @format_false = private constant [7 x i8] c"false\0A\00", align 1 - | @format_true = private constant [7 x i8] c"true\0A\00\00", align 1 + | @format_num = private constant [3 x i8] c"%d\00", align 16 + | @format_float = private constant [3 x i8] c"%f\00", align 16 + | @format_string = private constant [3 x i8] c"%s\00", align 16 + | @format_char = private constant [3 x i8] c"%c\00", align 16 + | @format_false = private constant [7 x i8] c"false\0A\00", align 16 + | @format_true = private constant [7 x i8] c"true\0A\00\00", align 16 + | @stdout = external global ptr, align 8 | %Type.array.double = type {i32, double*} | %Type.array.i32 = type {i32, i32*} | %Type.array.i8 = type {i32, i8*} @@ -92,7 +94,7 @@ object ToAssembly { }} //converted += defineFunctions(lambdas, true); converted += stringLiterals.mkString - converted += """attributes #0 = { mustprogress noinline norecurse optnone uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } + converted += """attributes #0 = { mustprogress noinline nounwind optnone uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } |attributes #1 = { nocallback nofree nosync nounwind readnone speculatable willreturn } |attributes #2 = { "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }""".stripMargin converted @@ -171,8 +173,9 @@ object ToAssembly { case Some(n) => { val intfDec = s"%Class.$name" val idx = props.indexOf(n); - val ret = code + s"${varc.next()} = getelementptr $intfDec, $intfDec* ${varc.secondLast()}, i32 0, i32 $idx\n" + - s"${varc.next()} = load ${Type.toLLVM(n.varType)}, ${Type.toLLVM(n.varType)}* ${varc.secondLast()}, align 128\n" + + val ret = code + s"${varc.next()} = getelementptr inbounds $intfDec, $intfDec* ${varc.secondLast()}, i32 0, i32 $idx\n" + + s"${varc.next()} = load ${Type.toLLVM(n.varType)}, ${Type.toLLVM(n.varType)}* ${varc.secondLast()}, align 8\n" (ret, n.varType) } case None => throw new Exception(s"interface ${interfaces.find(x=>x.args == props).get.name} does not have a property ${prop}") @@ -195,7 +198,7 @@ object ToAssembly { } case Expr.InstantiateInterface(name, values) => interfaces.find(x=>x.name == name) match { case Some(intf) => { - val struct_def = s"${varc.next()} = alloca %Class.$name, align 128\n" + val struct_def = s"${varc.next()} = alloca %Class.$name, align 64\n" val func_code = interpFunction(name+"_"+name, Expr.Compiled(struct_def, UserType(name), varc.last()) +: values, env)._1 val ret = func_code; @@ -242,12 +245,12 @@ object ToAssembly { val converted = convertLoc(value, env); if(makeUserTypesConcrete(look.varType) != converted._2) throw new Exception(s"mismatch when assigning value" + s" to variable $name, expected ${look.varType}, but got ${converted._2}") - val set = s"store ${Type.toLLVM(converted._2)} ${converted._3}, ${Type.toLLVM(converted._2)}* %$name, align 128\n" + val set = s"store ${Type.toLLVM(converted._2)} ${converted._3}, ${Type.toLLVM(converted._2)}* %$name, align ${arraySizeFromType(converted._2)}\n" converted._1 + set; }; case Expr.DefVal(name, varType) => { newenv = newVar(name, varType, newenv); - s"%$name = alloca ${Type.toLLVM(varType)}, align 128\n" + s"%$name = alloca ${Type.toLLVM(varType)}\n" //, align ${arraySizeFromType(varType)} } case Expr.DefValWithValue(variable, varType, value) => { var newType = varType @@ -255,9 +258,9 @@ object ToAssembly { if(newType == Type.Undefined()) newType = converted._2; if(makeUserTypesConcrete(newType) != converted._2) throw new Exception(s"mismatch when assigning value" + s" to variable $variable, expected ${newType}, but got ${converted._2}") - val set = s"store ${Type.toLLVM(converted._2)} ${converted._3}, ${Type.toLLVM(converted._2)}* %$variable, align 128\n"; + val set = s"store ${Type.toLLVM(converted._2)} ${converted._3}, ${Type.toLLVM(converted._2)}* %$variable, align ${arraySizeFromType(converted._2)}\n"; newenv = newVar(variable, newType, newenv); - s"%$variable = alloca ${Type.toLLVM(newType)}, align 128\n" + converted._1 + set; + s"%$variable = alloca ${Type.toLLVM(newType)}, align 64\n" + converted._1 + set; //, align 4 ${arraySizeFromType(newType)} } case Expr.If(condition, ifTrue, ifFalse) => { def compare(left: Expr, right: Expr, numeric: Boolean): String = @@ -301,7 +304,7 @@ object ToAssembly { val idx = props.indexOf(n); var ret = code + valCode ret += s"${varc.next()} = getelementptr $intfDec, $intfDec* $intfLoc, i32 0, i32 $idx\n" - ret += s"store ${Type.toLLVM(valType)} $valueLoc, ${Type.toLLVM(n.varType)}* ${varc.last()}, align 128\n" + ret += s"store ${Type.toLLVM(valType)} $valueLoc, ${Type.toLLVM(n.varType)}* ${varc.last()}, align ${arraySizeFromType(n.varType)}\n" ret } case (_, valType, _) => throw new Exception(s"trying to property ${n.name} of type ${n.varType} to incompatible type ${valType}") @@ -312,7 +315,7 @@ object ToAssembly { } case Expr.ThrowException(err) => { val msg = AnsiColor.RED + "RuntimeException: " + err + AnsiColor.RESET + "\n" - defineString(msg) + printTemplate("format_string", "i8*") + "call void @exit(i32 1)\n" // "br label %exception\n" + defineString(msg) + printTemplate("format_string", "i8*", varc.last()) + "call void @exit(i32 1)\n" // "br label %exception\n" } case x@Expr.CallObjFunc(obj, func) => convert(x, env)._1; case x@Expr.CallF(n, a) => convert(x, env)._1; @@ -466,12 +469,13 @@ object ToAssembly { functionScope = info; val fname = fNameSignature(info) val args = info.args.map(x=>s"${Type.toLLVM(x.varType)} %Input.${x.name}").mkString(", ") - var ret = s"define ${Type.toLLVM(info.retType)} @${fname}($args) {\n" + var ret = s"define ${Type.toLLVM(info.retType)} @${fname}($args) #0 {\n" val newEnv = upperScope ++ info.args.map(x=> (x.name, Variable(x.varType))).toMap var body = info.args.map(x=> - s"%${x.name} = alloca ${Type.toLLVM(x.varType)}, align 128\n" + - s"store ${Type.toLLVM(x.varType)} %Input.${x.name}, ${Type.toLLVM(x.varType)}* %${x.name}, align 128\n").mkString + s"%${x.name} = alloca ${Type.toLLVM(x.varType)}, align 64\n" + //, align ${arraySizeFromType(x.varType)} + s"store ${Type.toLLVM(x.varType)} %Input.${x.name}, ${Type.toLLVM(x.varType)}* %${x.name}\n").mkString //, align ${arraySizeFromType(x.varType)} + body += "%dummy = alloca i32\n" body += convert(function.body, newEnv)._1 if(info.retType == Type.Undefined()) body += "ret void\n" @@ -556,11 +560,11 @@ object ToAssembly { val n = value.length + 1; var toString = value.replace("\n", "\\0A") + "\\00" toString = toString.replace("\u0000", "\\00") - //%foo = bitcast i8* %1 to %Foo* - stringLiterals = stringLiterals :+ s"@$label = internal constant [$n x i8] c\"$toString\"\n" // + + stringLiterals = stringLiterals :+ s"@$label = internal constant [$n x i8] c\"$toString\", align 16\n" // //s"${varc.next()} = getelementptr inbounds i8*, i8** @$label, i32 0, i32 0" - s"${varc.next()} = alloca i8*, align 128\n" + - s"${varc.next()} = bitcast [$n x i8]* @$label to i8*\n" + //s"${varc.next()} = alloca i8*, align 8\n" + + s"${varc.next()} = bitcast [$n x i8]* @$label to ptr\n" //s"store i8* @$label, i8** ${varc.last()}\n" + //s"${varc.next()} = load i8*, i8** ${varc.secondLast()}\n" } @@ -597,7 +601,7 @@ object ToAssembly { var ret = ""; ret += s"${varc.next()} = getelementptr $arrTC, ptr $arrLoc, i32 0, i32 1\n" val arrStructLoc = varc.last() - ret += s"${varc.next()} = load ptr, ptr ${arrStructLoc}, align 128\n" + ret += s"${varc.next()} = load ptr, ptr ${arrStructLoc}, align 8\n" ret += s"${varc.next()} = getelementptr inbounds $Tsig, ptr ${varc.secondLast()}, i32 $indexLoc\n" ret @@ -616,7 +620,7 @@ object ToAssembly { var ret = code + indexCode; ret += getArrayPointerIndex(arrType, arrLoc, indexLoc) - ret += s"${varc.next()} = load $Tsig, $Tsig* ${varc.secondLast()}, align 128\n" + ret += s"${varc.next()} = load $Tsig, $Tsig* ${varc.secondLast()}, align ${arraySizeFromType(arrType)}\n" (ret, arrType) } case ((_, varType, _), _) => throw new Exception(s"trying to access variable ${arr} as an array, has type $varType") @@ -631,7 +635,7 @@ object ToAssembly { var ret = code + indexCode + valCode; ret += getArrayPointerIndex(arrType, arrLoc, indexLoc) - ret += s"store $Tsig $valLoc, $Tsig* ${varc.last()}, align 128\n" + ret += s"store $Tsig $valLoc, $Tsig* ${varc.last()}, align ${arraySizeFromType(arrType)}\n" ret } } @@ -642,7 +646,7 @@ object ToAssembly { val ret = code + s"${varc.next()} = getelementptr $arrTC, $arrTC* $loc, i32 0, i32 0\n" + - s"${varc.next()} = load $Tsig, $Tsig* ${varc.secondLast()}, align 128\n" + s"${varc.next()} = load $Tsig, $Tsig* ${varc.secondLast()}, align 4\n" (ret, Type.Num()) } case (_, varType, _) => throw new Exception(s"trying to access variable ${arr} as an array, has type $varType") @@ -666,13 +670,13 @@ object ToAssembly { val arrLoc = varc.last() val sizeStructPointer = s"%arr.size.${varc.extra()}" val arrStructPointer = s"%arr.arr.${varc.extra()}" - ret += s"${varc.next()} = alloca $arrTC, align 128\n" + ret += s"${varc.next()} = alloca $arrTC, align 128\n" //changing to 8 seems to cause fault val structLoc = varc.last() ret += s"${sizeStructPointer} = getelementptr $arrTC, $arrTC* $structLoc, i32 0, i32 0\n" - ret += s"store i32 $sizeLoc, i32* ${sizeStructPointer}, align 128\n" + ret += s"store i32 $sizeLoc, i32* ${sizeStructPointer}, align 4\n" ret += s"${arrStructPointer} = getelementptr $arrTC, $arrTC* $structLoc, i32 0, i32 1\n" - ret += s"store $Tsig* $arrLoc, $Tsig** ${arrStructPointer}, align 128\n" + ret += s"store $Tsig* $arrLoc, $Tsig** ${arrStructPointer}, align 8\n" ret += valuesConverted.zipWithIndex.map{case ((code, retTy, loc), index) => { setArray(Expr.Compiled("", Type.Array(elemType), structLoc), Expr.Num(index), Expr.Compiled("", retTy, loc), env) @@ -683,15 +687,16 @@ object ToAssembly { def arraySizeFromType(valtype: Type): Int = valtype match { case Type.Undefined() => 8 case Type.Character() => 1 - case Type.Num() => 8 + case Type.Num() => 4 case Type.NumFloat() => 8 case Type.Function(_,_) => 8 case Type.T1() => 8 + case _ => 8 } def lookup(tofind: String, env: Env): (String, Variable) = { val ret = lookupOffset(tofind, env) - (s"${varc.next()} = load ${Type.toLLVM(ret.varType)}, ${Type.toLLVM(ret.varType)}* %$tofind, align 128\n", ret) + (s"${varc.next()} = load ${Type.toLLVM(ret.varType)}, ${Type.toLLVM(ret.varType)}* %$tofind, align ${arraySizeFromType(ret.varType)}\n", ret) } def lookupOffset(tofind: String, env: Env): Variable = env.get(tofind) match { case Some(v) => v @@ -750,18 +755,23 @@ object ToAssembly { } */ - def printTemplate(format: String, ty: String): String = { - s"%ret.${varc.extra()} = call i32 (i8*, ...) @printf(i8* getelementptr inbounds" + + def printTemplate(format: String, ty: String, loc: String): String = { + /* + s"%ret.${varc.extra()} = call i32 (ptr, ...) @printf(ptr getelementptr inbounds" + s" ([3 x i8], [3 x i8]* @$format, i32 0, i32 0), $ty ${varc.last()})\n" + */ + s"%ret.${varc.extra()} = call i32 (ptr, ...) @printf(ptr @$format, $ty ${loc})\n" + + s"${varc.next()} = load ptr, ptr @stdout, align 8" + + s"call void @setbuf(ptr noundef ${varc.last()}, ptr noundef null)" } def printInterp(toPrint: Expr, env: Env): String = { val converted = convertLoc(toPrint, env) converted._2 match { - case Type.Num() => converted._1 + printTemplate("format_num", "i32"); - case Type.Bool() => converted._1 + printTemplate("format_num", "i1"); - case Type.NumFloat() => converted._1 + printTemplate("format_float", "double"); - case Type.Str() => converted._1 + printTemplate("format_string", "i8*"); - case Type.Character() => converted._1 + printTemplate("format_char", "i8"); + case Type.Num() => converted._1 + printTemplate("format_num", "i32", converted._3); + case Type.Bool() => converted._1 + printTemplate("format_num", "i1", converted._3); + case Type.NumFloat() => converted._1 + printTemplate("format_float", "double", converted._3); + case Type.Str() => converted._1 + printTemplate("format_string", "ptr", converted._3); + case Type.Character() => converted._1 + printTemplate("format_char", "i8", converted._3); case Type.Interface(a,b,c) => convert(Expr.CallObjFunc(Expr.Compiled(converted._1, converted._2, converted._3), Expr.CallF("__print__", List())), env)._1 /* case Type.Bool() => converted._1 + s"cmp rax, 0\nje bool_${ifCounter}\n" + printTemplate("format_true") + diff --git a/build-llvm.sh b/build-llvm.sh index d8608b3..e31452a 100644 --- a/build-llvm.sh +++ b/build-llvm.sh @@ -17,9 +17,11 @@ files_asm=() for i in $files; do files_asm+=("$i".s) - llc-15 "$i".ll -opaque-pointers + llc-15 "$i".ll -O0 -opaque-pointers --stackrealign --stack-size-section --debugify-level=location+variables --frame-pointer=all -align-all-nofallthru-blocks=4 -align-all-functions=4 + # --stackrealign --asm-show-inst --align-loops=64 done files="${files_asm[*]}" -gcc -O0 -ggdb -no-pie $files -o "hello" +#gcc -O0 -ggdb -mpreferred-stack-boundary=4 -no-pie $files -o "hello" +clang-15 -O0 -no-pie $files -o "hello" # -mstack-alignment=4 diff --git a/docs/tasklist.md b/docs/tasklist.md index 78122de..2100a2a 100644 --- a/docs/tasklist.md +++ b/docs/tasklist.md @@ -2,7 +2,8 @@ * conversions * lambdas -* arrays with default value + +## Bugs ## To do From 5280b73ae948084e28a6d733bcdf89d83350c4e1 Mon Sep 17 00:00:00 2001 From: Pijus Krisiukenas Date: Fri, 21 Oct 2022 01:43:33 +0200 Subject: [PATCH 074/112] bug found, arrays working --- app/src/main/scala/posharp/ToAssembly.scala | 24 +++++++++++++++------ docs/tasklist.md | 3 +++ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/app/src/main/scala/posharp/ToAssembly.scala b/app/src/main/scala/posharp/ToAssembly.scala index e6f1afb..9da14c4 100644 --- a/app/src/main/scala/posharp/ToAssembly.scala +++ b/app/src/main/scala/posharp/ToAssembly.scala @@ -61,12 +61,13 @@ object ToAssembly { | | declare i32 @printf(i8*, ...) | declare i64* @calloc(i32, i32) - | declare i8* @malloc(i32) - | declare void @free(i8*) - | declare i8* @memcpy(i8*, i8*, i32) + | declare ptr @malloc(i32) + | declare void @free(ptr) + | declare ptr @memcpy(ptr, ptr, i32) | declare void @setbuf(ptr noundef, ptr noundef) | declare void @exit(i32) | @format_num = private constant [3 x i8] c"%d\00", align 16 + | @format_uint = private constant [4 x i8] c"%lu\00", align 16 | @format_float = private constant [3 x i8] c"%f\00", align 16 | @format_string = private constant [3 x i8] c"%s\00", align 16 | @format_char = private constant [3 x i8] c"%c\00", align 16 @@ -202,6 +203,7 @@ object ToAssembly { val func_code = interpFunction(name+"_"+name, Expr.Compiled(struct_def, UserType(name), varc.last()) +: values, env)._1 val ret = func_code; + println(Expr.Compiled(struct_def, UserType(name), varc.last()) +: values) (ret, Type.Interface(name, intf.args, intf.funcs)) } case None => throw new Exception(s"no interface with name \"$name\" defined") @@ -620,7 +622,7 @@ object ToAssembly { var ret = code + indexCode; ret += getArrayPointerIndex(arrType, arrLoc, indexLoc) - ret += s"${varc.next()} = load $Tsig, $Tsig* ${varc.secondLast()}, align ${arraySizeFromType(arrType)}\n" + ret += s"${varc.next()} = load $Tsig, $Tsig* ${varc.secondLast()}\n" //${arraySizeFromType(arrType)} (ret, arrType) } case ((_, varType, _), _) => throw new Exception(s"trying to access variable ${arr} as an array, has type $varType") @@ -670,7 +672,7 @@ object ToAssembly { val arrLoc = varc.last() val sizeStructPointer = s"%arr.size.${varc.extra()}" val arrStructPointer = s"%arr.arr.${varc.extra()}" - ret += s"${varc.next()} = alloca $arrTC, align 128\n" //changing to 8 seems to cause fault + ret += s"${varc.next()} = alloca $arrTC\n" //changing to 8 seems to cause fault val structLoc = varc.last() ret += s"${sizeStructPointer} = getelementptr $arrTC, $arrTC* $structLoc, i32 0, i32 0\n" @@ -681,7 +683,16 @@ object ToAssembly { ret += valuesConverted.zipWithIndex.map{case ((code, retTy, loc), index) => { setArray(Expr.Compiled("", Type.Array(elemType), structLoc), Expr.Num(index), Expr.Compiled("", retTy, loc), env) }}.mkString; - ret += s"${varc.next()} = bitcast $arrTC* ${structLoc} to $arrTC*\n" + + //ret += s"${varc.next()} = getelementptr $arrTC, $arrTC* null, i32 1\n" + s"${varc.next()} = ptrtoint $arrTC** ${varc.secondLast()} to i32\n" + //ret += s"call i32 (ptr, ...) @printf(ptr @format_num, i32 ${varc.last()})\n" + ret += s"${varc.next()} = add i32 8, 8\n" + val bytesLoc = varc.last(); + ret += s"${varc.next()} = call ptr (i32) @malloc(i32 $bytesLoc)\n"; + val alocLoc = varc.last(); + ret += s"call ptr @memcpy(ptr $alocLoc, ptr $structLoc, i32 $bytesLoc)\n" + + ret += s"${varc.next()} = bitcast $arrTC* $alocLoc to $arrTC*\n" (ret, Type.Array(elemType)) } def arraySizeFromType(valtype: Type): Int = valtype match { @@ -779,6 +790,7 @@ object ToAssembly { case Type.Array(Type.Character()) => converted._1 + "add rax, 8\n" + printTemplate("format_string"); */ + case Type.Array(_) => converted._1 + s"${varc.next()} = ptrtoint ptr ${converted._3} to i64\n" + printTemplate("format_uint", "i64", varc.last()); case _ => throw new Exception(s"input of type ${converted._2} not recognized in print") } } diff --git a/docs/tasklist.md b/docs/tasklist.md index 2100a2a..086f345 100644 --- a/docs/tasklist.md +++ b/docs/tasklist.md @@ -2,9 +2,12 @@ * conversions * lambdas +* add copy command to arrays +* use malloc, currently struct alloc is wrong(always on stack, issues when used in fuctions) ## Bugs + ## To do * file name in error messages From f6cd90c1b7a935f53c675a845749a80098a25820 Mon Sep 17 00:00:00 2001 From: Pijus Krisiukenas Date: Sat, 22 Oct 2022 22:18:07 +0200 Subject: [PATCH 075/112] casts --- app/src/main/scala/posharp/Definitions.scala | 4 +- app/src/main/scala/posharp/Parser.scala | 3 +- app/src/main/scala/posharp/ToAssembly.scala | 95 +++++++++----------- docs/tasklist.md | 13 +-- 4 files changed, 53 insertions(+), 62 deletions(-) diff --git a/app/src/main/scala/posharp/Definitions.scala b/app/src/main/scala/posharp/Definitions.scala index 6aa97c0..ffad594 100644 --- a/app/src/main/scala/posharp/Definitions.scala +++ b/app/src/main/scala/posharp/Definitions.scala @@ -26,7 +26,6 @@ object Expr{ case class Not(condition: Expr) extends Expr //case class RetIf(condition: Expr, ifTrue: Expr, ifFalse: Expr) extends Expr - case class Print(value: Expr) extends Expr case class SetVal(variable: Expr, value: Expr) extends Expr case class DefVal(variable: String, varType: Type) extends Expr case class DefValWithValue(variable: String, varType: Type, value: Expr) extends Expr @@ -66,6 +65,9 @@ object Expr{ case class TopLevel(functions: List[Func], interfaces: List[DefineInterface], enums: List[DefineEnum], imports: List[Import]) extends Expr case class Import(toImport: String, file: String) extends Expr + case class Print(value: Expr) extends Expr + case class Free(value: Expr) extends Expr + case class ThrowException(errorMsg: String) extends Expr case class Nothing() extends Expr case class Compiled(code: String, raxType: Type, loc: String) extends Expr diff --git a/app/src/main/scala/posharp/Parser.scala b/app/src/main/scala/posharp/Parser.scala index 1bd593a..fd51554 100644 --- a/app/src/main/scala/posharp/Parser.scala +++ b/app/src/main/scala/posharp/Parser.scala @@ -62,7 +62,7 @@ object Parser { def line[_: P]: P[Expr] = P(expr ~/ ";") - def expr[_: P]: P[Expr] = P(arrayDef | arrayDefDefault | defAndSetVal | defVal | NoCut(setVar) | callFuncInLine | retFunction | IfOp | whileLoop | forLoop | print | callFunction | throwException) + def expr[_: P]: P[Expr] = P(arrayDef | arrayDefDefault | defAndSetVal | defVal | NoCut(setVar) | callFuncInLine | retFunction | IfOp | whileLoop | forLoop | print | free | callFunction | throwException) def prefixExpr[_: P]: P[Expr] = P( NoCut(callFunction) | defineLambda | NoCut(convert) | NoCut(parens) | NoCut(condition) | arrayDef | arrayDefDefault | instanceInterface | accessVar | NoCut(getArraySize) | numberFloat | number | ident | constant | str | char | trueC | falseC) @@ -236,6 +236,7 @@ object Parser { def falseC[_: P]: P[Expr.False] = P("false").map(_ => Expr.False()) def print[_: P]: P[Expr.Print] = P("print" ~ "(" ~/ (NoCut(binOp) | prefixExpr) ~ ")").map(Expr.Print) + def free[_: P]: P[Expr.Free] = P("free" ~ "(" ~/ (prefixExpr) ~ ")").map(Expr.Free) def throwException[_: P]: P[Expr.ThrowException] = P("throw" ~/ "exception" ~/ "(" ~/ str ~/ ")").map(x => Expr.ThrowException(x.s.dropRight(1))) diff --git a/app/src/main/scala/posharp/ToAssembly.scala b/app/src/main/scala/posharp/ToAssembly.scala index 9da14c4..4691bd9 100644 --- a/app/src/main/scala/posharp/ToAssembly.scala +++ b/app/src/main/scala/posharp/ToAssembly.scala @@ -3,6 +3,7 @@ package posharp import posharp.Type.{UserType, shortS, toLLVM} import scala.io.AnsiColor +import scala.language.postfixOps class Counter { var counter = 1; @@ -199,27 +200,38 @@ object ToAssembly { } case Expr.InstantiateInterface(name, values) => interfaces.find(x=>x.name == name) match { case Some(intf) => { - val struct_def = s"${varc.next()} = alloca %Class.$name, align 64\n" - - val func_code = interpFunction(name+"_"+name, Expr.Compiled(struct_def, UserType(name), varc.last()) +: values, env)._1 + var aloc = ""; + val classT = s"%Class.$name" + //ret += s"${varc.next()} = alloca %Class.$name, align 64\n" + + aloc += s"${varc.next()} = getelementptr $classT, $classT* null, i32 1\n" + s"${varc.next()} = ptrtoint $classT** ${varc.secondLast()} to i32\n" + val bytesLoc = varc.last(); + aloc += s"${varc.next()} = call ptr (i32) @malloc(i32 $bytesLoc)\n"; + val alocLoc = varc.last(); + + val valuesCompiled = values.map(x=>convertLoc(x, env)).map(x=>Expr.Compiled(x._1, x._2, x._3)) + intf.funcs.find(x=>x.name == name && x.args == values) + val func_code = interpFunction(name+"_"+name, Expr.Compiled(aloc, UserType(name), varc.last()) +: valuesCompiled, env)._1 val ret = func_code; - println(Expr.Compiled(struct_def, UserType(name), varc.last()) +: values) (ret, Type.Interface(name, intf.args, intf.funcs)) } case None => throw new Exception(s"no interface with name \"$name\" defined") } case Expr.Str(value) => (defineString(value), Type.Str()) - /* - case Expr.Convert(value, valType: Type) => (convert(value, reg, env), valType) match { - case ((code, Type.Num()), Type.NumFloat()) => (code + convertToFloat(reg.head), valType) - case ((code, Type.NumFloat()), Type.Num()) => (code + convertToInt(reg.head), valType) - case ((code, Type.Num()), Type.Character()) => (code, valType) - case ((code, l), r) => throw new Exception(s"cant convert from type ${l} to type $r") + case Expr.Character(value) => (s"${varc.next()} = add i8 0, ${value.toInt}\n", Type.Character()) + case Expr.Convert(value, valType: Type) => (convertLoc(value, env), valType) match { + case ((code, Type.Num(), loc), Type.NumFloat()) => (code + s"${varc.next()} = sitofp ${Type.toLLVM(Type.Num())} $loc to ${Type.toLLVM(Type.NumFloat())}\n", valType) + case ((code, Type.NumFloat(), loc), Type.Num()) => (code + s"${varc.next()} = fptosi ${Type.toLLVM(Type.NumFloat())} $loc to ${Type.toLLVM(Type.Num())}\n", valType) + case ((code, Type.Num(), loc), Type.Character()) => (code + s"${varc.next()} = sext ${Type.toLLVM(Type.Num())} $loc to ${Type.toLLVM(Type.Character())}\n", valType) + case ((code, Type.Character(), loc), Type.Num()) => (code + s"${varc.next()} = trunc ${Type.toLLVM(Type.Character())} $loc to ${Type.toLLVM(Type.Num())}\n", valType) + case ((code, l, _), r) => throw new Exception(s"cant convert from type ${l} to type $r") } + /* + // case Expr.Str(value) => (defineArrayKnown(value.length, Type.Character(), value.map(x=>Expr.Character(x)).toList, env)._1, Type.Array(Type.Character())) - case Expr.Character(value) => (s"mov ${reg.head}, ${value.toInt}\n", Type.Character()) + case Expr.Lambda(args, ret, body) => { val label = "lambda_" + lambdas.size @@ -275,7 +287,6 @@ object ToAssembly { case (code, Type.Bool(), loc) => compare(Expr.Compiled(code, Type.Bool(), loc), Expr.True(), false) case t => throw new Exception(s"got type $t inside condition, expected bool") } - //val cond = compare(condition, Expr.True(), false) val ret = cond + s"br i1 ${varc.last()}, label %$trueLabel, label %$falseLabel\n" + s"${trueLabel}:\n" + convert(ifTrue, env)._1 + s"br label %$endLabel\n" + s"${falseLabel}:\n" + convert(ifFalse, env)._1 + s"br label %$endLabel\n" + s"$endLabel:\n" @@ -331,14 +342,13 @@ object ToAssembly { } case None => "ret void\n"; } - /* - val defInside = (env.keys.toSet diff functionScope.args.map(x=>x.name).toSet); - //TODO fix issue when 2 variables reference same location - val free = "" //freeMemory(env.filter(x=>defInside.contains(x._1))) - */ - } case Expr.Print(toPrint) => printInterp(toPrint, env); + case Expr.Free(toFree) => convertLoc(toFree, env) match { + case (code, _:Type.Array | _:Type.UserType | _:Type.Interface, loc) => { + code + s"call void @free(ptr $loc)\n" + } + } case x@Expr.Block(n) => convert(x, env)._1; case Expr.ExtendBlock(n) => extendLines = extendLines.head +: n ::: extendLines.tail;"" case _ => throw new Exception(lines.head.toString + " should not be in block lines") @@ -575,31 +585,6 @@ object ToAssembly { val Tsig = Type.toLLVM(arrType) val arrTC = s"%Type.array.$Tsig" - /* - var ret = ""; - ret += s"${varc.next()} = getelementptr $arrTC, $arrTC* $arrLoc, i32 0, i32 1\n" - val arrStructLoc = varc.last() - ret += s"${varc.next()} = load $Tsig*, $Tsig** ${arrStructLoc}\n" - ret += s"${varc.next()} = getelementptr $Tsig, $Tsig* ${varc.secondLast()}, i32 $indexLoc\n" - - */ - - /* - var ret = ""; - ret += s"${varc.next()} = getelementptr $arrTC, $arrTC* $arrLoc, i32 0, i32 1\n" - val arrStructLoc = varc.last() - ret += s"${varc.next()} = getelementptr inbounds $Tsig*, $Tsig** ${arrStructLoc}, i32 $indexLoc\n" - val arrElemLoc = varc.last() - ret += s"${varc.next()} = load $Tsig*, $Tsig** ${arrElemLoc}\n" - */ - - /* - var ret = ""; - ret += s"${varc.next()} = getelementptr $arrTC, $arrTC* $arrLoc, i32 0, i32 1\n" - val arrStructLoc = varc.last() - ret += s"${varc.next()} = load $Tsig*, $Tsig** ${arrStructLoc}, align 128\n" - ret += s"${varc.next()} = getelementptr $Tsig, $Tsig* ${varc.secondLast()}, i32 $indexLoc\n" - */ var ret = ""; ret += s"${varc.next()} = getelementptr $arrTC, ptr $arrLoc, i32 0, i32 1\n" val arrStructLoc = varc.last() @@ -608,13 +593,7 @@ object ToAssembly { ret } - /* - %9 = getelementptr inbounds %struct.arr, ptr %2, i32 0, i32 1 - %10 = load ptr, ptr %9, align 8 - %11 = getelementptr inbounds i32, ptr %10, i64 0 - %12 = load i32, ptr %11, align 4 - store i32 %12, ptr %4, align 4 - */ + def getArray(arr: Expr, index: Expr, env: Env): (String, Type) = (convertLoc(arr, env), convertLoc(index, env)) match { case ((code, Type.Array(arrType), arrLoc), (indexCode, indexType, indexLoc)) => { if(indexType != Type.Num()) throw new Exception(s"wrong index for array, got $indexType"); @@ -643,7 +622,7 @@ object ToAssembly { } def getArraySize(arr: Expr, env: Env): (String, Type) = convertLoc(arr, env) match { case (code, Type.Array(arrType), loc) => { - val Tsig = Type.toLLVM(arrType) + val Tsig = "i32" val arrTC = s"%Type.array.$Tsig" val ret = code + @@ -750,8 +729,16 @@ object ToAssembly { case ((codeLeft, Type.NumFloat(), vLeft), (codeRight, Type.NumFloat(), vRight)) => if(command == "sdiv") floatBinOpTemplate(codeLeft, vLeft, codeRight, vRight, "div") else floatBinOpTemplate(codeLeft, vLeft, codeRight, vRight, command) - //case ((codeLeft, Type.Num()), (codeRight, Type.NumFloat())) => floatTemplate(codeLeft + convertToFloat(reg.head), codeRight, commandFloat, reg) - //case ((codeLeft, Type.NumFloat()), (codeRight, Type.Num())) => floatTemplate(codeLeft, codeRight + convertToFloat(reg.tail.head), commandFloat, reg) + case ((codeLeft, Type.Num(), vLeft), (codeRight, Type.NumFloat(), vRight)) => { + val con = s"${varc.next()} = sitofp ${Type.toLLVM(Type.Num())} $vLeft to ${Type.toLLVM(Type.NumFloat())}\n" + if(command == "sdiv") floatBinOpTemplate(codeLeft+con, varc.last(), codeRight, vRight, "div") + else floatBinOpTemplate(codeLeft+con, varc.last(), codeRight, vRight, command) + } + case ((codeLeft, Type.NumFloat(), vLeft), (codeRight, Type.Num(), vRight)) => { + val con = s"${varc.next()} = sitofp ${Type.toLLVM(Type.Num())} $vRight to ${Type.toLLVM(Type.NumFloat())}\n" + if(command == "sdiv") floatBinOpTemplate(codeLeft, vLeft, codeRight+con, varc.last(), "div") + else floatBinOpTemplate(codeLeft, vLeft, codeRight+con, varc.last(), command) + } case ((codeLeft, tyLeft: Type.Interface, vLeft), (codeRight, tyRight: Type.Interface, vRight)) if (tyLeft == tyRight && command == "add") => convert(Expr.CallObjFunc(Expr.Compiled(codeLeft, tyLeft, vLeft), Expr.CallF("__add__", List(Expr.Compiled(codeRight, tyRight, vRight)))), env) case ((codeLeft, typeLeft, x), (codeRight, typeRight, y)) => throw new Exception(s"can't perform arithmetic on operands of types ${typeLeft} and ${typeRight}"); @@ -790,7 +777,7 @@ object ToAssembly { case Type.Array(Type.Character()) => converted._1 + "add rax, 8\n" + printTemplate("format_string"); */ - case Type.Array(_) => converted._1 + s"${varc.next()} = ptrtoint ptr ${converted._3} to i64\n" + printTemplate("format_uint", "i64", varc.last()); + //case Type.Array(_) => converted._1 + s"${varc.next()} = ptrtoint ptr ${converted._3} to i64\n" + printTemplate("format_uint", "i64", varc.last()); case _ => throw new Exception(s"input of type ${converted._2} not recognized in print") } } diff --git a/docs/tasklist.md b/docs/tasklist.md index 086f345..fecfe02 100644 --- a/docs/tasklist.md +++ b/docs/tasklist.md @@ -1,22 +1,22 @@ ## LLVM migration -* conversions * lambdas -* add copy command to arrays -* use malloc, currently struct alloc is wrong(always on stack, issues when used in fuctions) ## Bugs - +* for some reason imports are still done in files +* functions have limited checking before llvm catches it (especially multiple constructors) ## To do +* add copy command to arrays(and maybe structs) +* rework file imports with explicit export command +* add more object intrinsics(equal) * file name in error messages * line numbers in compiler * static variables * add prinf -* Prevent array deferencing (point to a pointer that points to array) + * infer self in object functions -* Add array copy function * add method to get all interface arguments/names * function reflection(do this by declaring labels in file with strings and var names) * parser error reporting @@ -24,6 +24,7 @@ * add runtime index checking for arrays * optimise string to char array conversion +* ~~Prevent array deferencing (point to a pointer that points to array)~~ * ~~booleans~~ * ~~change functions names to consider their object~~ * ~~make self auto-pass~~ From b74598696fe568d0113e266e7973b5695756377f Mon Sep 17 00:00:00 2001 From: Pijus Krisiukenas Date: Mon, 31 Oct 2022 15:56:47 +0100 Subject: [PATCH 076/112] fixed multiple files --- .gitignore | 2 + README.md | 6 +- app/src/main/scala/posharp/Main.scala | 6 +- app/src/main/scala/posharp/ToAssembly.scala | 88 +++++------ build-llvm.sh | 27 +++- build.sh | 24 --- commands.txt | 8 + docs/Guide.md | 1 + docs/examples.txt | 30 ---- lib-code/nice.txt => docs/library.txt | 107 ++++--------- docs/tasklist.md | 6 +- llvm.sh | 165 ++++++++++++++++++++ 12 files changed, 279 insertions(+), 191 deletions(-) delete mode 100644 build.sh create mode 100644 commands.txt rename lib-code/nice.txt => docs/library.txt (55%) create mode 100644 llvm.sh diff --git a/.gitignore b/.gitignore index 4513b3f..df855ff 100644 --- a/.gitignore +++ b/.gitignore @@ -171,3 +171,5 @@ gradle-app.setting !po_src/lib/ po_src coverage.json +pwndbg/ +debugir diff --git a/README.md b/README.md index 1cf5575..b4a6166 100644 --- a/README.md +++ b/README.md @@ -157,14 +157,16 @@ def fib(n: int): int { * Enums * Objects * runtime exceptions -* lambda functions +* multiple files ### To do
#### Major -* multiple files +* Tuples +* Extension methods +* lambda functions * Generics * Object inheritance * library functions diff --git a/app/src/main/scala/posharp/Main.scala b/app/src/main/scala/posharp/Main.scala index b275f05..acf5f39 100644 --- a/app/src/main/scala/posharp/Main.scala +++ b/app/src/main/scala/posharp/Main.scala @@ -13,7 +13,7 @@ object Main extends App { if (args.length > 0) { sourceDir = args(0) } - val files = recursiveListFiles(new File(sourceDir)).toList.filter(x=>x.getName.contains(Constants.FileExtension)) + val files = recursiveListFiles(new File(sourceDir), "ignore").toList.filter(x=>x.getName.contains(Constants.FileExtension)) val sourceDirPath = Paths.get(sourceDir) val declarations: Map[String, Expr.TopLevel] = files.map(file => { val toCompile = readFile(file) @@ -64,10 +64,10 @@ object Main extends App { source.close() codetxt } - def recursiveListFiles(f: File): Array[File] = { + def recursiveListFiles(f: File, ignore: String): Array[File] = { if(f.isFile) return Array(f) val these = f.listFiles - these ++ these.filter(x=>x.isDirectory).flatMap(x=>recursiveListFiles(x)) + these ++ these.filter(x=>x.isDirectory && x.getName!=ignore).flatMap(x=>recursiveListFiles(x, ignore)) } } diff --git a/app/src/main/scala/posharp/ToAssembly.scala b/app/src/main/scala/posharp/ToAssembly.scala index 4691bd9..4245c78 100644 --- a/app/src/main/scala/posharp/ToAssembly.scala +++ b/app/src/main/scala/posharp/ToAssembly.scala @@ -43,7 +43,7 @@ object ToAssembly { var interfaces: List[InterfaceInfo] = List(); var enums: List[EnumInfo] = List() var lambdas: List[(Expr.Func, Env)] = List() - var functionScope: FunctionInfo = FunctionInfo("main", List(), Type.Num()); + var functionScope: FunctionInfo = FunctionInfo("main", "", List(), Type.Num()); def convertMain(input: Expr, currentFile: String, otherFiles: Map[String, Expr.TopLevel]): String = { ifCounter = 0; @@ -53,7 +53,7 @@ object ToAssembly { interfaces = List(); enums = List() lambdas = List() - functionScope = FunctionInfo("main", List(), Type.Num()); + functionScope = FunctionInfo("main", "", List(), Type.Num()); var converted = """ @@ -80,7 +80,7 @@ object ToAssembly { | %Type.array.i8 = type {i32, i8*} | %Type.array.i1 = type {i32, i1*} | - |""".stripMargin; //[0 x i32] + |""".stripMargin; input match { case x: Expr.TopLevel => { declareFunctions(x); converted += declareInterfaces(x) + "\n"; @@ -255,16 +255,17 @@ object ToAssembly { var extendLines = lines; var defstring: String = lines.head match { case Expr.SetVal(Expr.Ident(name), value) => { - val look = lookupOffset(name, env) + val look = lookupLoc(name, env) val converted = convertLoc(value, env); if(makeUserTypesConcrete(look.varType) != converted._2) throw new Exception(s"mismatch when assigning value" + s" to variable $name, expected ${look.varType}, but got ${converted._2}") - val set = s"store ${Type.toLLVM(converted._2)} ${converted._3}, ${Type.toLLVM(converted._2)}* %$name, align ${arraySizeFromType(converted._2)}\n" + val set = s"store ${Type.toLLVM(converted._2)} ${converted._3}, ${Type.toLLVM(converted._2)}* ${look.loc}, align ${arraySizeFromType(converted._2)}\n" converted._1 + set; }; case Expr.DefVal(name, varType) => { - newenv = newVar(name, varType, newenv); - s"%$name = alloca ${Type.toLLVM(varType)}\n" //, align ${arraySizeFromType(varType)} + val loc = s"%$name.${varc.extra()}" + newenv = newVar(name, loc, varType, newenv); + s"$loc = alloca ${Type.toLLVM(varType)}\n" //, align ${arraySizeFromType(varType)} } case Expr.DefValWithValue(variable, varType, value) => { var newType = varType @@ -272,9 +273,11 @@ object ToAssembly { if(newType == Type.Undefined()) newType = converted._2; if(makeUserTypesConcrete(newType) != converted._2) throw new Exception(s"mismatch when assigning value" + s" to variable $variable, expected ${newType}, but got ${converted._2}") - val set = s"store ${Type.toLLVM(converted._2)} ${converted._3}, ${Type.toLLVM(converted._2)}* %$variable, align ${arraySizeFromType(converted._2)}\n"; - newenv = newVar(variable, newType, newenv); - s"%$variable = alloca ${Type.toLLVM(newType)}, align 64\n" + converted._1 + set; //, align 4 ${arraySizeFromType(newType)} + + val loc = s"%$variable.${varc.extra()}" + val set = s"store ${Type.toLLVM(converted._2)} ${converted._3}, ${Type.toLLVM(converted._2)}* $loc, align ${arraySizeFromType(converted._2)}\n"; + newenv = newVar(variable, loc, newType, newenv); + s"$loc = alloca ${Type.toLLVM(newType)}, align 64\n" + converted._1 + set; //, align 4 ${arraySizeFromType(newType)} } case Expr.If(condition, ifTrue, ifFalse) => { def compare(left: Expr, right: Expr, numeric: Boolean): String = @@ -377,17 +380,17 @@ object ToAssembly { } private def declareFunctions(input: Expr.TopLevel): Unit = { - functions = input.functions.map(x=> FunctionInfo(x.name, x.argNames, x.retType)) + functions = input.functions.map(x=> FunctionInfo(x.name, "", x.argNames, x.retType)) } private def declareInterfaces(input: Expr.TopLevel): String = { - interfaces = input.interfaces.map(x=> InterfaceInfo(x.name, x.props, x.functions.map(x=>FunctionInfo(x.name,x.argNames,x.retType)))) + interfaces = input.interfaces.map(x=> InterfaceInfo(x.name, x.props, x.functions.map(x=>FunctionInfo(x.name,"", x.argNames,x.retType)))) functions = functions ::: interfaces.flatMap(x=>addPrefixToFunctions(x.name,x.funcs)) interfaces.map(intf => { val types = intf.args.map(x=>Type.toLLVM(x.varType)).mkString(", ") s"%Class.${intf.name} = type {$types}\n" }).mkString } - private def addPrefixToFunctions(prefix: String, funcs: List[FunctionInfo]): List[FunctionInfo] = funcs.map(y=>FunctionInfo(prefix+"_"+y.name, y.args, y.retType)) + private def addPrefixToFunctions(prefix: String, funcs: List[FunctionInfo]): List[FunctionInfo] = funcs.map(y=>FunctionInfo(prefix+"_"+y.name, y.prefix, y.args, y.retType)) private def declareEnums(input: Expr.TopLevel): Unit = { enums = input.enums.map(x=>EnumInfo(x.name,x.props)) } @@ -399,11 +402,11 @@ object ToAssembly { val funcsForImport = searchFileDeclarations(top, imp) match { case Expr.Func(name, argnames, retType, code) => { - functions = functions :+ FunctionInfo(name, argnames, retType) - List(FunctionInfo(name, argnames, retType)) + functions = functions :+ FunctionInfo(name, formatFName(imp.file), argnames, retType) + List(FunctionInfo(name, formatFName(imp.file), argnames, retType)) } case Expr.DefineInterface(name, props, i_functions) => { - val intf = InterfaceInfo(name, props, i_functions.map(x=>FunctionInfo(x.name,x.argNames,x.retType))) + val intf = InterfaceInfo(name, props, i_functions.map(x=>FunctionInfo(x.name,formatFName(imp.file), x.argNames,x.retType))) interfaces = interfaces :+ intf val types = intf.args.map(x=>Type.toLLVM(x.varType)).mkString(", ") @@ -411,15 +414,16 @@ object ToAssembly { val funcs = addPrefixToFunctions(intf.name,intf.funcs) functions = functions ::: funcs - funcs.map(x=>FunctionInfo(x.name, x.args, x.retType)) + funcs.map(x=>FunctionInfo(x.name, formatFName(imp.file), x.args, x.retType)) } } ret + funcsForImport.map(info=>{ val name = fNameSignature(info) val importName = formatFName(imp.file) + "_" + name val args = info.args.map(x=>s"${Type.toLLVM(x.varType)}").mkString(", ") - s"declare ${Type.toLLVM(info.retType)} @${importName}($args)\n" + - s"@$name = ifunc ${toLLVM(info)}, ${toLLVM(info)}* @${importName}\n" + //s"declare ${Type.toLLVM(info.retType)} @${importName}($args)\n" + + // s"@$name = ifunc ${toLLVM(info)}, ${toLLVM(info)}* @${importName}\n" + s"declare ${Type.toLLVM(info.retType)} @${importName}($args) \"${formatFName(imp.file)}\"\n" }).mkString /* intf.funcs.map(x=>{ @@ -481,11 +485,13 @@ object ToAssembly { functionScope = info; val fname = fNameSignature(info) val args = info.args.map(x=>s"${Type.toLLVM(x.varType)} %Input.${x.name}").mkString(", ") - var ret = s"define ${Type.toLLVM(info.retType)} @${fname}($args) #0 {\n" - val newEnv = upperScope ++ info.args.map(x=> (x.name, Variable(x.varType))).toMap + val addPrivate = if (info.name=="main") "" else "private "; + var ret = s"define $addPrivate${Type.toLLVM(info.retType)} @${fname}($args) #0 {\n" + val newEnv = upperScope ++ info.args.map(x=> (x.name, Variable(s"%${x.name}", x.varType))).toMap //Variable(s"%${x.name}.${varc.extra()}" var body = info.args.map(x=> s"%${x.name} = alloca ${Type.toLLVM(x.varType)}, align 64\n" + //, align ${arraySizeFromType(x.varType)} s"store ${Type.toLLVM(x.varType)} %Input.${x.name}, ${Type.toLLVM(x.varType)}* %${x.name}\n").mkString //, align ${arraySizeFromType(x.varType)} + //TODO might want to handle name shadowing here body += "%dummy = alloca i32\n" body += convert(function.body, newEnv)._1 @@ -509,8 +515,9 @@ object ToAssembly { case Some(x) => ; case None => throw new Exception(s"function of name $name undefined"); } functions.find(x=>x.name == name && Type.compare(argInputTypes, x.args.map(x=>makeUserTypesConcrete(x.varType)))) match { - case Some(info@FunctionInfo(p, argTypes, retType)) => { - val tName = fNameSignature(info) + case Some(info@FunctionInfo(p, prefix, argTypes, retType)) => { + var tName = fNameSignature(info) + if(prefix != "") tName = prefix+"_"+tName; val argTypeString = argTypes.map(x=>Type.toLLVM(x.varType)).mkString(", ") if (retType != Type.Undefined()) ret += s"${varc.next()} = "; ret += s"call ${Type.toLLVM(retType)} ($argTypeString) @$tName($argsString)\n" @@ -685,16 +692,16 @@ object ToAssembly { } def lookup(tofind: String, env: Env): (String, Variable) = { - val ret = lookupOffset(tofind, env) - (s"${varc.next()} = load ${Type.toLLVM(ret.varType)}, ${Type.toLLVM(ret.varType)}* %$tofind, align ${arraySizeFromType(ret.varType)}\n", ret) + val ret = lookupLoc(tofind, env) + (s"${varc.next()} = load ${Type.toLLVM(ret.varType)}, ${Type.toLLVM(ret.varType)}* ${ret.loc}, align ${arraySizeFromType(ret.varType)}\n", ret) } - def lookupOffset(tofind: String, env: Env): Variable = env.get(tofind) match { + def lookupLoc(tofind: String, env: Env): Variable = env.get(tofind) match { case Some(v) => v case None => throw new Exception(s"variable \"${tofind}\" undefined") } - def newVar(name: String, varType: Type, env: Env) : Env = { + def newVar(name: String, loc: String, varType: Type, env: Env) : Env = { if(env.contains(name)) throw new Exception(s"variable \"${name}\" already defined") - env + (name -> Variable(varType)) + env + (name -> Variable(loc, varType)) } def convertLoc(input: Expr, env: Env): (String, Type, String) = { @@ -744,20 +751,8 @@ object ToAssembly { case ((codeLeft, typeLeft, x), (codeRight, typeRight, y)) => throw new Exception(s"can't perform arithmetic on operands of types ${typeLeft} and ${typeRight}"); } } - /* - def convertToFloat(reg: String): String = { - s"cvtsi2sd xmm0, ${reg}\n" + s"movq ${reg}, xmm0\n" - } - def convertToInt(reg: String): String = { - s"movq xmm0, ${reg}\n" + s"cvtsd2si ${reg}, xmm0\n" - } - */ def printTemplate(format: String, ty: String, loc: String): String = { - /* - s"%ret.${varc.extra()} = call i32 (ptr, ...) @printf(ptr getelementptr inbounds" + - s" ([3 x i8], [3 x i8]* @$format, i32 0, i32 0), $ty ${varc.last()})\n" - */ s"%ret.${varc.extra()} = call i32 (ptr, ...) @printf(ptr @$format, $ty ${loc})\n" + s"${varc.next()} = load ptr, ptr @stdout, align 8" + s"call void @setbuf(ptr noundef ${varc.last()}, ptr noundef null)" @@ -781,21 +776,12 @@ object ToAssembly { case _ => throw new Exception(s"input of type ${converted._2} not recognized in print") } } - //TODO runtime memory garbage collection, currently only pointers on the stack are handled - /* - def freeMemory(env: Env): String = env.foldLeft("")((acc, entry) => entry._2.varType match { - case Type.Array(arrType) => acc + s"mov rdi, [rbp-${entry._2.pointer}]\n" + "call free\n"; - //case Type.Interface(args) => acc + s"mov rdi, [rbp-${entry._2.pointer}]\n" + "call free\n"; - case _ => acc - }) - - */ type Env = Map[String, Variable] - case class FunctionInfo(name: String, args: List[InputVar], retType: Type) + case class FunctionInfo(name: String, prefix: String, args: List[InputVar], retType: Type) //case class InterfaceInfo(name: String, args: List[InputVar]) case class InterfaceInfo(name: String, args: List[InputVar], funcs: List[FunctionInfo]) case class EnumInfo(name: String, el: List[String]) - case class Variable(varType: Type) + case class Variable(loc: String, varType: Type) } diff --git a/build-llvm.sh b/build-llvm.sh index e31452a..2311c45 100644 --- a/build-llvm.sh +++ b/build-llvm.sh @@ -1,27 +1,50 @@ #!/bin/bash +#for compatibility between installs +clang() { + if hash clang-15 2>/dev/null; then + clang-15 "$@" + else + clang "$@" + fi +} +llc() { + if hash llc-15 2>/dev/null; then + llc-15 "$@" + else + llc "$@" + fi +} + +#make compiled directory and go to it mkdir -p compiled && \ cd compiled/ || exit dir_succeeded=$? +#check if moving to directory succeeded if [ $dir_succeeded -ne 0 ]; then + echo 'Could not go to compiled/ directory' exit 1 fi +#sus code that gets all files that have a .ll ending files=$( cut -d '.' -f 1 <<< "$(ls -- *.ll)" ) files_asm=() +#compile all .ll files to assembly for i in $files; do files_asm+=("$i".s) - llc-15 "$i".ll -O0 -opaque-pointers --stackrealign --stack-size-section --debugify-level=location+variables --frame-pointer=all -align-all-nofallthru-blocks=4 -align-all-functions=4 + llc "$i".ll -O0 -opaque-pointers --stackrealign --stack-size-section --debugify-level=location+variables --frame-pointer=all -align-all-nofallthru-blocks=4 -align-all-functions=4 # --stackrealign --asm-show-inst --align-loops=64 done +#put all asm files to files files="${files_asm[*]}" #gcc -O0 -ggdb -mpreferred-stack-boundary=4 -no-pie $files -o "hello" -clang-15 -O0 -no-pie $files -o "hello" # -mstack-alignment=4 +#link and compile all assembly files to a single object file +clang -O0 -no-pie $files -o "hello" # -mstack-alignment=4 diff --git a/build.sh b/build.sh deleted file mode 100644 index c2363d8..0000000 --- a/build.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash - -mkdir -p compiled && \ -cd compiled/ || exit - -dir_succeeded=$? - -if [ $dir_succeeded -ne 0 ]; -then - exit 1 -fi - -files=$( cut -d '.' -f 1 <<< "$(ls | grep .asm)" ) -files_asm=() - -for i in $files; -do - files_asm+=($i.o) - nasm -felf64 $i.asm -done - -files="${files_asm[*]}" - -gcc -O0 -ggdb -no-pie $files -o "hello" \ No newline at end of file diff --git a/commands.txt b/commands.txt new file mode 100644 index 0000000..20d165a --- /dev/null +++ b/commands.txt @@ -0,0 +1,8 @@ +cd compiled/ +../debugir toCompile.ll -instnamer +gdb lli-14 +run --jit-kind=mcjit toCompile.dbg.ll + + +clang -mstackrealign +gcc -mpreferred-stack-boundary=3 diff --git a/docs/Guide.md b/docs/Guide.md index 6fc63f8..0a6f88c 100644 --- a/docs/Guide.md +++ b/docs/Guide.md @@ -48,6 +48,7 @@ def main(): int { * bool * float * array[*type*] +* void (return nothing for functions) ### Floats diff --git a/docs/examples.txt b/docs/examples.txt index a24b130..74ebd86 100644 --- a/docs/examples.txt +++ b/docs/examples.txt @@ -48,34 +48,4 @@ object Dynamic { }; return same; } -} - - -//example with templates -/* -def main(): int { - val a = array(1,2,3); - val b = array(4,5,6); - val c = concat(a,b); - print_arr(c); -} -*/ -def concat(a: array[T1], b: array[T1]): array[T1] { - val combined = (a.size + b.size); - val ret = array[T1][combined]; - for(val i = 0; i < a.size; i += 1;) { - ret[i] = a[i]; - }; - for(val i = 0; i < b.size; i += 1;) { - ret[(i + a.size)] = b[i]; - }; - return ret; -} -def print_arr(a: array[int]) { - val b = 0; - while(b < a.size) { - print(a[b]); - b += 1; - }; - return; } \ No newline at end of file diff --git a/lib-code/nice.txt b/docs/library.txt similarity index 55% rename from lib-code/nice.txt rename to docs/library.txt index a1744f1..2cc0e0e 100644 --- a/lib-code/nice.txt +++ b/docs/library.txt @@ -1,24 +1,16 @@ +def ok() { +}; +/* object Dynamic { size: int; allocated: int; arr: array[int]; - map: func[(func[(int) => int]) => Dynamic]; - def Dynamic(self: Dynamic) { - self.arr = array[int][4]; - self.allocated = 4; + def Dynamic(self: Dynamic): Dynamic { + self.arr = array[int][128]; + self.allocated = 128; self.size = 0; - - self.map = lambda (f: func[(int) => int]): Dynamic => { - val ret = self.copy(); - - for(val i = 0; i < self.size; i+= 1;) { - ret.arr[i] = f(ret.arr[i]); - }; - return ret; - }; - + return self; } - def expand(self: Dynamic, req: int) { val total = (req + self.size); if(total >= self.allocated) { @@ -27,10 +19,11 @@ object Dynamic { newsize = (newsize * 2); }; val old = self.arr; - self.arr = array[int][newsize]; + val narr = array[int][newsize]; for(val i = 0; i < self.size; i+= 1;) { - self.arr[i] = old[i]; + narr[i] = old[i]; }; + self.arr = narr; self.allocated = newsize; self.size = total; } else { @@ -41,7 +34,6 @@ object Dynamic { self.expand(1); self.arr[(self.size-1)] = value; } - def push(self: Dynamic, value: array[int]) { val oldSize = self.size; self.expand(value.size); @@ -62,6 +54,7 @@ object Dynamic { def concat(self: Dynamic, other: Dynamic): Dynamic { val ret = self.copy(); + //ret.print_arr(); ret.push(other); return ret; } @@ -73,83 +66,43 @@ object Dynamic { return self.arr[index]; } def print_arr(self: Dynamic) { + //print(self.size); + for(val i = 0; i < self.size; i+= 1;) { - print(self.arr[i]); + //print(i); + //print(" "); + //print(self.arr[i]); + //print(self.size); + //print("l"); }; + + //print(self.size); + //print("\n"); } def copy(self: Dynamic): Dynamic { val arr_new = new Dynamic(); for(val i = 0; i < self.size; i+= 1;) { - arr_new.push(self.arr[i]); + //arr_new.push(self.arr[i]); }; return arr_new; } def compare(self: Dynamic, other: Dynamic): bool { val same: bool = true; - if(self.size != other.size) {return false;}; + //if(self.size != other.size) {return false;}; for(val i = 0; i < self.size; i+= 1;) { if(self.get(i) != other.get(i)) {same = false;}; }; return same; } + def __add__(self: Dynamic, other: Dynamic): Dynamic { + return self.concat(other); + } + def __print__(self: Dynamic) { + self.print_arr(); + } def not_useful() { print("not useful, you're dumb"); } -} - -/* -def apply(a: int, b: int, f: func[(int, int) => int]): int { - return f(a, b); -} -def main(): int { - val c = 5; - val a = lambda (x: int, y: int): int => (x + y + c); - val b = apply(5,6,a); - print(b); -} -*/ - -/* - -def main(): int { - - val a = new Dynamic(); - a.push(array(3,5,5,7,8)); - a.print_arr(); - print(""); - a - .map(lambda(x: int): int => (x * x)) - .print_arr(); -} - -*/ - -/* -def main(): int { - val a = array(1,2,3); - val b = array(4,5,6); - val c = concat(a,b); - print_arr(c); - return 0; -} -def concat(a: array[T1], b: array[T1]): array[T1] { - val combined = (a.size + b.size); - val ret = array[T1][combined]; - for(val i = 0; i < a.size; i += 1;) { - ret[i] = a[i]; - }; - for(val i = 0; i < b.size; i += 1;) { - ret[(i + a.size)] = b[i]; - }; - return ret; -} -def print_arr(a: array[int]) { - val b = 0; - while(b < a.size) { - print(a[b]); - b += 1; - }; - return; } -*/ +*/ \ No newline at end of file diff --git a/docs/tasklist.md b/docs/tasklist.md index fecfe02..78b8c1b 100644 --- a/docs/tasklist.md +++ b/docs/tasklist.md @@ -3,11 +3,13 @@ * lambdas ## Bugs -* for some reason imports are still done in files -* functions have limited checking before llvm catches it (especially multiple constructors) +* ~~functions have limited checking before llvm catches it (especially multiple constructors)~~ +* remove l prefix from register names ## To do + + * add copy command to arrays(and maybe structs) * rework file imports with explicit export command * add more object intrinsics(equal) diff --git a/llvm.sh b/llvm.sh new file mode 100644 index 0000000..a8887ea --- /dev/null +++ b/llvm.sh @@ -0,0 +1,165 @@ +#!/bin/bash +################################################################################ +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +################################################################################ +# +# This script will install the llvm toolchain on the different +# Debian and Ubuntu versions + +set -eux + +usage() { + set +x + echo "Usage: $0 [llvm_major_version] [all] [OPTIONS]" 1>&2 + echo -e "all\t\t\tInstall all packages." 1>&2 + echo -e "-n=code_name\t\tSpecifies the distro codename, for example bionic" 1>&2 + echo -e "-h\t\t\tPrints this help." 1>&2 + echo -e "-m=repo_base_url\tSpecifies the base URL from which to download." 1>&2 + exit 1; +} + +CURRENT_LLVM_STABLE=15 +BASE_URL="http://apt.llvm.org" + +# Check for required tools +needed_binaries=(lsb_release wget add-apt-repository gpg) +missing_binaries=() +for binary in "${needed_binaries[@]}"; do + if ! which $binary &>/dev/null ; then + missing_binaries+=($binary) + fi +done +if [[ ${#missing_binaries[@]} -gt 0 ]] ; then + echo "You are missing some tools this script requires: ${missing_binaries[@]}" + echo "(hint: apt install lsb-release wget software-properties-common gnupg)" + exit 4 +fi + +# Set default values for commandline arguments +# We default to the current stable branch of LLVM +LLVM_VERSION=$CURRENT_LLVM_STABLE +ALL=0 +DISTRO=$(lsb_release -is) +VERSION=$(lsb_release -sr) +UBUNTU_CODENAME="" +CODENAME_FROM_ARGUMENTS="" +# Obtain VERSION_CODENAME and UBUNTU_CODENAME (for Ubuntu and its derivatives) +source /etc/os-release +DISTRO=${DISTRO,,} +case ${DISTRO} in + debian) + if [[ "${VERSION}" == "unstable" ]] || [[ "${VERSION}" == "testing" ]] || [[ "${VERSION_CODENAME}" == "bookworm" ]]; then + # For now, bookworm == sid. + # TODO change when bookworm is released + CODENAME=unstable + LINKNAME= + else + # "stable" Debian release + CODENAME=${VERSION_CODENAME} + LINKNAME=-${CODENAME} + fi + ;; + *) + # ubuntu and its derivatives + if [[ -n "${UBUNTU_CODENAME}" ]]; then + CODENAME=${UBUNTU_CODENAME} + if [[ -n "${CODENAME}" ]]; then + LINKNAME=-${CODENAME} + fi + fi + ;; +esac + +# read optional command line arguments +if [ "$#" -ge 1 ] && [ "${1::1}" != "-" ]; then + if [ "$1" != "all" ]; then + LLVM_VERSION=$1 + else + # special case for ./llvm.sh all + ALL=1 + fi + OPTIND=2 + if [ "$#" -ge 2 ]; then + if [ "$2" == "all" ]; then + # Install all packages + ALL=1 + OPTIND=3 + fi + fi +fi + +while getopts ":hm:n:" arg; do + case $arg in + h) + usage + ;; + m) + BASE_URL=${OPTARG} + ;; + n) + CODENAME=${OPTARG} + if [[ "${CODENAME}" == "unstable" ]]; then + # link name does not apply to unstable repository + LINKNAME= + else + LINKNAME=-${CODENAME} + fi + CODENAME_FROM_ARGUMENTS="true" + ;; + esac +done + +if [[ $EUID -ne 0 ]]; then + echo "This script must be run as root!" + exit 1 +fi + +declare -A LLVM_VERSION_PATTERNS +LLVM_VERSION_PATTERNS[9]="-9" +LLVM_VERSION_PATTERNS[10]="-10" +LLVM_VERSION_PATTERNS[11]="-11" +LLVM_VERSION_PATTERNS[12]="-12" +LLVM_VERSION_PATTERNS[13]="-13" +LLVM_VERSION_PATTERNS[14]="-14" +LLVM_VERSION_PATTERNS[15]="-15" +LLVM_VERSION_PATTERNS[16]="" + +if [ ! ${LLVM_VERSION_PATTERNS[$LLVM_VERSION]+_} ]; then + echo "This script does not support LLVM version $LLVM_VERSION" + exit 3 +fi + +LLVM_VERSION_STRING=${LLVM_VERSION_PATTERNS[$LLVM_VERSION]} + +# join the repository name +if [[ -n "${CODENAME}" ]]; then + REPO_NAME="deb ${BASE_URL}/${CODENAME}/ llvm-toolchain${LINKNAME}${LLVM_VERSION_STRING} main" + + # check if the repository exists for the distro and version + if ! wget -q --method=HEAD ${BASE_URL}/${CODENAME} &> /dev/null; then + if [[ -n "${CODENAME_FROM_ARGUMENTS}" ]]; then + echo "Specified codename '${CODENAME}' is not supported by this script." + else + echo "Distribution '${DISTRO}' in version '${VERSION}' is not supported by this script." + fi + exit 2 + fi +fi + + +# install everything +if [[ -z "`apt-key list | grep -i llvm`" ]]; then + # download GPG key once + wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - +fi +add-apt-repository "${REPO_NAME}" +apt-get update +PKG="clang-$LLVM_VERSION lldb-$LLVM_VERSION lld-$LLVM_VERSION clangd-$LLVM_VERSION" +if [[ $ALL -eq 1 ]]; then + # same as in test-install.sh + # No worries if we have dups + PKG="$PKG clang-tidy-$LLVM_VERSION clang-format-$LLVM_VERSION clang-tools-$LLVM_VERSION llvm-$LLVM_VERSION-dev lld-$LLVM_VERSION lldb-$LLVM_VERSION llvm-$LLVM_VERSION-tools libomp-$LLVM_VERSION-dev libc++-$LLVM_VERSION-dev libc++abi-$LLVM_VERSION-dev libclang-common-$LLVM_VERSION-dev libclang-$LLVM_VERSION-dev libclang-cpp$LLVM_VERSION-dev libunwind-$LLVM_VERSION-dev" +fi +apt-get install -y $PKG From bd6d436efea6880f3470d014a1a44da564e041a6 Mon Sep 17 00:00:00 2001 From: Pijus Krisiukenas Date: Mon, 31 Oct 2022 16:36:29 +0100 Subject: [PATCH 077/112] small fix to overloading --- app/src/main/scala/posharp/Definitions.scala | 2 +- app/src/main/scala/posharp/ToAssembly.scala | 6 +- docs/library.txt | 108 ------------------- 3 files changed, 3 insertions(+), 113 deletions(-) delete mode 100644 docs/library.txt diff --git a/app/src/main/scala/posharp/Definitions.scala b/app/src/main/scala/posharp/Definitions.scala index ffad594..c2a27fd 100644 --- a/app/src/main/scala/posharp/Definitions.scala +++ b/app/src/main/scala/posharp/Definitions.scala @@ -114,7 +114,7 @@ object Type { case _ => false } def compare(value: (Type, Type)): Boolean = compare(value._1,value._2) - def compare(val1: List[Type], val2: List[Type]): Boolean = (val1 zip val2).forall(x=>compare(x)) + def compare(val1: List[Type], val2: List[Type]): Boolean = val1.length == val2.length && (val1 zip val2).forall(x=>compare(x)) def defaultValue(valType: Type): Expr = valType match { case Num() => Expr.Num(0); case NumFloat() => Expr.NumFloat(0); diff --git a/app/src/main/scala/posharp/ToAssembly.scala b/app/src/main/scala/posharp/ToAssembly.scala index 4245c78..4089dc8 100644 --- a/app/src/main/scala/posharp/ToAssembly.scala +++ b/app/src/main/scala/posharp/ToAssembly.scala @@ -202,7 +202,6 @@ object ToAssembly { case Some(intf) => { var aloc = ""; val classT = s"%Class.$name" - //ret += s"${varc.next()} = alloca %Class.$name, align 64\n" aloc += s"${varc.next()} = getelementptr $classT, $classT* null, i32 1\n" + s"${varc.next()} = ptrtoint $classT** ${varc.secondLast()} to i32\n" val bytesLoc = varc.last(); @@ -211,9 +210,8 @@ object ToAssembly { val valuesCompiled = values.map(x=>convertLoc(x, env)).map(x=>Expr.Compiled(x._1, x._2, x._3)) intf.funcs.find(x=>x.name == name && x.args == values) - val func_code = interpFunction(name+"_"+name, Expr.Compiled(aloc, UserType(name), varc.last()) +: valuesCompiled, env)._1 - val ret = func_code; - (ret, Type.Interface(name, intf.args, intf.funcs)) + val func_code = interpFunction(name+"_"+name, Expr.Compiled(aloc, UserType(name), alocLoc) +: valuesCompiled, env)._1 + (func_code, Type.Interface(name, intf.args, intf.funcs)) } case None => throw new Exception(s"no interface with name \"$name\" defined") } diff --git a/docs/library.txt b/docs/library.txt deleted file mode 100644 index 2cc0e0e..0000000 --- a/docs/library.txt +++ /dev/null @@ -1,108 +0,0 @@ -def ok() { -}; -/* -object Dynamic { - size: int; - allocated: int; - arr: array[int]; - def Dynamic(self: Dynamic): Dynamic { - self.arr = array[int][128]; - self.allocated = 128; - self.size = 0; - return self; - } - def expand(self: Dynamic, req: int) { - val total = (req + self.size); - if(total >= self.allocated) { - val newsize = self.allocated; - while(newsize < (total+1)) { - newsize = (newsize * 2); - }; - val old = self.arr; - val narr = array[int][newsize]; - for(val i = 0; i < self.size; i+= 1;) { - narr[i] = old[i]; - }; - self.arr = narr; - self.allocated = newsize; - self.size = total; - } else { - self.size += req; - }; - } - def push(self: Dynamic, value: int) { - self.expand(1); - self.arr[(self.size-1)] = value; - } - def push(self: Dynamic, value: array[int]) { - val oldSize = self.size; - self.expand(value.size); - - for(val i = 0; i < value.size; i+= 1;) { - self.arr[(i + oldSize)] = value[i]; - }; - } - - def push(self: Dynamic, value: Dynamic) { - val oldSize = self.size; - self.expand(value.size); - - for(val i = 0; i < value.size; i+= 1;) { - self.arr[(i + oldSize)] = value.arr[i]; - }; - } - - def concat(self: Dynamic, other: Dynamic): Dynamic { - val ret = self.copy(); - //ret.print_arr(); - ret.push(other); - return ret; - } - //filter - def get(self: Dynamic, index: int): int { - if(index >= self.size) { - throw exception("index out of bounds"); - }; - return self.arr[index]; - } - def print_arr(self: Dynamic) { - //print(self.size); - - for(val i = 0; i < self.size; i+= 1;) { - //print(i); - //print(" "); - //print(self.arr[i]); - //print(self.size); - //print("l"); - }; - - //print(self.size); - //print("\n"); - } - def copy(self: Dynamic): Dynamic { - val arr_new = new Dynamic(); - for(val i = 0; i < self.size; i+= 1;) { - //arr_new.push(self.arr[i]); - }; - return arr_new; - } - def compare(self: Dynamic, other: Dynamic): bool { - val same: bool = true; - //if(self.size != other.size) {return false;}; - for(val i = 0; i < self.size; i+= 1;) { - if(self.get(i) != other.get(i)) {same = false;}; - }; - return same; - } - def __add__(self: Dynamic, other: Dynamic): Dynamic { - return self.concat(other); - } - def __print__(self: Dynamic) { - self.print_arr(); - } - def not_useful() { - print("not useful, you're dumb"); - } - -} -*/ \ No newline at end of file From b3458608505230cf8469de1a8448e8908affe870 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Mon, 31 Oct 2022 17:26:17 +0100 Subject: [PATCH 078/112] Added `opaque pointers` flag (requires LLVM 15) --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 55ea418..de79495 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ all: run_llvm build: mkdir -p compiled && \ cd compiled/ && \ - llc $(TARGET_FILE).ll && \ + llc $(TARGET_FILE).ll -opaque-pointers && \ gcc -O0 -ggdb -no-pie $(TARGET_FILE).s -o $(TARGET_FILE) compiled/$(TARGET_FILE) From ca782240d47bf7d82ba99eaa3b37a8366f21f015 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Mon, 31 Oct 2022 17:29:36 +0100 Subject: [PATCH 079/112] Fixed warnings --- veritas/src/main/scala/core/Coverage.scala | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/veritas/src/main/scala/core/Coverage.scala b/veritas/src/main/scala/core/Coverage.scala index daec4c3..17417f8 100644 --- a/veritas/src/main/scala/core/Coverage.scala +++ b/veritas/src/main/scala/core/Coverage.scala @@ -4,8 +4,8 @@ import org.reflections.Reflections import posharp.Expr import java.nio.file.{Files, Path} -import scala.collection.convert.ImplicitConversions._ import scala.collection.immutable.ListMap +import scala.jdk.CollectionConverters._ /** * Generates a simple coverage report indicating which case classes inheriting from [[Expr]] are being used by @@ -57,7 +57,7 @@ object Coverage { * @return (Class,ClassName) tuples */ private def GetExprClassNameTuples: List[(Class[_ <: Expr], String)] = { - reflections.getSubTypesOf(classOf[Expr]).toList + reflections.getSubTypesOf(classOf[Expr]).asScala.toList .map(el => (el, el.getName.split("\\$").last)) } @@ -67,7 +67,7 @@ object Coverage { * @param export True exports a CodeCov JSON report * @return Map[ClassName, TimesUsed] */ - def CalculateCoverage(export: Boolean = false): Map[String, Int] = { + def CalculateCoverage(`export`: Boolean = false): Map[String, Int] = { val coverages = SumCoverages(exprs) val res = GetAllExprCaseClasses() @@ -75,7 +75,7 @@ object Coverage { .map(el => el._1 -> 0) .filterNot(el => redundantExprs.contains(el._1)) - percentage = ((coverages.size / GetAllExprCaseClasses().size.toDouble) * 100).round + percentage = ((coverages.size / GetAllExprCaseClasses().size.toDouble) * 100).round.toDouble println(s"==== Covered $percentage% of Expr case classes ====\n") @@ -130,11 +130,11 @@ object Coverage { // Find where the object starts to avoid mismatching stuff with imports // This shouldn't happen because of the regex stuff I later added below but // you never know. - val objStart = txt.indexWhere(_.contains("object Expr")) + val objStart = txt.asScala.indexWhere(_.contains("object Expr")) cov.foreach(el => output = output :+ ExprUsagesLine(el._1, el._2, - txt + txt.asScala .drop(objStart) .indexWhere(line => ExtractClassName(line) == el._1) + objStart + 1 )) @@ -160,7 +160,7 @@ object Coverage { private def ExtractClassName(line: String): String = { val data = "case class ([a-zA-Z]+)\\(".r.findAllIn(line).matchData.toList if (data.isEmpty) "" - else data.get(0).group(1) + else data.asJava.get(0).group(1) } /** From a654f8dd06ceb5570079b0ca8376272e33519238 Mon Sep 17 00:00:00 2001 From: Pijus Krisiukenas Date: Fri, 4 Nov 2022 21:25:51 +0100 Subject: [PATCH 080/112] gradle pls --- README.md | 14 +- build.gradle.kts | 2 +- veritas/src/main/scala/test/TestExample.scala | 125 +++++++++++++----- 3 files changed, 100 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index b4a6166..8e6fe6c 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@

Po#

- Custom language compiler to X86_64 nasm assembly, written in Scala + Custom language frontend for LLVM IR, written in Scala

@@ -83,23 +83,23 @@ to translate to assembly. * [Scala](https://www.scala-lang.org/) * [FastParse](https://github.com/com-lihaoyi/fastparse) +* [LLVM](https://llvm.org/) ### Prerequisites
-* [Ubuntu 18.04]() and/or [WSL](https://docs.microsoft.com/en-us/windows/wsl/install) +* [Ubuntu 18.04 or newer]() and/or [WSL](https://docs.microsoft.com/en-us/windows/wsl/install) * [JDK 13+](https://www.oracle.com/java/technologies/downloads/) -* [Scala 2.13](https://www.scala-lang.org/download/) -* [SBT 1.6.1](https://www.scala-sbt.org/download.html) -* [NASM](https://www.nasm.us/) +* [Scala 2.13+](https://www.scala-lang.org/download/) +* [Gradle 7.5](https://gradle.org/install/) * [GCC](https://gcc.gnu.org/) +* [Clang 15+](https://releases.llvm.org/) +* [LLVM 15+](https://releases.llvm.org/download.html) ### Getting Started
-Currently, I run the scala compiler through IntelliJ. The conversion from -assembly to binary is handled by a makefile, that i run in WSL with ubuntu 18; There is also an option to compile with a single command using sbt. Just run `make full` in the main directory diff --git a/build.gradle.kts b/build.gradle.kts index 397c436..e8ece99 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -14,5 +14,5 @@ dependencies { } application { - mainClass.set("scala.Main") + mainClass.set("scala.Main") //posharp.Main } \ No newline at end of file diff --git a/veritas/src/main/scala/test/TestExample.scala b/veritas/src/main/scala/test/TestExample.scala index 5460faf..7cd5f9a 100644 --- a/veritas/src/main/scala/test/TestExample.scala +++ b/veritas/src/main/scala/test/TestExample.scala @@ -62,38 +62,93 @@ class TestExample { .ShouldThrow(new ParseException("")) def runTestBig(): (Boolean, String) = { - """object Dynamic { + """ + object Dynamic { size: int; allocated: int; - arr: array[int]; - def Dynamic(self: Dynamic) { - self.arr = array[int][2]; - self.allocated = 2; + arr: array[char]; + + def Dynamic(self: Dynamic): Dynamic { + self.arr = array[char][8]; + self.allocated = 8; self.size = 0; + return self; + } + def Dynamic(self: Dynamic, arr: array[char]): Dynamic { + self.Dynamic(); + self.push(arr); + return self; + } + + def expand(self: Dynamic, req: int) { + val total = (req + self.size); + + if(total >= self.allocated) { + val newsize = self.allocated; + while(newsize < (total+1)) { + newsize = (newsize * 2); + }; + val old = self.arr; + val narr = array[char][newsize]; + for(val i = 0; i < self.size; i+= 1;) { + narr[i] = old[i]; + }; + self.arr = narr; + free(old); + self.allocated = newsize; + self.size = total; + } else { + self.size += req; + }; + } + def push(self: Dynamic, value: char) { + self.expand(1); + self.arr[(self.size-1)] = value; } - def push(self: Dynamic, value: int) { - self.arr[self.size] = value; - self.size += 1; - if(self.allocated == self.size) { - val newsize = (self.allocated * 2); - val old = self.arr; - self.arr = array[int][newsize]; - for(val i = 0; i < self.size; i+= 1;) { - self.arr[i] = old[i]; - }; - self.allocated = newsize; + def push(self: Dynamic, value: array[char]) { + val oldSize = self.size; + self.expand(value.size); + + for(val i = 0; i < value.size; i+= 1;) { + self.arr[(i + oldSize)] = value[i]; }; } - def get(self: Dynamic, index: int): int { + + def push(self: Dynamic, value: Dynamic) { + val oldSize = self.size; + self.expand(value.size); + + for(val i = 0; i < value.size; i+= 1;) { + self.arr[(i + oldSize)] = value.arr[i]; + }; + } + + def concat(self: Dynamic, other: Dynamic): Dynamic { + val ret = self.copy(); + ret.push(other); + return ret; + } + //filter + def get(self: Dynamic, index: int): char { if(index >= self.size) { throw exception("index out of bounds"); }; return self.arr[index]; } def print_arr(self: Dynamic) { - for(val i = 0; i < self.size; i+= 1;) { + for(val i = 0; i< self.size; i+=1;) { print(self.arr[i]); + //print(" "); + }; + print("\n"); + } + def copy(self: Dynamic): Dynamic { //still sus cause on stack + val arr_new = new Dynamic(); + + for(val i = 0; i < self.size; i+= 1;) { + arr_new.push(self.arr[i]); }; + return arr_new; } def compare(self: Dynamic, other: Dynamic): bool { val same: bool = true; @@ -103,22 +158,26 @@ class TestExample { }; return same; } - } + def __add__(self: Dynamic, other: Dynamic): Dynamic { + return self.concat(other); + } + def __print__(self: Dynamic) { + self.print_arr(); + } + def not_useful() { + print("not useful, you're dumb"); + } + + } def main(): int { - val a = new Dynamic(); - //print(a.get(8)); - a.push(1); - a.push(2); - a.push(3); - a.print_arr(); - val b = new Dynamic(); - b.push(1); - b.push(2); - print(a.compare(b)); - b.push(3); - print(a.compare(b)); - }""" - .ShouldBe("true") + + val a = new Dynamic(); + a.push(array('a', 'b', 'c')); + val b = new Dynamic(array('d', 'e', 'f')); + print(a+b); + } + """ + .ShouldBe("abcdef") .Run() } } From a3eb41367f0c2486b0af044265a4da41064a003b Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Fri, 4 Nov 2022 22:09:37 +0100 Subject: [PATCH 081/112] Bumped versions --- app/build.gradle.kts | 2 +- build.gradle.kts | 8 ++------ veritas/build.gradle.kts | 4 ++-- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f5d59cc..68d92df 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -8,7 +8,7 @@ repositories { } dependencies { - implementation("org.scala-lang:scala-library:2.13.6") + implementation("org.scala-lang:scala-library:2.13.10") implementation("com.lihaoyi:fastparse_2.13:2.3.3") } diff --git a/build.gradle.kts b/build.gradle.kts index e8ece99..be2a52a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,11 +8,7 @@ repositories { } dependencies { - implementation("org.scala-lang:scala-library:2.13.6") - implementation("com.google.guava:guava:30.1.1-jre") + implementation("org.scala-lang:scala-library:2.13.10") + implementation("com.google.guava:guava:31.1-jre") implementation("com.lihaoyi:fastparse_2.13:2.3.3") } - -application { - mainClass.set("scala.Main") //posharp.Main -} \ No newline at end of file diff --git a/veritas/build.gradle.kts b/veritas/build.gradle.kts index 41a37a7..3db55f8 100644 --- a/veritas/build.gradle.kts +++ b/veritas/build.gradle.kts @@ -8,9 +8,9 @@ repositories { } dependencies { - implementation("org.scala-lang:scala-library:2.13.8") + implementation("org.scala-lang:scala-library:2.13.10") implementation("org.reflections:reflections:0.10.2") - implementation("org.scala-lang:scala-reflect:2.13.8") + implementation("org.scala-lang:scala-reflect:2.13.10") implementation(project(":app")) } From 0cc5578b8979df765f25d435a4332c22e9921e6c Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Fri, 4 Nov 2022 23:38:54 +0100 Subject: [PATCH 082/112] stuff --- Dockerfile | 23 ++++++++++------------- toast.yml | 22 ++++------------------ 2 files changed, 14 insertions(+), 31 deletions(-) diff --git a/Dockerfile b/Dockerfile index 70b1e0d..1d16b4d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,20 +1,17 @@ # antoniosbarotsis/posharp-veritas -# Packages all dependencies needed to run the tests +# Packages all project dependencies FROM openjdk:17-jdk-slim -RUN apt-get update && apt-get install -y \ - make \ - llvm \ - gcc \ - curl \ - nano \ - && apt-get clean \ - && rm -rf /var/cache/apt/archives /var/lib/apt/lists/* +RUN apt-get update -y && \ + apt-get install -y gcc make curl dos2unix lsb-release wget software-properties-common gnupg + +COPY llvm.sh llvm.sh +RUN chmod +x llvm.sh +RUN dos2unix llvm.sh +RUN cat ./llvm.sh +RUN ./llvm.sh 15 +RUN mv /usr/bin/llc-15 /usr/bin/llc -# Cache dependencies hopefully COPY *.gradle gradle.* gradlew ./ COPY gradle/ ./gradle/ - -# Make the build fail so the dependencies get resolved -RUN ./gradlew clean build --no-daemon 2>&1 || true diff --git a/toast.yml b/toast.yml index 5d389ba..4a3eea9 100644 --- a/toast.yml +++ b/toast.yml @@ -2,20 +2,6 @@ image: antoniosbarotsis/posharp-veritas command_prefix: set -e # Make Bash fail fast. tasks: - build_dependencies: - command: | - ./gradlew clean build --no-daemon 2>&1 || true - description: "Builds and caches all the used libraries to speedup subsequent steps." - input_paths: - - gradlew - - gradle - - build.gradle.kts - - gradle.properties - - settings.gradle.kts - - app/build.gradle.kts - - app/settings.gradle.kts - - veritas/build.gradle.kts - - veritas/settings.gradle.kts build: command: | @@ -25,13 +11,11 @@ tasks: else ./gradlew clean build --no-daemon fi - dependencies: - - build_dependencies description: "Performs a `gradle build`. Only runs in the CI environment to save time." environment: CI: '' excluded_input_paths: - - 'bin' + # - 'bin' - 'build' input_paths: - '.' @@ -39,9 +23,11 @@ tasks: test: cache: false command: | + chmod +x build-llvm.sh + dos2unix build-llvm.sh + ./gradlew runCoverage $args --no-daemon dependencies: - - build_dependencies - build description: "Runs the tests and generates a coverage report." environment: From 005b0cbf42a70f46b9081b1fb9b0664dc1127074 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Tue, 8 Nov 2022 14:07:31 +0100 Subject: [PATCH 083/112] Removed 'cat' --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 1d16b4d..2848f05 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,8 +9,8 @@ RUN apt-get update -y && \ COPY llvm.sh llvm.sh RUN chmod +x llvm.sh RUN dos2unix llvm.sh -RUN cat ./llvm.sh RUN ./llvm.sh 15 + RUN mv /usr/bin/llc-15 /usr/bin/llc COPY *.gradle gradle.* gradlew ./ From 30df5da98e891ca75c8774af2b3875a0dbdbe24f Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Tue, 8 Nov 2022 14:11:19 +0100 Subject: [PATCH 084/112] Removed old comment --- toast.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/toast.yml b/toast.yml index 4a3eea9..de2a093 100644 --- a/toast.yml +++ b/toast.yml @@ -15,7 +15,6 @@ tasks: environment: CI: '' excluded_input_paths: - # - 'bin' - 'build' input_paths: - '.' From 7efde68753413d6cc2a3ad140d6e10d916bb8c45 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Tue, 8 Nov 2022 14:16:12 +0100 Subject: [PATCH 085/112] Consistent badge style --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8e6fe6c..657c64b 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ [![Issues][issues-shield]][issues-url] [![MIT License][license-shield]][license-url] [![LinkedIn][linkedin-shield]][linkedin-url] -[![Build and Test](https://github.com/pijuskri/Po-Sharp/actions/workflows/workflow.yml/badge.svg?branch=master)](https://github.com/pijuskri/Po-Sharp/actions/workflows/workflow.yml) +[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/pijuskri/Po-Sharp/Build%20and%20Test?label=Build%20and%20Test&style=for-the-badge)](https://github.com/pijuskri/Po-Sharp/actions/workflows/workflow.yml)

Po#

From d982d3f5616122413e5a19749474f558301b5195 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Tue, 8 Nov 2022 23:25:19 +0100 Subject: [PATCH 086/112] Most advanced error prevention --- app/src/main/scala/posharp/Main.scala | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/scala/posharp/Main.scala b/app/src/main/scala/posharp/Main.scala index acf5f39..eb5e5c6 100644 --- a/app/src/main/scala/posharp/Main.scala +++ b/app/src/main/scala/posharp/Main.scala @@ -65,6 +65,10 @@ object Main extends App { codetxt } def recursiveListFiles(f: File, ignore: String): Array[File] = { + if (!f.exists()) { + return Array() + } + if(f.isFile) return Array(f) val these = f.listFiles these ++ these.filter(x=>x.isDirectory && x.getName!=ignore).flatMap(x=>recursiveListFiles(x, ignore)) From c0ca297d141c3884b0cac42b27fbf8ed5e10e95c Mon Sep 17 00:00:00 2001 From: Pijus Krisiukenas Date: Fri, 6 Jan 2023 00:22:38 +0200 Subject: [PATCH 087/112] gradle deth --- README.md | 11 ++--- app/src/main/scala/posharp/Main.scala | 71 ++++++++++++++------------- build-dependencies.sh | 17 +++++++ build-llvm.sh | 37 ++++++++------ 4 files changed, 79 insertions(+), 57 deletions(-) create mode 100644 build-dependencies.sh diff --git a/README.md b/README.md index 657c64b..38b3273 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,7 @@ to translate to assembly. * [Ubuntu 18.04 or newer]() and/or [WSL](https://docs.microsoft.com/en-us/windows/wsl/install) * [JDK 13+](https://www.oracle.com/java/technologies/downloads/) * [Scala 2.13+](https://www.scala-lang.org/download/) +* [JDK 11+](https://www.oracle.com/java/technologies/downloads/) * [Gradle 7.5](https://gradle.org/install/) * [GCC](https://gcc.gnu.org/) * [Clang 15+](https://releases.llvm.org/) @@ -104,14 +105,10 @@ to translate to assembly. There is also an option to compile with a single command using sbt. Just run `make full` in the main directory -For now the code that is interpreted can be typed in the Main object. The assembly file -will be generated in `compiled/hello.asm` (do not ask why I named it that). +For now the code that is interpreted can be typed in the Main object. The .ll files +will be generated in `compiled/`. -With IntelliJ -* Run `Main.scala` -* In root directory call `make` - -With sbt +With gradle * In root directory call `make full` [//]: # (TODO Does this still work? Probably a good idea to use gradle instead) diff --git a/app/src/main/scala/posharp/Main.scala b/app/src/main/scala/posharp/Main.scala index eb5e5c6..5803613 100644 --- a/app/src/main/scala/posharp/Main.scala +++ b/app/src/main/scala/posharp/Main.scala @@ -3,48 +3,47 @@ import java.io.{File, FileWriter} import java.nio.file.Paths import scala.io.{AnsiColor, Source} -package object Constants { +object Constants { val FileExtension = ".txt" } object Main extends App { - var sourceDir = "po_src" + var sourceDir = "po_src" - if (args.length > 0) { - sourceDir = args(0) - } - val files = recursiveListFiles(new File(sourceDir), "ignore").toList.filter(x=>x.getName.contains(Constants.FileExtension)) - val sourceDirPath = Paths.get(sourceDir) - val declarations: Map[String, Expr.TopLevel] = files.map(file => { - val toCompile = readFile(file) - val parsed = Parser.parseInput(toCompile); - val top = parsed match { - case x: Expr.TopLevel => x - case _ => throw new Exception("unexpected type in top level") - } - var relative_name = sourceDirPath.relativize(file.toPath).toFile.getPath.split(Constants.FileExtension)(0) - relative_name = relative_name.replace("\\", "/") - (relative_name -> top) - }).toMap - declarations.foreach(x => { - val file = x._1 - val code = x._2 - var asm = ""; - - try { - asm = ToAssembly.convertMain(code, file, declarations.filter(x => x._1 != file)); - //asm += StringCode.stringCode; + if (args.length > 0) { + sourceDir = args(0) } - catch { - case x: Exception => { - println( AnsiColor.RED + s"Compilation exception in \"$file\": ${x.getMessage}" + AnsiColor.RESET); - sys.exit(1); + val files = recursiveListFiles(new File(sourceDir), "ignore").toList.filter(x => x.getName.contains(Constants.FileExtension)) + val sourceDirPath = Paths.get(sourceDir) + val declarations: Map[String, Expr.TopLevel] = files.map(file => { + val toCompile = readFile(file) + val parsed = Parser.parseInput(toCompile); + val top = parsed match { + case x: Expr.TopLevel => x + case _ => throw new Exception("unexpected type in top level") } - } - println("") - writeCompiled(asm, "compiled/", file) - }) + var relative_name = sourceDirPath.relativize(file.toPath).toFile.getPath.split(Constants.FileExtension)(0) + relative_name = relative_name.replace("\\", "/") + (relative_name -> top) + }).toMap + declarations.foreach(x => { + val file = x._1 + val code = x._2 + var asm = ""; + try { + asm = ToAssembly.convertMain(code, file, declarations.filter(x => x._1 != file)); + //asm += StringCode.stringCode; + } + catch { + case x: Exception => { + println(AnsiColor.RED + s"Compilation exception in \"$file\": ${x.getMessage}" + AnsiColor.RESET); + sys.exit(1); + } + } + println("") + writeCompiled(asm, "compiled/", file) + }) def writeCompiled(asm: String, directoryPath: String, file: String): Unit = { val flatFile = file.split("/").last + ".ll" writeToFile(asm, directoryPath, flatFile) @@ -64,6 +63,7 @@ object Main extends App { source.close() codetxt } + def recursiveListFiles(f: File, ignore: String): Array[File] = { if (!f.exists()) { return Array() @@ -71,9 +71,10 @@ object Main extends App { if(f.isFile) return Array(f) val these = f.listFiles - these ++ these.filter(x=>x.isDirectory && x.getName!=ignore).flatMap(x=>recursiveListFiles(x, ignore)) + these ++ these.filter(x => x.isDirectory && x.getName != ignore).flatMap(x => recursiveListFiles(x, ignore)) } + } object StringCode { diff --git a/build-dependencies.sh b/build-dependencies.sh new file mode 100644 index 0000000..23f3e64 --- /dev/null +++ b/build-dependencies.sh @@ -0,0 +1,17 @@ +#sudo apt update + +sudo apt-get update -y && \ + sudo apt-get install -y gcc make curl dos2unix lsb-release wget software-properties-common gnupg + +sudo chmod +x llvm.sh +dos2unix llvm.sh +sudo ./llvm.sh 15 +sudo mv /usr/bin/llc-15 /usr/bin/llc +# sudo mv /usr/bin/clang-15 /usr/bin/clang + +sudo apt install openjdk-17-jdk openjdk-17-jre + +sudo chmod +x build-llvm.sh +dos2unix build-llvm.sh + +#./gradlew app:run $args --no-daemon \ No newline at end of file diff --git a/build-llvm.sh b/build-llvm.sh index 2311c45..9b29234 100644 --- a/build-llvm.sh +++ b/build-llvm.sh @@ -1,20 +1,20 @@ #!/bin/bash #for compatibility between installs -clang() { - if hash clang-15 2>/dev/null; then - clang-15 "$@" - else - clang "$@" - fi -} -llc() { - if hash llc-15 2>/dev/null; then - llc-15 "$@" - else - llc "$@" - fi -} +#clang() { +# if hash clang-15 2>/dev/null; then +# clang-15 "$@" +# else +# clang "$@" +# fi +#} +#llc() { +# if hash llc-15 2>/dev/null; then +# llc-15 "$@" +# else +# llc "$@" +# fi +#} #make compiled directory and go to it mkdir -p compiled && \ @@ -38,13 +38,20 @@ files_asm=() for i in $files; do files_asm+=("$i".s) - llc "$i".ll -O0 -opaque-pointers --stackrealign --stack-size-section --debugify-level=location+variables --frame-pointer=all -align-all-nofallthru-blocks=4 -align-all-functions=4 + echo "$i".ll + llc "$i".ll -O0 -opaque-pointers + # --stackrealign --stack-size-section --debugify-level=location+variables --frame-pointer=all -align-all-nofallthru-blocks=4 -align-all-functions=4 # --stackrealign --asm-show-inst --align-loops=64 done +#llc test.ll -O0 -opaque-pointers #put all asm files to files files="${files_asm[*]}" #gcc -O0 -ggdb -mpreferred-stack-boundary=4 -no-pie $files -o "hello" #link and compile all assembly files to a single object file clang -O0 -no-pie $files -o "hello" # -mstack-alignment=4 + +#cd compiled/ +#llc test.ll -O0 -opaque-pointers +#clang -O0 -no-pie test.s -o "hello" # -mstack-alignment=4 \ No newline at end of file From 592e1b2f2407dd464602c5275e88c9eb5c52bcc6 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Fri, 6 Jan 2023 12:06:57 +0200 Subject: [PATCH 088/112] Fixed CI badge See https://github.com/badges/shields/issues/8671 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 38b3273..62ede04 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ [![Issues][issues-shield]][issues-url] [![MIT License][license-shield]][license-url] [![LinkedIn][linkedin-shield]][linkedin-url] -[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/pijuskri/Po-Sharp/Build%20and%20Test?label=Build%20and%20Test&style=for-the-badge)](https://github.com/pijuskri/Po-Sharp/actions/workflows/workflow.yml) +[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/pijuskri/Po-Sharp/workflow.yml?label=Build%20and%20Test&style=for-the-badge)](https://github.com/pijuskri/Po-Sharp/actions/workflows/workflow.yml)

Po#

From e7a7f5a21c7542ad242b923bf3a240f5dc79ab35 Mon Sep 17 00:00:00 2001 From: Pijus Krisiukenas Date: Tue, 28 Feb 2023 23:03:31 +0100 Subject: [PATCH 089/112] more doc --- README.md | 16 ++++++++-------- docs/Guide.md | 40 ++++++++++++++++++++++++++++++++++++++-- docs/tasklist.md | 1 + 3 files changed, 47 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 62ede04..b3ee55c 100644 --- a/README.md +++ b/README.md @@ -101,12 +101,11 @@ to translate to assembly. ### Getting Started
- -There is also an option to compile with a single command using sbt. Just run `make full` -in the main directory - -For now the code that is interpreted can be typed in the Main object. The .ll files -will be generated in `compiled/`. +1. All prerequisites should be installed and verified. +2. Create a .txt file in ``/po_src/`` which will act as the main source file +3. Create a main function +4. To compile to llvm, run ```gradle app:run``` +5. To run, With gradle * In root directory call `make full` @@ -161,20 +160,21 @@ def fib(n: int): int { #### Major +* Fully functional import/export system * Tuples * Extension methods * lambda functions * Generics * Object inheritance * library functions -* typeof -* Garbage collector +* Garbage collector/manual memory * packages * File i/o * Optimisation #### Minor +* typeof * Structs * ref/out * Type alias diff --git a/docs/Guide.md b/docs/Guide.md index 0a6f88c..73e0df7 100644 --- a/docs/Guide.md +++ b/docs/Guide.md @@ -5,9 +5,9 @@ It is very WIP, but I do want to provide basic info here so anyone would be able to write Po# code. ### Basics - +* Source files use a .txt extension +* currently, a main function must be defined, together with its return type ```int``` ```scala -//currently, a main function must be defined, together with its return type def main(): int { print(5); // each statement must be ended with ";", does not need to be on new line { @@ -190,6 +190,42 @@ object Coord { return ((self.x == other.x) && (self.y == other.y)); } } +def main(): int { + //instantiate an object using the new keyword and calling the constructor + val a = new Coord(0,1); + val b = new Coord(1,5); + print(a.compare(b)); //call object function with . op +} +``` + +### Multiple files + +* Current system for multiple files supports javascript style import syntax for functions and classes. +* There is no export keyword, as every declaration is public. +* Currently, the function/class name is directly added to the namespace, so there will be conflicts with same named classes/functions. +* Only a single main function can be declared across files +#### /lib/lib.txt +```scala +object Coord { + x: int; + y: int; + + def Coord(self: Coord, x: int, y: int) { + self.x = x; //reference elements of an object used . operator + self.y = y; + } + + def compare(self: Coord, other: Coord): bool { + return ((self.x == other.x) && (self.y == other.y)); + } +} + +``` + +#### /main.txt +```scala +import Coord from "lib/lib.txt"; + def main(): int { //instantiate an object using the new keyword and calling the constructor val a = new Coord(0,1); diff --git a/docs/tasklist.md b/docs/tasklist.md index 78b8c1b..447cbc8 100644 --- a/docs/tasklist.md +++ b/docs/tasklist.md @@ -17,6 +17,7 @@ * line numbers in compiler * static variables * add prinf +* Allow selecting main function/file * infer self in object functions * add method to get all interface arguments/names From 82021dedf179b205c6c90248c80504169353ec24 Mon Sep 17 00:00:00 2001 From: Pijus Krisiukenas Date: Sat, 4 Mar 2023 00:11:25 +0100 Subject: [PATCH 090/112] templating for funcs --- app/src/main/scala/posharp/Definitions.scala | 12 +- app/src/main/scala/posharp/Main.scala | 3 +- app/src/main/scala/posharp/Parser.scala | 5 +- app/src/main/scala/posharp/ToAssembly.scala | 200 ++++++++++++++++--- 4 files changed, 186 insertions(+), 34 deletions(-) diff --git a/app/src/main/scala/posharp/Definitions.scala b/app/src/main/scala/posharp/Definitions.scala index c2a27fd..1702dd3 100644 --- a/app/src/main/scala/posharp/Definitions.scala +++ b/app/src/main/scala/posharp/Definitions.scala @@ -87,7 +87,7 @@ object Type { case class Interface(name: String, properties: List[InputVar], functions: List[FunctionInfo]) extends Type case class StaticInterface(properties: List[InputVar], functions: List[FunctionInfo]) extends Type case class Function(args: List[Type], retType: Type) extends Type - case class T1() extends Type + case class T(num: Int) extends Type case class Enum(el: List[String]) extends Type //to be converted when parsing @@ -103,14 +103,14 @@ object Type { case Interface(_, inner, innerf) => "itf_"+inner.map(x=>shortS(x.varType)).mkString+"_"+innerf.map(x=>x.name)+"_" case Function(args, retType) => "func_"+args.map(x=>shortS(x)).mkString+"_"+shortS(retType) case UserType(name) => name - case T1() => "T1" + case T(a) => s"T$a" } def compare(val1: Type, val2: Type): Boolean = (val1, val2) match { case (a,b) if a == b => true - case (T1(), _) => true - case (_, T1()) => true - case (Array(T1()), _) => true - case (_, Array(T1())) => true + case (T(_), _) => true + case (_, T(_)) => true + case (Array(T(_)), _) => true + case (_, Array(T(_))) => true case _ => false } def compare(value: (Type, Type)): Boolean = compare(value._1,value._2) diff --git a/app/src/main/scala/posharp/Main.scala b/app/src/main/scala/posharp/Main.scala index 5803613..73559bc 100644 --- a/app/src/main/scala/posharp/Main.scala +++ b/app/src/main/scala/posharp/Main.scala @@ -37,10 +37,11 @@ object Main extends App { } catch { case x: Exception => { - println(AnsiColor.RED + s"Compilation exception in \"$file\": ${x.getMessage}" + AnsiColor.RESET); + println(AnsiColor.RED + s"Compilation exception in \"$file\": ${x.getMessage} ${x.getStackTrace.mkString("\n")}" + AnsiColor.RESET); sys.exit(1); } } + println("") writeCompiled(asm, "compiled/", file) }) diff --git a/app/src/main/scala/posharp/Parser.scala b/app/src/main/scala/posharp/Parser.scala index fd51554..aac8f09 100644 --- a/app/src/main/scala/posharp/Parser.scala +++ b/app/src/main/scala/posharp/Parser.scala @@ -138,14 +138,15 @@ object Parser { def typeUser[_: P]: P[Type] = P(ident).map(x => Type.UserType(x.name)) - def typeBase[_: P]: P[Type] = P(StringIn("int", "char", "float", "bool", "string", "void", "T1", "T2").!).map { + def typeBase[_: P]: P[Type] = P((StringIn("int", "char", "float", "bool", "string", "void").!) | ("T" ~ CharsWhileIn("0-9", 1)).!).map { case "int" => Type.Num(); case "char" => Type.Character(); case "float" => Type.NumFloat(); case "bool" => Type.Bool(); case "string" => Type.Str(); case "void" => Type.Undefined(); - case "T1" => Type.T1(); + case "T1" => Type.T(1); + case "T2" => Type.T(2); } def parens[_: P] = P("(" ~/ (binOp | prefixExpr) ~ ")") diff --git a/app/src/main/scala/posharp/ToAssembly.scala b/app/src/main/scala/posharp/ToAssembly.scala index 4089dc8..75de1d2 100644 --- a/app/src/main/scala/posharp/ToAssembly.scala +++ b/app/src/main/scala/posharp/ToAssembly.scala @@ -43,6 +43,8 @@ object ToAssembly { var interfaces: List[InterfaceInfo] = List(); var enums: List[EnumInfo] = List() var lambdas: List[(Expr.Func, Env)] = List() + var templateFunctions: List[Expr.Func] = List() + var templateFunctionInstances: List[Expr.Func] = List() var functionScope: FunctionInfo = FunctionInfo("main", "", List(), Type.Num()); def convertMain(input: Expr, currentFile: String, otherFiles: Map[String, Expr.TopLevel]): String = { @@ -53,6 +55,8 @@ object ToAssembly { interfaces = List(); enums = List() lambdas = List() + templateFunctions = List() + templateFunctionInstances = List() functionScope = FunctionInfo("main", "", List(), Type.Num()); var converted = @@ -85,6 +89,7 @@ object ToAssembly { declareFunctions(x); converted += declareInterfaces(x) + "\n"; //declareEnums(x) + templateFunctions = x.functions.filter(x=>isTemplateFunction(x)) converted += exportDeclarations(currentFile) + "\n" converted += handleImports(x, otherFiles) + "\n" converted += defineFunctions(x.functions.map(y=>(y, Map())), false); @@ -94,6 +99,7 @@ object ToAssembly { false ); }} + converted += defineFunctions(templateFunctionInstances.map(x=>(x, Map())), false) //converted += defineFunctions(lambdas, true); converted += stringLiterals.mkString converted += """attributes #0 = { mustprogress noinline nounwind optnone uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } @@ -444,6 +450,7 @@ object ToAssembly { functions //interfaces.flatMap(intf=> addPrefixToFunctions(intf.name, intf.funcs)) ) + .filter(x=> ! isTemplateFunction(x)) .map(info => { val formatFile = formatFName(file) val name = fNameSignature(info) @@ -478,29 +485,34 @@ object ToAssembly { private def defineFunctions(input: List[(Expr.Func, Env)], lambdaMode: Boolean): String = { input.map{ case (function, upperScope) => { - - val info = functions.find(x=>x.name == function.name && x.args==function.argNames).get; - functionScope = info; - val fname = fNameSignature(info) - val args = info.args.map(x=>s"${Type.toLLVM(x.varType)} %Input.${x.name}").mkString(", ") - val addPrivate = if (info.name=="main") "" else "private "; - var ret = s"define $addPrivate${Type.toLLVM(info.retType)} @${fname}($args) #0 {\n" - val newEnv = upperScope ++ info.args.map(x=> (x.name, Variable(s"%${x.name}", x.varType))).toMap //Variable(s"%${x.name}.${varc.extra()}" - var body = info.args.map(x=> - s"%${x.name} = alloca ${Type.toLLVM(x.varType)}, align 64\n" + //, align ${arraySizeFromType(x.varType)} - s"store ${Type.toLLVM(x.varType)} %Input.${x.name}, ${Type.toLLVM(x.varType)}* %${x.name}\n").mkString //, align ${arraySizeFromType(x.varType)} - //TODO might want to handle name shadowing here - - body += "%dummy = alloca i32\n" - body += convert(function.body, newEnv)._1 - - if(info.retType == Type.Undefined()) body += "ret void\n" - if(info.name == "main") body += "ret i32 0\n" - varc.reset() - - ret += body.split("\n").map(x=>"\t"+x).mkString("\n") - ret += "\n}\n" - ret + if(isTemplateFunction(function)) { + "" + } + else { + val info = functions.find(x => x.name == function.name && x.args == function.argNames).get; + functionScope = info; + + val fname = fNameSignature(info) + val args = info.args.map(x => s"${Type.toLLVM(x.varType)} %Input.${x.name}").mkString(", ") + val addPrivate = if (info.name == "main") "" else "private "; + var ret = s"define $addPrivate${Type.toLLVM(info.retType)} @${fname}($args) #0 {\n" + val newEnv = upperScope ++ info.args.map(x => (x.name, Variable(s"%${x.name}", x.varType))).toMap //Variable(s"%${x.name}.${varc.extra()}" + var body = info.args.map(x => + s"%${x.name} = alloca ${Type.toLLVM(x.varType)}, align 64\n" + //, align ${arraySizeFromType(x.varType)} + s"store ${Type.toLLVM(x.varType)} %Input.${x.name}, ${Type.toLLVM(x.varType)}* %${x.name}\n").mkString //, align ${arraySizeFromType(x.varType)} + //TODO might want to handle name shadowing here + + body += "%dummy = alloca i32\n" + body += convert(function.body, newEnv)._1 + + if (info.retType == Type.Undefined()) body += "ret void\n" + if (info.name == "main") body += "ret i32 0\n" + varc.reset() + + ret += body.split("\n").map(x => "\t" + x).mkString("\n") + ret += "\n}\n" + ret + } }}.mkString } def interpFunction(name: String, args: List[Expr], env: Env ): (String, Type) = { @@ -512,7 +524,39 @@ object ToAssembly { functions.find(x=>x.name == name) match { case Some(x) => ; case None => throw new Exception(s"function of name $name undefined"); } - functions.find(x=>x.name == name && Type.compare(argInputTypes, x.args.map(x=>makeUserTypesConcrete(x.varType)))) match { + functions.find(x=>x.name == name && argInputTypes.length==x.args.length) match { + case Some(info@FunctionInfo(p, prefix, argTypes, retType)) if isTemplateFunction(info)=> { + val replace = templateFunctionArgs(info) + + /* + val template_mappings = argInputTypes.zipWithIndex.filter(x => x._1 match { + case Type.T(i) => true + case _ => false + }).map(x=> (replace.find(y=>y._1 == x._2).get._2, x._1) ) + */ + val template_mappings = replace.map(x => (x._2, argInputTypes.zipWithIndex.find(y=>y._2 == x._1).get._1)) + + //TODO full error message + template_mappings.groupBy(x=>x._1).filter(x=>x._2.length>1).foreach(x=>throw new Exception(s"Template function input types conflicting")) + //println(template_mappings) + val replace_func = (toReplace: Type) => {toReplace match { + case t@Type.T(i) => { + template_mappings.find(x=>t == x._1) match { + case Some((_, typeToReplaceWith)) => typeToReplaceWith + case None => throw new Exception(s"T$i template type could not be replaced") + } + } + case x => x + }} + val func_expr = templateFunctions.find(x=>x.name==name && argTypes == x.argNames).get + templateFunctionInstances = templateFunctionInstances :+ replaceType(func_expr, replace_func).asInstanceOf[Expr.Func] + functions = functions :+ FunctionInfo(p, prefix, argTypes.map(x=>InputVar(x.name, traverseTypeTree(x.varType, replace_func))), traverseTypeTree(retType, replace_func)) + } + case _ => () + } + + //functions.find(x=>x.name == name && Type.compare(argInputTypes, x.args.map(x=>makeUserTypesConcrete(x.varType)))) match { + functions.find(x=>x.name == name && argInputTypes == x.args.map(x=>makeUserTypesConcrete(x.varType))) match { case Some(info@FunctionInfo(p, prefix, argTypes, retType)) => { var tName = fNameSignature(info) if(prefix != "") tName = prefix+"_"+tName; @@ -529,6 +573,53 @@ object ToAssembly { } } + /* + + val out = (functions.find(x=>x.name == name && argInputTypes.length==x.args.length) match { + case Some(info@FunctionInfo(p, prefix, argTypes, retType)) if isTemplateFunction(info)=> { + val replace = templateFunctionArgs(info) + + val template_mappings = argInputTypes.zipWithIndex.filter(x => x._1 match { + case Type.T(i) => true + case _ => false + }).map(x=> (replace.find(y=>y._1 == x._2).get._2, x._1) ) + + //TODO full error message + template_mappings.groupBy(x=>x._1).filter(x=>x._2.length>1).foreach(x=>throw new Exception(s"Template function input types conflicting")) + + val replace_func = (toReplace: Type) => toReplace match { + case t@Type.T(i) => { + template_mappings.find(x=>t == x._1) match { + case Some((_, typeToReplaceWith)) => typeToReplaceWith + case None => throw new Exception(s"T$i template type could not be replaced") + } + } + case x => x + } + val func_expr = templateFunctions.find(x=>x.name==name && argTypes == x.argNames).get + templateFunctionInstances = templateFunctionInstances :+ replaceType(func_expr, replace_func).asInstanceOf[Expr.Func] + + } + case None => None + }).getOrElse( + functions.find(x=>x.name == name && Type.compare(argInputTypes, x.args.map(x=>makeUserTypesConcrete(x.varType)))) match { + case Some(info@FunctionInfo(p, prefix, argTypes, retType)) => { + var tName = fNameSignature(info) + if(prefix != "") tName = prefix+"_"+tName; + val argTypeString = argTypes.map(x=>Type.toLLVM(x.varType)).mkString(", ") + if (retType != Type.Undefined()) ret += s"${varc.next()} = "; + ret += s"call ${Type.toLLVM(retType)} ($argTypeString) @$tName($argsString)\n" + (ret, retType) + } + case None => { + //println(Util.prettyPrint(functions.find(x=>x.name == name).map(x=>x.args.map(y=>InputVar(y.name,makeUserTypesConcrete(y.varType)))).get)); + println(Util.prettyPrint(argInputTypes.last)); + throw new Exception(s"no overload of function $name matches argument list $argInputTypes") + }; + }) + out + */ + def callObjFunction(obj: Expr, func: Expr.CallF, props: List[InputVar], funcs: List[FunctionInfo], isStatic: Boolean, env: Env): (String, Type) = { funcs.find(x=>x.name == func.name) match { case Some(n) => { @@ -685,7 +776,7 @@ object ToAssembly { case Type.Num() => 4 case Type.NumFloat() => 8 case Type.Function(_,_) => 8 - case Type.T1() => 8 + case Type.T(_) => 8 case _ => 8 } @@ -720,6 +811,65 @@ object ToAssembly { varc.counter = c; ret } + + + def isTemplateFunction(input: Expr.Func): Boolean = { + templateFunctionArgs(input).nonEmpty + } + def isTemplateFunction(input: FunctionInfo): Boolean = { + templateFunctionArgs(input).nonEmpty + } + def templateFunctionArgs(input: Expr.Func): List[(Int, Type.T)] = { + return input.argNames.zipWithIndex.filter(x=>x._1.varType match { + case Type.T(a) => true + case _ => false + }).map(x=>x._1.varType match { + case Type.T(a) => (x._2, Type.T(a)) + }) + } + def templateFunctionArgs(input: FunctionInfo): List[(Int, Type.T)] = { + return input.args.zipWithIndex.filter(x=>x._1.varType match { + case Type.T(a) => true + case _ => false + }).map(x=>x._1.varType match { + case Type.T(a) => (x._2, Type.T(a)) + }) + } + + def traverseTypeTree(input: Type, func: (Type) => Type): Type = input match { + /* + case UserType(name) => interfaces.find(x=>x.name == name) match { + case Some(n) => traverseTypeTree(Type.Interface(n.args, n.funcs)) match { + case t@Type.Interface(newargs,f) => + interfaces = interfaces.map(x=>if(x == n) InterfaceInfo(x.name, newargs, x.funcs) else x) + t + } + case _ => throw new Exception (s"no interface of name $name"); + } + */ + case Type.Interface(name, args,f) => Type.Interface(name, + args.map(x=>InputVar(x.name, traverseTypeTree(x.varType, func))), + f.map(x=>FunctionInfo(x.name, x.prefix, x.args.map(y=> + InputVar(y.name, traverseTypeTree(y.varType, func)) + ), x.retType)) + ) + case Type.Array(valType) => Type.Array(traverseTypeTree(valType, func)) + case Type.Function(args: List[Type], retType: Type) => Type.Function(args.map(x=>traverseTypeTree(x, func)), traverseTypeTree(retType, func)) + //StaticInterface(properties: List[InputVar], functions: List[FunctionInfo]) + case x => func(x) + } + def replaceType(input: Expr, func: (Type) => Type): Expr = input match { + case Expr.DefVal(a, varType) => Expr.DefVal(a, func(varType)) + case Expr.DefValWithValue(variable, varType, value) => Expr.DefValWithValue(variable, func(varType), value) + case Expr.Func(name, argNames, retType, body) => Expr.Func(name, argNames.map(x=>InputVar(x.name, traverseTypeTree(x.varType, func))), func(retType), replaceType(body, func).asInstanceOf[Expr.Block]) + case Expr.Block(lines) => Expr.Block(lines.map(x=>replaceType(x, func))) + case x => x + //case Expr.While(condition: Expr, execute: Expr.Block) => + //case Expr.If(condition: Expr, ifTrue: Expr.Block, ifFalse: Expr) => + //case class Convert(value: Expr, to: Type) extends Expr + //case class Lambda(argNames: List[InputVar], retType: Type, body: Expr.Block) extends Expr + } + def intBinOpTemplate(codeLeft: String, vLeft: String, codeRight: String, vRight: String, command: String): (String, Type) = { (codeLeft + codeRight + s"${varc.next()} = $command i32 $vLeft, $vRight\n", Type.Num()); } From 17454f29d1989e15cdc383a3d9cc5a8ceb1f224e Mon Sep 17 00:00:00 2001 From: Pijus Krisiukenas Date: Fri, 22 Dec 2023 04:02:09 +0200 Subject: [PATCH 091/112] better generic function impl --- app/src/main/scala/posharp/Definitions.scala | 4 +- app/src/main/scala/posharp/Parser.scala | 19 +-- app/src/main/scala/posharp/ToAssembly.scala | 135 +++++++------------ 3 files changed, 62 insertions(+), 96 deletions(-) diff --git a/app/src/main/scala/posharp/Definitions.scala b/app/src/main/scala/posharp/Definitions.scala index 1702dd3..16cefc1 100644 --- a/app/src/main/scala/posharp/Definitions.scala +++ b/app/src/main/scala/posharp/Definitions.scala @@ -40,9 +40,9 @@ object Expr{ case class ArraySize(array: Expr) extends Expr //case class StackVar(offset: Int) extends Expr - case class Func(name: String, argNames: List[InputVar], retType: Type, body: Expr.Block) extends Expr + case class Func(name: String, argNames: List[InputVar], retType: Type, body: Expr.Block, templates: List[Type.T]) extends Expr case class Lambda(argNames: List[InputVar], retType: Type, body: Expr.Block) extends Expr - case class CallF(name: String, args: List[Expr]) extends Expr + case class CallF(name: String, args: List[Expr], templates: List[Type]) extends Expr case class Return(value: Option[Expr]) extends Expr case class DefineInterface(name: String, props: List[InputVar], functions: List[Func]) extends Expr diff --git a/app/src/main/scala/posharp/Parser.scala b/app/src/main/scala/posharp/Parser.scala index aac8f09..cb86e98 100644 --- a/app/src/main/scala/posharp/Parser.scala +++ b/app/src/main/scala/posharp/Parser.scala @@ -2,8 +2,9 @@ package posharp import fastparse.JavaWhitespace._ import fastparse._ - import Expr.GetProperty +import jdk.jshell.spi.ExecutionControl.NotImplementedException + import scala.compat.Platform.EOL object Parser { @@ -14,7 +15,7 @@ object Parser { var enum: List[Expr.DefineEnum] = List() var imports: List[Expr.Import] = List() x.foreach { - case y@Expr.Func(a, b, c, d) => func = func :+ y + case y@Expr.Func(a, b, c, d, e) => func = func :+ y case y@Expr.DefineInterface(a, b, c) => intf = intf :+ y case y@Expr.DefineEnum(a, b) => enum = enum :+ y case y@Expr.Import(a, b) => imports = imports :+ y @@ -22,13 +23,13 @@ object Parser { Expr.TopLevel(func, intf, enum, imports) }) - def function[_: P]: P[Expr.Func] = P("def " ~/ ident ~ "(" ~/ functionArgs ~ ")" ~/ typeDef.? ~ block).map { - case (name, args, retType, body) => { + def function[_: P]: P[Expr.Func] = P("def " ~/ ident ~ templateTypes.? ~ "(" ~/ functionArgs ~ ")" ~/ typeDef.? ~ block).map { + case (name, templates, args, retType, body) => { val ret = retType match { case Some(typ) => typ case None => Type.Undefined() } - Expr.Func(name.name, args, ret, body) + Expr.Func(name.name, args, ret, body, templates.getOrElse(List()).asInstanceOf[List[Type.T]]) } } @@ -76,7 +77,7 @@ object Parser { acs.foldLeft(start: Expr)((acc, v) => v match { case (".", Expr.Ident(ident)) => GetProperty(acc, ident) case ("[", index: Expr) => Expr.GetArray(acc, index) - case (".", Expr.Ident(ident), "(", args: List[Expr]) => Expr.CallObjFunc(acc, Expr.CallF(ident, args)) + case (".", Expr.Ident(ident), "(", args: List[Expr]) => throw new NotImplementedException("")//Expr.CallObjFunc(acc, Expr.CallF(ident, args)) case x => throw new ParseException(s"bad var access: $x"); }) } @@ -157,10 +158,12 @@ object Parser { case (value, "toChar") => Expr.Convert(value, Type.Character()) } - def callFunction[_: P]: P[Expr.CallF] = P(ident ~ "(" ~/ prefixExpr.rep(sep = ",") ~/ ")").map { - case (name, args) => Expr.CallF(name.name, args.toList); + def callFunction[_: P]: P[Expr.CallF] = P(ident ~ templateTypes.? ~ "(" ~/ prefixExpr.rep(sep = ",") ~/ ")").map { + case (name, templates, args) => Expr.CallF(name.name, args.toList, templates.getOrElse(List())); }//.filter((x) => !reservedKeywords.contains(x.name)) + def templateTypes[_: P]: P[List[Type]] = (P("[" ~/ typeDefNoCol.rep(min=1, sep = ",") ~/ "]")).map(x=>x.toList) + def retFunction[_: P]: P[Expr.Return] = P("return" ~/ prefixExpr.?).map(Expr.Return) def binOp[_: P] = P(prefixExpr ~ (StringIn("+", "-", "*", "/", "++").! ~/ prefixExpr).rep(1)).map(list => parseBinOpList(list._1, list._2.toList)) diff --git a/app/src/main/scala/posharp/ToAssembly.scala b/app/src/main/scala/posharp/ToAssembly.scala index 75de1d2..fe8eb57 100644 --- a/app/src/main/scala/posharp/ToAssembly.scala +++ b/app/src/main/scala/posharp/ToAssembly.scala @@ -45,7 +45,7 @@ object ToAssembly { var lambdas: List[(Expr.Func, Env)] = List() var templateFunctions: List[Expr.Func] = List() var templateFunctionInstances: List[Expr.Func] = List() - var functionScope: FunctionInfo = FunctionInfo("main", "", List(), Type.Num()); + var functionScope: FunctionInfo = FunctionInfo("main", "", List(), Type.Num(), List()); def convertMain(input: Expr, currentFile: String, otherFiles: Map[String, Expr.TopLevel]): String = { ifCounter = 0; @@ -57,7 +57,7 @@ object ToAssembly { lambdas = List() templateFunctions = List() templateFunctionInstances = List() - functionScope = FunctionInfo("main", "", List(), Type.Num()); + functionScope = FunctionInfo("main", "", List(), Type.Num(), List()); var converted = """ @@ -92,14 +92,14 @@ object ToAssembly { templateFunctions = x.functions.filter(x=>isTemplateFunction(x)) converted += exportDeclarations(currentFile) + "\n" converted += handleImports(x, otherFiles) + "\n" - converted += defineFunctions(x.functions.map(y=>(y, Map())), false); + converted += defineFunctions(x.functions.map(y=>(y, Map())), false, false); converted += defineFunctions(x.interfaces.flatMap(intf=> - intf.functions.map(func=>Expr.Func(intf.name + "_" + func.name, func.argNames, func.retType, func.body))) + intf.functions.map(func=>Expr.Func(intf.name + "_" + func.name, func.argNames, func.retType, func.body, func.templates))) .map(y=>(y, Map())), - false + false, false ); }} - converted += defineFunctions(templateFunctionInstances.map(x=>(x, Map())), false) + converted += defineFunctions(templateFunctionInstances.map(x=>(x, Map())), false, true) //converted += defineFunctions(lambdas, true); converted += stringLiterals.mkString converted += """attributes #0 = { mustprogress noinline nounwind optnone uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } @@ -195,8 +195,8 @@ object ToAssembly { case (code, Type.Array(a), loc) if prop == "size" => getArraySize(Expr.Compiled(code, Type.Array(a), loc), env)//conv(Expr.ArraySize(obj)) case (_, valType, _) => throw new Exception(s"expected a interface, got ${valType}") } - case Expr.CallF(name, args) => { - if(functions.exists(x=>x.name == name)) interpFunction(name, args, env) + case Expr.CallF(name, args, templates) => { + if(functions.exists(x=>x.name == name)) interpFunction(name, args, templates, env) //else if(env.contains(name)) callLambda(Expr.Ident(name), args, reg, env) else throw new Exception(s"unknown identifier $name") } @@ -216,7 +216,7 @@ object ToAssembly { val valuesCompiled = values.map(x=>convertLoc(x, env)).map(x=>Expr.Compiled(x._1, x._2, x._3)) intf.funcs.find(x=>x.name == name && x.args == values) - val func_code = interpFunction(name+"_"+name, Expr.Compiled(aloc, UserType(name), alocLoc) +: valuesCompiled, env)._1 + val func_code = interpFunction(name+"_"+name, Expr.Compiled(aloc, UserType(name), alocLoc) +: valuesCompiled, List(), env)._1 (func_code, Type.Interface(name, intf.args, intf.funcs)) } case None => throw new Exception(s"no interface with name \"$name\" defined") @@ -338,7 +338,7 @@ object ToAssembly { defineString(msg) + printTemplate("format_string", "i8*", varc.last()) + "call void @exit(i32 1)\n" // "br label %exception\n" } case x@Expr.CallObjFunc(obj, func) => convert(x, env)._1; - case x@Expr.CallF(n, a) => convert(x, env)._1; + case x@Expr.CallF(_,_,_) => convert(x, env)._1; case Expr.Return(in) => { in match { case Some(value) => { @@ -384,17 +384,17 @@ object ToAssembly { } private def declareFunctions(input: Expr.TopLevel): Unit = { - functions = input.functions.map(x=> FunctionInfo(x.name, "", x.argNames, x.retType)) + functions = input.functions.map(x=> FunctionInfo(x.name, "", x.argNames, x.retType, x.templates)) } private def declareInterfaces(input: Expr.TopLevel): String = { - interfaces = input.interfaces.map(x=> InterfaceInfo(x.name, x.props, x.functions.map(x=>FunctionInfo(x.name,"", x.argNames,x.retType)))) + interfaces = input.interfaces.map(x=> InterfaceInfo(x.name, x.props, x.functions.map(x=>FunctionInfo(x.name,"", x.argNames,x.retType,x.templates)))) functions = functions ::: interfaces.flatMap(x=>addPrefixToFunctions(x.name,x.funcs)) interfaces.map(intf => { val types = intf.args.map(x=>Type.toLLVM(x.varType)).mkString(", ") s"%Class.${intf.name} = type {$types}\n" }).mkString } - private def addPrefixToFunctions(prefix: String, funcs: List[FunctionInfo]): List[FunctionInfo] = funcs.map(y=>FunctionInfo(prefix+"_"+y.name, y.prefix, y.args, y.retType)) + private def addPrefixToFunctions(prefix: String, funcs: List[FunctionInfo]): List[FunctionInfo] = funcs.map(y=>FunctionInfo(prefix+"_"+y.name, y.prefix, y.args, y.retType,y.templates)) private def declareEnums(input: Expr.TopLevel): Unit = { enums = input.enums.map(x=>EnumInfo(x.name,x.props)) } @@ -405,12 +405,12 @@ object ToAssembly { var ret = "" val funcsForImport = searchFileDeclarations(top, imp) match { - case Expr.Func(name, argnames, retType, code) => { - functions = functions :+ FunctionInfo(name, formatFName(imp.file), argnames, retType) - List(FunctionInfo(name, formatFName(imp.file), argnames, retType)) + case Expr.Func(name, argnames, retType, code, templates) => { + functions = functions :+ FunctionInfo(name, formatFName(imp.file), argnames, retType, templates) + List(FunctionInfo(name, formatFName(imp.file), argnames, retType, templates)) } case Expr.DefineInterface(name, props, i_functions) => { - val intf = InterfaceInfo(name, props, i_functions.map(x=>FunctionInfo(x.name,formatFName(imp.file), x.argNames,x.retType))) + val intf = InterfaceInfo(name, props, i_functions.map(x=>FunctionInfo(x.name,formatFName(imp.file), x.argNames,x.retType,x.templates))) interfaces = interfaces :+ intf val types = intf.args.map(x=>Type.toLLVM(x.varType)).mkString(", ") @@ -418,7 +418,7 @@ object ToAssembly { val funcs = addPrefixToFunctions(intf.name,intf.funcs) functions = functions ::: funcs - funcs.map(x=>FunctionInfo(x.name, formatFName(imp.file), x.args, x.retType)) + funcs.map(x=>FunctionInfo(x.name, formatFName(imp.file), x.args, x.retType,x.templates)) } } ret + funcsForImport.map(info=>{ @@ -483,9 +483,9 @@ object ToAssembly { s"${Type.toLLVM(info.retType)} ($args)" } - private def defineFunctions(input: List[(Expr.Func, Env)], lambdaMode: Boolean): String = { + private def defineFunctions(input: List[(Expr.Func, Env)], lambdaMode: Boolean, templated: Boolean): String = { input.map{ case (function, upperScope) => { - if(isTemplateFunction(function)) { + if(isTemplateFunction(function) && !templated) { "" } else { @@ -515,7 +515,7 @@ object ToAssembly { } }}.mkString } - def interpFunction(name: String, args: List[Expr], env: Env ): (String, Type) = { + def interpFunction(name: String, args: List[Expr], templates: List[Type], env: Env ): (String, Type) = { val argRet = args.map(arg => convertLoc(arg, env)) val argInputTypes = argRet.map(x => makeUserTypesConcrete(x._2)) var ret = argRet.map(x=>x._1).mkString @@ -525,8 +525,9 @@ object ToAssembly { case Some(x) => ; case None => throw new Exception(s"function of name $name undefined"); } functions.find(x=>x.name == name && argInputTypes.length==x.args.length) match { - case Some(info@FunctionInfo(p, prefix, argTypes, retType)) if isTemplateFunction(info)=> { - val replace = templateFunctionArgs(info) + case Some(info@FunctionInfo(p, prefix, argTypes, retType, _)) if isTemplateFunction(info)=> { + //TODO error check + val template_mappings = info.templates.zip(templates)//templateFunctionArgs(info, templates) /* val template_mappings = argInputTypes.zipWithIndex.filter(x => x._1 match { @@ -534,10 +535,11 @@ object ToAssembly { case _ => false }).map(x=> (replace.find(y=>y._1 == x._2).get._2, x._1) ) */ - val template_mappings = replace.map(x => (x._2, argInputTypes.zipWithIndex.find(y=>y._2 == x._1).get._1)) + //val template_mappings = replace.map(x => (x._2, argInputTypes.zipWithIndex.find(y=>y._2 == x._1).get._1)) + //val template_mappings = replace //TODO full error message - template_mappings.groupBy(x=>x._1).filter(x=>x._2.length>1).foreach(x=>throw new Exception(s"Template function input types conflicting")) + template_mappings.groupBy(x=>x._1).filter(x=>Set(x._2).toList.length>1).foreach(x=>throw new Exception(s"Template function input types conflicting")) //println(template_mappings) val replace_func = (toReplace: Type) => {toReplace match { case t@Type.T(i) => { @@ -548,16 +550,18 @@ object ToAssembly { } case x => x }} - val func_expr = templateFunctions.find(x=>x.name==name && argTypes == x.argNames).get + var func_expr = templateFunctions.find(x=>x.name==name && argTypes == x.argNames).get + //func_expr = Expr.Func(func_expr.name,func_expr.argNames,func_expr.retType,func_expr.body,func_expr.templates) templateFunctionInstances = templateFunctionInstances :+ replaceType(func_expr, replace_func).asInstanceOf[Expr.Func] - functions = functions :+ FunctionInfo(p, prefix, argTypes.map(x=>InputVar(x.name, traverseTypeTree(x.varType, replace_func))), traverseTypeTree(retType, replace_func)) + //TODO unsure if templates or input.templates is best + functions = functions :+ FunctionInfo(p, prefix, argTypes.map(x=>InputVar(x.name, traverseTypeTree(x.varType, replace_func))), traverseTypeTree(retType, replace_func), info.templates) } case _ => () } //functions.find(x=>x.name == name && Type.compare(argInputTypes, x.args.map(x=>makeUserTypesConcrete(x.varType)))) match { functions.find(x=>x.name == name && argInputTypes == x.args.map(x=>makeUserTypesConcrete(x.varType))) match { - case Some(info@FunctionInfo(p, prefix, argTypes, retType)) => { + case Some(info@FunctionInfo(p, prefix, argTypes, retType, templates)) => { var tName = fNameSignature(info) if(prefix != "") tName = prefix+"_"+tName; val argTypeString = argTypes.map(x=>Type.toLLVM(x.varType)).mkString(", ") @@ -573,52 +577,6 @@ object ToAssembly { } } - /* - - val out = (functions.find(x=>x.name == name && argInputTypes.length==x.args.length) match { - case Some(info@FunctionInfo(p, prefix, argTypes, retType)) if isTemplateFunction(info)=> { - val replace = templateFunctionArgs(info) - - val template_mappings = argInputTypes.zipWithIndex.filter(x => x._1 match { - case Type.T(i) => true - case _ => false - }).map(x=> (replace.find(y=>y._1 == x._2).get._2, x._1) ) - - //TODO full error message - template_mappings.groupBy(x=>x._1).filter(x=>x._2.length>1).foreach(x=>throw new Exception(s"Template function input types conflicting")) - - val replace_func = (toReplace: Type) => toReplace match { - case t@Type.T(i) => { - template_mappings.find(x=>t == x._1) match { - case Some((_, typeToReplaceWith)) => typeToReplaceWith - case None => throw new Exception(s"T$i template type could not be replaced") - } - } - case x => x - } - val func_expr = templateFunctions.find(x=>x.name==name && argTypes == x.argNames).get - templateFunctionInstances = templateFunctionInstances :+ replaceType(func_expr, replace_func).asInstanceOf[Expr.Func] - - } - case None => None - }).getOrElse( - functions.find(x=>x.name == name && Type.compare(argInputTypes, x.args.map(x=>makeUserTypesConcrete(x.varType)))) match { - case Some(info@FunctionInfo(p, prefix, argTypes, retType)) => { - var tName = fNameSignature(info) - if(prefix != "") tName = prefix+"_"+tName; - val argTypeString = argTypes.map(x=>Type.toLLVM(x.varType)).mkString(", ") - if (retType != Type.Undefined()) ret += s"${varc.next()} = "; - ret += s"call ${Type.toLLVM(retType)} ($argTypeString) @$tName($argsString)\n" - (ret, retType) - } - case None => { - //println(Util.prettyPrint(functions.find(x=>x.name == name).map(x=>x.args.map(y=>InputVar(y.name,makeUserTypesConcrete(y.varType)))).get)); - println(Util.prettyPrint(argInputTypes.last)); - throw new Exception(s"no overload of function $name matches argument list $argInputTypes") - }; - }) - out - */ def callObjFunction(obj: Expr, func: Expr.CallF, props: List[InputVar], funcs: List[FunctionInfo], isStatic: Boolean, env: Env): (String, Type) = { funcs.find(x=>x.name == func.name) match { @@ -630,7 +588,7 @@ object ToAssembly { if(isStatic) throw new Exception(s"can not call method $func staticly") else args = obj +: args } - interpFunction(intfName+"_"+func.name, args, env) + interpFunction(intfName+"_"+func.name, args, func.templates, env) } case None => props.find(x=>x.name == func.name) match { case Some(InputVar(_, Type.Function(_,_))) => { @@ -814,11 +772,14 @@ object ToAssembly { def isTemplateFunction(input: Expr.Func): Boolean = { - templateFunctionArgs(input).nonEmpty + //templateFunctionArgs(input).nonEmpty + input.templates.nonEmpty } def isTemplateFunction(input: FunctionInfo): Boolean = { - templateFunctionArgs(input).nonEmpty + input.templates.nonEmpty + //templateFunctionArgs(input).nonEmpty } + /* def templateFunctionArgs(input: Expr.Func): List[(Int, Type.T)] = { return input.argNames.zipWithIndex.filter(x=>x._1.varType match { case Type.T(a) => true @@ -835,6 +796,7 @@ object ToAssembly { case Type.T(a) => (x._2, Type.T(a)) }) } + */ def traverseTypeTree(input: Type, func: (Type) => Type): Type = input match { /* @@ -851,7 +813,7 @@ object ToAssembly { args.map(x=>InputVar(x.name, traverseTypeTree(x.varType, func))), f.map(x=>FunctionInfo(x.name, x.prefix, x.args.map(y=> InputVar(y.name, traverseTypeTree(y.varType, func)) - ), x.retType)) + ), x.retType, x.templates)) ) case Type.Array(valType) => Type.Array(traverseTypeTree(valType, func)) case Type.Function(args: List[Type], retType: Type) => Type.Function(args.map(x=>traverseTypeTree(x, func)), traverseTypeTree(retType, func)) @@ -859,9 +821,10 @@ object ToAssembly { case x => func(x) } def replaceType(input: Expr, func: (Type) => Type): Expr = input match { - case Expr.DefVal(a, varType) => Expr.DefVal(a, func(varType)) - case Expr.DefValWithValue(variable, varType, value) => Expr.DefValWithValue(variable, func(varType), value) - case Expr.Func(name, argNames, retType, body) => Expr.Func(name, argNames.map(x=>InputVar(x.name, traverseTypeTree(x.varType, func))), func(retType), replaceType(body, func).asInstanceOf[Expr.Block]) + case Expr.DefVal(a, varType) => Expr.DefVal(a, traverseTypeTree(varType, func)) + case Expr.DefValWithValue(variable, varType, value) => Expr.DefValWithValue(variable, traverseTypeTree(varType, func), value) + case Expr.Func(name, argNames, retType, body, templates) => Expr.Func(name, argNames.map(x=> + InputVar(x.name, traverseTypeTree(x.varType, func))), traverseTypeTree(retType, func), replaceType(body, func).asInstanceOf[Expr.Block], templates) case Expr.Block(lines) => Expr.Block(lines.map(x=>replaceType(x, func))) case x => x //case Expr.While(condition: Expr, execute: Expr.Block) => @@ -895,15 +858,15 @@ object ToAssembly { else floatBinOpTemplate(codeLeft, vLeft, codeRight+con, varc.last(), command) } case ((codeLeft, tyLeft: Type.Interface, vLeft), (codeRight, tyRight: Type.Interface, vRight)) if (tyLeft == tyRight && command == "add") => - convert(Expr.CallObjFunc(Expr.Compiled(codeLeft, tyLeft, vLeft), Expr.CallF("__add__", List(Expr.Compiled(codeRight, tyRight, vRight)))), env) + convert(Expr.CallObjFunc(Expr.Compiled(codeLeft, tyLeft, vLeft), Expr.CallF("__add__", List(Expr.Compiled(codeRight, tyRight, vRight)), List())), env) case ((codeLeft, typeLeft, x), (codeRight, typeRight, y)) => throw new Exception(s"can't perform arithmetic on operands of types ${typeLeft} and ${typeRight}"); } } def printTemplate(format: String, ty: String, loc: String): String = { s"%ret.${varc.extra()} = call i32 (ptr, ...) @printf(ptr @$format, $ty ${loc})\n" + - s"${varc.next()} = load ptr, ptr @stdout, align 8" + - s"call void @setbuf(ptr noundef ${varc.last()}, ptr noundef null)" + s"${varc.next()} = load ptr, ptr @stdout, align 8\n" + + s"call void @setbuf(ptr noundef ${varc.last()}, ptr noundef null)\n" } def printInterp(toPrint: Expr, env: Env): String = { val converted = convertLoc(toPrint, env) @@ -913,7 +876,7 @@ object ToAssembly { case Type.NumFloat() => converted._1 + printTemplate("format_float", "double", converted._3); case Type.Str() => converted._1 + printTemplate("format_string", "ptr", converted._3); case Type.Character() => converted._1 + printTemplate("format_char", "i8", converted._3); - case Type.Interface(a,b,c) => convert(Expr.CallObjFunc(Expr.Compiled(converted._1, converted._2, converted._3), Expr.CallF("__print__", List())), env)._1 + case Type.Interface(a,b,c) => convert(Expr.CallObjFunc(Expr.Compiled(converted._1, converted._2, converted._3), Expr.CallF("__print__", List(), List())), env)._1 /* case Type.Bool() => converted._1 + s"cmp rax, 0\nje bool_${ifCounter}\n" + printTemplate("format_true") + s"jmp boole_${ifCounter}\nbool_${ifCounter}:\n" + printTemplate("format_false") + s"boole_${ifCounter}:\n"; @@ -926,7 +889,7 @@ object ToAssembly { } type Env = Map[String, Variable] - case class FunctionInfo(name: String, prefix: String, args: List[InputVar], retType: Type) + case class FunctionInfo(name: String, prefix: String, args: List[InputVar], retType: Type, templates: List[Type.T]) //case class InterfaceInfo(name: String, args: List[InputVar]) case class InterfaceInfo(name: String, args: List[InputVar], funcs: List[FunctionInfo]) case class EnumInfo(name: String, el: List[String]) From 83eb344b7a47a008b737af4c2f8f29850e9ab9e4 Mon Sep 17 00:00:00 2001 From: Pijus Krisiukenas Date: Sun, 24 Dec 2023 03:42:40 +0200 Subject: [PATCH 092/112] implemented generic classes --- app/src/main/scala/posharp/Definitions.scala | 20 +- app/src/main/scala/posharp/Parser.scala | 17 +- app/src/main/scala/posharp/ToAssembly.scala | 210 +++++++++++++----- docs/tasklist.md | 3 +- veritas/src/main/scala/test/TestExample.scala | 14 ++ 5 files changed, 187 insertions(+), 77 deletions(-) diff --git a/app/src/main/scala/posharp/Definitions.scala b/app/src/main/scala/posharp/Definitions.scala index 16cefc1..5bfb748 100644 --- a/app/src/main/scala/posharp/Definitions.scala +++ b/app/src/main/scala/posharp/Definitions.scala @@ -45,8 +45,9 @@ object Expr{ case class CallF(name: String, args: List[Expr], templates: List[Type]) extends Expr case class Return(value: Option[Expr]) extends Expr - case class DefineInterface(name: String, props: List[InputVar], functions: List[Func]) extends Expr - case class InstantiateInterface(intf: String, args: List[Expr]) extends Expr + //currently code needs templates to be generic type. idk how to do it while enforcing template type + case class DefineInterface(name: String, props: List[InputVar], functions: List[Func], templates: List[Type]) extends Expr + case class InstantiateInterface(intf: String, args: List[Expr], templates: List[Type]) extends Expr case class GetProperty(obj: Expr, prop: String) extends Expr case class CallObjFunc(obj: Expr, func: CallF) extends Expr case class SetInterfaceProp(intf: Expr, prop: String, value: Expr) extends Expr @@ -83,15 +84,14 @@ object Type { case class Array(elemType: Type) extends Type case class Bool() extends Type case class Str() extends Type - //case class Interface(properties: List[InputVar]) extends Type - case class Interface(name: String, properties: List[InputVar], functions: List[FunctionInfo]) extends Type + case class Interface(name: String, properties: List[InputVar], functions: List[FunctionInfo], templates: List[Type]) extends Type case class StaticInterface(properties: List[InputVar], functions: List[FunctionInfo]) extends Type case class Function(args: List[Type], retType: Type) extends Type case class T(num: Int) extends Type case class Enum(el: List[String]) extends Type //to be converted when parsing - case class UserType(name: String) extends Type + case class UserType(name: String, templates: List[Type]) extends Type def shortS(value: Type): String = value match { case Num() => "i"; @@ -100,9 +100,9 @@ object Type { case Bool() => "b" case Array(inner) => "arr_"+shortS(inner)+"_" //case Interface(inner) => "itf_"+inner.map(x=>shortS(x.varType))+"_" - case Interface(_, inner, innerf) => "itf_"+inner.map(x=>shortS(x.varType)).mkString+"_"+innerf.map(x=>x.name)+"_" + case Interface(_, inner, innerf, templates) => "itf_"+inner.map(x=>shortS(x.varType)).mkString+"_"+innerf.map(x=>x.name)+"_"+ templates.map(x=>shortS(x)).mkString case Function(args, retType) => "func_"+args.map(x=>shortS(x)).mkString+"_"+shortS(retType) - case UserType(name) => name + case UserType(name, templates) => name + templates.map(x=>shortS(x)).mkString case T(a) => s"T$a" } def compare(val1: Type, val2: Type): Boolean = (val1, val2) match { @@ -129,9 +129,9 @@ object Type { case Array(inner) => s"%Type.array.${toLLVM(inner)}*" case Undefined() => "void" case Str() => "i8*" - //should be avoided, as usertype could be not a class - case UserType(name) => s"%Class.$name*" - case Interface(name, _, _) => s"%Class.$name*" + //presume that usertype is a class. Might have aliases in the future + case UserType(name, templates) => s"%Class.$name.${templates.map(x=>toLLVM(x)).mkString}*" + case Interface(name, _, _, templates) => s"%Class.$name.${templates.map(x=>toLLVM(x)).mkString}*" /* case Interface(vars, funcs) => { val argS = vars.map(x=>toLLVM(x.varType)).mkString(", ") diff --git a/app/src/main/scala/posharp/Parser.scala b/app/src/main/scala/posharp/Parser.scala index cb86e98..4577a74 100644 --- a/app/src/main/scala/posharp/Parser.scala +++ b/app/src/main/scala/posharp/Parser.scala @@ -16,7 +16,7 @@ object Parser { var imports: List[Expr.Import] = List() x.foreach { case y@Expr.Func(a, b, c, d, e) => func = func :+ y - case y@Expr.DefineInterface(a, b, c) => intf = intf :+ y + case y@Expr.DefineInterface(a, b, c, d) => intf = intf :+ y case y@Expr.DefineEnum(a, b) => enum = enum :+ y case y@Expr.Import(a, b) => imports = imports :+ y } @@ -40,9 +40,8 @@ object Parser { Expr.DefineInterface(props._1.name, props._2.toList.map(x=>InputVar(x._1.name, x._2))) ) */ - def interfaceDef[_: P]: P[Expr.DefineInterface] = P("object " ~/ ident ~ "{" ~/ objValue ~ function.rep ~ "}").map(props => - Expr.DefineInterface(props._1.name, props._2.map(x => InputVar(x.name, x.varType)), props._3.toList) - ) + def interfaceDef[_: P]: P[Expr.DefineInterface] = P("object " ~/ ident ~ templateTypes.? ~ "{" ~/ objValue ~ function.rep ~ "}").map(props => + Expr.DefineInterface(props._1.name, props._3.map(x => InputVar(x.name, x.varType)), props._4.toList, props._2.getOrElse(List()).asInstanceOf[List[Type.T]])) def objValue[_: P]: P[List[ObjVal]] = P(ident ~ typeDef ~ ("=" ~ prefixExpr).? ~ ";").rep.map(x => x.map { case (id, valtype, Some(value)) => ObjVal(id.name, valtype, value) @@ -77,7 +76,8 @@ object Parser { acs.foldLeft(start: Expr)((acc, v) => v match { case (".", Expr.Ident(ident)) => GetProperty(acc, ident) case ("[", index: Expr) => Expr.GetArray(acc, index) - case (".", Expr.Ident(ident), "(", args: List[Expr]) => throw new NotImplementedException("")//Expr.CallObjFunc(acc, Expr.CallF(ident, args)) + //TODO template functions not handled + case (".", Expr.Ident(ident), "(", args: List[Expr]) => Expr.CallObjFunc(acc, Expr.CallF(ident, args, List())) //throw new NotImplementedException("") case x => throw new ParseException(s"bad var access: $x"); }) } @@ -129,7 +129,8 @@ object Parser { def getArraySize[_: P]: P[Expr.ArraySize] = P(returnsArray ~~ ".size").map((x) => Expr.ArraySize(x)) - def instanceInterface[_: P]: P[Expr.InstantiateInterface] = P("new " ~/ ident ~ "(" ~/ prefixExpr.rep(sep = ",") ~ ")").map(x => Expr.InstantiateInterface(x._1.name, x._2.toList)) + def instanceInterface[_: P]: P[Expr.InstantiateInterface] = P("new " ~/ ident ~ templateTypes.? ~ "(" ~/ prefixExpr.rep(sep = ",") ~ ")") + .map(x => Expr.InstantiateInterface(x._1.name, x._3.toList, x._2.getOrElse(List()))) def typeDef[_: P]: P[Type] = P(":" ~/ (typeBase | typeArray | typeFunc | typeUser)) def typeDefNoCol[_: P]: P[Type] = P(typeBase | typeArray | typeFunc | typeUser) @@ -137,7 +138,7 @@ object Parser { def typeArray[_: P]: P[Type] = P("array" ~/ "[" ~ typeDefNoCol ~ "]").map(x => Type.Array(x)) def typeFunc[_: P]: P[Type] = P("func" ~/ "[" ~ "(" ~ typeDefNoCol.rep(sep=",") ~ ")" ~/ "=>" ~ typeDefNoCol ~ "]").map(x => Type.Function(x._1.toList, x._2)) - def typeUser[_: P]: P[Type] = P(ident).map(x => Type.UserType(x.name)) + def typeUser[_: P]: P[Type] = P(ident ~ templateTypes.?).map(x => Type.UserType(x._1.name, x._2.getOrElse(List()))) def typeBase[_: P]: P[Type] = P((StringIn("int", "char", "float", "bool", "string", "void").!) | ("T" ~ CharsWhileIn("0-9", 1)).!).map { case "int" => Type.Num(); @@ -255,7 +256,7 @@ object Parser { def parseInput(input: String): Expr = { val parsed = fastparse.parse(input, topLevel(_), verboseFailures = true); parsed match { - case Parsed.Success(expr, n) => expr; + case Parsed.Success(expr, n) => expr.asInstanceOf[Expr]; case t: Parsed.Failure => { println(t.trace(true).longAggregateMsg); throw new ParseException("parsing fail"); } diff --git a/app/src/main/scala/posharp/ToAssembly.scala b/app/src/main/scala/posharp/ToAssembly.scala index fe8eb57..4fa5ca7 100644 --- a/app/src/main/scala/posharp/ToAssembly.scala +++ b/app/src/main/scala/posharp/ToAssembly.scala @@ -46,6 +46,8 @@ object ToAssembly { var templateFunctions: List[Expr.Func] = List() var templateFunctionInstances: List[Expr.Func] = List() var functionScope: FunctionInfo = FunctionInfo("main", "", List(), Type.Num(), List()); + var templateInterfaces: List[Expr.DefineInterface] = List() + var templateInterfaceInstances: List[Expr.DefineInterface] = List() def convertMain(input: Expr, currentFile: String, otherFiles: Map[String, Expr.TopLevel]): String = { ifCounter = 0; @@ -86,20 +88,29 @@ object ToAssembly { | |""".stripMargin; input match { case x: Expr.TopLevel => { + val interfaceFunctionList: List[(Expr.Func, Env)] = x.interfaces.filter(x=> !isTemplateInterface(x.templates)).flatMap(intf => + addPrefixToFunctions(intf.name, intf.functions) + ).filter(y=>y.templates.isEmpty) + .map(y => (y, Map())) + //TODO template function in interfaces ignored for now + //templateFunctions = (x.functions ::: interfaceFunctionList.map(y=>y._1)).filter(x=>isTemplateFunction(x)) + declareFunctions(x); converted += declareInterfaces(x) + "\n"; //declareEnums(x) + templateInterfaces = x.interfaces.filter(x=>isTemplateInterface(x.templates)) templateFunctions = x.functions.filter(x=>isTemplateFunction(x)) converted += exportDeclarations(currentFile) + "\n" converted += handleImports(x, otherFiles) + "\n" converted += defineFunctions(x.functions.map(y=>(y, Map())), false, false); - converted += defineFunctions(x.interfaces.flatMap(intf=> - intf.functions.map(func=>Expr.Func(intf.name + "_" + func.name, func.argNames, func.retType, func.body, func.templates))) - .map(y=>(y, Map())), - false, false - ); + converted += defineFunctions(interfaceFunctionList, false, false); }} converted += defineFunctions(templateFunctionInstances.map(x=>(x, Map())), false, true) + converted += templateInterfaceInstances.map(intf => { + val llvm_intf = Type.toLLVM(Type.Interface(intf.name, intf.props, List(), intf.templates)).dropRight(1) + val types = intf.props.map(x => Type.toLLVM(x.varType)).mkString(", ") + s"$llvm_intf = type {$types}\n" + }).mkString //converted += defineFunctions(lambdas, true); converted += stringLiterals.mkString converted += """attributes #0 = { mustprogress noinline nounwind optnone uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } @@ -125,7 +136,7 @@ object ToAssembly { case Expr.Ident(name) => { enums.find(x => x.name == name).orElse(interfaces.find(x=>x.name == name)).orElse(Some(lookup(name, env))) match { case Some(EnumInfo(_, el)) => ("", Type.Enum(el)) - case Some(InterfaceInfo(itfName, props, funcs)) => ("", Type.Interface(itfName, props, funcs)) + case Some(InterfaceInfo(itfName, props, funcs, templates)) => ("", Type.Interface(itfName, props, funcs, templates)) case Some((code: String, variable: Variable)) => (code, variable.varType) case _ => throw new Exception(s"unrecognised identifier $name") } @@ -177,12 +188,12 @@ object ToAssembly { case Expr.ArraySize(name) => getArraySize(name, env); case Expr.GetProperty(obj, prop) => convertLoc(obj, env) match { - case(code, Type.Interface(name, props, funcs), loc) => props.find(x=>x.name == prop) match { + case(code, intf_type@Type.Interface(name, props, funcs, templates), loc) => props.find(x=>x.name == prop) match { case Some(n) => { - val intfDec = s"%Class.$name" + val intfDec = Type.toLLVM(intf_type) val idx = props.indexOf(n); - - val ret = code + s"${varc.next()} = getelementptr inbounds $intfDec, $intfDec* ${varc.secondLast()}, i32 0, i32 $idx\n" + + //TODO i32 0, + val ret = code + s"${varc.next()} = getelementptr inbounds $intfDec, $intfDec* ${varc.secondLast()}, i32 $idx\n" + s"${varc.next()} = load ${Type.toLLVM(n.varType)}, ${Type.toLLVM(n.varType)}* ${varc.secondLast()}, align 8\n" (ret, n.varType) } @@ -201,25 +212,55 @@ object ToAssembly { else throw new Exception(s"unknown identifier $name") } case Expr.CallObjFunc(obj, func) => convertLoc(obj, env) match { - case (code, t@Type.Interface(_, props, funcs), loc) => callObjFunction(Expr.Compiled(code, t, loc), func, props, funcs, isStatic = false, env) + //TODO investigate callobj + //case _ => throw new NotImplementedError("") + case (code, t@Type.Interface(_, props, funcs, templates), loc) => callObjFunction(Expr.Compiled(code, t, loc), func, props, funcs, isStatic = false, env) case (code, t@Type.StaticInterface(props, funcs), loc) => callObjFunction(Expr.Compiled(code, t, loc), func, props, funcs, isStatic = true, env) } - case Expr.InstantiateInterface(name, values) => interfaces.find(x=>x.name == name) match { - case Some(intf) => { - var aloc = ""; - val classT = s"%Class.$name" - - aloc += s"${varc.next()} = getelementptr $classT, $classT* null, i32 1\n" + s"${varc.next()} = ptrtoint $classT** ${varc.secondLast()} to i32\n" - val bytesLoc = varc.last(); - aloc += s"${varc.next()} = call ptr (i32) @malloc(i32 $bytesLoc)\n"; - val alocLoc = varc.last(); - - val valuesCompiled = values.map(x=>convertLoc(x, env)).map(x=>Expr.Compiled(x._1, x._2, x._3)) - intf.funcs.find(x=>x.name == name && x.args == values) - val func_code = interpFunction(name+"_"+name, Expr.Compiled(aloc, UserType(name), alocLoc) +: valuesCompiled, List(), env)._1 - (func_code, Type.Interface(name, intf.args, intf.funcs)) + case Expr.InstantiateInterface(name, values, templates) => { + if (templates.nonEmpty) interfaces.find(x=>x.name == name && x.templates == templates) match { + case Some(intf) => {} + case None => interfaces.find(x=>x.name == name && isTemplateInterface(x)) match { + case Some(intf) => { + //val classT = Type.toLLVM(Type.Interface(intf.name, intf.args, intf.funcs, templates)) // s"%Class.$name" + + //TODO full error message + val template_mappings = intf.templates.zip(templates) + template_mappings.groupBy(x => x._1).filter(x => Set(x._2).toList.length > 1).foreach(x => throw new Exception(s"Template interface input types conflicting")) + //println(template_mappings) + val replace_func = replaceWithMappingFunc(template_mappings) + + val intf_expr = templateInterfaces.find(x => x.name == name && intf.templates == x.templates).get + //func_expr = Expr.Func(func_expr.name,func_expr.argNames,func_expr.retType,func_expr.body,func_expr.templates) + //templateFunctionInstances = templateFunctionInstances :+ replaceType(func_expr, replace_func).asInstanceOf[Expr.Func] + val new_intf_expr = replaceType(intf_expr, replace_func).asInstanceOf[Expr.DefineInterface] + templateInterfaceInstances = templateInterfaceInstances :+ new_intf_expr + val newInterfaceInfo = traverseTypeTree(intf, replace_func) + interfaces = interfaces :+ newInterfaceInfo + + functions = functions ::: addPrefixToFunctions(intf.name, newInterfaceInfo.funcs) + templateFunctionInstances = templateFunctionInstances ::: addPrefixToFunctions(intf.name, new_intf_expr.functions) + } + case None => throw new Exception(s"no interface with name \"$name\" defined") + } + } + interfaces.find(x=>x.name == name && x.templates == templates) match { + case Some(intf) => { + val classT = toLLVM(intf) // s"%Class.$name" + + var aloc = ""; + aloc += s"${varc.next()} = getelementptr $classT, $classT* null, i32 1\n" + s"${varc.next()} = ptrtoint $classT** ${varc.secondLast()} to i32\n" + val bytesLoc = varc.last(); + aloc += s"${varc.next()} = call ptr (i32) @malloc(i32 $bytesLoc)\n"; + val alocLoc = varc.last(); + + val valuesCompiled = values.map(x => convertLoc(x, env)).map(x => Expr.Compiled(x._1, x._2, x._3)) + intf.funcs.find(x => x.name == name && x.args == values) + val func_code = interpFunction(name + "_" + name, Expr.Compiled(aloc, UserType(name, templates), alocLoc) +: valuesCompiled, List(), env)._1 + (func_code, Type.Interface(name, intf.args, intf.funcs, intf.templates)) + } + case None => throw new Exception(s"no interface with name \"$name\" defined") } - case None => throw new Exception(s"no interface with name \"$name\" defined") } case Expr.Str(value) => (defineString(value), Type.Str()) case Expr.Character(value) => (s"${varc.next()} = add i8 0, ${value.toInt}\n", Type.Character()) @@ -317,13 +358,14 @@ object ToAssembly { } case Expr.SetArray(expr, index, value) => setArray(expr, index, value, env) case Expr.SetInterfaceProp(intf, prop, valueRaw) => convertLoc(intf, env) match { - case(code, Type.Interface(name, props,f), intfLoc) => props.find(x=>x.name == prop) match { + case(code, intf_type@Type.Interface(name, props,f, templates), intfLoc) => props.find(x=>x.name == prop) match { case Some(n) => convertLoc(valueRaw, env) match { case (valCode, valType, valueLoc) if(valType == n.varType) => { - val intfDec = s"%Class.$name" + val intfDec = Type.toLLVM(intf_type)//s"%Class.$name" val idx = props.indexOf(n); var ret = code + valCode - ret += s"${varc.next()} = getelementptr $intfDec, $intfDec* $intfLoc, i32 0, i32 $idx\n" + //removed i32 0, not sure if that could cause issues + ret += s"${varc.next()} = getelementptr $intfDec, $intfDec* $intfLoc, i32 $idx\n" ret += s"store ${Type.toLLVM(valType)} $valueLoc, ${Type.toLLVM(n.varType)}* ${varc.last()}, align ${arraySizeFromType(n.varType)}\n" ret } @@ -387,14 +429,21 @@ object ToAssembly { functions = input.functions.map(x=> FunctionInfo(x.name, "", x.argNames, x.retType, x.templates)) } private def declareInterfaces(input: Expr.TopLevel): String = { - interfaces = input.interfaces.map(x=> InterfaceInfo(x.name, x.props, x.functions.map(x=>FunctionInfo(x.name,"", x.argNames,x.retType,x.templates)))) - functions = functions ::: interfaces.flatMap(x=>addPrefixToFunctions(x.name,x.funcs)) - interfaces.map(intf => { + interfaces = input.interfaces.map(x=> InterfaceInfo(x.name, x.props, x.functions.map(x=>FunctionInfo(x.name,"", x.argNames,x.retType,x.templates)), x.templates)) + val non_generic_intf = interfaces.filter(x=> !isTemplateInterface(x)) + functions = functions ::: non_generic_intf.flatMap(x=>addPrefixToFunctions(x.name,x.funcs)) + non_generic_intf.map(intf => { val types = intf.args.map(x=>Type.toLLVM(x.varType)).mkString(", ") s"%Class.${intf.name} = type {$types}\n" }).mkString } - private def addPrefixToFunctions(prefix: String, funcs: List[FunctionInfo]): List[FunctionInfo] = funcs.map(y=>FunctionInfo(prefix+"_"+y.name, y.prefix, y.args, y.retType,y.templates)) + //https://stackoverflow.com/questions/3307427/scala-double-definition-2-methods-have-the-same-type-erasure + private def addPrefixToFunctions(prefix: String, funcs: => List[FunctionInfo]): List[FunctionInfo] = + funcs.map(y=>FunctionInfo(prefix+"_"+y.name, y.prefix, y.args, y.retType,y.templates)) + + private def addPrefixToFunctions(prefix: String, funcs: List[Expr.Func]): List[Expr.Func] = { + funcs.map(func => Expr.Func(prefix + "_" + func.name, func.argNames, func.retType, func.body, func.templates)) + } private def declareEnums(input: Expr.TopLevel): Unit = { enums = input.enums.map(x=>EnumInfo(x.name,x.props)) } @@ -409,8 +458,9 @@ object ToAssembly { functions = functions :+ FunctionInfo(name, formatFName(imp.file), argnames, retType, templates) List(FunctionInfo(name, formatFName(imp.file), argnames, retType, templates)) } - case Expr.DefineInterface(name, props, i_functions) => { - val intf = InterfaceInfo(name, props, i_functions.map(x=>FunctionInfo(x.name,formatFName(imp.file), x.argNames,x.retType,x.templates))) + case Expr.DefineInterface(name, props, i_functions, templates) => { + throw new NotImplementedError("") + val intf = InterfaceInfo(name, props, i_functions.map(x=>FunctionInfo(x.name,formatFName(imp.file), x.argNames,x.retType,x.templates)), templates) interfaces = interfaces :+ intf val types = intf.args.map(x=>Type.toLLVM(x.varType)).mkString(", ") @@ -469,8 +519,8 @@ object ToAssembly { } def makeUserTypesConcrete(input: Type): Type = input match { - case UserType(name) => interfaces.find(x=>x.name == name) match { - case Some(n) => Type.Interface(name, n.args, n.funcs) + case UserType(name, templates) => interfaces.find(x=>x.name == name && x.templates == templates) match { + case Some(n) => Type.Interface(name, n.args, n.funcs, n.templates) case _ => throw new Exception (s"no interface of name $name"); } case x => x @@ -478,6 +528,7 @@ object ToAssembly { def fNameSignature(info: FunctionInfo): String = fNameSignature(info.name, info.args.map(x=>x.varType)) def fNameSignature(name: String, args: List[Type]):String = name + (if(args.isEmpty) "" else ".") + args.map(x=>shortS(x)).mkString + def toLLVM(intf: InterfaceInfo): String = Type.toLLVM(Type.Interface(intf.name, intf.args, intf.funcs, intf.templates)) def toLLVM(info: FunctionInfo): String = { val args = info.args.map(x=>s"${Type.toLLVM(x.varType)}").mkString(", ") s"${Type.toLLVM(info.retType)} ($args)" @@ -485,10 +536,13 @@ object ToAssembly { private def defineFunctions(input: List[(Expr.Func, Env)], lambdaMode: Boolean, templated: Boolean): String = { input.map{ case (function, upperScope) => { + println(function.templates) if(isTemplateFunction(function) && !templated) { "" } else { + //print(Util.prettyPrint(functions)) + //print(Util.prettyPrint(function)) val info = functions.find(x => x.name == function.name && x.args == function.argNames).get; functionScope = info; @@ -541,16 +595,9 @@ object ToAssembly { //TODO full error message template_mappings.groupBy(x=>x._1).filter(x=>Set(x._2).toList.length>1).foreach(x=>throw new Exception(s"Template function input types conflicting")) //println(template_mappings) - val replace_func = (toReplace: Type) => {toReplace match { - case t@Type.T(i) => { - template_mappings.find(x=>t == x._1) match { - case Some((_, typeToReplaceWith)) => typeToReplaceWith - case None => throw new Exception(s"T$i template type could not be replaced") - } - } - case x => x - }} - var func_expr = templateFunctions.find(x=>x.name==name && argTypes == x.argNames).get + val replace_func = replaceWithMappingFunc(template_mappings) + + val func_expr = templateFunctions.find(x=>x.name==name && argTypes == x.argNames).get //func_expr = Expr.Func(func_expr.name,func_expr.argNames,func_expr.retType,func_expr.body,func_expr.templates) templateFunctionInstances = templateFunctionInstances :+ replaceType(func_expr, replace_func).asInstanceOf[Expr.Func] //TODO unsure if templates or input.templates is best @@ -560,6 +607,9 @@ object ToAssembly { } //functions.find(x=>x.name == name && Type.compare(argInputTypes, x.args.map(x=>makeUserTypesConcrete(x.varType)))) match { + //println(Util.prettyPrint(functions)) + //println(env) + // && x.templates == templates functions.find(x=>x.name == name && argInputTypes == x.args.map(x=>makeUserTypesConcrete(x.varType))) match { case Some(info@FunctionInfo(p, prefix, argTypes, retType, templates)) => { var tName = fNameSignature(info) @@ -775,6 +825,15 @@ object ToAssembly { //templateFunctionArgs(input).nonEmpty input.templates.nonEmpty } + def isTemplateInterface(input: InterfaceInfo): Boolean = { + isTemplateInterface(input.templates) + } + def isTemplateInterface(templates: List[Type]): Boolean = { + templates.exists { + case Type.T(_) => true + case _ => false + } + } def isTemplateFunction(input: FunctionInfo): Boolean = { input.templates.nonEmpty //templateFunctionArgs(input).nonEmpty @@ -797,7 +856,25 @@ object ToAssembly { }) } */ - + def traverseTypeTree(input: FunctionInfo, func: (Type) => Type): FunctionInfo = { + FunctionInfo(input.name, + input.prefix, + input.args.map(y => + InputVar(y.name, traverseTypeTree(y.varType, func)) + ), + traverseTypeTree(input.retType, func), + input.templates.map(y => traverseTypeTree(y, func).asInstanceOf[Type.T]) + ) + } + def traverseTypeTree(input: InterfaceInfo, func: (Type) => Type): InterfaceInfo = { + InterfaceInfo(input.name, + input.args.map(y => + InputVar(y.name, traverseTypeTree(y.varType, func)) + ), + input.funcs.map(y=> traverseTypeTree(y, func)), + input.templates.map(y=>traverseTypeTree(y,func)) + ) + } def traverseTypeTree(input: Type, func: (Type) => Type): Type = input match { /* case UserType(name) => interfaces.find(x=>x.name == name) match { @@ -809,29 +886,47 @@ object ToAssembly { case _ => throw new Exception (s"no interface of name $name"); } */ - case Type.Interface(name, args,f) => Type.Interface(name, + case Type.Interface(name, args,f, templates) => Type.Interface(name, args.map(x=>InputVar(x.name, traverseTypeTree(x.varType, func))), - f.map(x=>FunctionInfo(x.name, x.prefix, x.args.map(y=> - InputVar(y.name, traverseTypeTree(y.varType, func)) - ), x.retType, x.templates)) + f.map(x=>traverseTypeTree(x, func)), + templates.map(x=>traverseTypeTree(x, func)) ) case Type.Array(valType) => Type.Array(traverseTypeTree(valType, func)) case Type.Function(args: List[Type], retType: Type) => Type.Function(args.map(x=>traverseTypeTree(x, func)), traverseTypeTree(retType, func)) //StaticInterface(properties: List[InputVar], functions: List[FunctionInfo]) + case Type.UserType(name, templates) => Type.UserType(name, templates.map(x=>traverseTypeTree(x, func))) case x => func(x) } + def replaceType(input: List[InputVar], func: (Type) => Type): List[InputVar] = { + input.map(x => + InputVar(x.name, traverseTypeTree(x.varType, func))) + } def replaceType(input: Expr, func: (Type) => Type): Expr = input match { case Expr.DefVal(a, varType) => Expr.DefVal(a, traverseTypeTree(varType, func)) case Expr.DefValWithValue(variable, varType, value) => Expr.DefValWithValue(variable, traverseTypeTree(varType, func), value) - case Expr.Func(name, argNames, retType, body, templates) => Expr.Func(name, argNames.map(x=> - InputVar(x.name, traverseTypeTree(x.varType, func))), traverseTypeTree(retType, func), replaceType(body, func).asInstanceOf[Expr.Block], templates) + case Expr.Func(name, argNames, retType, body, templates) => Expr.Func(name, replaceType(argNames, func), traverseTypeTree(retType, func), replaceType(body, func).asInstanceOf[Expr.Block], templates) case Expr.Block(lines) => Expr.Block(lines.map(x=>replaceType(x, func))) + case Expr.DefineInterface(name, props, functions, templates) => + Expr.DefineInterface(name, replaceType(props, func), functions.map(x=>replaceType(x, func).asInstanceOf[Expr.Func]), + templates.map(x=>traverseTypeTree(x, func)) + ) case x => x //case Expr.While(condition: Expr, execute: Expr.Block) => //case Expr.If(condition: Expr, ifTrue: Expr.Block, ifFalse: Expr) => //case class Convert(value: Expr, to: Type) extends Expr //case class Lambda(argNames: List[InputVar], retType: Type, body: Expr.Block) extends Expr } + def replaceWithMappingFunc(template_mappings: List[(Type, Type)]): Type => Type = { + return { + case t@Type.T(i) => { + template_mappings.find(x => t == x._1) match { + case Some((_, typeToReplaceWith)) => typeToReplaceWith + case None => throw new Exception(s"T$i template type could not be replaced") + } + } + case x => x + } + } def intBinOpTemplate(codeLeft: String, vLeft: String, codeRight: String, vRight: String, command: String): (String, Type) = { (codeLeft + codeRight + s"${varc.next()} = $command i32 $vLeft, $vRight\n", Type.Num()); @@ -876,7 +971,7 @@ object ToAssembly { case Type.NumFloat() => converted._1 + printTemplate("format_float", "double", converted._3); case Type.Str() => converted._1 + printTemplate("format_string", "ptr", converted._3); case Type.Character() => converted._1 + printTemplate("format_char", "i8", converted._3); - case Type.Interface(a,b,c) => convert(Expr.CallObjFunc(Expr.Compiled(converted._1, converted._2, converted._3), Expr.CallF("__print__", List(), List())), env)._1 + case Type.Interface(_,_,_,_) => convert(Expr.CallObjFunc(Expr.Compiled(converted._1, converted._2, converted._3), Expr.CallF("__print__", List(), List())), env)._1 /* case Type.Bool() => converted._1 + s"cmp rax, 0\nje bool_${ifCounter}\n" + printTemplate("format_true") + s"jmp boole_${ifCounter}\nbool_${ifCounter}:\n" + printTemplate("format_false") + s"boole_${ifCounter}:\n"; @@ -889,9 +984,8 @@ object ToAssembly { } type Env = Map[String, Variable] - case class FunctionInfo(name: String, prefix: String, args: List[InputVar], retType: Type, templates: List[Type.T]) - //case class InterfaceInfo(name: String, args: List[InputVar]) - case class InterfaceInfo(name: String, args: List[InputVar], funcs: List[FunctionInfo]) + case class FunctionInfo(name: String, prefix: String, args: List[InputVar], retType: Type, templates: List[Type]) + case class InterfaceInfo(name: String, args: List[InputVar], funcs: List[FunctionInfo], templates: List[Type]) case class EnumInfo(name: String, el: List[String]) case class Variable(loc: String, varType: Type) } diff --git a/docs/tasklist.md b/docs/tasklist.md index 447cbc8..f73cb23 100644 --- a/docs/tasklist.md +++ b/docs/tasklist.md @@ -5,7 +5,8 @@ ## Bugs * ~~functions have limited checking before llvm catches it (especially multiple constructors)~~ * remove l prefix from register names - +* call same template function twice +* print is a reserved keyword ## To do diff --git a/veritas/src/main/scala/test/TestExample.scala b/veritas/src/main/scala/test/TestExample.scala index 7cd5f9a..c3f2483 100644 --- a/veritas/src/main/scala/test/TestExample.scala +++ b/veritas/src/main/scala/test/TestExample.scala @@ -61,6 +61,20 @@ class TestExample { "{def a; a = 5; print(a);}" .ShouldThrow(new ParseException("")) + def runTestGenericFunction1(): (Boolean, String) = + """def add[T1](a: T1, b: T1): T1 { + | return (a+b); + |} + | + |def main(): int { + | val b = add[int](2,3); + | val a = add[float](2.0,3.0); + | print(b); + |}""".stripMargin + .ShouldBe("5") + .Run() + + def runTestBig(): (Boolean, String) = { """ object Dynamic { From a15c12fd9b4fcc82d7fca1a0a78241f77ea6c5e6 Mon Sep 17 00:00:00 2001 From: Pijus Krisiukenas Date: Tue, 26 Dec 2023 01:52:45 +0200 Subject: [PATCH 093/112] added test --- app/src/main/scala/posharp/ToAssembly.scala | 29 +++++++------------ veritas/src/main/scala/test/TestExample.scala | 23 +++++++++++++++ 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/app/src/main/scala/posharp/ToAssembly.scala b/app/src/main/scala/posharp/ToAssembly.scala index 4fa5ca7..51b9aa9 100644 --- a/app/src/main/scala/posharp/ToAssembly.scala +++ b/app/src/main/scala/posharp/ToAssembly.scala @@ -59,6 +59,8 @@ object ToAssembly { lambdas = List() templateFunctions = List() templateFunctionInstances = List() + templateInterfaces = List() + templateInterfaceInstances = List() functionScope = FunctionInfo("main", "", List(), Type.Num(), List()); var converted = @@ -93,7 +95,6 @@ object ToAssembly { ).filter(y=>y.templates.isEmpty) .map(y => (y, Map())) //TODO template function in interfaces ignored for now - //templateFunctions = (x.functions ::: interfaceFunctionList.map(y=>y._1)).filter(x=>isTemplateFunction(x)) declareFunctions(x); converted += declareInterfaces(x) + "\n"; @@ -434,7 +435,7 @@ object ToAssembly { functions = functions ::: non_generic_intf.flatMap(x=>addPrefixToFunctions(x.name,x.funcs)) non_generic_intf.map(intf => { val types = intf.args.map(x=>Type.toLLVM(x.varType)).mkString(", ") - s"%Class.${intf.name} = type {$types}\n" + s"%Class.${intf.name}. = type {$types}\n" }).mkString } //https://stackoverflow.com/questions/3307427/scala-double-definition-2-methods-have-the-same-type-erasure @@ -536,7 +537,6 @@ object ToAssembly { private def defineFunctions(input: List[(Expr.Func, Env)], lambdaMode: Boolean, templated: Boolean): String = { input.map{ case (function, upperScope) => { - println(function.templates) if(isTemplateFunction(function) && !templated) { "" } @@ -578,22 +578,14 @@ object ToAssembly { functions.find(x=>x.name == name) match { case Some(x) => ; case None => throw new Exception(s"function of name $name undefined"); } - functions.find(x=>x.name == name && argInputTypes.length==x.args.length) match { - case Some(info@FunctionInfo(p, prefix, argTypes, retType, _)) if isTemplateFunction(info)=> { - //TODO error check - val template_mappings = info.templates.zip(templates)//templateFunctionArgs(info, templates) + functions.find(x=>x.name == name && argInputTypes.length==x.args.length && isTemplateFunction(x)) match { + case Some(info@FunctionInfo(p, prefix, argTypes, retType, _)) => { - /* - val template_mappings = argInputTypes.zipWithIndex.filter(x => x._1 match { - case Type.T(i) => true - case _ => false - }).map(x=> (replace.find(y=>y._1 == x._2).get._2, x._1) ) - */ - //val template_mappings = replace.map(x => (x._2, argInputTypes.zipWithIndex.find(y=>y._2 == x._1).get._1)) - //val template_mappings = replace + if(info.templates.length != templates.length) throw new Exception(s"Wrong template count for calling function $name") + val template_mappings = info.templates.zip(templates)//templateFunctionArgs(info, templates) - //TODO full error message - template_mappings.groupBy(x=>x._1).filter(x=>Set(x._2).toList.length>1).foreach(x=>throw new Exception(s"Template function input types conflicting")) + template_mappings.groupBy(x=>x._1).filter(x=>Set(x._2).toList.length>1) + .foreach(x=>throw new Exception(s"Template function $name input types conflicting, received: ${x._2}")) //println(template_mappings) val replace_func = replaceWithMappingFunc(template_mappings) @@ -608,8 +600,7 @@ object ToAssembly { //functions.find(x=>x.name == name && Type.compare(argInputTypes, x.args.map(x=>makeUserTypesConcrete(x.varType)))) match { //println(Util.prettyPrint(functions)) - //println(env) - // && x.templates == templates + functions.find(x=>x.name == name && argInputTypes == x.args.map(x=>makeUserTypesConcrete(x.varType))) match { case Some(info@FunctionInfo(p, prefix, argTypes, retType, templates)) => { var tName = fNameSignature(info) diff --git a/veritas/src/main/scala/test/TestExample.scala b/veritas/src/main/scala/test/TestExample.scala index c3f2483..3feae21 100644 --- a/veritas/src/main/scala/test/TestExample.scala +++ b/veritas/src/main/scala/test/TestExample.scala @@ -74,6 +74,29 @@ class TestExample { .ShouldBe("5") .Run() + def runTestGenericInterface1(): (Boolean, String) = + """object Test[T1] { + | toPrint: T1; + | def Test(self: Test[T1], toPrint: T1): Test[T1] { + | self.toPrint = toPrint; + | return self; + | } + | def __print__(self: Test[T1]) { + | print(self.toPrint); + | print("\n"); + | } + |} + | + |def main(): int { + | val c = new Test[int](10); + | val d = new Test[float](10.5); + | print(d); + | print("\n"); + | print(c); + |}""".stripMargin.stripMargin + .ShouldBe("10") + .Run() + def runTestBig(): (Boolean, String) = { """ From a43c36c67a75db91fbbbb22d84cc9a04441595ae Mon Sep 17 00:00:00 2001 From: Pijus Krisiukenas Date: Tue, 26 Dec 2023 03:44:29 +0200 Subject: [PATCH 094/112] reflection based ast tree traversal --- app/src/main/scala/posharp/ToAssembly.scala | 87 +++++++++++++-------- 1 file changed, 53 insertions(+), 34 deletions(-) diff --git a/app/src/main/scala/posharp/ToAssembly.scala b/app/src/main/scala/posharp/ToAssembly.scala index 51b9aa9..9b4491e 100644 --- a/app/src/main/scala/posharp/ToAssembly.scala +++ b/app/src/main/scala/posharp/ToAssembly.scala @@ -1,9 +1,13 @@ package posharp -import posharp.Type.{UserType, shortS, toLLVM} +import posharp.Type.{UserType, defaultValue, shortS, toLLVM} +import sourcecode.Text.generate import scala.io.AnsiColor import scala.language.postfixOps +import scala.reflect.runtime.currentMirror +import scala.reflect.runtime.universe.{TypeTag, termNames, typeOf} +import scala.reflect.ClassTag class Counter { var counter = 1; @@ -736,6 +740,7 @@ object ToAssembly { else if(converted._2 != elemType) throw new Exception(s"array elements are of different types") converted }) + val array_elem_size = arraySizeFromType(elemType); val Tsig = Type.toLLVM(elemType) val arrTC = s"%Type.array.$Tsig" @@ -827,26 +832,8 @@ object ToAssembly { } def isTemplateFunction(input: FunctionInfo): Boolean = { input.templates.nonEmpty - //templateFunctionArgs(input).nonEmpty - } - /* - def templateFunctionArgs(input: Expr.Func): List[(Int, Type.T)] = { - return input.argNames.zipWithIndex.filter(x=>x._1.varType match { - case Type.T(a) => true - case _ => false - }).map(x=>x._1.varType match { - case Type.T(a) => (x._2, Type.T(a)) - }) - } - def templateFunctionArgs(input: FunctionInfo): List[(Int, Type.T)] = { - return input.args.zipWithIndex.filter(x=>x._1.varType match { - case Type.T(a) => true - case _ => false - }).map(x=>x._1.varType match { - case Type.T(a) => (x._2, Type.T(a)) - }) } - */ + def traverseTypeTree(input: FunctionInfo, func: (Type) => Type): FunctionInfo = { FunctionInfo(input.name, input.prefix, @@ -867,16 +854,7 @@ object ToAssembly { ) } def traverseTypeTree(input: Type, func: (Type) => Type): Type = input match { - /* - case UserType(name) => interfaces.find(x=>x.name == name) match { - case Some(n) => traverseTypeTree(Type.Interface(n.args, n.funcs)) match { - case t@Type.Interface(newargs,f) => - interfaces = interfaces.map(x=>if(x == n) InterfaceInfo(x.name, newargs, x.funcs) else x) - t - } - case _ => throw new Exception (s"no interface of name $name"); - } - */ + case Type.Interface(name, args,f, templates) => Type.Interface(name, args.map(x=>InputVar(x.name, traverseTypeTree(x.varType, func))), f.map(x=>traverseTypeTree(x, func)), @@ -892,21 +870,62 @@ object ToAssembly { input.map(x => InputVar(x.name, traverseTypeTree(x.varType, func))) } + def replaceType(input: Expr, func: (Type) => Type): Expr = input match { case Expr.DefVal(a, varType) => Expr.DefVal(a, traverseTypeTree(varType, func)) - case Expr.DefValWithValue(variable, varType, value) => Expr.DefValWithValue(variable, traverseTypeTree(varType, func), value) + case Expr.DefValWithValue(variable, varType, value) => Expr.DefValWithValue(variable, traverseTypeTree(varType, func), replaceType(value, func)) case Expr.Func(name, argNames, retType, body, templates) => Expr.Func(name, replaceType(argNames, func), traverseTypeTree(retType, func), replaceType(body, func).asInstanceOf[Expr.Block], templates) case Expr.Block(lines) => Expr.Block(lines.map(x=>replaceType(x, func))) + case Expr.While(condition: Expr, execute: Expr.Block) => Expr.While(replaceType(condition, func), replaceType(execute,func).asInstanceOf[Expr.Block]) + case Expr.If(condition: Expr, ifTrue: Expr.Block, ifFalse: Expr) => + Expr.If(replaceType(condition, func), replaceType(ifTrue,func).asInstanceOf[Expr.Block],replaceType(ifFalse,func).asInstanceOf[Expr.Block]) case Expr.DefineInterface(name, props, functions, templates) => Expr.DefineInterface(name, replaceType(props, func), functions.map(x=>replaceType(x, func).asInstanceOf[Expr.Func]), templates.map(x=>traverseTypeTree(x, func)) ) - case x => x - //case Expr.While(condition: Expr, execute: Expr.Block) => - //case Expr.If(condition: Expr, ifTrue: Expr.Block, ifFalse: Expr) => + //case Expr.DefineArray(size, elemType:Type, defaultValues: List[Expr]) => Expr.DefineArray(size, traverseTypeTree(elemType, func), defaultValues) + //case Expr.InstantiateInterface + case x => { + val param_func: (Any => Any) = new ((Any) => Any) { + def apply(in: Any): Any = in match { + case expr: Expr => replaceType(expr, func) + case t: Type => traverseTypeTree(t, func) + case h :: t => (h +: t).map(y=>apply(y)) + case _ => in + } + } + applyFunctionToUnknownCaseClass(x, param_func) + } + //case class Convert(value: Expr, to: Type) extends Expr //case class Lambda(argNames: List[InputVar], retType: Type, body: Expr.Block) extends Expr } + + def applyFunctionToUnknownCaseClass(instance: Expr, func: Any => Any): Expr = { + val mirror = currentMirror + val instanceMirror = mirror.reflect(instance) + val classSymbol = instanceMirror.symbol + + if (!classSymbol.isClass || !classSymbol.asClass.isCaseClass) { + throw new IllegalArgumentException("The provided instance is not a case class.") + } + + val classType = instanceMirror.symbol.typeSignature + val constructorSymbol = classType.decl(termNames.CONSTRUCTOR).asMethod + val constructorMirror = mirror.reflectClass(classSymbol.asClass).reflectConstructor(constructorSymbol) + + // Collecting parameters from the constructor + val params = constructorSymbol.paramLists.flatten + + val fieldValues = params.map { param => + val fieldTerm = classType.decl(param.name).asTerm.accessed.asTerm + val fieldValue = instanceMirror.reflectField(fieldTerm).get + func(fieldValue) + } + + constructorMirror(fieldValues: _*).asInstanceOf[Expr] + } + def replaceWithMappingFunc(template_mappings: List[(Type, Type)]): Type => Type = { return { case t@Type.T(i) => { From a9ed1ee51ce3b8324ffcac7dc3693397601989fd Mon Sep 17 00:00:00 2001 From: Pijus Krisiukenas Date: Tue, 26 Dec 2023 20:36:41 +0200 Subject: [PATCH 095/112] cleanup --- app/src/main/scala/posharp/ToAssembly.scala | 41 ++++--- docs/tasklist.md | 1 + veritas/src/main/scala/test/TestExample.scala | 115 ++++++++++++++++++ 3 files changed, 137 insertions(+), 20 deletions(-) diff --git a/app/src/main/scala/posharp/ToAssembly.scala b/app/src/main/scala/posharp/ToAssembly.scala index 9b4491e..69e9572 100644 --- a/app/src/main/scala/posharp/ToAssembly.scala +++ b/app/src/main/scala/posharp/ToAssembly.scala @@ -6,8 +6,7 @@ import sourcecode.Text.generate import scala.io.AnsiColor import scala.language.postfixOps import scala.reflect.runtime.currentMirror -import scala.reflect.runtime.universe.{TypeTag, termNames, typeOf} -import scala.reflect.ClassTag +import scala.reflect.runtime.universe.termNames class Counter { var counter = 1; @@ -582,24 +581,26 @@ object ToAssembly { functions.find(x=>x.name == name) match { case Some(x) => ; case None => throw new Exception(s"function of name $name undefined"); } - functions.find(x=>x.name == name && argInputTypes.length==x.args.length && isTemplateFunction(x)) match { - case Some(info@FunctionInfo(p, prefix, argTypes, retType, _)) => { - - if(info.templates.length != templates.length) throw new Exception(s"Wrong template count for calling function $name") - val template_mappings = info.templates.zip(templates)//templateFunctionArgs(info, templates) - - template_mappings.groupBy(x=>x._1).filter(x=>Set(x._2).toList.length>1) - .foreach(x=>throw new Exception(s"Template function $name input types conflicting, received: ${x._2}")) - //println(template_mappings) - val replace_func = replaceWithMappingFunc(template_mappings) - - val func_expr = templateFunctions.find(x=>x.name==name && argTypes == x.argNames).get - //func_expr = Expr.Func(func_expr.name,func_expr.argNames,func_expr.retType,func_expr.body,func_expr.templates) - templateFunctionInstances = templateFunctionInstances :+ replaceType(func_expr, replace_func).asInstanceOf[Expr.Func] - //TODO unsure if templates or input.templates is best - functions = functions :+ FunctionInfo(p, prefix, argTypes.map(x=>InputVar(x.name, traverseTypeTree(x.varType, replace_func))), traverseTypeTree(retType, replace_func), info.templates) + if (!functions.exists(x=>x.name == name && argInputTypes == x.args.map(x=>makeUserTypesConcrete(x.varType)))) { + functions.find(x => x.name == name && argInputTypes.length == x.args.length && isTemplateFunction(x)) match { + case Some(info@FunctionInfo(p, prefix, argTypes, retType, _)) => { + + if (info.templates.length != templates.length) throw new Exception(s"Wrong template count for calling function $name") + val template_mappings = info.templates.zip(templates) //templateFunctionArgs(info, templates) + + template_mappings.groupBy(x => x._1).filter(x => Set(x._2).toList.length > 1) + .foreach(x => throw new Exception(s"Template function $name input types conflicting, received: ${x._2}")) + //println(template_mappings) + val replace_func = replaceWithMappingFunc(template_mappings) + + val func_expr = templateFunctions.find(x => x.name == name && argTypes == x.argNames).get + //func_expr = Expr.Func(func_expr.name,func_expr.argNames,func_expr.retType,func_expr.body,func_expr.templates) + templateFunctionInstances = templateFunctionInstances :+ replaceType(func_expr, replace_func).asInstanceOf[Expr.Func] + //TODO unsure if templates or input.templates is best + functions = functions :+ FunctionInfo(p, prefix, argTypes.map(x => InputVar(x.name, traverseTypeTree(x.varType, replace_func))), traverseTypeTree(retType, replace_func), info.templates) + } + case _ => () } - case _ => () } //functions.find(x=>x.name == name && Type.compare(argInputTypes, x.args.map(x=>makeUserTypesConcrete(x.varType)))) match { @@ -911,7 +912,7 @@ object ToAssembly { } val classType = instanceMirror.symbol.typeSignature - val constructorSymbol = classType.decl(termNames.CONSTRUCTOR).asMethod + val constructorSymbol = classType.decl(termNames.CONSTRUCTOR.asInstanceOf[scala.reflect.runtime.universe.Name]).asMethod val constructorMirror = mirror.reflectClass(classSymbol.asClass).reflectConstructor(constructorSymbol) // Collecting parameters from the constructor diff --git a/docs/tasklist.md b/docs/tasklist.md index f73cb23..c835221 100644 --- a/docs/tasklist.md +++ b/docs/tasklist.md @@ -12,6 +12,7 @@ * add copy command to arrays(and maybe structs) +* operator overloading/type alias ith 2 step parsing (maybe use if in parser to parse once with less dept hand then more) * rework file imports with explicit export command * add more object intrinsics(equal) * file name in error messages diff --git a/veritas/src/main/scala/test/TestExample.scala b/veritas/src/main/scala/test/TestExample.scala index 3feae21..5590867 100644 --- a/veritas/src/main/scala/test/TestExample.scala +++ b/veritas/src/main/scala/test/TestExample.scala @@ -217,4 +217,119 @@ class TestExample { .ShouldBe("abcdef") .Run() } + + def runTestGenericInterfaceBig1(): (Boolean, String) = { + """ +object Dynamic[T1] { + size: int; + allocated: int; + arr: array[T1]; + + def Dynamic(self: Dynamic[T1]): Dynamic[T1] { + self.arr = array[T1][8]; + self.allocated = 8; + self.size = 0; + return self; + } + def Dynamic(self: Dynamic[T1], arr: array[T1]): Dynamic[T1] { + self.Dynamic(); + self.push(arr); + return self; + } + + def expand(self: Dynamic[T1], req: int) { + val total = (req + self.size); + + if(total >= self.allocated) { + val newsize = self.allocated; + while(newsize < (total+1)) { + newsize = (newsize * 2); + }; + val old = self.arr; + val narr = array[T1][newsize]; + for(val i = 0; i < self.size; i+= 1;) { + narr[i] = old[i]; + }; + self.arr = narr; + free(old); + self.allocated = newsize; + self.size = total; + } else { + self.size += req; + }; + } + def push(self: Dynamic[T1], value: T1) { + self.expand(1); + self.arr[(self.size-1)] = value; + } + def push(self: Dynamic[T1], value: array[T1]) { + val oldSize = self.size; + self.expand(value.size); + + for(val i = 0; i < value.size; i+= 1;) { + self.arr[(i + oldSize)] = value[i]; + }; + } + + def push(self: Dynamic[T1], value: Dynamic[T1]) { + val oldSize = self.size; + self.expand(value.size); + + for(val i = 0; i < value.size; i+= 1;) { + self.arr[(i + oldSize)] = value.arr[i]; + }; + } + + def concat(self: Dynamic[T1], other: Dynamic[T1]): Dynamic[T1] { + val ret = self.copy(); + ret.push(other); + return ret; + } + //filter + def get(self: Dynamic[T1], index: int): T1 { + if(index >= self.size) { + throw exception("index out of bounds"); + }; + return self.arr[index]; + } + def print_arr(self: Dynamic[T1]) { + for(val i = 0; i< self.size; i+=1;) { + print(self.arr[i]); + //print(" "); + }; + print("\n"); + } + def copy(self: Dynamic[T1]): Dynamic[T1] { //still sus cause on stack + val arr_new = new Dynamic[T1](); + + for(val i = 0; i < self.size; i+= 1;) { + arr_new.push(self.arr[i]); + }; + return arr_new; + } + def compare(self: Dynamic[T1], other: Dynamic[T1]): bool { + val same: bool = true; + if(self.size != other.size) {return false;}; + for(val i = 0; i < self.size; i+= 1;) { + if(self.get(i) != other.get(i)) {same = false;}; + }; + return same; + } + def __add__(self: Dynamic[T1], other: Dynamic[T1]): Dynamic[T1] { + return self.concat(other); + } + def __print__(self: Dynamic[T1]) { + self.print_arr(); + } + } + def main(): int { + val a = new Dynamic[char](); + a.push(array('a', 'b', 'c')); + val b = new Dynamic[char](array('d', 'e', 'f')); + print(a+b); + } + """ + .ShouldBe("abcdef") + .Run() + } } From c523a522920ec8ec7fde84d007d5b11722d48f0a Mon Sep 17 00:00:00 2001 From: Pijus Krisiukenas Date: Thu, 4 Jan 2024 03:33:28 +0200 Subject: [PATCH 096/112] conversion structure rework --- app/src/main/scala/posharp/Definitions.scala | 2 +- app/src/main/scala/posharp/Main.scala | 15 +- app/src/main/scala/posharp/ToAssembly.scala | 155 ++++++++++++------- docs/tasklist.md | 2 +- 4 files changed, 109 insertions(+), 65 deletions(-) diff --git a/app/src/main/scala/posharp/Definitions.scala b/app/src/main/scala/posharp/Definitions.scala index 5bfb748..dcc0195 100644 --- a/app/src/main/scala/posharp/Definitions.scala +++ b/app/src/main/scala/posharp/Definitions.scala @@ -1,6 +1,6 @@ package posharp -import ToAssembly.FunctionInfo +import posharp.ToAssembly.FunctionInfo sealed trait Expr object Expr{ diff --git a/app/src/main/scala/posharp/Main.scala b/app/src/main/scala/posharp/Main.scala index 73559bc..7f53a08 100644 --- a/app/src/main/scala/posharp/Main.scala +++ b/app/src/main/scala/posharp/Main.scala @@ -15,7 +15,7 @@ object Main extends App { } val files = recursiveListFiles(new File(sourceDir), "ignore").toList.filter(x => x.getName.contains(Constants.FileExtension)) val sourceDirPath = Paths.get(sourceDir) - val declarations: Map[String, Expr.TopLevel] = files.map(file => { + val declarations: Map[String, (ToAssembly, Expr.TopLevel)] = files.map(file => { val toCompile = readFile(file) val parsed = Parser.parseInput(toCompile); val top = parsed match { @@ -24,15 +24,22 @@ object Main extends App { } var relative_name = sourceDirPath.relativize(file.toPath).toFile.getPath.split(Constants.FileExtension)(0) relative_name = relative_name.replace("\\", "/") - (relative_name -> top) + (relative_name -> (new ToAssembly(relative_name), top)) }).toMap + //declaration step + declarations.foreach(x => { + val code = x._2._2 + val converter = x._2._1 + converter.declarationPass(code) + }) declarations.foreach(x => { val file = x._1 - val code = x._2 + val code = x._2._2 + val converter = x._2._1 var asm = ""; try { - asm = ToAssembly.convertMain(code, file, declarations.filter(x => x._1 != file)); + asm = converter.convertMain(code, declarations.map(x=>x._1->x._2._2).filter(x => x._1 != file)); //asm += StringCode.stringCode; } catch { diff --git a/app/src/main/scala/posharp/ToAssembly.scala b/app/src/main/scala/posharp/ToAssembly.scala index 69e9572..4679941 100644 --- a/app/src/main/scala/posharp/ToAssembly.scala +++ b/app/src/main/scala/posharp/ToAssembly.scala @@ -1,5 +1,6 @@ package posharp +import posharp.ToAssembly.{FunctionInfo, InterfaceInfo, EnumInfo, Variable, FileDeclaration} import posharp.Type.{UserType, defaultValue, shortS, toLLVM} import sourcecode.Text.generate @@ -8,36 +9,10 @@ import scala.language.postfixOps import scala.reflect.runtime.currentMirror import scala.reflect.runtime.universe.termNames -class Counter { - var counter = 1; - private var pausedCounter = 1; - private var counterExtra = 0; - private var paused = false; - def next(): String = { - val cur = counter; - if(!paused) counter += 1; - s"%l$cur"; - } - def extra(): Int = { - counterExtra += 1; - counterExtra - 1; - } - def last(): String = s"%l${counter-1}"; - def secondLast(): String = s"%l${counter-2}"; - def reset(): Unit = { - counter = 1; - } - def pause(): Unit = { - paused = true; - pausedCounter = counter; - } - def unpause(): Unit = { - paused = false; - counter = pausedCounter; - } -} +class ToAssembly(currentFile: String) { + type Env = Map[String, Variable] + -object ToAssembly { var varc: Counter = new Counter(); var ifCounter = 0; var subconditionCounter: Int = 0; @@ -52,18 +27,31 @@ object ToAssembly { var templateInterfaces: List[Expr.DefineInterface] = List() var templateInterfaceInstances: List[Expr.DefineInterface] = List() - def convertMain(input: Expr, currentFile: String, otherFiles: Map[String, Expr.TopLevel]): String = { + //var otherFilesGenerate: Map[String, (FileDeclaration, List[Expr.Func])] = Map() + + def declarationPass(input: Expr): Unit = { + input match { + case x: Expr.TopLevel => { + declareFunctions(x); + declareInterfaces(x) + //declareEnums(x) + templateInterfaces = x.interfaces.filter(x => isTemplateInterface(x.templates)) + templateFunctions = x.functions.filter(x => isTemplateFunction(x)) + } + } + } + def convertMain(input: Expr, otherFiles: Map[String, Expr.TopLevel]): String = { ifCounter = 0; subconditionCounter = 0; - stringLiterals = List() - functions = List(); - interfaces = List(); - enums = List() - lambdas = List() - templateFunctions = List() - templateFunctionInstances = List() - templateInterfaces = List() - templateInterfaceInstances = List() + //stringLiterals = List() + //functions = List(); + //interfaces = List(); + //enums = List() + //lambdas = List() + //templateFunctions = List() + //templateFunctionInstances = List() + //templateInterfaces = List() + //templateInterfaceInstances = List() functionScope = FunctionInfo("main", "", List(), Type.Num(), List()); var converted = @@ -99,11 +87,7 @@ object ToAssembly { .map(y => (y, Map())) //TODO template function in interfaces ignored for now - declareFunctions(x); - converted += declareInterfaces(x) + "\n"; - //declareEnums(x) - templateInterfaces = x.interfaces.filter(x=>isTemplateInterface(x.templates)) - templateFunctions = x.functions.filter(x=>isTemplateFunction(x)) + converted += declareInterfaces(x, onlyLLVM = true) converted += exportDeclarations(currentFile) + "\n" converted += handleImports(x, otherFiles) + "\n" converted += defineFunctions(x.functions.map(y=>(y, Map())), false, false); @@ -432,10 +416,13 @@ object ToAssembly { private def declareFunctions(input: Expr.TopLevel): Unit = { functions = input.functions.map(x=> FunctionInfo(x.name, "", x.argNames, x.retType, x.templates)) } - private def declareInterfaces(input: Expr.TopLevel): String = { - interfaces = input.interfaces.map(x=> InterfaceInfo(x.name, x.props, x.functions.map(x=>FunctionInfo(x.name,"", x.argNames,x.retType,x.templates)), x.templates)) - val non_generic_intf = interfaces.filter(x=> !isTemplateInterface(x)) - functions = functions ::: non_generic_intf.flatMap(x=>addPrefixToFunctions(x.name,x.funcs)) + private def declareInterfaces(input: Expr.TopLevel, onlyLLVM: Boolean = false): String = { + val interfaces_local = input.interfaces.map(x=> InterfaceInfo(x.name, x.props, x.functions.map(x=>FunctionInfo(x.name,"", x.argNames,x.retType,x.templates)), x.templates)) + val non_generic_intf = interfaces_local.filter(x=> !isTemplateInterface(x)) + if (!onlyLLVM) { + functions = functions ::: non_generic_intf.flatMap(x => addPrefixToFunctions(x.name, x.funcs)) + interfaces = interfaces_local + } non_generic_intf.map(intf => { val types = intf.args.map(x=>Type.toLLVM(x.varType)).mkString(", ") s"%Class.${intf.name}. = type {$types}\n" @@ -458,21 +445,35 @@ object ToAssembly { var ret = "" val funcsForImport = searchFileDeclarations(top, imp) match { - case Expr.Func(name, argnames, retType, code, templates) => { - functions = functions :+ FunctionInfo(name, formatFName(imp.file), argnames, retType, templates) - List(FunctionInfo(name, formatFName(imp.file), argnames, retType, templates)) + case f_def@Expr.Func(name, argnames, retType, code, templates) => { + val info = FunctionInfo(name, formatFName(imp.file), argnames, retType, templates) + functions = functions :+ info + //TODO MAJOR issue with where generic classes will be generated. Should be in source file but currently in current. + if (isTemplateFunction(info)) { + templateFunctions = templateFunctions :+ f_def + List() + } + else List(FunctionInfo(name, formatFName(imp.file), argnames, retType, templates)) } - case Expr.DefineInterface(name, props, i_functions, templates) => { - throw new NotImplementedError("") + case intf_def@Expr.DefineInterface(name, props, i_functions, templates) => { val intf = InterfaceInfo(name, props, i_functions.map(x=>FunctionInfo(x.name,formatFName(imp.file), x.argNames,x.retType,x.templates)), templates) + + //templateFunctions = x.functions.filter(x => isTemplateFunction(x)) + //TODO MAJOR issue with where generic classes will be generated. Should be in source file but currently in current. + if(isTemplateInterface(intf)) templateInterfaces = templateInterfaces :+ intf_def + interfaces = interfaces :+ intf - val types = intf.args.map(x=>Type.toLLVM(x.varType)).mkString(", ") - ret += s"%Class.${intf.name} = type {$types}\n" + if(!isTemplateInterface(intf)) { + val types = intf.args.map(x => Type.toLLVM(x.varType)).mkString(", ") + val intf_llvm = toLLVM(intf).dropRight(1) + //ret += s"%Class.${intf.name} = type {$types}\n" + ret += s"$intf_llvm = type {$types}\n" - val funcs = addPrefixToFunctions(intf.name,intf.funcs) - functions = functions ::: funcs - funcs.map(x=>FunctionInfo(x.name, formatFName(imp.file), x.args, x.retType,x.templates)) + val funcs = addPrefixToFunctions(intf.name, intf.funcs) + functions = functions ::: funcs + funcs.map(x => FunctionInfo(x.name, formatFName(imp.file), x.args, x.retType, x.templates)) + } else List() } } ret + funcsForImport.map(info=>{ @@ -994,10 +995,46 @@ object ToAssembly { } } - type Env = Map[String, Variable] +} + +object ToAssembly extends ToAssembly("") { case class FunctionInfo(name: String, prefix: String, args: List[InputVar], retType: Type, templates: List[Type]) + case class InterfaceInfo(name: String, args: List[InputVar], funcs: List[FunctionInfo], templates: List[Type]) + case class EnumInfo(name: String, el: List[String]) + case class Variable(loc: String, varType: Type) + + case class FileDeclaration(functions: List[FunctionInfo], interfaces: List[InterfaceInfo]) +} + +class Counter { + var counter = 1; + private var pausedCounter = 1; + private var counterExtra = 0; + private var paused = false; + def next(): String = { + val cur = counter; + if(!paused) counter += 1; + s"%l$cur"; + } + def extra(): Int = { + counterExtra += 1; + counterExtra - 1; + } + def last(): String = s"%l${counter-1}"; + def secondLast(): String = s"%l${counter-2}"; + def reset(): Unit = { + counter = 1; + } + def pause(): Unit = { + paused = true; + pausedCounter = counter; + } + def unpause(): Unit = { + paused = false; + counter = pausedCounter; + } } diff --git a/docs/tasklist.md b/docs/tasklist.md index c835221..ba7687f 100644 --- a/docs/tasklist.md +++ b/docs/tasklist.md @@ -10,7 +10,7 @@ ## To do - +* all imported types should be nameless (i.e. not usertype) * add copy command to arrays(and maybe structs) * operator overloading/type alias ith 2 step parsing (maybe use if in parser to parse once with less dept hand then more) * rework file imports with explicit export command From d41c579f7efb671d1d92dfcc7bd6080f9202341c Mon Sep 17 00:00:00 2001 From: Pijus Krisiukenas Date: Fri, 5 Jan 2024 23:40:56 +0200 Subject: [PATCH 097/112] all usertypes presume current file namespace --- app/src/main/scala/posharp/Main.scala | 8 +++-- app/src/main/scala/posharp/Parser.scala | 34 +++++++++++------- app/src/main/scala/posharp/ToAssembly.scala | 40 ++++++++++----------- 3 files changed, 45 insertions(+), 37 deletions(-) diff --git a/app/src/main/scala/posharp/Main.scala b/app/src/main/scala/posharp/Main.scala index 7f53a08..1e61fb2 100644 --- a/app/src/main/scala/posharp/Main.scala +++ b/app/src/main/scala/posharp/Main.scala @@ -17,13 +17,15 @@ object Main extends App { val sourceDirPath = Paths.get(sourceDir) val declarations: Map[String, (ToAssembly, Expr.TopLevel)] = files.map(file => { val toCompile = readFile(file) - val parsed = Parser.parseInput(toCompile); + var relative_name = sourceDirPath.relativize(file.toPath).toFile.getPath.split(Constants.FileExtension)(0) + relative_name = relative_name.replace("\\", "/") + + val parsed = Parser.parseInput(toCompile, relative_name.replace("/", "_")); val top = parsed match { case x: Expr.TopLevel => x case _ => throw new Exception("unexpected type in top level") } - var relative_name = sourceDirPath.relativize(file.toPath).toFile.getPath.split(Constants.FileExtension)(0) - relative_name = relative_name.replace("\\", "/") + (relative_name -> (new ToAssembly(relative_name), top)) }).toMap //declaration step diff --git a/app/src/main/scala/posharp/Parser.scala b/app/src/main/scala/posharp/Parser.scala index 4577a74..1f31e45 100644 --- a/app/src/main/scala/posharp/Parser.scala +++ b/app/src/main/scala/posharp/Parser.scala @@ -23,7 +23,7 @@ object Parser { Expr.TopLevel(func, intf, enum, imports) }) - def function[_: P]: P[Expr.Func] = P("def " ~/ ident ~ templateTypes.? ~ "(" ~/ functionArgs ~ ")" ~/ typeDef.? ~ block).map { + def function[_: P]: P[Expr.Func] = P("def " ~/ mod_ident ~ templateTypes.? ~ "(" ~/ functionArgs ~ ")" ~/ typeDef.? ~ block).map { case (name, templates, args, retType, body) => { val ret = retType match { case Some(typ) => typ @@ -40,7 +40,7 @@ object Parser { Expr.DefineInterface(props._1.name, props._2.toList.map(x=>InputVar(x._1.name, x._2))) ) */ - def interfaceDef[_: P]: P[Expr.DefineInterface] = P("object " ~/ ident ~ templateTypes.? ~ "{" ~/ objValue ~ function.rep ~ "}").map(props => + def interfaceDef[_: P]: P[Expr.DefineInterface] = P("object " ~/ mod_ident ~ templateTypes.? ~ "{" ~/ objValue ~ function.rep ~ "}").map(props => Expr.DefineInterface(props._1.name, props._3.map(x => InputVar(x.name, x.varType)), props._4.toList, props._2.getOrElse(List()).asInstanceOf[List[Type.T]])) def objValue[_: P]: P[List[ObjVal]] = P(ident ~ typeDef ~ ("=" ~ prefixExpr).? ~ ";").rep.map(x => x.map { @@ -48,7 +48,7 @@ object Parser { case (id, valtype, None) => ObjVal(id.name, valtype, Type.defaultValue(valtype)) }.toList) - def enumDef[_: P]: P[Expr.DefineEnum] = P("enum " ~ ident ~/ "{" ~ ident.rep(sep = ",") ~ "}").map(props => + def enumDef[_: P]: P[Expr.DefineEnum] = P("enum " ~ mod_ident ~/ "{" ~ ident.rep(sep = ",") ~ "}").map(props => Expr.DefineEnum(props._1.name, props._2.toList.map(x => x.name)) ) @@ -72,7 +72,7 @@ object Parser { case (ident, None) => Expr.DefVal(ident.name, Type.Undefined()) } - def accessVar[_: P]: P[Expr] = P(ident ~ ((".".! ~ ident ~ "(".! ~ prefixExpr.rep(sep = ",") ~ ")") | (".".! ~ ident) | ("[".! ~/ prefixExpr ~ "]")).rep).map { case (start, acs) => + def accessVar[_: P]: P[Expr] = P(mod_ident ~ ((".".! ~ ident ~ "(".! ~ prefixExpr.rep(sep = ",") ~ ")") | (".".! ~ ident) | ("[".! ~/ prefixExpr ~ "]")).rep).map { case (start, acs) => acs.foldLeft(start: Expr)((acc, v) => v match { case (".", Expr.Ident(ident)) => GetProperty(acc, ident) case ("[", index: Expr) => Expr.GetArray(acc, index) @@ -129,7 +129,7 @@ object Parser { def getArraySize[_: P]: P[Expr.ArraySize] = P(returnsArray ~~ ".size").map((x) => Expr.ArraySize(x)) - def instanceInterface[_: P]: P[Expr.InstantiateInterface] = P("new " ~/ ident ~ templateTypes.? ~ "(" ~/ prefixExpr.rep(sep = ",") ~ ")") + def instanceInterface[_: P]: P[Expr.InstantiateInterface] = P("new " ~/ mod_ident ~ templateTypes.? ~ "(" ~/ prefixExpr.rep(sep = ",") ~ ")") .map(x => Expr.InstantiateInterface(x._1.name, x._3.toList, x._2.getOrElse(List()))) def typeDef[_: P]: P[Type] = P(":" ~/ (typeBase | typeArray | typeFunc | typeUser)) @@ -138,7 +138,7 @@ object Parser { def typeArray[_: P]: P[Type] = P("array" ~/ "[" ~ typeDefNoCol ~ "]").map(x => Type.Array(x)) def typeFunc[_: P]: P[Type] = P("func" ~/ "[" ~ "(" ~ typeDefNoCol.rep(sep=",") ~ ")" ~/ "=>" ~ typeDefNoCol ~ "]").map(x => Type.Function(x._1.toList, x._2)) - def typeUser[_: P]: P[Type] = P(ident ~ templateTypes.?).map(x => Type.UserType(x._1.name, x._2.getOrElse(List()))) + def typeUser[_: P]: P[Type] = P(mod_ident ~ templateTypes.?).map(x => Type.UserType(x._1.name, x._2.getOrElse(List()))) def typeBase[_: P]: P[Type] = P((StringIn("int", "char", "float", "bool", "string", "void").!) | ("T" ~ CharsWhileIn("0-9", 1)).!).map { case "int" => Type.Num(); @@ -159,7 +159,7 @@ object Parser { case (value, "toChar") => Expr.Convert(value, Type.Character()) } - def callFunction[_: P]: P[Expr.CallF] = P(ident ~ templateTypes.? ~ "(" ~/ prefixExpr.rep(sep = ",") ~/ ")").map { + def callFunction[_: P]: P[Expr.CallF] = P(mod_ident ~ templateTypes.? ~ "(" ~/ prefixExpr.rep(sep = ",") ~/ ")").map { case (name, templates, args) => Expr.CallF(name.name, args.toList, templates.getOrElse(List())); }//.filter((x) => !reservedKeywords.contains(x.name)) @@ -220,9 +220,17 @@ object Parser { def str[_: P]: P[Expr.Str] = P("\"" ~~/ CharsWhile(_ != '"', 0).! ~~ "\"").map(x => Expr.Str(x.replace("\\n", ""+10.toChar) + "\u0000")) def fileName[_: P]: P[Expr.Str] = P("\"" ~~/ CharsWhile(_ != '"', 0).! ~~ "\"").map(x => Expr.Str(x)) - def ident[_: P]: P[Expr.Ident] = P(CharIn("a-zA-Z_") ~~ CharsWhileIn("a-zA-Z0-9_", 0)).!.map((input) => { - Expr.Ident(input) - }).filter(x => !reservedKeywords.contains(x.name)) + def ident[_: P]: P[Expr.Ident] = P(CharIn("a-zA-Z_") ~~ CharsWhileIn("a-zA-Z0-9_", 0)).! + .filter(x => !reservedKeywords.contains(x)) + .map((input) => { + Expr.Ident(input) + }) + + def mod_ident[_: P]: P[Expr.Ident] = P(CharIn("a-zA-Z_") ~~ CharsWhileIn("a-zA-Z0-9_", 0)).! + .filter(x => !reservedKeywords.contains(x)) + .map((input) => { + Expr.Ident(file_name + "_" + input) + }) def number[_: P]: P[Expr.Num] = P("-".!.? ~~ CharsWhileIn("0-9", 1)).!.map(x => Expr.Num(Integer.parseInt(x))) @@ -253,12 +261,14 @@ object Parser { if (reservedKeywords.contains(input.name)) throw new ParseException(s"${input.name} is a reserved keyword"); } - def parseInput(input: String): Expr = { + var file_name: String = "" + def parseInput(input: String, _file_name: String): Expr = { + file_name = _file_name; val parsed = fastparse.parse(input, topLevel(_), verboseFailures = true); parsed match { case Parsed.Success(expr, n) => expr.asInstanceOf[Expr]; case t: Parsed.Failure => { - println(t.trace(true).longAggregateMsg); throw new ParseException("parsing fail"); + println(t.trace(true).longAggregateMsg); throw new ParseException(s"parsing fail in $file_name"); } case _ => throw new ParseException("parsing fail") } diff --git a/app/src/main/scala/posharp/ToAssembly.scala b/app/src/main/scala/posharp/ToAssembly.scala index 4679941..adaa458 100644 --- a/app/src/main/scala/posharp/ToAssembly.scala +++ b/app/src/main/scala/posharp/ToAssembly.scala @@ -26,6 +26,7 @@ class ToAssembly(currentFile: String) { var functionScope: FunctionInfo = FunctionInfo("main", "", List(), Type.Num(), List()); var templateInterfaces: List[Expr.DefineInterface] = List() var templateInterfaceInstances: List[Expr.DefineInterface] = List() + val file_prefix: String = formatFName(currentFile) //var otherFilesGenerate: Map[String, (FileDeclaration, List[Expr.Func])] = Map() @@ -43,15 +44,6 @@ class ToAssembly(currentFile: String) { def convertMain(input: Expr, otherFiles: Map[String, Expr.TopLevel]): String = { ifCounter = 0; subconditionCounter = 0; - //stringLiterals = List() - //functions = List(); - //interfaces = List(); - //enums = List() - //lambdas = List() - //templateFunctions = List() - //templateFunctionInstances = List() - //templateInterfaces = List() - //templateInterfaceInstances = List() functionScope = FunctionInfo("main", "", List(), Type.Num(), List()); var converted = @@ -439,7 +431,9 @@ class ToAssembly(currentFile: String) { enums = input.enums.map(x=>EnumInfo(x.name,x.props)) } private def handleImports(input: Expr.TopLevel, otherFiles: Map[String, Expr.TopLevel]): String = { - input.imports.map(imp=>{ + input.imports.map(user_imp=>{ + val imp = Expr.Import(formatFName(user_imp.file)+"_"+user_imp.toImport, user_imp.file) + def change_file(name: String) = name.replace(formatFName(imp.file)+"_", file_prefix+"_") if (!otherFiles.contains(imp.file)) throw new Exception(s"file \"${imp.file}\" could not be imported"); val top = otherFiles(imp.file) var ret = "" @@ -463,12 +457,16 @@ class ToAssembly(currentFile: String) { if(isTemplateInterface(intf)) templateInterfaces = templateInterfaces :+ intf_def interfaces = interfaces :+ intf + val same_namespace_intf = InterfaceInfo(change_file(name), props, intf.funcs, templates) + interfaces = interfaces :+ same_namespace_intf if(!isTemplateInterface(intf)) { val types = intf.args.map(x => Type.toLLVM(x.varType)).mkString(", ") val intf_llvm = toLLVM(intf).dropRight(1) + val same_namespace_intf_llvm = toLLVM(same_namespace_intf).dropRight(1) //ret += s"%Class.${intf.name} = type {$types}\n" ret += s"$intf_llvm = type {$types}\n" + ret += s"$same_namespace_intf_llvm = type {$types}\n" val funcs = addPrefixToFunctions(intf.name, intf.funcs) functions = functions ::: funcs @@ -476,21 +474,19 @@ class ToAssembly(currentFile: String) { } else List() } } + funcsForImport.foreach(info=>{ + functions = functions :+ FunctionInfo(change_file(info.name), info.prefix, info.args, info.retType, info.templates) + }) ret + funcsForImport.map(info=>{ - val name = fNameSignature(info) - val importName = formatFName(imp.file) + "_" + name + val importName: String = fNameSignature(info) + val name = change_file(importName) //formatFName(imp.file) + "_" + val args = info.args.map(x=>s"${Type.toLLVM(x.varType)}").mkString(", ") //s"declare ${Type.toLLVM(info.retType)} @${importName}($args)\n" + // s"@$name = ifunc ${toLLVM(info)}, ${toLLVM(info)}* @${importName}\n" - s"declare ${Type.toLLVM(info.retType)} @${importName}($args) \"${formatFName(imp.file)}\"\n" - }).mkString - /* - intf.funcs.map(x=>{ - val label = imp.file + "_" + x.name - s"extern ${label}\n" + s"${x.name}:\njmp ${label}\n" - }).mkString - */ + s"declare ${Type.toLLVM(info.retType)} @${importName}($args) \"${formatFName(imp.file)}\"\n"+ + s"@${name} = alias ${toLLVM(info)}, ${toLLVM(info)}* @${importName}\n" + }).mkString }).mkString } private def searchFileDeclarations(top: Expr.TopLevel, imp: Expr.Import): Expr = { @@ -605,12 +601,12 @@ class ToAssembly(currentFile: String) { } //functions.find(x=>x.name == name && Type.compare(argInputTypes, x.args.map(x=>makeUserTypesConcrete(x.varType)))) match { - //println(Util.prettyPrint(functions)) + println(Util.prettyPrint(functions)) functions.find(x=>x.name == name && argInputTypes == x.args.map(x=>makeUserTypesConcrete(x.varType))) match { case Some(info@FunctionInfo(p, prefix, argTypes, retType, templates)) => { var tName = fNameSignature(info) - if(prefix != "") tName = prefix+"_"+tName; + //if(prefix != "") tName = prefix+"_"+tName; val argTypeString = argTypes.map(x=>Type.toLLVM(x.varType)).mkString(", ") if (retType != Type.Undefined()) ret += s"${varc.next()} = "; ret += s"call ${Type.toLLVM(retType)} ($argTypeString) @$tName($argsString)\n" From bacb9138f6946468ba419da6e44b70047aae6279 Mon Sep 17 00:00:00 2001 From: Pijus Krisiukenas Date: Sat, 6 Jan 2024 01:41:43 +0200 Subject: [PATCH 098/112] module prefix implemented --- app/src/main/scala/posharp/Parser.scala | 21 ++++++++--- app/src/main/scala/posharp/ToAssembly.scala | 39 ++++++++++++++------- 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/app/src/main/scala/posharp/Parser.scala b/app/src/main/scala/posharp/Parser.scala index 1f31e45..fbd10c2 100644 --- a/app/src/main/scala/posharp/Parser.scala +++ b/app/src/main/scala/posharp/Parser.scala @@ -33,7 +33,9 @@ object Parser { } } - def imports[_: P]: P[Expr.Import] = P("import " ~/ ident ~ "from" ~ fileName ~ ";").map(x=> Expr.Import(x._1.name, x._2.s)) + def imports[_: P]: P[Expr.Import] = P(importPartial | importModule) + def importPartial[_: P]: P[Expr.Import] = P("import " ~ ident ~ "from" ~/ fileName ~ ";").map(x=> Expr.Import(x._1.name, x._2.s)) + def importModule[_: P]: P[Expr.Import] = P("import " ~/ fileName ~ ";").map(x=> Expr.Import("__module__", x.s)) /* def interfaceDef[_: P]: P[Expr.DefineInterface] = P("interface " ~ ident ~/ "{" ~ (ident ~ typeDef).rep(sep = ",") ~ "}").map(props=> @@ -72,7 +74,7 @@ object Parser { case (ident, None) => Expr.DefVal(ident.name, Type.Undefined()) } - def accessVar[_: P]: P[Expr] = P(mod_ident ~ ((".".! ~ ident ~ "(".! ~ prefixExpr.rep(sep = ",") ~ ")") | (".".! ~ ident) | ("[".! ~/ prefixExpr ~ "]")).rep).map { case (start, acs) => + def accessVar[_: P]: P[Expr] = P(ident ~ ((".".! ~ ident ~ "(".! ~ prefixExpr.rep(sep = ",") ~ ")") | (".".! ~ ident) | ("[".! ~/ prefixExpr ~ "]")).rep).map { case (start, acs) => acs.foldLeft(start: Expr)((acc, v) => v match { case (".", Expr.Ident(ident)) => GetProperty(acc, ident) case ("[", index: Expr) => Expr.GetArray(acc, index) @@ -226,10 +228,19 @@ object Parser { Expr.Ident(input) }) - def mod_ident[_: P]: P[Expr.Ident] = P(CharIn("a-zA-Z_") ~~ CharsWhileIn("a-zA-Z0-9_", 0)).! - .filter(x => !reservedKeywords.contains(x)) + def mod_ident[_: P]: P[Expr.Ident] = P(mod_ident_raw) + .map((input) => { + val modules = input._1.toList.mkString("_") + val prefix = if (modules.nonEmpty) modules else file_name + Expr.Ident(prefix + "_" + input._2) + }) + def mod_ident_raw[_: P] = P((ident.! ~ "::").rep() ~ ident.!) + .filter(x => !x._1.exists(y => reservedKeywords.contains(y)) && !reservedKeywords.contains(x._2)) + def mod_ident_no_default[_: P]: P[Expr.Ident] = P(mod_ident_raw) .map((input) => { - Expr.Ident(file_name + "_" + input) + val modules = input._1.toList.mkString("_") + "_" + val prefix = if (modules.length > 1) modules else "" + Expr.Ident(prefix + input._2) }) def number[_: P]: P[Expr.Num] = P("-".!.? ~~ CharsWhileIn("0-9", 1)).!.map(x => Expr.Num(Integer.parseInt(x))) diff --git a/app/src/main/scala/posharp/ToAssembly.scala b/app/src/main/scala/posharp/ToAssembly.scala index adaa458..9a158e5 100644 --- a/app/src/main/scala/posharp/ToAssembly.scala +++ b/app/src/main/scala/posharp/ToAssembly.scala @@ -44,7 +44,7 @@ class ToAssembly(currentFile: String) { def convertMain(input: Expr, otherFiles: Map[String, Expr.TopLevel]): String = { ifCounter = 0; subconditionCounter = 0; - functionScope = FunctionInfo("main", "", List(), Type.Num(), List()); + functionScope = FunctionInfo(file_prefix+"_main", "", List(), Type.Num(), List()); var converted = """ @@ -432,13 +432,23 @@ class ToAssembly(currentFile: String) { } private def handleImports(input: Expr.TopLevel, otherFiles: Map[String, Expr.TopLevel]): String = { input.imports.map(user_imp=>{ - val imp = Expr.Import(formatFName(user_imp.file)+"_"+user_imp.toImport, user_imp.file) + val module_import: Boolean = user_imp.toImport == "__module__" + val imp = if (!module_import) + Expr.Import(formatFName(user_imp.file)+"_"+user_imp.toImport, user_imp.file) + else user_imp + def change_file(name: String) = name.replace(formatFName(imp.file)+"_", file_prefix+"_") if (!otherFiles.contains(imp.file)) throw new Exception(s"file \"${imp.file}\" could not be imported"); val top = otherFiles(imp.file) var ret = "" + val import_list: List[Expr] = if(module_import) { + val cur_top = otherFiles(imp.file) + cur_top.interfaces ::: cur_top.functions + } else { + List(searchFileDeclarations(top, imp)) + } - val funcsForImport = searchFileDeclarations(top, imp) match { + val funcsForImport = import_list.flatMap{ case f_def@Expr.Func(name, argnames, retType, code, templates) => { val info = FunctionInfo(name, formatFName(imp.file), argnames, retType, templates) functions = functions :+ info @@ -450,17 +460,17 @@ class ToAssembly(currentFile: String) { else List(FunctionInfo(name, formatFName(imp.file), argnames, retType, templates)) } case intf_def@Expr.DefineInterface(name, props, i_functions, templates) => { - val intf = InterfaceInfo(name, props, i_functions.map(x=>FunctionInfo(x.name,formatFName(imp.file), x.argNames,x.retType,x.templates)), templates) + val intf = InterfaceInfo(name, props, i_functions.map(x => FunctionInfo(x.name, formatFName(imp.file), x.argNames, x.retType, x.templates)), templates) //templateFunctions = x.functions.filter(x => isTemplateFunction(x)) //TODO MAJOR issue with where generic classes will be generated. Should be in source file but currently in current. - if(isTemplateInterface(intf)) templateInterfaces = templateInterfaces :+ intf_def + if (isTemplateInterface(intf)) templateInterfaces = templateInterfaces :+ intf_def interfaces = interfaces :+ intf val same_namespace_intf = InterfaceInfo(change_file(name), props, intf.funcs, templates) - interfaces = interfaces :+ same_namespace_intf + //interfaces = interfaces :+ same_namespace_intf - if(!isTemplateInterface(intf)) { + if (!isTemplateInterface(intf)) { val types = intf.args.map(x => Type.toLLVM(x.varType)).mkString(", ") val intf_llvm = toLLVM(intf).dropRight(1) val same_namespace_intf_llvm = toLLVM(same_namespace_intf).dropRight(1) @@ -475,7 +485,7 @@ class ToAssembly(currentFile: String) { } } funcsForImport.foreach(info=>{ - functions = functions :+ FunctionInfo(change_file(info.name), info.prefix, info.args, info.retType, info.templates) + //functions = functions :+ FunctionInfo(change_file(info.name), info.prefix, info.args, info.retType, info.templates) }) ret + funcsForImport.map(info=>{ val importName: String = fNameSignature(info) @@ -483,8 +493,8 @@ class ToAssembly(currentFile: String) { val args = info.args.map(x=>s"${Type.toLLVM(x.varType)}").mkString(", ") //s"declare ${Type.toLLVM(info.retType)} @${importName}($args)\n" + // s"@$name = ifunc ${toLLVM(info)}, ${toLLVM(info)}* @${importName}\n" - s"declare ${Type.toLLVM(info.retType)} @${importName}($args) \"${formatFName(imp.file)}\"\n"+ - s"@${name} = alias ${toLLVM(info)}, ${toLLVM(info)}* @${importName}\n" + s"declare ${Type.toLLVM(info.retType)} @${importName}($args) \"${formatFName(imp.file)}\"\n" + //s"@${name} = alias ${toLLVM(info)}, ${toLLVM(info)}* @${importName}\n" }).mkString }).mkString @@ -548,8 +558,11 @@ class ToAssembly(currentFile: String) { val fname = fNameSignature(info) val args = info.args.map(x => s"${Type.toLLVM(x.varType)} %Input.${x.name}").mkString(", ") - val addPrivate = if (info.name == "main") "" else "private "; - var ret = s"define $addPrivate${Type.toLLVM(info.retType)} @${fname}($args) #0 {\n" + val addPrivate = ""//if (info.name == file_prefix+"_main") "" else "private "; + val main_alias = s"@main = external alias i32 (), i32 ()* @${file_prefix}_main\n" + var ret = "" + if (info.name == file_prefix+"_main") ret += main_alias + ret += s"define $addPrivate${Type.toLLVM(info.retType)} @${fname}($args) #0 {\n" val newEnv = upperScope ++ info.args.map(x => (x.name, Variable(s"%${x.name}", x.varType))).toMap //Variable(s"%${x.name}.${varc.extra()}" var body = info.args.map(x => s"%${x.name} = alloca ${Type.toLLVM(x.varType)}, align 64\n" + //, align ${arraySizeFromType(x.varType)} @@ -560,7 +573,7 @@ class ToAssembly(currentFile: String) { body += convert(function.body, newEnv)._1 if (info.retType == Type.Undefined()) body += "ret void\n" - if (info.name == "main") body += "ret i32 0\n" + if (info.name == file_prefix+"_main") body += "ret i32 0\n" varc.reset() ret += body.split("\n").map(x => "\t" + x).mkString("\n") From 707ff11fa8bc44f35f0688e938494be970be37e1 Mon Sep 17 00:00:00 2001 From: Pijus Krisiukenas Date: Sun, 7 Jan 2024 01:14:30 +0200 Subject: [PATCH 099/112] template imports work, changed constructor syntax --- app/src/main/scala/posharp/Main.scala | 1 + app/src/main/scala/posharp/Parser.scala | 29 ++++++++++++++++----- app/src/main/scala/posharp/ToAssembly.scala | 8 +++--- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/app/src/main/scala/posharp/Main.scala b/app/src/main/scala/posharp/Main.scala index 1e61fb2..1a6c4ae 100644 --- a/app/src/main/scala/posharp/Main.scala +++ b/app/src/main/scala/posharp/Main.scala @@ -21,6 +21,7 @@ object Main extends App { relative_name = relative_name.replace("\\", "/") val parsed = Parser.parseInput(toCompile, relative_name.replace("/", "_")); + //println(Util.prettyPrint(parsed)) val top = parsed match { case x: Expr.TopLevel => x case _ => throw new Exception("unexpected type in top level") diff --git a/app/src/main/scala/posharp/Parser.scala b/app/src/main/scala/posharp/Parser.scala index fbd10c2..9c321fb 100644 --- a/app/src/main/scala/posharp/Parser.scala +++ b/app/src/main/scala/posharp/Parser.scala @@ -32,6 +32,15 @@ object Parser { Expr.Func(name.name, args, ret, body, templates.getOrElse(List()).asInstanceOf[List[Type.T]]) } } + def function_intf[_: P]: P[Expr.Func] = P("def " ~/ ident ~ templateTypes.? ~ "(" ~/ functionArgs ~ ")" ~/ typeDef.? ~ block).map { + case (name, templates, args, retType, body) => { + val ret = retType match { + case Some(typ) => typ + case None => Type.Undefined() + } + Expr.Func(name.name, args, ret, body, templates.getOrElse(List()).asInstanceOf[List[Type.T]]) + } + } def imports[_: P]: P[Expr.Import] = P(importPartial | importModule) def importPartial[_: P]: P[Expr.Import] = P("import " ~ ident ~ "from" ~/ fileName ~ ";").map(x=> Expr.Import(x._1.name, x._2.s)) @@ -42,7 +51,7 @@ object Parser { Expr.DefineInterface(props._1.name, props._2.toList.map(x=>InputVar(x._1.name, x._2))) ) */ - def interfaceDef[_: P]: P[Expr.DefineInterface] = P("object " ~/ mod_ident ~ templateTypes.? ~ "{" ~/ objValue ~ function.rep ~ "}").map(props => + def interfaceDef[_: P]: P[Expr.DefineInterface] = P("object " ~/ mod_ident ~ templateTypes.? ~ "{" ~/ objValue ~ function_intf.rep ~ "}").map(props => Expr.DefineInterface(props._1.name, props._3.map(x => InputVar(x.name, x.varType)), props._4.toList, props._2.getOrElse(List()).asInstanceOf[List[Type.T]])) def objValue[_: P]: P[List[ObjVal]] = P(ident ~ typeDef ~ ("=" ~ prefixExpr).? ~ ";").rep.map(x => x.map { @@ -74,12 +83,17 @@ object Parser { case (ident, None) => Expr.DefVal(ident.name, Type.Undefined()) } - def accessVar[_: P]: P[Expr] = P(ident ~ ((".".! ~ ident ~ "(".! ~ prefixExpr.rep(sep = ",") ~ ")") | (".".! ~ ident) | ("[".! ~/ prefixExpr ~ "]")).rep).map { case (start, acs) => - acs.foldLeft(start: Expr)((acc, v) => v match { + def accessVar[_: P]: P[Expr] = P(mod_ident_raw ~ ((".".! ~ ident ~ "(".! ~ prefixExpr.rep(sep = ",") ~ ")") | (".".! ~ ident) | ("[".! ~/ prefixExpr ~ "]")).rep) + .map { case (prefix, start, acs) => + acs.foldLeft(Expr.Ident(start): Expr)((acc, v) => v match { case (".", Expr.Ident(ident)) => GetProperty(acc, ident) case ("[", index: Expr) => Expr.GetArray(acc, index) //TODO template functions not handled - case (".", Expr.Ident(ident), "(", args: List[Expr]) => Expr.CallObjFunc(acc, Expr.CallF(ident, args, List())) //throw new NotImplementedException("") + case (".", Expr.Ident(ident), "(", args: List[Expr]) => { + //val callf_prefix = if(prefix.nonEmpty) prefix else file_name + //callf_prefix+"_"+ + Expr.CallObjFunc(acc, Expr.CallF(ident, args, List())) + }//throw new NotImplementedException("") case x => throw new ParseException(s"bad var access: $x"); }) } @@ -230,15 +244,16 @@ object Parser { def mod_ident[_: P]: P[Expr.Ident] = P(mod_ident_raw) .map((input) => { - val modules = input._1.toList.mkString("_") + val modules = input._1 val prefix = if (modules.nonEmpty) modules else file_name Expr.Ident(prefix + "_" + input._2) }) - def mod_ident_raw[_: P] = P((ident.! ~ "::").rep() ~ ident.!) + def mod_ident_raw[_: P]: P[(String, String)] = P((ident.! ~ "::").rep() ~ ident.!) .filter(x => !x._1.exists(y => reservedKeywords.contains(y)) && !reservedKeywords.contains(x._2)) + .map(x=> (x._1.toList.mkString("_"),x._2)) def mod_ident_no_default[_: P]: P[Expr.Ident] = P(mod_ident_raw) .map((input) => { - val modules = input._1.toList.mkString("_") + "_" + val modules = input._1 + "_" val prefix = if (modules.length > 1) modules else "" Expr.Ident(prefix + input._2) }) diff --git a/app/src/main/scala/posharp/ToAssembly.scala b/app/src/main/scala/posharp/ToAssembly.scala index 9a158e5..b4e08d3 100644 --- a/app/src/main/scala/posharp/ToAssembly.scala +++ b/app/src/main/scala/posharp/ToAssembly.scala @@ -235,8 +235,9 @@ class ToAssembly(currentFile: String) { val alocLoc = varc.last(); val valuesCompiled = values.map(x => convertLoc(x, env)).map(x => Expr.Compiled(x._1, x._2, x._3)) - intf.funcs.find(x => x.name == name && x.args == values) - val func_code = interpFunction(name + "_" + name, Expr.Compiled(aloc, UserType(name, templates), alocLoc) +: valuesCompiled, List(), env)._1 + //intf.funcs.find(x => x.name == name && x.args == values) + intf.funcs.find(x => x.name == "Constructor" && x.args == values) + val func_code = interpFunction(name + "_" + "Constructor", Expr.Compiled(aloc, UserType(name, templates), alocLoc) +: valuesCompiled, List(), env)._1 (func_code, Type.Interface(name, intf.args, intf.funcs, intf.templates)) } case None => throw new Exception(s"no interface with name \"$name\" defined") @@ -587,7 +588,6 @@ class ToAssembly(currentFile: String) { val argInputTypes = argRet.map(x => makeUserTypesConcrete(x._2)) var ret = argRet.map(x=>x._1).mkString val argsString = argRet.map(x=>s"${Type.toLLVM(x._2)} ${x._3}").mkString(", ") - functions.find(x=>x.name == name) match { case Some(x) => ; case None => throw new Exception(s"function of name $name undefined"); } @@ -614,7 +614,7 @@ class ToAssembly(currentFile: String) { } //functions.find(x=>x.name == name && Type.compare(argInputTypes, x.args.map(x=>makeUserTypesConcrete(x.varType)))) match { - println(Util.prettyPrint(functions)) + //println(Util.prettyPrint(functions)) functions.find(x=>x.name == name && argInputTypes == x.args.map(x=>makeUserTypesConcrete(x.varType))) match { case Some(info@FunctionInfo(p, prefix, argTypes, retType, templates)) => { From 65931735bcd958104bfca0256cd0c1697143623f Mon Sep 17 00:00:00 2001 From: Pijus Krisiukenas Date: Thu, 11 Jan 2024 00:35:56 +0200 Subject: [PATCH 100/112] fix var access parsing --- app/src/main/scala/posharp/Parser.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/scala/posharp/Parser.scala b/app/src/main/scala/posharp/Parser.scala index 9c321fb..5aaf0f1 100644 --- a/app/src/main/scala/posharp/Parser.scala +++ b/app/src/main/scala/posharp/Parser.scala @@ -83,9 +83,9 @@ object Parser { case (ident, None) => Expr.DefVal(ident.name, Type.Undefined()) } - def accessVar[_: P]: P[Expr] = P(mod_ident_raw ~ ((".".! ~ ident ~ "(".! ~ prefixExpr.rep(sep = ",") ~ ")") | (".".! ~ ident) | ("[".! ~/ prefixExpr ~ "]")).rep) - .map { case (prefix, start, acs) => - acs.foldLeft(Expr.Ident(start): Expr)((acc, v) => v match { + def accessVar[_: P]: P[Expr] = P(ident ~ ((".".! ~ ident ~ "(".! ~ prefixExpr.rep(sep = ",") ~ ")") | (".".! ~ ident) | ("[".! ~/ prefixExpr ~ "]")).rep) + .map { case (start, acs) => + acs.foldLeft(start: Expr)((acc, v) => v match { case (".", Expr.Ident(ident)) => GetProperty(acc, ident) case ("[", index: Expr) => Expr.GetArray(acc, index) //TODO template functions not handled From d6f0af3a4f36429f8fc874a1474a8918540f854c Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Thu, 11 Jan 2024 16:29:44 +0100 Subject: [PATCH 101/112] Remove `scala-reflect` --- app/src/main/scala/posharp/ToAssembly.scala | 30 +++++---------------- veritas/build.gradle.kts | 1 - veritas/src/main/scala/core/Veritas.scala | 15 ++++++----- 3 files changed, 15 insertions(+), 31 deletions(-) diff --git a/app/src/main/scala/posharp/ToAssembly.scala b/app/src/main/scala/posharp/ToAssembly.scala index b4e08d3..4ecd70e 100644 --- a/app/src/main/scala/posharp/ToAssembly.scala +++ b/app/src/main/scala/posharp/ToAssembly.scala @@ -6,8 +6,6 @@ import sourcecode.Text.generate import scala.io.AnsiColor import scala.language.postfixOps -import scala.reflect.runtime.currentMirror -import scala.reflect.runtime.universe.termNames class ToAssembly(currentFile: String) { type Env = Map[String, Variable] @@ -913,28 +911,14 @@ class ToAssembly(currentFile: String) { } def applyFunctionToUnknownCaseClass(instance: Expr, func: Any => Any): Expr = { - val mirror = currentMirror - val instanceMirror = mirror.reflect(instance) - val classSymbol = instanceMirror.symbol - - if (!classSymbol.isClass || !classSymbol.asClass.isCaseClass) { - throw new IllegalArgumentException("The provided instance is not a case class.") - } - - val classType = instanceMirror.symbol.typeSignature - val constructorSymbol = classType.decl(termNames.CONSTRUCTOR.asInstanceOf[scala.reflect.runtime.universe.Name]).asMethod - val constructorMirror = mirror.reflectClass(classSymbol.asClass).reflectConstructor(constructorSymbol) - - // Collecting parameters from the constructor - val params = constructorSymbol.paramLists.flatten - - val fieldValues = params.map { param => - val fieldTerm = classType.decl(param.name).asTerm.accessed.asTerm - val fieldValue = instanceMirror.reflectField(fieldTerm).get - func(fieldValue) - } + val _class = instance.getClass + val fields = _class.getDeclaredFields + .map(f => { + f.setAccessible(true) + func(f.get(instance)) + }) - constructorMirror(fieldValues: _*).asInstanceOf[Expr] + _class.getConstructors()(0).newInstance(fields:_*).asInstanceOf[Expr] } def replaceWithMappingFunc(template_mappings: List[(Type, Type)]): Type => Type = { diff --git a/veritas/build.gradle.kts b/veritas/build.gradle.kts index 3db55f8..dedc588 100644 --- a/veritas/build.gradle.kts +++ b/veritas/build.gradle.kts @@ -10,7 +10,6 @@ repositories { dependencies { implementation("org.scala-lang:scala-library:2.13.10") implementation("org.reflections:reflections:0.10.2") - implementation("org.scala-lang:scala-reflect:2.13.10") implementation(project(":app")) } diff --git a/veritas/src/main/scala/core/Veritas.scala b/veritas/src/main/scala/core/Veritas.scala index 47b0d58..48954ee 100644 --- a/veritas/src/main/scala/core/Veritas.scala +++ b/veritas/src/main/scala/core/Veritas.scala @@ -10,7 +10,6 @@ import java.lang.reflect.Method import java.util.concurrent.{Executors, TimeUnit} import scala.collection.mutable import scala.io.AnsiColor._ -import scala.reflect.internal.util.ScalaClassLoader import scala.util.{Failure, Success, Try} object Veritas { @@ -93,7 +92,8 @@ object Veritas { // Get the class and instantiate it res.foreach(c => { - val testClass = ScalaClassLoader(getClass.getClassLoader).tryToInitializeClass(c) + val testClass = Class.forName(c).getConstructor().newInstance().getClass + var lastMethodName = "" def runTest(instance: AnyRef, tests: Array[Method]): Unit = { @@ -130,11 +130,10 @@ object Veritas { } try { - val instance = ScalaClassLoader(getClass.getClassLoader).create(c) + val instance = Class.forName(c).getConstructor().newInstance().asInstanceOf[AnyRef] // Run all tests in the class - testClass.get.getMethods.filter(m => - m.getName.toLowerCase().contains("test")) // Filter out non-test methods + testClass.getMethods.filter(m => m.getName.toLowerCase().contains("test")) // Filter out non-test methods .grouped(chunkSize) // Group in chunks .foreach(chunk => { pool.execute(() => runTest(instance, chunk)) @@ -167,12 +166,14 @@ object Veritas { */ def Compile(input: String): Try[String] = { try { - val parsed = Parser.parseInput(input) + val parsed = Parser.parseInput(input, "file_name") + val something = new ToAssembly("file_name") + something.declarationPass(parsed) if (calculateCoverage) cov.AddCoverage(parsed) - this.synchronized(Success(ToAssembly.convertMain(parsed, "", Map[String, Expr.TopLevel]()))) + this.synchronized(Success(something.convertMain(parsed, Map[String, Expr.TopLevel]()))) } catch { case e: Exception => Failure(e) } From 575c5a6f3aa6598aa1b99e0452f90281018f59b4 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Thu, 11 Jan 2024 16:29:52 +0100 Subject: [PATCH 102/112] Fix outdated test code --- veritas/src/main/scala/test/TestExample.scala | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/veritas/src/main/scala/test/TestExample.scala b/veritas/src/main/scala/test/TestExample.scala index 5590867..fe1ba83 100644 --- a/veritas/src/main/scala/test/TestExample.scala +++ b/veritas/src/main/scala/test/TestExample.scala @@ -77,7 +77,7 @@ class TestExample { def runTestGenericInterface1(): (Boolean, String) = """object Test[T1] { | toPrint: T1; - | def Test(self: Test[T1], toPrint: T1): Test[T1] { + | def Constructor(self: Test[T1], toPrint: T1): Test[T1] { | self.toPrint = toPrint; | return self; | } @@ -105,14 +105,14 @@ class TestExample { allocated: int; arr: array[char]; - def Dynamic(self: Dynamic): Dynamic { + def Constructor(self: Dynamic): Dynamic { self.arr = array[char][8]; self.allocated = 8; self.size = 0; return self; } - def Dynamic(self: Dynamic, arr: array[char]): Dynamic { - self.Dynamic(); + def Constructor(self: Dynamic, arr: array[char]): Dynamic { + self.Constructor(); self.push(arr); return self; } @@ -225,14 +225,14 @@ object Dynamic[T1] { allocated: int; arr: array[T1]; - def Dynamic(self: Dynamic[T1]): Dynamic[T1] { + def Constructor(self: Dynamic[T1]): Dynamic[T1] { self.arr = array[T1][8]; self.allocated = 8; self.size = 0; return self; } - def Dynamic(self: Dynamic[T1], arr: array[T1]): Dynamic[T1] { - self.Dynamic(); + def Constructor(self: Dynamic[T1], arr: array[T1]): Dynamic[T1] { + self.Constructor(); self.push(arr); return self; } From b73e368c3585569e64caf9962d2cbf174301da34 Mon Sep 17 00:00:00 2001 From: Pijus Krisiukenas Date: Thu, 11 Jan 2024 18:38:33 +0200 Subject: [PATCH 103/112] migrated to scala 3 --- app/build.gradle.kts | 5 +- app/src/main/scala/posharp/Main.scala | 137 ++++++++++--------- app/src/main/scala/posharp/Parser.scala | 138 ++++++++++---------- app/src/main/scala/posharp/ToAssembly.scala | 110 +++++++++++----- build.gradle.kts | 5 +- 5 files changed, 220 insertions(+), 175 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 68d92df..a3ea035 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -8,8 +8,9 @@ repositories { } dependencies { - implementation("org.scala-lang:scala-library:2.13.10") - implementation("com.lihaoyi:fastparse_2.13:2.3.3") + implementation("org.scala-lang:scala3-library_3:3.3.1") + implementation("com.lihaoyi:fastparse_3:3.0.2") + implementation("co.blocke:scala-reflection_3:2.0.0") } application { diff --git a/app/src/main/scala/posharp/Main.scala b/app/src/main/scala/posharp/Main.scala index 1a6c4ae..8f19d52 100644 --- a/app/src/main/scala/posharp/Main.scala +++ b/app/src/main/scala/posharp/Main.scala @@ -6,86 +6,81 @@ import scala.io.{AnsiColor, Source} object Constants { val FileExtension = ".txt" } - -object Main extends App { - var sourceDir = "po_src" - - if (args.length > 0) { - sourceDir = args(0) +//input_sourceDir: Option[String] +@main def CompileMain(): Unit = { + val sourceDir = "po_src"//input_sourceDir.getOrElse("po_src") + + val files = recursiveListFiles(new File(sourceDir), "ignore").toList.filter(x => x.getName.contains(Constants.FileExtension)) + val sourceDirPath = Paths.get(sourceDir) + val declarations: Map[String, (ToAssembly, Expr.TopLevel)] = files.map(file => { + val toCompile = readFile(file) + var relative_name = sourceDirPath.relativize(file.toPath).toFile.getPath.split(Constants.FileExtension)(0) + relative_name = relative_name.replace("\\", "/") + + val parsed = Parser.parseInput(toCompile, relative_name.replace("/", "_")); + //println(Util.prettyPrint(parsed)) + val top = parsed match { + case x: Expr.TopLevel => x + case _ => throw new Exception("unexpected type in top level") } - val files = recursiveListFiles(new File(sourceDir), "ignore").toList.filter(x => x.getName.contains(Constants.FileExtension)) - val sourceDirPath = Paths.get(sourceDir) - val declarations: Map[String, (ToAssembly, Expr.TopLevel)] = files.map(file => { - val toCompile = readFile(file) - var relative_name = sourceDirPath.relativize(file.toPath).toFile.getPath.split(Constants.FileExtension)(0) - relative_name = relative_name.replace("\\", "/") - - val parsed = Parser.parseInput(toCompile, relative_name.replace("/", "_")); - //println(Util.prettyPrint(parsed)) - val top = parsed match { - case x: Expr.TopLevel => x - case _ => throw new Exception("unexpected type in top level") - } - - (relative_name -> (new ToAssembly(relative_name), top)) - }).toMap - //declaration step - declarations.foreach(x => { - val code = x._2._2 - val converter = x._2._1 - converter.declarationPass(code) - }) - declarations.foreach(x => { - val file = x._1 - val code = x._2._2 - val converter = x._2._1 - var asm = ""; - try { - asm = converter.convertMain(code, declarations.map(x=>x._1->x._2._2).filter(x => x._1 != file)); - //asm += StringCode.stringCode; - } - catch { - case x: Exception => { - println(AnsiColor.RED + s"Compilation exception in \"$file\": ${x.getMessage} ${x.getStackTrace.mkString("\n")}" + AnsiColor.RESET); - sys.exit(1); - } + (relative_name -> (new ToAssembly(relative_name), top)) + }).toMap + //declaration step + declarations.foreach(x => { + val code = x._2._2 + val converter = x._2._1 + converter.declarationPass(code) + }) + declarations.foreach(x => { + val file = x._1 + val code = x._2._2 + val converter = x._2._1 + var asm = ""; + + try { + asm = converter.convertMain(code, declarations.map(x => x._1 -> x._2._2).filter(x => x._1 != file)); + //asm += StringCode.stringCode; + } + catch { + case x: Exception => { + println(AnsiColor.RED + s"Compilation exception in \"$file\": ${x.getMessage} ${x.getStackTrace.mkString("\n")}" + AnsiColor.RESET); + sys.exit(1); } + } - println("") - writeCompiled(asm, "compiled/", file) - }) - def writeCompiled(asm: String, directoryPath: String, file: String): Unit = { - val flatFile = file.split("/").last + ".ll" - writeToFile(asm, directoryPath, flatFile) - } - - def writeToFile(input: String, directoryPath: String, filename: String): Unit = { - val directory = new File(directoryPath); - if (!directory.exists()) directory.mkdir(); + println("") + writeCompiled(asm, "compiled/", file) + }) +} +def writeCompiled(asm: String, directoryPath: String, file: String): Unit = { + val flatFile = file.split("/").last + ".ll" + writeToFile(asm, directoryPath, flatFile) +} - val fileWriter = new FileWriter(new File(directoryPath+filename)) - fileWriter.write(input) - fileWriter.close() - } - def readFile(src: File): String = { - val source = Source.fromFile(src) - val codetxt = source.mkString - source.close() - codetxt - } +def writeToFile(input: String, directoryPath: String, filename: String): Unit = { + val directory = new File(directoryPath); + if (!directory.exists()) directory.mkdir(); - def recursiveListFiles(f: File, ignore: String): Array[File] = { - if (!f.exists()) { - return Array() - } + val fileWriter = new FileWriter(new File(directoryPath + filename)) + fileWriter.write(input) + fileWriter.close() +} +def readFile(src: File): String = { + val source = Source.fromFile(src) + val codetxt = source.mkString + source.close() + codetxt +} - if(f.isFile) return Array(f) - val these = f.listFiles - these ++ these.filter(x => x.isDirectory && x.getName != ignore).flatMap(x => recursiveListFiles(x, ignore)) +def recursiveListFiles(f: File, ignore: String): Array[File] = { + if (!f.exists()) { + return Array() } - + if (f.isFile) return Array(f) + val these = f.listFiles + these ++ these.filter(x => x.isDirectory && x.getName != ignore).flatMap(x => recursiveListFiles(x, ignore)) } object StringCode { diff --git a/app/src/main/scala/posharp/Parser.scala b/app/src/main/scala/posharp/Parser.scala index 5aaf0f1..bfd4d9d 100644 --- a/app/src/main/scala/posharp/Parser.scala +++ b/app/src/main/scala/posharp/Parser.scala @@ -9,21 +9,21 @@ import scala.compat.Platform.EOL object Parser { //TODO fix issue when spacing at start of file - def topLevel[_: P]: P[Expr.TopLevel] = P(StringIn(" ").? ~ (function | interfaceDef | enumDef | imports).rep(1)).map(x => { + def topLevel[$: P]: P[Expr.TopLevel] = P(StringIn(" ").? ~ (function | interfaceDef | enumDef | imports).rep(1)).map(x => { var func: List[Expr.Func] = List() var intf: List[Expr.DefineInterface] = List() - var enum: List[Expr.DefineEnum] = List() + var enumm: List[Expr.DefineEnum] = List() var imports: List[Expr.Import] = List() x.foreach { case y@Expr.Func(a, b, c, d, e) => func = func :+ y case y@Expr.DefineInterface(a, b, c, d) => intf = intf :+ y - case y@Expr.DefineEnum(a, b) => enum = enum :+ y + case y@Expr.DefineEnum(a, b) => enumm = enumm :+ y case y@Expr.Import(a, b) => imports = imports :+ y } - Expr.TopLevel(func, intf, enum, imports) + Expr.TopLevel(func, intf, enumm, imports) }) - def function[_: P]: P[Expr.Func] = P("def " ~/ mod_ident ~ templateTypes.? ~ "(" ~/ functionArgs ~ ")" ~/ typeDef.? ~ block).map { + def function[$: P]: P[Expr.Func] = P("def " ~/ mod_ident ~ templateTypes.? ~ "(" ~/ functionArgs ~ ")" ~/ typeDef.? ~ block).map { case (name, templates, args, retType, body) => { val ret = retType match { case Some(typ) => typ @@ -32,7 +32,7 @@ object Parser { Expr.Func(name.name, args, ret, body, templates.getOrElse(List()).asInstanceOf[List[Type.T]]) } } - def function_intf[_: P]: P[Expr.Func] = P("def " ~/ ident ~ templateTypes.? ~ "(" ~/ functionArgs ~ ")" ~/ typeDef.? ~ block).map { + def function_intf[$: P]: P[Expr.Func] = P("def " ~/ ident ~ templateTypes.? ~ "(" ~/ functionArgs ~ ")" ~/ typeDef.? ~ block).map { case (name, templates, args, retType, body) => { val ret = retType match { case Some(typ) => typ @@ -42,48 +42,48 @@ object Parser { } } - def imports[_: P]: P[Expr.Import] = P(importPartial | importModule) - def importPartial[_: P]: P[Expr.Import] = P("import " ~ ident ~ "from" ~/ fileName ~ ";").map(x=> Expr.Import(x._1.name, x._2.s)) - def importModule[_: P]: P[Expr.Import] = P("import " ~/ fileName ~ ";").map(x=> Expr.Import("__module__", x.s)) + def imports[$: P]: P[Expr.Import] = P(importPartial | importModule) + def importPartial[$: P]: P[Expr.Import] = P("import " ~ ident ~ "from" ~/ fileName ~ ";").map(x=> Expr.Import(x._1.name, x._2.s)) + def importModule[$: P]: P[Expr.Import] = P("import " ~/ fileName ~ ";").map(x=> Expr.Import("__module__", x.s)) /* - def interfaceDef[_: P]: P[Expr.DefineInterface] = P("interface " ~ ident ~/ "{" ~ (ident ~ typeDef).rep(sep = ",") ~ "}").map(props=> + def interfaceDef[$: P]: P[Expr.DefineInterface] = P("interface " ~ ident ~/ "{" ~ (ident ~ typeDef).rep(sep = ",") ~ "}").map(props=> Expr.DefineInterface(props._1.name, props._2.toList.map(x=>InputVar(x._1.name, x._2))) ) */ - def interfaceDef[_: P]: P[Expr.DefineInterface] = P("object " ~/ mod_ident ~ templateTypes.? ~ "{" ~/ objValue ~ function_intf.rep ~ "}").map(props => + def interfaceDef[$: P]: P[Expr.DefineInterface] = P("object " ~/ mod_ident ~ templateTypes.? ~ "{" ~/ objValue ~ function_intf.rep ~ "}").map(props => Expr.DefineInterface(props._1.name, props._3.map(x => InputVar(x.name, x.varType)), props._4.toList, props._2.getOrElse(List()).asInstanceOf[List[Type.T]])) - def objValue[_: P]: P[List[ObjVal]] = P(ident ~ typeDef ~ ("=" ~ prefixExpr).? ~ ";").rep.map(x => x.map { + def objValue[$: P]: P[List[ObjVal]] = P(ident ~ typeDef ~ ("=" ~ prefixExpr).? ~ ";").rep.map(x => x.map { case (id, valtype, Some(value)) => ObjVal(id.name, valtype, value) case (id, valtype, None) => ObjVal(id.name, valtype, Type.defaultValue(valtype)) }.toList) - def enumDef[_: P]: P[Expr.DefineEnum] = P("enum " ~ mod_ident ~/ "{" ~ ident.rep(sep = ",") ~ "}").map(props => + def enumDef[$: P]: P[Expr.DefineEnum] = P("enum " ~ mod_ident ~/ "{" ~ ident.rep(sep = ",") ~ "}").map(props => Expr.DefineEnum(props._1.name, props._2.toList.map(x => x.name)) ) - def functionArgs[_: P]: P[List[InputVar]] = P((ident ~ typeDef).rep(sep = ",")).map(x => x.map(y => InputVar(y._1.name, y._2)).toList) + def functionArgs[$: P]: P[List[InputVar]] = P((ident ~ typeDef).rep(sep = ",")).map(x => x.map(y => InputVar(y._1.name, y._2)).toList) - //def parseType[_: P] : P[Type] = P(ident ~ "(" ~/ ")") - def block[_: P] = P("{" ~/ line.rep(0) ~ "}").map(lines => lines.foldLeft(List(): List[Expr])((acc, el) => el match { + //def parseType[$: P] : P[Type] = P(ident ~ "(" ~/ ")") + def block[$: P] = P("{" ~/ line.rep(0) ~ "}").map(lines => lines.foldLeft(List(): List[Expr])((acc, el) => el match { case Expr.ExtendBlock(sub) => acc ::: sub; case _ => acc :+ el; })).map(x => Expr.Block(x)) - def line[_: P]: P[Expr] = P(expr ~/ ";") + def line[$: P]: P[Expr] = P(expr ~/ ";") - def expr[_: P]: P[Expr] = P(arrayDef | arrayDefDefault | defAndSetVal | defVal | NoCut(setVar) | callFuncInLine | retFunction | IfOp | whileLoop | forLoop | print | free | callFunction | throwException) + def expr[$: P]: P[Expr] = P(arrayDef | arrayDefDefault | defAndSetVal | defVal | NoCut(setVar) | callFuncInLine | retFunction | IfOp | whileLoop | forLoop | print | free | callFunction | throwException) - def prefixExpr[_: P]: P[Expr] = P( NoCut(callFunction) | defineLambda | NoCut(convert) | NoCut(parens) | NoCut(condition) | arrayDef | arrayDefDefault | instanceInterface | accessVar | NoCut(getArraySize) | + def prefixExpr[$: P]: P[Expr] = P( NoCut(callFunction) | defineLambda | NoCut(convert) | NoCut(parens) | NoCut(condition) | arrayDef | arrayDefDefault | instanceInterface | accessVar | NoCut(getArraySize) | numberFloat | number | ident | constant | str | char | trueC | falseC) - def defVal[_: P]: P[Expr.DefVal] = P("val " ~/ ident ~ typeDef.?).map { + def defVal[$: P]: P[Expr.DefVal] = P("val " ~/ ident ~ typeDef.?).map { case (ident, Some(varType)) => Expr.DefVal(ident.name, varType) case (ident, None) => Expr.DefVal(ident.name, Type.Undefined()) } - def accessVar[_: P]: P[Expr] = P(ident ~ ((".".! ~ ident ~ "(".! ~ prefixExpr.rep(sep = ",") ~ ")") | (".".! ~ ident) | ("[".! ~/ prefixExpr ~ "]")).rep) + def accessVar[$: P]: P[Expr] = P(ident ~ ((".".! ~ ident ~ "(".! ~ prefixExpr.rep(sep = ",") ~ ")") | (".".! ~ ident) | ("[".! ~/ prefixExpr ~ "]")).rep) .map { case (start, acs) => acs.foldLeft(start: Expr)((acc, v) => v match { case (".", Expr.Ident(ident)) => GetProperty(acc, ident) @@ -98,12 +98,12 @@ object Parser { }) } - def callFuncInLine[_: P]: P[Expr] = P(accessVar).filter { + def callFuncInLine[$: P]: P[Expr] = P(accessVar).filter { case _: Expr.CallObjFunc => true case x => false } - def setVar[_: P]: P[Expr] = P(accessVar ~ StringIn("+=", "-=", "*=", "/=", "=").! ~/ prefixExpr ~ &(";")).map { + def setVar[$: P]: P[Expr] = P(accessVar ~ StringIn("+=", "-=", "*=", "/=", "=").! ~/ prefixExpr ~ &(";")).map { case (variable, op, value) => { val opType = op match { case "=" => value @@ -122,9 +122,9 @@ object Parser { } } - def defAndSetVal[_: P] = P(defVal ~ "=" ~/ prefixExpr).map(x => Expr.DefValWithValue(x._1.variable, x._1.varType, x._2)) + def defAndSetVal[$: P] = P(defVal ~ "=" ~/ prefixExpr).map(x => Expr.DefValWithValue(x._1.variable, x._1.varType, x._2)) - def defineLambda[_: P]: P[Expr.Lambda] = P("lambda" ~/ "(" ~ (ident ~ typeDef).rep(sep=",") ~ ")" ~ typeDef ~ "=>" ~/ (block | prefixExpr) ).map{ case (args, ret, body) => { + def defineLambda[$: P]: P[Expr.Lambda] = P("lambda" ~/ "(" ~ (ident ~ typeDef).rep(sep=",") ~ ")" ~ typeDef ~ "=>" ~/ (block | prefixExpr) ).map{ case (args, ret, body) => { val argsf = args.map(x=>InputVar(x._1.name, x._2)).toList body match { case b@Expr.Block(_) => Expr.Lambda(argsf, ret, b) @@ -132,31 +132,31 @@ object Parser { } }} - def arrayDef[_: P]: P[Expr.DefineArray] = P("array" ~ "[" ~/ typeBase ~ "]" ~ "[" ~/ prefixExpr ~ "]").map(x => Expr.DefineArray(x._2, x._1, List())) - def arrayDefDefault[_: P]: P[Expr.DefineArray] = P("array" ~ "(" ~/ prefixExpr.rep(sep = ",") ~ ")").map(x => Expr.DefineArray(Expr.Num(x.size), Type.Undefined(), x.toList)) + def arrayDef[$: P]: P[Expr.DefineArray] = P("array" ~ "[" ~/ typeBase ~ "]" ~ "[" ~/ prefixExpr ~ "]").map(x => Expr.DefineArray(x._2, x._1, List())) + def arrayDefDefault[$: P]: P[Expr.DefineArray] = P("array" ~ "(" ~/ prefixExpr.rep(sep = ",") ~ ")").map(x => Expr.DefineArray(Expr.Num(x.size), Type.Undefined(), x.toList)) /* - def getArray[_: P]: P[Expr.GetArray] = P(returnsArray ~ "[" ~/ prefixExpr ~ "]").map((x) => Expr.GetArray(x._1, x._2)) - def setArray[_: P]: P[Expr.SetArray] = P(returnsArray ~ "[" ~/ prefixExpr ~ "]" ~/ "=" ~ prefixExpr).map((x) => Expr.SetArray(x._1, x._2, x._3)) + def getArray[$: P]: P[Expr.GetArray] = P(returnsArray ~ "[" ~/ prefixExpr ~ "]").map((x) => Expr.GetArray(x._1, x._2)) + def setArray[$: P]: P[Expr.SetArray] = P(returnsArray ~ "[" ~/ prefixExpr ~ "]" ~/ "=" ~ prefixExpr).map((x) => Expr.SetArray(x._1, x._2, x._3)) */ - def returnsArray[_: P]: P[Expr] = P(ident) + def returnsArray[$: P]: P[Expr] = P(ident) - def getArraySize[_: P]: P[Expr.ArraySize] = P(returnsArray ~~ ".size").map((x) => Expr.ArraySize(x)) + def getArraySize[$: P]: P[Expr.ArraySize] = P(returnsArray ~~ ".size").map((x) => Expr.ArraySize(x)) - def instanceInterface[_: P]: P[Expr.InstantiateInterface] = P("new " ~/ mod_ident ~ templateTypes.? ~ "(" ~/ prefixExpr.rep(sep = ",") ~ ")") + def instanceInterface[$: P]: P[Expr.InstantiateInterface] = P("new " ~/ mod_ident ~ templateTypes.? ~ "(" ~/ prefixExpr.rep(sep = ",") ~ ")") .map(x => Expr.InstantiateInterface(x._1.name, x._3.toList, x._2.getOrElse(List()))) - def typeDef[_: P]: P[Type] = P(":" ~/ (typeBase | typeArray | typeFunc | typeUser)) - def typeDefNoCol[_: P]: P[Type] = P(typeBase | typeArray | typeFunc | typeUser) + def typeDef[$: P]: P[Type] = P(":" ~/ (typeBase | typeArray | typeFunc | typeUser)) + def typeDefNoCol[$: P]: P[Type] = P(typeBase | typeArray | typeFunc | typeUser) - def typeArray[_: P]: P[Type] = P("array" ~/ "[" ~ typeDefNoCol ~ "]").map(x => Type.Array(x)) - def typeFunc[_: P]: P[Type] = P("func" ~/ "[" ~ "(" ~ typeDefNoCol.rep(sep=",") ~ ")" ~/ "=>" ~ typeDefNoCol ~ "]").map(x => Type.Function(x._1.toList, x._2)) + def typeArray[$: P]: P[Type] = P("array" ~/ "[" ~ typeDefNoCol ~ "]").map(x => Type.Array(x)) + def typeFunc[$: P]: P[Type] = P("func" ~/ "[" ~ "(" ~ typeDefNoCol.rep(sep=",") ~ ")" ~/ "=>" ~ typeDefNoCol ~ "]").map(x => Type.Function(x._1.toList, x._2)) - def typeUser[_: P]: P[Type] = P(mod_ident ~ templateTypes.?).map(x => Type.UserType(x._1.name, x._2.getOrElse(List()))) + def typeUser[$: P]: P[Type] = P(mod_ident ~ templateTypes.?).map(x => Type.UserType(x._1.name, x._2.getOrElse(List()))) - def typeBase[_: P]: P[Type] = P((StringIn("int", "char", "float", "bool", "string", "void").!) | ("T" ~ CharsWhileIn("0-9", 1)).!).map { + def typeBase[$: P]: P[Type] = P((StringIn("int", "char", "float", "bool", "string", "void").!) | ("T" ~ CharsWhileIn("0-9", 1)).!).map { case "int" => Type.Num(); case "char" => Type.Character(); case "float" => Type.NumFloat(); @@ -167,23 +167,23 @@ object Parser { case "T2" => Type.T(2); } - def parens[_: P] = P("(" ~/ (binOp | prefixExpr) ~ ")") + def parens[$: P] = P("(" ~/ (binOp | prefixExpr) ~ ")") - def convert[_: P]: P[Expr.Convert] = P("(" ~ (binOp | prefixExpr) ~ ")" ~ "." ~/ StringIn("toInt", "toFloat", "toChar").!).map { + def convert[$: P]: P[Expr.Convert] = P("(" ~ (binOp | prefixExpr) ~ ")" ~ "." ~/ StringIn("toInt", "toFloat", "toChar").!).map { case (value, "toInt") => Expr.Convert(value, Type.Num()) case (value, "toFloat") => Expr.Convert(value, Type.NumFloat()) case (value, "toChar") => Expr.Convert(value, Type.Character()) } - def callFunction[_: P]: P[Expr.CallF] = P(mod_ident ~ templateTypes.? ~ "(" ~/ prefixExpr.rep(sep = ",") ~/ ")").map { + def callFunction[$: P]: P[Expr.CallF] = P(mod_ident ~ templateTypes.? ~ "(" ~/ prefixExpr.rep(sep = ",") ~/ ")").map { case (name, templates, args) => Expr.CallF(name.name, args.toList, templates.getOrElse(List())); }//.filter((x) => !reservedKeywords.contains(x.name)) - def templateTypes[_: P]: P[List[Type]] = (P("[" ~/ typeDefNoCol.rep(min=1, sep = ",") ~/ "]")).map(x=>x.toList) + def templateTypes[$: P]: P[List[Type]] = (P("[" ~/ typeDefNoCol.rep(min=1, sep = ",") ~/ "]")).map(x=>x.toList) - def retFunction[_: P]: P[Expr.Return] = P("return" ~/ prefixExpr.?).map(Expr.Return) + def retFunction[$: P]: P[Expr.Return] = P("return" ~/ prefixExpr.?).map(Expr.Return) - def binOp[_: P] = P(prefixExpr ~ (StringIn("+", "-", "*", "/", "++").! ~/ prefixExpr).rep(1)).map(list => parseBinOpList(list._1, list._2.toList)) + def binOp[$: P] = P(prefixExpr ~ (StringIn("+", "-", "*", "/", "++").! ~/ prefixExpr).rep(1)).map(list => parseBinOpList(list._1, list._2.toList)) def parseBinOpList(initial: Expr, rest: List[(String, Expr)]): Expr = { rest.foldLeft(initial) { @@ -197,19 +197,19 @@ object Parser { } } - def IfOp[_: P]: P[Expr.If] = P("if" ~/ "(" ~ conditionNoParen ~ ")" ~/ block ~/ elseOp.?).map { + def IfOp[$: P]: P[Expr.If] = P("if" ~/ "(" ~ conditionNoParen ~ ")" ~/ block ~/ elseOp.?).map { case (cond, ex_true, Some(ex_else)) => Expr.If(cond, ex_true, ex_else); case (cond, ex_true, None) => Expr.If(cond, ex_true, Expr.Block(List())); } - def condition[_: P]: P[Expr] = P(("(" ~/ conditionNoParen ~ ")") | returnsBool) + def condition[$: P]: P[Expr] = P(("(" ~/ conditionNoParen ~ ")") | returnsBool) - def conditionNoParen[_: P]: P[Expr] = P(negate | condOp | conditionBin | constant | returnsBool | condition) - def returnsBool[_: P]: P[Expr] = P(NoCut(convert) | NoCut(parens) | accessVar | callFunction | ident | constant) + def conditionNoParen[$: P]: P[Expr] = P(negate | condOp | conditionBin | constant | returnsBool | condition) + def returnsBool[$: P]: P[Expr] = P(NoCut(convert) | NoCut(parens) | accessVar | callFunction | ident | constant) - def negate[_: P]: P[Expr.Not] = P("!" ~/ condition).map(Expr.Not) + def negate[$: P]: P[Expr.Not] = P("!" ~/ condition).map(Expr.Not) - def conditionBin[_: P]: P[Expr] = P(prefixExpr ~ StringIn("==", "!=", ">", "<", "<=", ">=").! ~/ prefixExpr).map { + def conditionBin[$: P]: P[Expr] = P(prefixExpr ~ StringIn("==", "!=", ">", "<", "<=", ">=").! ~/ prefixExpr).map { case (left, operator, right) => operator match { case "==" => Expr.Equals(left, right) case "!=" => Expr.Not(Expr.Equals(left, right)) @@ -220,64 +220,64 @@ object Parser { } } - def condOp[_: P]: P[Expr] = P(condition ~ ("&&" | "||").! ~/ condition ~ (("&&" | "||") ~/ condition).rep).map { + def condOp[$: P]: P[Expr] = P(condition ~ ("&&" | "||").! ~/ condition ~ (("&&" | "||") ~/ condition).rep).map { case (first, "&&", second, rest) => Expr.And(List(first, second) ::: rest.toList) case (first, "||", second, rest) => Expr.Or(List(first, second) ::: rest.toList) } - def elseOp[_: P] = P("else" ~/ block) + def elseOp[$: P] = P("else" ~/ block) - def whileLoop[_: P]: P[Expr.While] = P("while" ~/ condition ~ block).map((input) => Expr.While(input._1, input._2)) + def whileLoop[$: P]: P[Expr.While] = P("while" ~/ condition ~ block).map((input) => Expr.While(input._1, input._2)) - def forLoop[_: P]: P[Expr.Block] = P("for" ~/ "(" ~ line ~ conditionNoParen ~ ";" ~ line ~ ")" ~/ block).map((input) => { + def forLoop[$: P]: P[Expr.Block] = P("for" ~/ "(" ~ line ~ conditionNoParen ~ ";" ~ line ~ ")" ~/ block).map((input) => { Expr.Block(List(input._1, Expr.While(input._2, Expr.Block(input._4.lines :+ input._3)))); }) - def str[_: P]: P[Expr.Str] = P("\"" ~~/ CharsWhile(_ != '"', 0).! ~~ "\"").map(x => Expr.Str(x.replace("\\n", ""+10.toChar) + "\u0000")) - def fileName[_: P]: P[Expr.Str] = P("\"" ~~/ CharsWhile(_ != '"', 0).! ~~ "\"").map(x => Expr.Str(x)) + def str[$: P]: P[Expr.Str] = P("\"" ~~/ CharsWhile(_ != '"', 0).! ~~ "\"").map(x => Expr.Str(x.replace("\\n", ""+10.toChar) + "\u0000")) + def fileName[$: P]: P[Expr.Str] = P("\"" ~~/ CharsWhile(_ != '"', 0).! ~~ "\"").map(x => Expr.Str(x)) - def ident[_: P]: P[Expr.Ident] = P(CharIn("a-zA-Z_") ~~ CharsWhileIn("a-zA-Z0-9_", 0)).! + def ident[$: P]: P[Expr.Ident] = P(CharIn("a-zA-Z_") ~~ CharsWhileIn("a-zA-Z0-9_", 0)).! .filter(x => !reservedKeywords.contains(x)) .map((input) => { Expr.Ident(input) }) - def mod_ident[_: P]: P[Expr.Ident] = P(mod_ident_raw) + def mod_ident[$: P]: P[Expr.Ident] = P(mod_ident_raw) .map((input) => { val modules = input._1 val prefix = if (modules.nonEmpty) modules else file_name Expr.Ident(prefix + "_" + input._2) }) - def mod_ident_raw[_: P]: P[(String, String)] = P((ident.! ~ "::").rep() ~ ident.!) + def mod_ident_raw[$: P]: P[(String, String)] = P((ident.! ~ "::").rep() ~ ident.!) .filter(x => !x._1.exists(y => reservedKeywords.contains(y)) && !reservedKeywords.contains(x._2)) .map(x=> (x._1.toList.mkString("_"),x._2)) - def mod_ident_no_default[_: P]: P[Expr.Ident] = P(mod_ident_raw) + def mod_ident_no_default[$: P]: P[Expr.Ident] = P(mod_ident_raw) .map((input) => { val modules = input._1 + "_" val prefix = if (modules.length > 1) modules else "" Expr.Ident(prefix + input._2) }) - def number[_: P]: P[Expr.Num] = P("-".!.? ~~ CharsWhileIn("0-9", 1)).!.map(x => Expr.Num(Integer.parseInt(x))) + def number[$: P]: P[Expr.Num] = P("-".!.? ~~ CharsWhileIn("0-9", 1)).!.map(x => Expr.Num(Integer.parseInt(x))) - def numberFloat[_: P]: P[Expr.NumFloat] = P("-".? ~~ CharsWhileIn("0-9", 1) ~~ "." ~~ CharsWhileIn("0-9", 1)).!.map(x => Expr.NumFloat(x.toFloat)) + def numberFloat[$: P]: P[Expr.NumFloat] = P("-".? ~~ CharsWhileIn("0-9", 1) ~~ "." ~~ CharsWhileIn("0-9", 1)).!.map(x => Expr.NumFloat(x.toFloat)) - def char[_: P]: P[Expr.Character] = P("'" ~/ !"'" ~ (AnyChar.! | "\\n".!) ~/ "'").map(x => { + def char[$: P]: P[Expr.Character] = P("'" ~/ !"'" ~ (AnyChar.! | "\\n".!) ~/ "'").map(x => { var c = x.charAt(0); if(x.length == 2 && x=="\n") c = 10; Expr.Character(c) }); - def constant[_: P]: P[Expr] = P(trueC | falseC) + def constant[$: P]: P[Expr] = P(trueC | falseC) - def trueC[_: P]: P[Expr.True] = P("true").map(_ => Expr.True()) + def trueC[$: P]: P[Expr.True] = P("true").map(_ => Expr.True()) - def falseC[_: P]: P[Expr.False] = P("false").map(_ => Expr.False()) + def falseC[$: P]: P[Expr.False] = P("false").map(_ => Expr.False()) - def print[_: P]: P[Expr.Print] = P("print" ~ "(" ~/ (NoCut(binOp) | prefixExpr) ~ ")").map(Expr.Print) - def free[_: P]: P[Expr.Free] = P("free" ~ "(" ~/ (prefixExpr) ~ ")").map(Expr.Free) + def print[$: P]: P[Expr.Print] = P("print" ~ "(" ~/ (NoCut(binOp) | prefixExpr) ~ ")").map(Expr.Print) + def free[$: P]: P[Expr.Free] = P("free" ~ "(" ~/ (prefixExpr) ~ ")").map(Expr.Free) - def throwException[_: P]: P[Expr.ThrowException] = P("throw" ~/ "exception" ~/ "(" ~/ str ~/ ")").map(x => Expr.ThrowException(x.s.dropRight(1))) + def throwException[$: P]: P[Expr.ThrowException] = P("throw" ~/ "exception" ~/ "(" ~/ str ~/ ")").map(x => Expr.ThrowException(x.s.dropRight(1))) class ParseException(s: String) extends RuntimeException(s) diff --git a/app/src/main/scala/posharp/ToAssembly.scala b/app/src/main/scala/posharp/ToAssembly.scala index b4e08d3..1a901a5 100644 --- a/app/src/main/scala/posharp/ToAssembly.scala +++ b/app/src/main/scala/posharp/ToAssembly.scala @@ -1,13 +1,9 @@ package posharp -import posharp.ToAssembly.{FunctionInfo, InterfaceInfo, EnumInfo, Variable, FileDeclaration} +import posharp.ToAssembly.{EnumInfo, FileDeclaration, FunctionInfo, InterfaceInfo, Variable} import posharp.Type.{UserType, defaultValue, shortS, toLLVM} -import sourcecode.Text.generate import scala.io.AnsiColor -import scala.language.postfixOps -import scala.reflect.runtime.currentMirror -import scala.reflect.runtime.universe.termNames class ToAssembly(currentFile: String) { type Env = Map[String, Variable] @@ -878,11 +874,15 @@ class ToAssembly(currentFile: String) { case x => func(x) } def replaceType(input: List[InputVar], func: (Type) => Type): List[InputVar] = { - input.map(x => - InputVar(x.name, traverseTypeTree(x.varType, func))) + input.map(x => replaceType(x, func)) + } + + def replaceType(x: InputVar, func: (Type) => Type): InputVar = { + InputVar(x.name, traverseTypeTree(x.varType, func)) } def replaceType(input: Expr, func: (Type) => Type): Expr = input match { + /* case Expr.DefVal(a, varType) => Expr.DefVal(a, traverseTypeTree(varType, func)) case Expr.DefValWithValue(variable, varType, value) => Expr.DefValWithValue(variable, traverseTypeTree(varType, func), replaceType(value, func)) case Expr.Func(name, argNames, retType, body, templates) => Expr.Func(name, replaceType(argNames, func), traverseTypeTree(retType, func), replaceType(body, func).asInstanceOf[Expr.Block], templates) @@ -894,47 +894,54 @@ class ToAssembly(currentFile: String) { Expr.DefineInterface(name, replaceType(props, func), functions.map(x=>replaceType(x, func).asInstanceOf[Expr.Func]), templates.map(x=>traverseTypeTree(x, func)) ) + + */ //case Expr.DefineArray(size, elemType:Type, defaultValues: List[Expr]) => Expr.DefineArray(size, traverseTypeTree(elemType, func), defaultValues) //case Expr.InstantiateInterface case x => { + /* val param_func: (Any => Any) = new ((Any) => Any) { def apply(in: Any): Any = in match { case expr: Expr => replaceType(expr, func) case t: Type => traverseTypeTree(t, func) + case vr: InputVar => replaceType(vr, func) case h :: t => (h +: t).map(y=>apply(y)) - case _ => in + case _ => println(in);in } } + */ + val param_func = traversalFunctionBuilder { + case expr: Expr => replaceType(expr, func) + case t: Type => traverseTypeTree(t, func) + case vr: InputVar => replaceType(vr, func) + case in => in + } + //transformCaseClass(x, param_func) applyFunctionToUnknownCaseClass(x, param_func) + //x } - + //case class Convert(value: Expr, to: Type) extends Expr //case class Lambda(argNames: List[InputVar], retType: Type, body: Expr.Block) extends Expr } - - def applyFunctionToUnknownCaseClass(instance: Expr, func: Any => Any): Expr = { - val mirror = currentMirror - val instanceMirror = mirror.reflect(instance) - val classSymbol = instanceMirror.symbol - - if (!classSymbol.isClass || !classSymbol.asClass.isCaseClass) { - throw new IllegalArgumentException("The provided instance is not a case class.") - } - - val classType = instanceMirror.symbol.typeSignature - val constructorSymbol = classType.decl(termNames.CONSTRUCTOR.asInstanceOf[scala.reflect.runtime.universe.Name]).asMethod - val constructorMirror = mirror.reflectClass(classSymbol.asClass).reflectConstructor(constructorSymbol) - - // Collecting parameters from the constructor - val params = constructorSymbol.paramLists.flatten - - val fieldValues = params.map { param => - val fieldTerm = classType.decl(param.name).asTerm.accessed.asTerm - val fieldValue = instanceMirror.reflectField(fieldTerm).get - func(fieldValue) + def traversalFunctionBuilder(func: Any => Any) = { + new((Any) => Any) { + def apply(in: Any): Any = in match { + case h :: t => (h +: t).map(y => apply(y)) + case Some(x) => Some(apply(x)) + case x => func(x) + } } + } + def applyFunctionToUnknownCaseClass(instance: Expr, func: Any => Any): Expr = { + val _class = instance.getClass + val fields = _class.getDeclaredFields + .map(f => { + f.setAccessible(true) + func(f.get(instance)) + }) - constructorMirror(fieldValues: _*).asInstanceOf[Expr] + _class.getConstructors()(0).newInstance(fields:_*).asInstanceOf[Expr] } def replaceWithMappingFunc(template_mappings: List[(Type, Type)]): Type => Type = { @@ -1046,4 +1053,45 @@ class Counter { counter = pausedCounter; } } +/* +inline def transformCaseClass[A](inline obj: A): A = ${ transformCaseClassImpl('obj) } + +import scala.quoted.* +//import sourcecode.Text.generate + +//import scala.language.postfixOps +//import scala.quoted.Quotes +def transformCaseClassImpl[A: Type](objExpr: Expr[A])(using Quotes): Expr[A] = { + import quotes.reflect.* + val v: Term = objExpr.asTerm + + def transformTerm(term: Term): Term = term match { + case Inlined(_, _, expr) => transformTerm(expr) + case Block(stats, expr) => Block(stats, transformTerm(expr)) + case Apply(Select(lhs, name), args) => + Apply(Select.unique(transformTerm(lhs), name), args.map(arg => transformTerm(arg))) + case Ident(_) => + val sym = term.symbol //.asTerm + if sym.isValDef && sym.owner.isClassDef then + val fieldType = sym.tree.asInstanceOf[ValDef].tpt.tpe + fieldType.asType match { + case '[t] => '{ transformField(${ Ref(sym).asExprOf[t] }) }.asTerm + } + else term + case _ => term + } + + + val transformedTerm = transformTerm(objExpr.asTerm) + transformedTerm.asExprOf[A] +} +def transformField[A](field: A): A = { + // Example transformation logic + // Customize this as per your requirements + field match { + case _ => println(field); field + } +} +*/ + diff --git a/build.gradle.kts b/build.gradle.kts index be2a52a..05bfdf0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,8 @@ repositories { } dependencies { - implementation("org.scala-lang:scala-library:2.13.10") + implementation("org.scala-lang:scala3-library_3:3.3.1") implementation("com.google.guava:guava:31.1-jre") - implementation("com.lihaoyi:fastparse_2.13:2.3.3") + implementation("com.lihaoyi:fastparse_3:3.0.2") + //implementation("co.blocke:scala-reflection_3:2.0.0") } From dd25f4007709564d1103b45292ed4072e779baae Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Thu, 11 Jan 2024 19:21:41 +0100 Subject: [PATCH 104/112] unreflect the testing framework --- veritas/build.gradle.kts | 2 +- veritas/src/main/scala/core/Coverage.scala | 6 +++--- veritas/src/main/scala/core/FileHelpers.scala | 2 +- veritas/src/main/scala/core/Veritas.scala | 14 ++++++-------- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/veritas/build.gradle.kts b/veritas/build.gradle.kts index dedc588..3040a1c 100644 --- a/veritas/build.gradle.kts +++ b/veritas/build.gradle.kts @@ -8,7 +8,7 @@ repositories { } dependencies { - implementation("org.scala-lang:scala-library:2.13.10") + implementation("org.scala-lang:scala3-library_3:3.3.1") implementation("org.reflections:reflections:0.10.2") implementation(project(":app")) } diff --git a/veritas/src/main/scala/core/Coverage.scala b/veritas/src/main/scala/core/Coverage.scala index 17417f8..4583d78 100644 --- a/veritas/src/main/scala/core/Coverage.scala +++ b/veritas/src/main/scala/core/Coverage.scala @@ -64,10 +64,10 @@ object Coverage { /** * Generates a map between each case class and the amount of times they were used in the tests. * - * @param export True exports a CodeCov JSON report + * @param _expression True exports a CodeCov JSON report * @return Map[ClassName, TimesUsed] */ - def CalculateCoverage(`export`: Boolean = false): Map[String, Int] = { + def CalculateCoverage(_expression: Boolean = false): Map[String, Int] = { val coverages = SumCoverages(exprs) val res = GetAllExprCaseClasses() @@ -81,7 +81,7 @@ object Coverage { val output = ListMap.from((res ++ coverages).toSeq.sortBy(_._2)) - if (export) + if (_expression) CreateCodeCovReport(output) output diff --git a/veritas/src/main/scala/core/FileHelpers.scala b/veritas/src/main/scala/core/FileHelpers.scala index db2e9da..873e273 100644 --- a/veritas/src/main/scala/core/FileHelpers.scala +++ b/veritas/src/main/scala/core/FileHelpers.scala @@ -1,6 +1,6 @@ package core -import posharp.Main.writeToFile +import posharp.writeToFile import java.io.File import scala.sys.process._ diff --git a/veritas/src/main/scala/core/Veritas.scala b/veritas/src/main/scala/core/Veritas.scala index 48954ee..9004096 100644 --- a/veritas/src/main/scala/core/Veritas.scala +++ b/veritas/src/main/scala/core/Veritas.scala @@ -4,12 +4,15 @@ import core.FileHelpers.deleteTestArtifacts import org.reflections.Reflections import org.reflections.scanners.Scanners.TypesAnnotated import org.reflections.util.ConfigurationBuilder + +import scala.quoted.* import posharp.{Expr, Parser, ToAssembly} +import test.TestExample import java.lang.reflect.Method import java.util.concurrent.{Executors, TimeUnit} import scala.collection.mutable -import scala.io.AnsiColor._ +import scala.io.AnsiColor.* import scala.util.{Failure, Success, Try} object Veritas { @@ -80,13 +83,8 @@ object Veritas { .setScanners(TypesAnnotated)) // Get all annotated types from package test - val res = reflections - .getStore - .get("TypesAnnotated") - .get("scala.reflect.ScalaSignature") - .toArray - .filter(_.asInstanceOf[String].contains("test.")) - .map(_.asInstanceOf[String]) + // TODO: Un-hardcode classes and use annotations + val res = List(TestExample().getClass.getName) println() From 40e93af5cb39c0867415ad12daf64b135a4f1e19 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Thu, 11 Jan 2024 19:29:44 +0100 Subject: [PATCH 105/112] Move test classes to top const --- veritas/src/main/scala/core/Veritas.scala | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/veritas/src/main/scala/core/Veritas.scala b/veritas/src/main/scala/core/Veritas.scala index 9004096..296a102 100644 --- a/veritas/src/main/scala/core/Veritas.scala +++ b/veritas/src/main/scala/core/Veritas.scala @@ -5,7 +5,6 @@ import org.reflections.Reflections import org.reflections.scanners.Scanners.TypesAnnotated import org.reflections.util.ConfigurationBuilder -import scala.quoted.* import posharp.{Expr, Parser, ToAssembly} import test.TestExample @@ -15,6 +14,9 @@ import scala.collection.mutable import scala.io.AnsiColor.* import scala.util.{Failure, Success, Try} +// TODO: Un-hardcode classes and use annotations +val CLASSES_TO_TEST = List(TestExample().getClass.getName); + object Veritas { private val numOfThreads = 10 private val chunkSize = 1 @@ -82,14 +84,10 @@ object Veritas { .forPackage("test") .setScanners(TypesAnnotated)) - // Get all annotated types from package test - // TODO: Un-hardcode classes and use annotations - val res = List(TestExample().getClass.getName) - println() // Get the class and instantiate it - res.foreach(c => { + CLASSES_TO_TEST.foreach(c => { val testClass = Class.forName(c).getConstructor().newInstance().getClass var lastMethodName = "" From 213a7134fd8abcbfcb4886e2ee269ff1ab3fdbc1 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Thu, 11 Jan 2024 19:29:52 +0100 Subject: [PATCH 106/112] document test class discoverability --- veritas/src/main/scala/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/veritas/src/main/scala/README.md b/veritas/src/main/scala/README.md index 0a6af7c..26b8bcc 100644 --- a/veritas/src/main/scala/README.md +++ b/veritas/src/main/scala/README.md @@ -9,6 +9,11 @@ for linux or mac locally but do note that there were issues on the remote pipeli ### Writing a Test +> [!WARNING] +> The tests are no longer discovered via the annotation, instead there is a hardcoded list at the top of +> [`veritas`](./core/Veritas.scala). This is because I have no clue how to get reflection to work properly in Scala 3. +> The tests inside the classes are still "discovered" the same way as before. + Writing tests is pretty straight forward. For a method to be considered a test it must: - Be inside the `test` package From 2652600e8a5b4a528f053dcdc935c9d8d3dbcd20 Mon Sep 17 00:00:00 2001 From: Antonios Barotsis Date: Thu, 11 Jan 2024 19:31:36 +0100 Subject: [PATCH 107/112] Move readme up --- veritas/{src/main/scala => }/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename veritas/{src/main/scala => }/README.md (85%) diff --git a/veritas/src/main/scala/README.md b/veritas/README.md similarity index 85% rename from veritas/src/main/scala/README.md rename to veritas/README.md index 26b8bcc..3613949 100644 --- a/veritas/src/main/scala/README.md +++ b/veritas/README.md @@ -11,7 +11,7 @@ for linux or mac locally but do note that there were issues on the remote pipeli > [!WARNING] > The tests are no longer discovered via the annotation, instead there is a hardcoded list at the top of -> [`veritas`](./core/Veritas.scala). This is because I have no clue how to get reflection to work properly in Scala 3. +> [`veritas`](./src/main/scala/core/Veritas.scala). This is because I have no clue how to get reflection to work properly in Scala 3. > The tests inside the classes are still "discovered" the same way as before. Writing tests is pretty straight forward. For a method to be considered a test it must: @@ -21,7 +21,7 @@ Writing tests is pretty straight forward. For a method to be considered a test i - Include the word `test` in the method name - Have a return type of `(Boolean, String)` -The [`PoSharp.scala`](./core/PoSharp.scala) class is created to provide an interface as well as some helper methods to aid in +The [`PoSharp.scala`](./src/main/scala/core/PoSharp.scala) class is created to provide an interface as well as some helper methods to aid in writing tests. Do note that the framework considers the last printed value to be the value to check, this means that each snippet must have a print statement. @@ -58,7 +58,7 @@ class test.TestExample { } ``` -The [`PoSharp.scala`](./core/PoSharp.scala) file contains thorough documentation on all the different methods that it provides +The [`PoSharp.scala`](./src/main/scala/core/PoSharp.scala) file contains thorough documentation on all the different methods that it provides which I will be keeping up to date so be sure to read through the JavaDoc. ### Running the Tests From bf0e03ba4739976bae622900efa73aaf7209ceca Mon Sep 17 00:00:00 2001 From: Pijus Krisiukenas Date: Mon, 15 Jan 2024 23:16:25 +0200 Subject: [PATCH 108/112] lambdas without closures --- app/src/main/scala/posharp/Definitions.scala | 10 +-- app/src/main/scala/posharp/ToAssembly.scala | 89 +++++++++++++++++--- 2 files changed, 83 insertions(+), 16 deletions(-) diff --git a/app/src/main/scala/posharp/Definitions.scala b/app/src/main/scala/posharp/Definitions.scala index dcc0195..45225ca 100644 --- a/app/src/main/scala/posharp/Definitions.scala +++ b/app/src/main/scala/posharp/Definitions.scala @@ -89,6 +89,7 @@ object Type { case class Function(args: List[Type], retType: Type) extends Type case class T(num: Int) extends Type case class Enum(el: List[String]) extends Type + case class Closure(func: Function, env: List[Type]) extends Type //to be converted when parsing case class UserType(name: String, templates: List[Type]) extends Type @@ -104,13 +105,10 @@ object Type { case Function(args, retType) => "func_"+args.map(x=>shortS(x)).mkString+"_"+shortS(retType) case UserType(name, templates) => name + templates.map(x=>shortS(x)).mkString case T(a) => s"T$a" + case _ => throw new Exception(s"$value unrecognised"); } def compare(val1: Type, val2: Type): Boolean = (val1, val2) match { case (a,b) if a == b => true - case (T(_), _) => true - case (_, T(_)) => true - case (Array(T(_)), _) => true - case (_, Array(T(_))) => true case _ => false } def compare(value: (Type, Type)): Boolean = compare(value._1,value._2) @@ -126,12 +124,14 @@ object Type { case NumFloat() => "double"; case Character() => "i8" case Bool() => "i1" - case Array(inner) => s"%Type.array.${toLLVM(inner)}*" + case Array(inner) => s"{i32, ${toLLVM(inner)}*}*"//s"%Type.array.${toLLVM(inner)}*" case Undefined() => "void" case Str() => "i8*" + case Function(args, retType) => s"${toLLVM(retType)} (${args.map(x=>toLLVM(x)).mkString(",")})*" //presume that usertype is a class. Might have aliases in the future case UserType(name, templates) => s"%Class.$name.${templates.map(x=>toLLVM(x)).mkString}*" case Interface(name, _, _, templates) => s"%Class.$name.${templates.map(x=>toLLVM(x)).mkString}*" + case Closure(func, env) => s"{${toLLVM(func)}, {${env.map(x=>toLLVM(x)).mkString(",")}}}*" /* case Interface(vars, funcs) => { val argS = vars.map(x=>toLLVM(x.varType)).mkString(", ") diff --git a/app/src/main/scala/posharp/ToAssembly.scala b/app/src/main/scala/posharp/ToAssembly.scala index 2a35872..ac500f7 100644 --- a/app/src/main/scala/posharp/ToAssembly.scala +++ b/app/src/main/scala/posharp/ToAssembly.scala @@ -87,7 +87,7 @@ class ToAssembly(currentFile: String) { val types = intf.props.map(x => Type.toLLVM(x.varType)).mkString(", ") s"$llvm_intf = type {$types}\n" }).mkString - //converted += defineFunctions(lambdas, true); + converted += defineFunctions(lambdas, true, false); converted += stringLiterals.mkString converted += """attributes #0 = { mustprogress noinline nounwind optnone uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } |attributes #1 = { nocallback nofree nosync nounwind readnone speculatable willreturn } @@ -183,15 +183,15 @@ class ToAssembly(currentFile: String) { case (_, valType, _) => throw new Exception(s"expected a interface, got ${valType}") } case Expr.CallF(name, args, templates) => { + val prefix_removed = name.replaceFirst(file_prefix+"_", "") if(functions.exists(x=>x.name == name)) interpFunction(name, args, templates, env) - //else if(env.contains(name)) callLambda(Expr.Ident(name), args, reg, env) + else if(env.contains(prefix_removed)) callLambda(Expr.Ident(prefix_removed), args, env) else throw new Exception(s"unknown identifier $name") } case Expr.CallObjFunc(obj, func) => convertLoc(obj, env) match { - //TODO investigate callobj - //case _ => throw new NotImplementedError("") case (code, t@Type.Interface(_, props, funcs, templates), loc) => callObjFunction(Expr.Compiled(code, t, loc), func, props, funcs, isStatic = false, env) case (code, t@Type.StaticInterface(props, funcs), loc) => callObjFunction(Expr.Compiled(code, t, loc), func, props, funcs, isStatic = true, env) + case (_, t, _) => throw new Exception(s"Expected a interface, got ${t}") } case Expr.InstantiateInterface(name, values, templates) => { if (templates.nonEmpty) interfaces.find(x=>x.name == name && x.templates == templates) match { @@ -224,11 +224,14 @@ class ToAssembly(currentFile: String) { case Some(intf) => { val classT = toLLVM(intf) // s"%Class.$name" + /* var aloc = ""; aloc += s"${varc.next()} = getelementptr $classT, $classT* null, i32 1\n" + s"${varc.next()} = ptrtoint $classT** ${varc.secondLast()} to i32\n" val bytesLoc = varc.last(); aloc += s"${varc.next()} = call ptr (i32) @malloc(i32 $bytesLoc)\n"; val alocLoc = varc.last(); + */ + val (aloc, alocLoc) = allocate_on_heap(classT) val valuesCompiled = values.map(x => convertLoc(x, env)).map(x => Expr.Compiled(x._1, x._2, x._3)) //intf.funcs.find(x => x.name == name && x.args == values) @@ -255,13 +258,40 @@ class ToAssembly(currentFile: String) { case Expr.Str(value) => (defineArrayKnown(value.length, Type.Character(), value.map(x=>Expr.Character(x)).toList, env)._1, Type.Array(Type.Character())) - case Expr.Lambda(args, ret, body) => { + + */ + //TODO check if lambda already been generated + case Expr.Lambda(args, retType, body) => { val label = "lambda_" + lambdas.size - functions = functions :+ FunctionInfo(label, args, ret) - lambdas = lambdas :+ (Expr.Func(label, args, ret, body), env) - (s"mov ${reg.head}, $label\n", Type.Function(args.map(x=>x.varType), ret)) + + val info = FunctionInfo(label, "", args, retType, List()) + functions = functions :+ info + lambdas = lambdas :+ (Expr.Func(label, args, retType, body, List()), env) + val func_Type = Type.Function(args.map(x=>x.varType), retType) + val func_name = fNameSignature(info) + val struct_loc = varc.next() + + var ret = "" + val struct_llvm_type = s"{${env.map(x=>Type.toLLVM(x._2.varType)).mkString(",")}}" + val struct_llvm_type_dec = s"%Closure.$label = type {${env.map(x=>Type.toLLVM(x._2.varType))}}\n" + ret += s"$struct_loc = alloca $struct_llvm_type\n" + val (arg_code: List[String], arg_types: List[Type]) = env.zipWithIndex.map((x,idx)=>{ + val (code, ty, loc) = convertLoc(Expr.Ident(x._1), env) + val ret = code + + s"${varc.next()} = getelementptr $struct_llvm_type, $struct_llvm_type* $struct_loc, i32 0, i32 $idx\n" + + s"store ${Type.toLLVM(ty)} $loc, ${Type.toLLVM(ty)}* ${varc.last()}\n" + (ret, ty) + }).unzip: @unchecked + ret += arg_code.mkString("") + val closure_type = Type.Closure(func_Type, arg_types) + val (heap_code, closure_aloc) = allocate_on_heap(closure_type) + ret += heap_code + + ret += s"${varc.next()} = alloca ${Type.toLLVM(func_Type)}\n" + + s"store ${Type.toLLVM(func_Type)} @$func_name, ${Type.toLLVM(func_Type)}* ${varc.last()}\n" + + s"${varc.next()} = load ${Type.toLLVM(func_Type)}, ${Type.toLLVM(func_Type)}* ${varc.secondLast()}\n" + (ret, func_Type) } - */ case Expr.Nothing() => ("", Type.Undefined()); case Expr.Compiled(code, retType, _) => (code, retType); case Expr.Block(lines) => convertBlock(lines, env); @@ -269,6 +299,23 @@ class ToAssembly(currentFile: String) { } (ret._1, makeUserTypesConcrete(ret._2)) } + + def allocate_on_heap(llvm_type: Type): (String, String) = allocate_on_heap(Type.toLLVM(llvm_type)) + /** + * Call malloc and reserve memory based on type size + * @param llvm_type: llvm type string + * @return (code, pointer aloc) + */ + def allocate_on_heap(llvm_type: String): (String, String) = { + var aloc = ""; + aloc += s"${varc.next()} = getelementptr $llvm_type, $llvm_type* null, i32 1\n" + + s"${varc.next()} = ptrtoint $llvm_type** ${varc.secondLast()} to i32\n" + val bytesLoc = varc.last(); + aloc += s"${varc.next()} = call ptr (i32) @malloc(i32 $bytesLoc)\n"; + val alocLoc = varc.last(); + (aloc, alocLoc) + } + def alloc_struct_with_args(args: List[Expr], env: Env): (String, List[Type]) = ??? //TODO add line awareness for error reporting private def convertBlock(lines: List[Expr], env: Env): (String, Type) = { val conv = (_input: Expr) => convert(_input, env) @@ -644,13 +691,33 @@ class ToAssembly(currentFile: String) { } case None => props.find(x=>x.name == func.name) match { case Some(InputVar(_, Type.Function(_,_))) => { - //callLambda(Expr.GetProperty(obj, func.name), func.args, reg, env) - ("",Type.Undefined()) + callLambda(Expr.GetProperty(obj, func.name), func.args, env) + //("",Type.Undefined()) } case None => throw new Exception(s"object has no property or function named $func") } } } + + def callLambda(input: Expr, args: List[Expr], env: Env): (String, Type) = convertLoc(input, env) match { + case (code, Type.Function(argTypes, retType), f_loc) => { + val argRet = args.map(arg => convertLoc(arg, env)) + var ret = code; + ret += argRet.map(x => x._1).mkString + + if (argTypes.length != args.length) throw new Exception(s"wrong number of arguments: expected ${argTypes.length}, got ${args.length}"); + (argRet.map(x=>x._2) zip argTypes).foreach( x=> { + if (x._1!=x._2) throw new Exception(s"Wrong argument for function: expected ${x._2}, got ${x._1}"); + }) + + val argsString = argRet.map(x=>s"${Type.toLLVM(x._2)} ${x._3}").mkString(", ") + val argTypeString = argTypes.map(x => Type.toLLVM(x)).mkString(", ") + if (retType != Type.Undefined()) ret += s"${varc.next()} = "; + ret += s"call ${Type.toLLVM(retType)} ($argTypeString) $f_loc($argsString)\n" + (ret, retType) + } + case (_, x, _) => throw new Exception(s"Can not call variable of type $x"); + } /* def callLambda(input: Expr, args: List[Expr], reg: List[String], env: Env): (String, Type) = convert(input, reg, env) match { case (code, Type.Function(argTypes, retType)) => { From 00093efc8f0e5c428a1d76725fe468dc7959bac2 Mon Sep 17 00:00:00 2001 From: Pijus Krisiukenas Date: Tue, 16 Jan 2024 04:10:57 +0200 Subject: [PATCH 109/112] lambdas with closures --- app/src/main/scala/posharp/Definitions.scala | 26 ++++-- app/src/main/scala/posharp/Parser.scala | 3 +- app/src/main/scala/posharp/ToAssembly.scala | 91 +++++++++++++++----- 3 files changed, 94 insertions(+), 26 deletions(-) diff --git a/app/src/main/scala/posharp/Definitions.scala b/app/src/main/scala/posharp/Definitions.scala index 45225ca..6943652 100644 --- a/app/src/main/scala/posharp/Definitions.scala +++ b/app/src/main/scala/posharp/Definitions.scala @@ -2,6 +2,8 @@ package posharp import posharp.ToAssembly.FunctionInfo +import scala.annotation.targetName + sealed trait Expr object Expr{ case class Str(s: String) extends Expr @@ -89,9 +91,21 @@ object Type { case class Function(args: List[Type], retType: Type) extends Type case class T(num: Int) extends Type case class Enum(el: List[String]) extends Type - case class Closure(func: Function, env: List[Type]) extends Type + //https://stackoverflow.com/questions/10373715/scala-ignore-case-class-field-for-equals-hascode + case class Closure(func: Function)(val env: List[(String, Type)]) extends Type + object Closure { + @targetName("custom_closure_apply") + def apply(func: Function, env: List[(String, Type)]): Closure = Closure(func)(env) + /* + def unapply(u: Closure): Option[(Function, List[(String, Type)])] = + if (u eq null) None + else Some((u.func, u.env)) + */ + def unapply(u: Closure): Option[(Function, List[(String, Type)])] = + Some(( u.func, u.env)) + + } - //to be converted when parsing case class UserType(name: String, templates: List[Type]) extends Type def shortS(value: Type): String = value match { @@ -101,10 +115,12 @@ object Type { case Bool() => "b" case Array(inner) => "arr_"+shortS(inner)+"_" //case Interface(inner) => "itf_"+inner.map(x=>shortS(x.varType))+"_" - case Interface(_, inner, innerf, templates) => "itf_"+inner.map(x=>shortS(x.varType)).mkString+"_"+innerf.map(x=>x.name)+"_"+ templates.map(x=>shortS(x)).mkString + case Interface(_, inner, innerf, templates) => "itf_"+inner.map(x=>shortS(x.varType)).mkString+"_"+innerf.map(x=>x.name).mkString+"_"+ templates.map(x=>shortS(x)).mkString case Function(args, retType) => "func_"+args.map(x=>shortS(x)).mkString+"_"+shortS(retType) case UserType(name, templates) => name + templates.map(x=>shortS(x)).mkString - case T(a) => s"T$a" + //case T(a) => s"T$a" + case Closure(func, env) => shortS(func)+env.map(x=>shortS(x._2)).mkString + case Undefined() => "v" case _ => throw new Exception(s"$value unrecognised"); } def compare(val1: Type, val2: Type): Boolean = (val1, val2) match { @@ -131,7 +147,7 @@ object Type { //presume that usertype is a class. Might have aliases in the future case UserType(name, templates) => s"%Class.$name.${templates.map(x=>toLLVM(x)).mkString}*" case Interface(name, _, _, templates) => s"%Class.$name.${templates.map(x=>toLLVM(x)).mkString}*" - case Closure(func, env) => s"{${toLLVM(func)}, {${env.map(x=>toLLVM(x)).mkString(",")}}}*" + case Closure(func, env) => s"{${toLLVM(func)}, {${env.map(x=>toLLVM(x._2)).mkString(",")}}}*" /* case Interface(vars, funcs) => { val argS = vars.map(x=>toLLVM(x.varType)).mkString(", ") diff --git a/app/src/main/scala/posharp/Parser.scala b/app/src/main/scala/posharp/Parser.scala index bfd4d9d..4a9149e 100644 --- a/app/src/main/scala/posharp/Parser.scala +++ b/app/src/main/scala/posharp/Parser.scala @@ -152,7 +152,8 @@ object Parser { def typeDefNoCol[$: P]: P[Type] = P(typeBase | typeArray | typeFunc | typeUser) def typeArray[$: P]: P[Type] = P("array" ~/ "[" ~ typeDefNoCol ~ "]").map(x => Type.Array(x)) - def typeFunc[$: P]: P[Type] = P("func" ~/ "[" ~ "(" ~ typeDefNoCol.rep(sep=",") ~ ")" ~/ "=>" ~ typeDefNoCol ~ "]").map(x => Type.Function(x._1.toList, x._2)) + //Type.Function(x._1.toList, x._2) + def typeFunc[$: P]: P[Type] = P("func" ~/ "[" ~ "(" ~ typeDefNoCol.rep(sep=",") ~ ")" ~/ "=>" ~ typeDefNoCol ~ "]").map(x => Type.Closure(Type.Function(x._1.toList, x._2), List())) def typeUser[$: P]: P[Type] = P(mod_ident ~ templateTypes.?).map(x => Type.UserType(x._1.name, x._2.getOrElse(List()))) diff --git a/app/src/main/scala/posharp/ToAssembly.scala b/app/src/main/scala/posharp/ToAssembly.scala index ac500f7..455f5b8 100644 --- a/app/src/main/scala/posharp/ToAssembly.scala +++ b/app/src/main/scala/posharp/ToAssembly.scala @@ -264,33 +264,53 @@ class ToAssembly(currentFile: String) { case Expr.Lambda(args, retType, body) => { val label = "lambda_" + lambdas.size - val info = FunctionInfo(label, "", args, retType, List()) - functions = functions :+ info - lambdas = lambdas :+ (Expr.Func(label, args, retType, body, List()), env) + + val func_Type = Type.Function(args.map(x=>x.varType), retType) - val func_name = fNameSignature(info) - val struct_loc = varc.next() + val env_loc = varc.next() var ret = "" - val struct_llvm_type = s"{${env.map(x=>Type.toLLVM(x._2.varType)).mkString(",")}}" + val env_llvm_type = s"{${env.map(x=>Type.toLLVM(x._2.varType)).mkString(",")}}" val struct_llvm_type_dec = s"%Closure.$label = type {${env.map(x=>Type.toLLVM(x._2.varType))}}\n" - ret += s"$struct_loc = alloca $struct_llvm_type\n" - val (arg_code: List[String], arg_types: List[Type]) = env.zipWithIndex.map((x,idx)=>{ + ret += s"$env_loc = alloca $env_llvm_type\n" + val arg_code = env.zipWithIndex.map((x,idx)=>{ val (code, ty, loc) = convertLoc(Expr.Ident(x._1), env) val ret = code + - s"${varc.next()} = getelementptr $struct_llvm_type, $struct_llvm_type* $struct_loc, i32 0, i32 $idx\n" + + s"${varc.next()} = getelementptr $env_llvm_type, $env_llvm_type* $env_loc, i32 0, i32 $idx\n" + s"store ${Type.toLLVM(ty)} $loc, ${Type.toLLVM(ty)}* ${varc.last()}\n" - (ret, ty) - }).unzip: @unchecked + ret + }) ret += arg_code.mkString("") - val closure_type = Type.Closure(func_Type, arg_types) - val (heap_code, closure_aloc) = allocate_on_heap(closure_type) + val closure_type = Type.Closure(func_Type,env.map(x=>(x._2.loc,x._2.varType)).toList) + val closure_llvm = Type.toLLVM(closure_type) + val (heap_code, closure_loc) = allocate_on_heap(closure_type) ret += heap_code + val info = FunctionInfo(label, "", InputVar("__closure__", closure_type) +: args, retType, List()) + functions = functions :+ info + val func_name = fNameSignature(info) + ret += s"${varc.next()} = alloca ${Type.toLLVM(func_Type)}\n" + s"store ${Type.toLLVM(func_Type)} @$func_name, ${Type.toLLVM(func_Type)}* ${varc.last()}\n" + s"${varc.next()} = load ${Type.toLLVM(func_Type)}, ${Type.toLLVM(func_Type)}* ${varc.secondLast()}\n" - (ret, func_Type) + val func_loc = varc.last() + + ret += s"${varc.next()} = getelementptr $closure_llvm, $closure_llvm* $closure_loc, i32 0\n" + + s"store ${Type.toLLVM(func_Type)} $func_loc, ${Type.toLLVM(func_Type)}* ${varc.last()}\n" + //ret += s"${varc.next()} = getelementptr $closure_llvm, $closure_llvm* $closure_loc, i32 1\n" + + // s"store $env_llvm_type* $env_loc, $env_llvm_type** ${varc.last()}\n" + ret += env.zipWithIndex.map((x, idx) => { + val (code, ty, loc) = convertLoc(Expr.Ident(x._1), env) + val ret = code + + s"${varc.next()} = getelementptr $closure_llvm, $closure_llvm* $closure_loc, i32 ${1+idx}\n" + + s"store ${Type.toLLVM(ty)} $loc, ${Type.toLLVM(ty)}* ${varc.last()}\n" + ret + }).mkString("") + + lambdas = lambdas :+ (Expr.Func(label, InputVar("__closure__", closure_type) +: args, retType, body, List()), env) + + ret += s"${varc.next()} = bitcast $closure_llvm* $closure_loc to $closure_llvm*\n" + (ret, closure_type) } case Expr.Nothing() => ("", Type.Undefined()); case Expr.Compiled(code, retType, _) => (code, retType); @@ -315,6 +335,7 @@ class ToAssembly(currentFile: String) { val alocLoc = varc.last(); (aloc, alocLoc) } + //def set_struct_elem(struct_type, struct_loc, elem_type, elem_loc, elem_idx) def alloc_struct_with_args(args: List[Expr], env: Env): (String, List[Type]) = ??? //TODO add line awareness for error reporting private def convertBlock(lines: List[Expr], env: Env): (String, Type) = { @@ -613,7 +634,32 @@ class ToAssembly(currentFile: String) { s"store ${Type.toLLVM(x.varType)} %Input.${x.name}, ${Type.toLLVM(x.varType)}* %${x.name}\n").mkString //, align ${arraySizeFromType(x.varType)} //TODO might want to handle name shadowing here - body += "%dummy = alloca i32\n" + if (lambdaMode) { + if (info.args.isEmpty) throw new Exception("lambda does not have closure") + val (closure_type, closure_env) = info.args.head match { + case InputVar(_, Type.Closure(f, closure_env)) => (Type.Closure(f,closure_env), closure_env) + case _ => throw new Exception("lambda does not have closure") + } + var lambda_entry = "" + val env_type_llvm = s"{${closure_env.map(x => Type.toLLVM(x._2)).mkString(",")}}" + //lambda_entry += s"${varc.next()} = getelementptr inbounds ${Type.toLLVM(closure_type)}, ${Type.toLLVM(closure_type)}* %__closure__, i32 1\n" + //+ s"${varc.next()} = load $env_type_llvm*, $env_type_llvm** ${varc.secondLast()}\n" + val env_loc = varc.last() + + lambda_entry += closure_env.zipWithIndex.map { case ((var_name, var_type), idx) => { + val var_llvm = Type.toLLVM(var_type) + s"${varc.next()} = load ${Type.toLLVM(closure_type)}*, ${Type.toLLVM(closure_type)}** %__closure__" + + s"${varc.next()} = getelementptr inbounds ${Type.toLLVM(closure_type)}, ${Type.toLLVM(closure_type)}* ${varc.secondLast()}, i32 ${1+idx}\n"+ + s"${varc.next()} = load $var_llvm, $var_llvm* ${varc.secondLast()}\n"+ + s"${var_name} = alloca $var_llvm\n" + + s"store $var_llvm ${varc.last()}, $var_llvm* ${var_name}\n" + } + }.mkString("") + + body += lambda_entry + } + + //body += "%dummy = alloca i32\n" body += convert(function.body, newEnv)._1 if (info.retType == Type.Undefined()) body += "ret void\n" @@ -700,18 +746,23 @@ class ToAssembly(currentFile: String) { } def callLambda(input: Expr, args: List[Expr], env: Env): (String, Type) = convertLoc(input, env) match { - case (code, Type.Function(argTypes, retType), f_loc) => { + case (code, closure_type@Type.Closure(func_type@Type.Function(argTypes, retType), closure_env), closure_loc) => { val argRet = args.map(arg => convertLoc(arg, env)) - var ret = code; + var ret = "\t;call lambda\n" + code; ret += argRet.map(x => x._1).mkString if (argTypes.length != args.length) throw new Exception(s"wrong number of arguments: expected ${argTypes.length}, got ${args.length}"); (argRet.map(x=>x._2) zip argTypes).foreach( x=> { if (x._1!=x._2) throw new Exception(s"Wrong argument for function: expected ${x._2}, got ${x._1}"); }) - - val argsString = argRet.map(x=>s"${Type.toLLVM(x._2)} ${x._3}").mkString(", ") - val argTypeString = argTypes.map(x => Type.toLLVM(x)).mkString(", ") + val closure_type_llvm = Type.toLLVM(closure_type) + ret += s"${varc.next()} = getelementptr inbounds $closure_type_llvm, $closure_type_llvm* $closure_loc, i32 0\n" + + s"${varc.next()} = load ${Type.toLLVM(func_type)}, ${Type.toLLVM(func_type)}* ${varc.secondLast()}\n" + val f_loc = varc.last() + + //TODO right now whole closure is passed, could be only environment + val argsString = (s"$closure_type_llvm $closure_loc" +: argRet.map(x=>s"${Type.toLLVM(x._2)} ${x._3}")).mkString(", ") + val argTypeString = (closure_type_llvm +: argTypes.map(x => Type.toLLVM(x))).mkString(", ") if (retType != Type.Undefined()) ret += s"${varc.next()} = "; ret += s"call ${Type.toLLVM(retType)} ($argTypeString) $f_loc($argsString)\n" (ret, retType) From ca46ec9a3bf5943019a629184fdbbad461c98e1a Mon Sep 17 00:00:00 2001 From: Pijus Krisiukenas Date: Tue, 16 Jan 2024 19:34:35 +0200 Subject: [PATCH 110/112] recursive lambdas --- app/src/main/scala/posharp/ToAssembly.scala | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/app/src/main/scala/posharp/ToAssembly.scala b/app/src/main/scala/posharp/ToAssembly.scala index 455f5b8..debf7a1 100644 --- a/app/src/main/scala/posharp/ToAssembly.scala +++ b/app/src/main/scala/posharp/ToAssembly.scala @@ -286,7 +286,7 @@ class ToAssembly(currentFile: String) { val (heap_code, closure_loc) = allocate_on_heap(closure_type) ret += heap_code - val info = FunctionInfo(label, "", InputVar("__closure__", closure_type) +: args, retType, List()) + val info = FunctionInfo(label, "", InputVar("__current_function__", closure_type) +: args, retType, List()) functions = functions :+ info val func_name = fNameSignature(info) @@ -307,7 +307,7 @@ class ToAssembly(currentFile: String) { ret }).mkString("") - lambdas = lambdas :+ (Expr.Func(label, InputVar("__closure__", closure_type) +: args, retType, body, List()), env) + lambdas = lambdas :+ (Expr.Func(label, InputVar("__current_function__", closure_type) +: args, retType, body, List()), env) ret += s"${varc.next()} = bitcast $closure_llvm* $closure_loc to $closure_llvm*\n" (ret, closure_type) @@ -502,7 +502,7 @@ class ToAssembly(currentFile: String) { Expr.Import(formatFName(user_imp.file)+"_"+user_imp.toImport, user_imp.file) else user_imp - def change_file(name: String) = name.replace(formatFName(imp.file)+"_", file_prefix+"_") + def change_file(name: String) = name.replaceFirst(formatFName(imp.file)+"_", file_prefix+"_") if (!otherFiles.contains(imp.file)) throw new Exception(s"file \"${imp.file}\" could not be imported"); val top = otherFiles(imp.file) var ret = "" @@ -628,7 +628,7 @@ class ToAssembly(currentFile: String) { var ret = "" if (info.name == file_prefix+"_main") ret += main_alias ret += s"define $addPrivate${Type.toLLVM(info.retType)} @${fname}($args) #0 {\n" - val newEnv = upperScope ++ info.args.map(x => (x.name, Variable(s"%${x.name}", x.varType))).toMap //Variable(s"%${x.name}.${varc.extra()}" + var newEnv = upperScope ++ info.args.map(x => (x.name, Variable(s"%${x.name}", x.varType))).toMap //Variable(s"%${x.name}.${varc.extra()}" var body = info.args.map(x => s"%${x.name} = alloca ${Type.toLLVM(x.varType)}, align 64\n" + //, align ${arraySizeFromType(x.varType)} s"store ${Type.toLLVM(x.varType)} %Input.${x.name}, ${Type.toLLVM(x.varType)}* %${x.name}\n").mkString //, align ${arraySizeFromType(x.varType)} @@ -641,14 +641,10 @@ class ToAssembly(currentFile: String) { case _ => throw new Exception("lambda does not have closure") } var lambda_entry = "" - val env_type_llvm = s"{${closure_env.map(x => Type.toLLVM(x._2)).mkString(",")}}" - //lambda_entry += s"${varc.next()} = getelementptr inbounds ${Type.toLLVM(closure_type)}, ${Type.toLLVM(closure_type)}* %__closure__, i32 1\n" - //+ s"${varc.next()} = load $env_type_llvm*, $env_type_llvm** ${varc.secondLast()}\n" - val env_loc = varc.last() lambda_entry += closure_env.zipWithIndex.map { case ((var_name, var_type), idx) => { val var_llvm = Type.toLLVM(var_type) - s"${varc.next()} = load ${Type.toLLVM(closure_type)}*, ${Type.toLLVM(closure_type)}** %__closure__" + + s"${varc.next()} = load ${Type.toLLVM(closure_type)}*, ${Type.toLLVM(closure_type)}** %__current_function__" + s"${varc.next()} = getelementptr inbounds ${Type.toLLVM(closure_type)}, ${Type.toLLVM(closure_type)}* ${varc.secondLast()}, i32 ${1+idx}\n"+ s"${varc.next()} = load $var_llvm, $var_llvm* ${varc.secondLast()}\n"+ s"${var_name} = alloca $var_llvm\n" + @@ -656,6 +652,8 @@ class ToAssembly(currentFile: String) { } }.mkString("") + newEnv = newEnv + ("__current_function__" -> Variable(s"%__current_function__", closure_type)) + body += lambda_entry } From 4320a185978b21ba66b75daea4d08f1bdc3e643b Mon Sep 17 00:00:00 2001 From: Pijus Krisiukenas Date: Mon, 12 Feb 2024 22:30:50 +0200 Subject: [PATCH 111/112] cleanup --- README.md | 9 +- app/src/main/scala/posharp/Parser.scala | 6 +- app/src/main/scala/posharp/ToAssembly.scala | 244 +++++++------------- docs/examples.txt | 12 + docs/tasklist.md | 2 + 5 files changed, 102 insertions(+), 171 deletions(-) diff --git a/README.md b/README.md index b3ee55c..82ef195 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,8 @@ def fib(n: int): int { * Objects * runtime exceptions * multiple files +* lambda functions +* Generics ### To do
@@ -161,19 +163,16 @@ def fib(n: int): int { #### Major * Fully functional import/export system -* Tuples * Extension methods -* lambda functions -* Generics -* Object inheritance -* library functions * Garbage collector/manual memory * packages * File i/o +* library functions * Optimisation #### Minor +* Tuples * typeof * Structs * ref/out diff --git a/app/src/main/scala/posharp/Parser.scala b/app/src/main/scala/posharp/Parser.scala index 4a9149e..52dcec3 100644 --- a/app/src/main/scala/posharp/Parser.scala +++ b/app/src/main/scala/posharp/Parser.scala @@ -83,16 +83,16 @@ object Parser { case (ident, None) => Expr.DefVal(ident.name, Type.Undefined()) } - def accessVar[$: P]: P[Expr] = P(ident ~ ((".".! ~ ident ~ "(".! ~ prefixExpr.rep(sep = ",") ~ ")") | (".".! ~ ident) | ("[".! ~/ prefixExpr ~ "]")).rep) + def accessVar[$: P]: P[Expr] = P(ident ~ ((".".! ~ ident ~ NoCut(templateTypes).? ~ "(".! ~ prefixExpr.rep(sep = ",") ~ ")") | (".".! ~ ident) | ("[".! ~/ prefixExpr ~ "]")).rep) .map { case (start, acs) => acs.foldLeft(start: Expr)((acc, v) => v match { case (".", Expr.Ident(ident)) => GetProperty(acc, ident) case ("[", index: Expr) => Expr.GetArray(acc, index) //TODO template functions not handled - case (".", Expr.Ident(ident), "(", args: List[Expr]) => { + case (".", Expr.Ident(ident), templates: Option[List[Type]], "(", args: List[Expr]) => { //val callf_prefix = if(prefix.nonEmpty) prefix else file_name //callf_prefix+"_"+ - Expr.CallObjFunc(acc, Expr.CallF(ident, args, List())) + Expr.CallObjFunc(acc, Expr.CallF(ident, args, templates.getOrElse(List()))) }//throw new NotImplementedException("") case x => throw new ParseException(s"bad var access: $x"); }) diff --git a/app/src/main/scala/posharp/ToAssembly.scala b/app/src/main/scala/posharp/ToAssembly.scala index debf7a1..44a5d3e 100644 --- a/app/src/main/scala/posharp/ToAssembly.scala +++ b/app/src/main/scala/posharp/ToAssembly.scala @@ -3,6 +3,7 @@ package posharp import posharp.ToAssembly.{EnumInfo, FileDeclaration, FunctionInfo, InterfaceInfo, Variable} import posharp.Type.{UserType, defaultValue, shortS, toLLVM} +import javax.xml.transform.Templates import scala.io.AnsiColor class ToAssembly(currentFile: String) { @@ -193,55 +194,7 @@ class ToAssembly(currentFile: String) { case (code, t@Type.StaticInterface(props, funcs), loc) => callObjFunction(Expr.Compiled(code, t, loc), func, props, funcs, isStatic = true, env) case (_, t, _) => throw new Exception(s"Expected a interface, got ${t}") } - case Expr.InstantiateInterface(name, values, templates) => { - if (templates.nonEmpty) interfaces.find(x=>x.name == name && x.templates == templates) match { - case Some(intf) => {} - case None => interfaces.find(x=>x.name == name && isTemplateInterface(x)) match { - case Some(intf) => { - //val classT = Type.toLLVM(Type.Interface(intf.name, intf.args, intf.funcs, templates)) // s"%Class.$name" - - //TODO full error message - val template_mappings = intf.templates.zip(templates) - template_mappings.groupBy(x => x._1).filter(x => Set(x._2).toList.length > 1).foreach(x => throw new Exception(s"Template interface input types conflicting")) - //println(template_mappings) - val replace_func = replaceWithMappingFunc(template_mappings) - - val intf_expr = templateInterfaces.find(x => x.name == name && intf.templates == x.templates).get - //func_expr = Expr.Func(func_expr.name,func_expr.argNames,func_expr.retType,func_expr.body,func_expr.templates) - //templateFunctionInstances = templateFunctionInstances :+ replaceType(func_expr, replace_func).asInstanceOf[Expr.Func] - val new_intf_expr = replaceType(intf_expr, replace_func).asInstanceOf[Expr.DefineInterface] - templateInterfaceInstances = templateInterfaceInstances :+ new_intf_expr - val newInterfaceInfo = traverseTypeTree(intf, replace_func) - interfaces = interfaces :+ newInterfaceInfo - - functions = functions ::: addPrefixToFunctions(intf.name, newInterfaceInfo.funcs) - templateFunctionInstances = templateFunctionInstances ::: addPrefixToFunctions(intf.name, new_intf_expr.functions) - } - case None => throw new Exception(s"no interface with name \"$name\" defined") - } - } - interfaces.find(x=>x.name == name && x.templates == templates) match { - case Some(intf) => { - val classT = toLLVM(intf) // s"%Class.$name" - - /* - var aloc = ""; - aloc += s"${varc.next()} = getelementptr $classT, $classT* null, i32 1\n" + s"${varc.next()} = ptrtoint $classT** ${varc.secondLast()} to i32\n" - val bytesLoc = varc.last(); - aloc += s"${varc.next()} = call ptr (i32) @malloc(i32 $bytesLoc)\n"; - val alocLoc = varc.last(); - */ - val (aloc, alocLoc) = allocate_on_heap(classT) - - val valuesCompiled = values.map(x => convertLoc(x, env)).map(x => Expr.Compiled(x._1, x._2, x._3)) - //intf.funcs.find(x => x.name == name && x.args == values) - intf.funcs.find(x => x.name == "Constructor" && x.args == values) - val func_code = interpFunction(name + "_" + "Constructor", Expr.Compiled(aloc, UserType(name, templates), alocLoc) +: valuesCompiled, List(), env)._1 - (func_code, Type.Interface(name, intf.args, intf.funcs, intf.templates)) - } - case None => throw new Exception(s"no interface with name \"$name\" defined") - } - } + case Expr.InstantiateInterface(name, values, templates) => instantiateInterface(name, values, templates, env) case Expr.Str(value) => (defineString(value), Type.Str()) case Expr.Character(value) => (s"${varc.next()} = add i8 0, ${value.toInt}\n", Type.Character()) case Expr.Convert(value, valType: Type) => (convertLoc(value, env), valType) match { @@ -251,16 +204,8 @@ class ToAssembly(currentFile: String) { case ((code, Type.Character(), loc), Type.Num()) => (code + s"${varc.next()} = trunc ${Type.toLLVM(Type.Character())} $loc to ${Type.toLLVM(Type.Num())}\n", valType) case ((code, l, _), r) => throw new Exception(s"cant convert from type ${l} to type $r") } - /* - - - // - case Expr.Str(value) => (defineArrayKnown(value.length, Type.Character(), value.map(x=>Expr.Character(x)).toList, env)._1, Type.Array(Type.Character())) - - - - */ - //TODO check if lambda already been generated + //case Expr.Str(value) => (defineArrayKnown(value.length, Type.Character(), value.map(x=>Expr.Character(x)).toList, env)._1, Type.Array(Type.Character())) + //TODO check if lambda already been generated(loops) case Expr.Lambda(args, retType, body) => { val label = "lambda_" + lambdas.size @@ -597,7 +542,7 @@ class ToAssembly(currentFile: String) { def makeUserTypesConcrete(input: Type): Type = input match { case UserType(name, templates) => interfaces.find(x=>x.name == name && x.templates == templates) match { case Some(n) => Type.Interface(name, n.args, n.funcs, n.templates) - case _ => throw new Exception (s"no interface of name $name"); + case _ => throw new Exception (s"no interface of name $name with templates $templates"); } case x => x } @@ -678,30 +623,9 @@ class ToAssembly(currentFile: String) { functions.find(x=>x.name == name) match { case Some(x) => ; case None => throw new Exception(s"function of name $name undefined"); } - if (!functions.exists(x=>x.name == name && argInputTypes == x.args.map(x=>makeUserTypesConcrete(x.varType)))) { - functions.find(x => x.name == name && argInputTypes.length == x.args.length && isTemplateFunction(x)) match { - case Some(info@FunctionInfo(p, prefix, argTypes, retType, _)) => { - - if (info.templates.length != templates.length) throw new Exception(s"Wrong template count for calling function $name") - val template_mappings = info.templates.zip(templates) //templateFunctionArgs(info, templates) - - template_mappings.groupBy(x => x._1).filter(x => Set(x._2).toList.length > 1) - .foreach(x => throw new Exception(s"Template function $name input types conflicting, received: ${x._2}")) - //println(template_mappings) - val replace_func = replaceWithMappingFunc(template_mappings) - - val func_expr = templateFunctions.find(x => x.name == name && argTypes == x.argNames).get - //func_expr = Expr.Func(func_expr.name,func_expr.argNames,func_expr.retType,func_expr.body,func_expr.templates) - templateFunctionInstances = templateFunctionInstances :+ replaceType(func_expr, replace_func).asInstanceOf[Expr.Func] - //TODO unsure if templates or input.templates is best - functions = functions :+ FunctionInfo(p, prefix, argTypes.map(x => InputVar(x.name, traverseTypeTree(x.varType, replace_func))), traverseTypeTree(retType, replace_func), info.templates) - } - case _ => () - } - } + createTemplateFunctionInstance(name, argInputTypes, templates) //functions.find(x=>x.name == name && Type.compare(argInputTypes, x.args.map(x=>makeUserTypesConcrete(x.varType)))) match { - //println(Util.prettyPrint(functions)) functions.find(x=>x.name == name && argInputTypes == x.args.map(x=>makeUserTypesConcrete(x.varType))) match { case Some(info@FunctionInfo(p, prefix, argTypes, retType, templates)) => { @@ -719,6 +643,30 @@ class ToAssembly(currentFile: String) { }; } } + def createTemplateFunctionInstance(name: String, argInputTypes: List[Type], templates: List[Type]): Unit = { + if (!functions.exists(x => x.name == name && argInputTypes == x.args.map(x => makeUserTypesConcrete(x.varType)))) { + //TODO find can fail in certaib conditions + functions.find(x => x.name == name && argInputTypes.length == x.args.length && isTemplateFunction(x)) match { + case Some(info@FunctionInfo(p, prefix, argTypes, retType, _)) => { + + if (info.templates.length != templates.length) throw new Exception(s"Wrong template count for calling function $name") + val template_mappings = info.templates.zip(templates) //templateFunctionArgs(info, templates) + + template_mappings.groupBy(x => x._1).filter(x => Set(x._2).toList.length > 1) + .foreach(x => throw new Exception(s"Template function $name input types conflicting, received: ${x._2}")) + + val replace_func = replaceWithMappingFunc(template_mappings) + //println(Util.prettyPrint(templateFunctions)) + val func_expr = templateFunctions.find(x => x.name == name && argTypes == x.argNames).get + //func_expr = Expr.Func(func_expr.name,func_expr.argNames,func_expr.retType,func_expr.body,func_expr.templates) + templateFunctionInstances = templateFunctionInstances :+ replaceType(func_expr, replace_func).asInstanceOf[Expr.Func] + //TODO unsure if templates or input.templates is best + functions = functions :+ FunctionInfo(p, prefix, argTypes.map(x => InputVar(x.name, traverseTypeTree(x.varType, replace_func))), traverseTypeTree(retType, replace_func), info.templates) + } + case _ => println(s"Warning: template for function $name not found") + } + } + } def callObjFunction(obj: Expr, func: Expr.CallF, props: List[InputVar], funcs: List[FunctionInfo], isStatic: Boolean, env: Env): (String, Type) = { @@ -734,7 +682,8 @@ class ToAssembly(currentFile: String) { interpFunction(intfName+"_"+func.name, args, func.templates, env) } case None => props.find(x=>x.name == func.name) match { - case Some(InputVar(_, Type.Function(_,_))) => { + case Some(_) => { + //Type.Function(_,_) callLambda(Expr.GetProperty(obj, func.name), func.args, env) //("",Type.Undefined()) } @@ -767,28 +716,53 @@ class ToAssembly(currentFile: String) { } case (_, x, _) => throw new Exception(s"Can not call variable of type $x"); } - /* - def callLambda(input: Expr, args: List[Expr], reg: List[String], env: Env): (String, Type) = convert(input, reg, env) match { - case (code, Type.Function(argTypes, retType)) => { - val usedReg = defaultReg.filter(x => !reg.contains(x)); - var ret = usedReg.map(x=>s"push $x\n").mkString - ret += code + s"push ${reg.head}\n" - if(argTypes.length != args.length) throw new Exception(s"wrong number of arguments: expected ${argTypes.length}, got ${args.length}"); - val argRet = (args zip argTypes).map{case (arg, argType) => convert(arg, reg, env) match { - case (argCode, t) if Type.compare(t, argType) => argCode + s"push ${reg.head}\n" - case (_, t) => throw new Exception(s"Wrong argument for function: expected $argType, got $t"); - }} - ret += argRet.mkString - ret += args.zipWithIndex.reverse.map{case (arg, index) => s"pop ${functionCallReg(index)}\n"}.mkString - ret += s"pop rax\n" - ret += s"call rax\n" - ret += s"mov ${reg.head}, rax\n" - ret += usedReg.reverse.map(x=>s"pop $x\n").mkString - (ret, retType) + + def instantiateInterface(name: String, values: List[Expr], templates: List[Type], env: Env): (String, Type) = { + if (templates.nonEmpty) interfaces.find(x => x.name == name && x.templates == templates) match { + case Some(intf) => {} + case None => declareTemplatedInterface(name, templates) + } + interfaces.find(x => x.name == name && x.templates == templates) match { + case Some(intf) => { + val classT = toLLVM(intf) // s"%Class.$name" + val (aloc, alocLoc) = allocate_on_heap(classT) + + val valuesCompiled = values.map(x => convertLoc(x, env)).map(x => Expr.Compiled(x._1, x._2, x._3)) + intf.funcs.find(x => x.name == "Constructor" && x.args == values) + val func_code = interpFunction(name + "_" + "Constructor", Expr.Compiled(aloc, UserType(name, templates), alocLoc) +: valuesCompiled, List(), env)._1 + (func_code, Type.Interface(name, intf.args, intf.funcs, intf.templates)) + } + case None => throw new Exception(s"no interface with name \"$name\" defined") } - case (_, x) => throw new Exception(s"Can not call variable of type $x"); } - */ + def declareTemplatedInterface(name: String, templates: List[Type]): Unit = { + interfaces.find(x => x.name == name && isTemplateInterface(x)) match { + case Some(intf) => { + + //TODO full error message + val template_mappings = intf.templates.zip(templates) + template_mappings.groupBy(x => x._1).filter(x => Set(x._2).toList.length > 1).foreach(x => throw new Exception(s"Template interface input types conflicting")) + //println(template_mappings) + val replace_func = replaceWithMappingFunc(template_mappings) + + val intf_expr = templateInterfaces.find(x => x.name == name && intf.templates == x.templates).get + //func_expr = Expr.Func(func_expr.name,func_expr.argNames,func_expr.retType,func_expr.body,func_expr.templates) + //templateFunctionInstances = templateFunctionInstances :+ replaceType(func_expr, replace_func).asInstanceOf[Expr.Func] + val new_intf_expr = replaceType(intf_expr, replace_func).asInstanceOf[Expr.DefineInterface] + templateInterfaceInstances = templateInterfaceInstances :+ new_intf_expr + val newInterfaceInfo = traverseTypeTree(intf, replace_func) + interfaces = interfaces :+ newInterfaceInfo + + val newTemplateFunctionInstances = addPrefixToFunctions(intf.name, new_intf_expr.functions) + templateFunctions = templateFunctions ::: newTemplateFunctionInstances.filter(x=>isTemplateFunction(x)) + + functions = functions ::: addPrefixToFunctions(intf.name, newInterfaceInfo.funcs) + templateFunctionInstances = templateFunctionInstances ::: newTemplateFunctionInstances.filter(x=> !isTemplateFunction(x)) + } + case None => throw new Exception(s"no interface with name \"$name\" defined") + } + } + def defineString(value: String): String = { val label = s"string.${stringLiterals.length}"; val n = value.length + 1; @@ -987,6 +961,7 @@ class ToAssembly(currentFile: String) { case Type.Function(args: List[Type], retType: Type) => Type.Function(args.map(x=>traverseTypeTree(x, func)), traverseTypeTree(retType, func)) //StaticInterface(properties: List[InputVar], functions: List[FunctionInfo]) case Type.UserType(name, templates) => Type.UserType(name, templates.map(x=>traverseTypeTree(x, func))) + case Type.Closure(c_func, env) => Type.Closure(traverseTypeTree(c_func, func).asInstanceOf[Type.Function], env.map(x=>(x._1, traverseTypeTree(x._2, func)))) case x => func(x) } def replaceType(input: List[InputVar], func: (Type) => Type): List[InputVar] = { @@ -999,21 +974,9 @@ class ToAssembly(currentFile: String) { def replaceType(input: Expr, func: (Type) => Type): Expr = input match { /* - case Expr.DefVal(a, varType) => Expr.DefVal(a, traverseTypeTree(varType, func)) - case Expr.DefValWithValue(variable, varType, value) => Expr.DefValWithValue(variable, traverseTypeTree(varType, func), replaceType(value, func)) - case Expr.Func(name, argNames, retType, body, templates) => Expr.Func(name, replaceType(argNames, func), traverseTypeTree(retType, func), replaceType(body, func).asInstanceOf[Expr.Block], templates) - case Expr.Block(lines) => Expr.Block(lines.map(x=>replaceType(x, func))) - case Expr.While(condition: Expr, execute: Expr.Block) => Expr.While(replaceType(condition, func), replaceType(execute,func).asInstanceOf[Expr.Block]) - case Expr.If(condition: Expr, ifTrue: Expr.Block, ifFalse: Expr) => - Expr.If(replaceType(condition, func), replaceType(ifTrue,func).asInstanceOf[Expr.Block],replaceType(ifFalse,func).asInstanceOf[Expr.Block]) - case Expr.DefineInterface(name, props, functions, templates) => - Expr.DefineInterface(name, replaceType(props, func), functions.map(x=>replaceType(x, func).asInstanceOf[Expr.Func]), - templates.map(x=>traverseTypeTree(x, func)) - ) - */ - //case Expr.DefineArray(size, elemType:Type, defaultValues: List[Expr]) => Expr.DefineArray(size, traverseTypeTree(elemType, func), defaultValues) - //case Expr.InstantiateInterface + case Expr.Func(name, argNames, retType, body, templates) => Expr.Func(name, replaceType(argNames, func), traverseTypeTree(retType, func), replaceType(body, func).asInstanceOf[Expr.Block], templates) + */ case x => { /* val param_func: (Any => Any) = new ((Any) => Any) { @@ -1032,13 +995,8 @@ class ToAssembly(currentFile: String) { case vr: InputVar => replaceType(vr, func) case in => in } - //transformCaseClass(x, param_func) applyFunctionToUnknownCaseClass(x, param_func) - //x } - - //case class Convert(value: Expr, to: Type) extends Expr - //case class Lambda(argNames: List[InputVar], retType: Type, body: Expr.Block) extends Expr } def traversalFunctionBuilder(func: Any => Any) = { @@ -1067,7 +1025,7 @@ class ToAssembly(currentFile: String) { case t@Type.T(i) => { template_mappings.find(x => t == x._1) match { case Some((_, typeToReplaceWith)) => typeToReplaceWith - case None => throw new Exception(s"T$i template type could not be replaced") + case None => println(s"Warning: T$i was not replaced");t//throw new Exception(s"T$i template type could not be replaced") } } case x => x @@ -1171,45 +1129,5 @@ class Counter { counter = pausedCounter; } } -/* -inline def transformCaseClass[A](inline obj: A): A = ${ transformCaseClassImpl('obj) } - -import scala.quoted.* -//import sourcecode.Text.generate - -//import scala.language.postfixOps -//import scala.quoted.Quotes -def transformCaseClassImpl[A: Type](objExpr: Expr[A])(using Quotes): Expr[A] = { - import quotes.reflect.* - val v: Term = objExpr.asTerm - - def transformTerm(term: Term): Term = term match { - case Inlined(_, _, expr) => transformTerm(expr) - case Block(stats, expr) => Block(stats, transformTerm(expr)) - case Apply(Select(lhs, name), args) => - Apply(Select.unique(transformTerm(lhs), name), args.map(arg => transformTerm(arg))) - case Ident(_) => - val sym = term.symbol //.asTerm - if sym.isValDef && sym.owner.isClassDef then - val fieldType = sym.tree.asInstanceOf[ValDef].tpt.tpe - fieldType.asType match { - case '[t] => '{ transformField(${ Ref(sym).asExprOf[t] }) }.asTerm - } - else term - case _ => term - } - - - val transformedTerm = transformTerm(objExpr.asTerm) - transformedTerm.asExprOf[A] -} -def transformField[A](field: A): A = { - // Example transformation logic - // Customize this as per your requirements - field match { - case _ => println(field); field - } -} -*/ diff --git a/docs/examples.txt b/docs/examples.txt index 74ebd86..24075bc 100644 --- a/docs/examples.txt +++ b/docs/examples.txt @@ -48,4 +48,16 @@ object Dynamic { }; return same; } +} + +//recursive lambda example using intrinsic __current_function__ +def recursive_lambda() { + val l = lambda (b: int): void => { + if (b>0) { + print(b); + __current_function__((b-1)); + }; + }; + + l(5); } \ No newline at end of file diff --git a/docs/tasklist.md b/docs/tasklist.md index ba7687f..51fa050 100644 --- a/docs/tasklist.md +++ b/docs/tasklist.md @@ -10,6 +10,8 @@ ## To do +* make classes also store function pointers +* lambda closure local names might cause llvm error because of varc.extra * all imported types should be nameless (i.e. not usertype) * add copy command to arrays(and maybe structs) * operator overloading/type alias ith 2 step parsing (maybe use if in parser to parse once with less dept hand then more) From 288e69ee39032b4405c2d01d5978bd5a414c9264 Mon Sep 17 00:00:00 2001 From: Pijus Krisiukenas Date: Thu, 9 May 2024 20:04:18 +0200 Subject: [PATCH 112/112] Dynamic array lib --- .gitignore | 5 +- po_src/lib/dynamic.txt | 120 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 po_src/lib/dynamic.txt diff --git a/.gitignore b/.gitignore index df855ff..c0efad9 100644 --- a/.gitignore +++ b/.gitignore @@ -168,8 +168,9 @@ gradle-app.setting # JDT-specific (Eclipse Java Development Tools) .classpath -!po_src/lib/ -po_src +app/po_src +po_src/* +!po_src/lib coverage.json pwndbg/ debugir diff --git a/po_src/lib/dynamic.txt b/po_src/lib/dynamic.txt new file mode 100644 index 0000000..c9f409f --- /dev/null +++ b/po_src/lib/dynamic.txt @@ -0,0 +1,120 @@ +/* +object Dynamic { + def Dynamic(self: Dynamic): Dynamic { + return self; + } +} +*/ + + +object Dynamic[T1] { + size: int; + allocated: int; + arr: array[T1]; + + def Constructor(self: Dynamic[T1]): Dynamic[T1] { + self.arr = array[T1][8]; + self.allocated = 8; + self.size = 0; + return self; + } + def Constructor(self: Dynamic[T1], arr: array[T1]): Dynamic[T1] { + self.Constructor(); + self.push(arr); + return self; + } + + def expand(self: Dynamic[T1], req: int) { + val total = (req + self.size); + + if(total >= self.allocated) { + val newsize = self.allocated; + while(newsize < (total+1)) { + newsize = (newsize * 2); + }; + val old = self.arr; + val narr = array[T1][newsize]; + for(val i = 0; i < self.size; i+= 1;) { + narr[i] = old[i]; + }; + self.arr = narr; + free(old); + self.allocated = newsize; + self.size = total; + } else { + self.size += req; + }; + } + def push(self: Dynamic[T1], value: T1) { + self.expand(1); + self.arr[(self.size-1)] = value; + } + def push(self: Dynamic[T1], value: array[T1]) { + val oldSize = self.size; + self.expand(value.size); + + for(val i = 0; i < value.size; i+= 1;) { + self.arr[(i + oldSize)] = value[i]; + }; + } + + def push(self: Dynamic[T1], value: Dynamic[T1]) { + val oldSize = self.size; + self.expand(value.size); + + for(val i = 0; i < value.size; i+= 1;) { + self.arr[(i + oldSize)] = value.arr[i]; + }; + } + + def concat(self: Dynamic[T1], other: Dynamic[T1]): Dynamic[T1] { + val ret = self.copy(); + ret.push(other); + return ret; + } + //filter + def get(self: Dynamic[T1], index: int): T1 { + if(index >= self.size) { + throw exception("index out of bounds"); + }; + return self.arr[index]; + } + def print_arr(self: Dynamic[T1]) { + for(val i = 0; i< self.size; i+=1;) { + print(self.arr[i]); + //print(" "); + }; + print("\n"); + } + def copy(self: Dynamic[T1]): Dynamic[T1] { //still sus cause on stack + val arr_new = new Dynamic[T1](); + + for(val i = 0; i < self.size; i+= 1;) { + arr_new.push(self.arr[i]); + }; + return arr_new; + } + def compare(self: Dynamic[T1], other: Dynamic[T1]): bool { + val same: bool = true; + if(self.size != other.size) {return false;}; + for(val i = 0; i < self.size; i+= 1;) { + if(self.get(i) != other.get(i)) {same = false;}; + }; + return same; + } + def map[T2](self: Dynamic[T1], func: func[(T1) => T2]): Dynamic[T2] { + val ret = new Dynamic[T2](); + for(val i = 0; i < self.size; i+= 1;) { + val v = func(self.arr[i]); + ret.push(v); + }; + return ret; + } + + def __add__(self: Dynamic[T1], other: Dynamic[T1]): Dynamic[T1] { + return self.concat(other); + } + def __print__(self: Dynamic[T1]) { + self.print_arr(); + } +}