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/.github/workflows/workflow.yml b/.github/workflows/workflow.yml
index 0621d4f..e6af8cb 100644
--- a/.github/workflows/workflow.yml
+++ b/.github/workflows/workflow.yml
@@ -1,66 +1,19 @@
name: Build and Test
-
on:
+ workflow_dispatch:
push:
branches:
- "master"
pull_request:
jobs:
- gradle:
- runs-on: windows-2022
- steps:
- - uses: actions/checkout@v2
-
- - name: Set up WSL
- uses: Vampire/setup-wsl@v1
-
- - 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
- shell: wsl-bash {0}
- run: |
- apt update
- apt install make nasm gcc -y
-
- - name: Run tests
- run: gradle runTests
-
-
- # 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
+ env:
+ args: -PextraArgs=export -q
+ - uses: codecov/codecov-action@v2
+ with:
+ files: coverage.json
diff --git a/.gitignore b/.gitignore
index d6cdb71..c0efad9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -142,7 +142,7 @@ project/plugins/project/
.cache
.lib/
-/compiled
+**/compiled
/.bsp
@@ -168,4 +168,9 @@ gradle-app.setting
# JDT-specific (Eclipse Java Development Tools)
.classpath
-toCompile.txt
\ No newline at end of file
+app/po_src
+po_src/*
+!po_src/lib
+coverage.json
+pwndbg/
+debugir
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..2848f05
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,17 @@
+# antoniosbarotsis/posharp-veritas
+# Packages all project dependencies
+
+FROM openjdk:17-jdk-slim
+
+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 ./llvm.sh 15
+
+RUN mv /usr/bin/llc-15 /usr/bin/llc
+
+COPY *.gradle gradle.* gradlew ./
+COPY gradle/ ./gradle/
diff --git a/Makefile b/Makefile
index d4a8761..de79495 100644
--- a/Makefile
+++ b/Makefile
@@ -1,27 +1,28 @@
TARGET_FILE = 'hello'
-all: run
+all: run_llvm
#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
+ llc $(TARGET_FILE).ll -opaque-pointers && \
+ gcc -O0 -ggdb -no-pie $(TARGET_FILE).s -o $(TARGET_FILE)
compiled/$(TARGET_FILE)
-#compile Po# using sbt and then run it, also running the generated .asm
-full: sbt run
+#compile all files in directory
+.PHONY: build_all
+build_all:
+ bash ./build.sh
+
+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/README.md b/README.md
index 73b6633..82ef195 100644
--- a/README.md
+++ b/README.md
@@ -18,20 +18,20 @@
-->
- Custom language compiler to X86_64 nasm assembly, written in Scala
+ Custom language frontend for LLVM IR, written in Scala
@@ -83,36 +83,38 @@ 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/)
+* [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/)
+* [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;
+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,
-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).
+With gradle
+* In root directory call `make full`
-With IntelliJ
-* Run `Main.scala`
-* In root directory call `make`
+[//]: # (TODO Does this still work? Probably a good idea to use gradle instead)
-With sbt
-* In root directory call `make full`
+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](./veritas/src/main/scala/README.md).
### Language specification
@@ -150,26 +152,28 @@ def fib(n: int): int {
* Strings
* Enums
* Objects
+* runtime exceptions
+* multiple files
+* lambda functions
+* Generics
### To do
#### Major
-* Generics
-* runtime exceptions
-* Object inheritance
-* lambda functions
-* library functions
-* typeof
-* Garbage collector
-* multiple files
+* Fully functional import/export system
+* Extension methods
+* Garbage collector/manual memory
* packages
* File i/o
+* library functions
* Optimisation
#### Minor
+* Tuples
+* typeof
* Structs
* ref/out
* Type alias
@@ -195,4 +199,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
deleted file mode 100644
index ec7774e..0000000
--- a/app/build.gradle
+++ /dev/null
@@ -1,50 +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'
- 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"
-}
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
new file mode 100644
index 0000000..a3ea035
--- /dev/null
+++ b/app/build.gradle.kts
@@ -0,0 +1,18 @@
+plugins {
+ scala
+ application
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ 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 {
+ mainClass.set("posharp.Main")
+}
\ 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/app/src/main/scala/scala/Definitions.scala b/app/src/main/scala/posharp/Definitions.scala
similarity index 51%
rename from app/src/main/scala/scala/Definitions.scala
rename to app/src/main/scala/posharp/Definitions.scala
index a3e4bbd..6943652 100644
--- a/app/src/main/scala/scala/Definitions.scala
+++ b/app/src/main/scala/posharp/Definitions.scala
@@ -1,6 +1,8 @@
-package scala
+package posharp
-import scala.ToAssembly.FunctionInfo
+import posharp.ToAssembly.FunctionInfo
+
+import scala.annotation.targetName
sealed trait Expr
object Expr{
@@ -26,9 +28,9 @@ 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: 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
@@ -40,12 +42,14 @@ 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 CallF(name: String, args: List[Expr]) 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], 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
@@ -61,9 +65,15 @@ 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 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
case class RawReference() extends Expr
}
@@ -75,13 +85,28 @@ object Type {
case class Character() extends 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 T1() extends Type
+ case class Str() 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
+ //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) extends Type
+ case class UserType(name: String, templates: List[Type]) extends Type
def shortS(value: Type): String = value match {
case Num() => "i";
@@ -90,26 +115,47 @@ 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 UserType(name) => name
- case T1() => "T1"
+ 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 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 {
case (a,b) if a == b => true
- case (T1(), _) => true
- case (_, T1()) => true
- case (Array(T1()), _) => true
- case (_, Array(T1())) => true
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);
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"{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._2)).mkString(",")}}}*"
+ /*
+ 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
new file mode 100644
index 0000000..8f19d52
--- /dev/null
+++ b/app/src/main/scala/posharp/Main.scala
@@ -0,0 +1,191 @@
+package posharp
+import java.io.{File, FileWriter}
+import java.nio.file.Paths
+import scala.io.{AnsiColor, Source}
+
+object Constants {
+ val FileExtension = ".txt"
+}
+//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")
+ }
+
+ (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();
+
+ 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 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 {
+ 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 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
+ |
+ | ; 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 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
+ | %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 private 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 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
+ | %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
new file mode 100644
index 0000000..52dcec3
--- /dev/null
+++ b/app/src/main/scala/posharp/Parser.scala
@@ -0,0 +1,303 @@
+package posharp
+
+import fastparse.JavaWhitespace._
+import fastparse._
+import Expr.GetProperty
+import jdk.jshell.spi.ExecutionControl.NotImplementedException
+
+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 => {
+ var func: List[Expr.Func] = List()
+ var intf: List[Expr.DefineInterface] = 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) => enumm = enumm :+ y
+ case y@Expr.Import(a, b) => imports = imports :+ y
+ }
+ Expr.TopLevel(func, intf, enumm, imports)
+ })
+
+ 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
+ case None => Type.Undefined()
+ }
+ 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))
+ 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=>
+ 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 =>
+ 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)
+ 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 =>
+ 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 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 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)
+
+ 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 ~ 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), 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, templates.getOrElse(List())))
+ }//throw new NotImplementedException("")
+ case x => throw new ParseException(s"bad var access: $x");
+ })
+ }
+
+ 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 {
+ case (variable, op, value) => {
+ val opType = op match {
+ case "=" => value
+ case "+=" => Expr.Plus(variable, value)
+ case "-=" => Expr.Minus(variable, value)
+ case "*=" => Expr.Mult(variable, value)
+ case "/=" => Expr.Div(variable, value)
+ }
+ (variable, opType) match {
+ case (id: Expr.Ident, value) => Expr.SetVal(id, value)
+ case (Expr.GetProperty(expr, ident), value) => Expr.SetInterfaceProp(expr, ident, value)
+ case (Expr.GetArray(arr, index), value) => Expr.SetArray(arr, index, value)
+ case x => throw new ParseException(s"bad var set: $x");
+ }
+
+ }
+ }
+
+ 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
+ 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))
+
+ /*
+
+ 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 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 = ",") ~ ")")
+ .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 typeArray[$: P]: P[Type] = P("array" ~/ "[" ~ typeDefNoCol ~ "]").map(x => Type.Array(x))
+ //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())))
+
+ 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.T(1);
+ case "T2" => Type.T(2);
+ }
+
+ def parens[$: P] = P("(" ~/ (binOp | prefixExpr) ~ ")")
+
+ 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 {
+ 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))
+
+ def parseBinOpList(initial: Expr, rest: List[(String, Expr)]): Expr = {
+ rest.foldLeft(initial) {
+ case (left, (operator, right)) => operator match {
+ case "+" => Expr.Plus(left, right)
+ case "-" => Expr.Minus(left, right)
+ case "*" => Expr.Mult(left, right)
+ case "/" => Expr.Div(left, right)
+ case "++" => Expr.ConcatArray(left, right)
+ }
+ }
+ }
+
+ 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 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 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))
+ case "<" => Expr.LessThan(left, right)
+ case ">" => Expr.MoreThan(left, right)
+ case "<=" => Expr.Not(Expr.MoreThan(left, right))
+ case ">=" => Expr.Not(Expr.LessThan(left, right))
+ }
+ }
+
+ 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 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) => {
+ 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 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)
+ .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.!)
+ .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 + "_"
+ 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 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 => {
+ 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 trueC[$: P]: P[Expr.True] = P("true").map(_ => Expr.True())
+
+ 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)))
+
+ 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", "lambda", "function")
+
+ def checkForReservedKeyword(input: Expr.Ident): Unit = {
+ if (reservedKeywords.contains(input.name)) throw new ParseException(s"${input.name} is a reserved keyword");
+ }
+
+ 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(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
new file mode 100644
index 0000000..44a5d3e
--- /dev/null
+++ b/app/src/main/scala/posharp/ToAssembly.scala
@@ -0,0 +1,1133 @@
+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) {
+ type Env = Map[String, Variable]
+
+
+ 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 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()
+ val file_prefix: String = formatFName(currentFile)
+
+ //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;
+ functionScope = FunctionInfo(file_prefix+"_main", "", List(), Type.Num(), List());
+
+ 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 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
+ | @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*}
+ | %Type.array.i1 = type {i32, i1*}
+ |
+ |""".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
+
+ converted += declareInterfaces(x, onlyLLVM = true)
+ converted += exportDeclarations(currentFile) + "\n"
+ converted += handleImports(x, otherFiles) + "\n"
+ converted += defineFunctions(x.functions.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, 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 }
+ |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
+ }
+
+
+ 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"${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) => {
+ 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, templates)) => ("", Type.Interface(itfName, props, funcs, templates))
+ 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", "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.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) => 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) => convertLoc(obj, env) match {
+ case(code, intf_type@Type.Interface(name, props, funcs, templates), loc) => props.find(x=>x.name == prop) match {
+ case Some(n) => {
+ val intfDec = Type.toLLVM(intf_type)
+ val idx = props.indexOf(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)
+ }
+ 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), 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, templates) => {
+ val prefix_removed = name.replaceFirst(file_prefix+"_", "")
+ if(functions.exists(x=>x.name == name)) interpFunction(name, args, templates, 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 {
+ 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) => 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 {
+ 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()))
+ //TODO check if lambda already been generated(loops)
+ case Expr.Lambda(args, retType, body) => {
+ val label = "lambda_" + lambdas.size
+
+
+
+ val func_Type = Type.Function(args.map(x=>x.varType), retType)
+ val env_loc = varc.next()
+
+ var ret = ""
+ 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"$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 $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
+ })
+ ret += arg_code.mkString("")
+ 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("__current_function__", 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"
+ 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("__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)
+ }
+ 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))
+ }
+
+ 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 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) = {
+ 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 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)}* ${look.loc}, align ${arraySizeFromType(converted._2)}\n"
+ converted._1 + set;
+ };
+ case Expr.DefVal(name, 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
+ 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 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 =
+ 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 = 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 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, "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.SetInterfaceProp(intf, prop, valueRaw) => convertLoc(intf, env) 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 = Type.toLLVM(intf_type)//s"%Class.$name"
+ val idx = props.indexOf(n);
+ var ret = code + valCode
+ //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
+ }
+ 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 (_, valType, _) => throw new Exception(s"expected an interface, got ${valType}")
+ }
+ case Expr.ThrowException(err) => {
+ val msg = AnsiColor.RED + "RuntimeException: " + err + AnsiColor.RESET + "\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(_,_,_) => 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";
+ }
+ }
+ 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")
+ }
+ if(extendLines.tail.nonEmpty) {
+ defstring += convertBlock(extendLines.tail, newenv)._1
+ }
+
+ (defstring, Type.Undefined());
+ }
+
+ 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()) => isFloat = true;
+ case (Type.Character(), Type.Character()) => ;
+ case (t1, t2) => throw new Exception(s"can not compare types of $t1 and $t2")
+ }
+ 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, x.templates))
+ }
+ 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"
+ }).mkString
+ }
+ //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))
+ }
+ private def handleImports(input: Expr.TopLevel, otherFiles: Map[String, Expr.TopLevel]): String = {
+ input.imports.map(user_imp=>{
+ 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.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 = ""
+ 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 = 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
+ //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 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 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
+ funcs.map(x => FunctionInfo(x.name, formatFName(imp.file), x.args, x.retType, x.templates))
+ } 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 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"
+ //s"@${name} = alias ${toLLVM(info)}, ${toLLVM(info)}* @${importName}\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))
+ )
+ .filter(x=> ! isTemplateFunction(x))
+ .map(info => {
+ val formatFile = formatFName(file)
+ val name = fNameSignature(info)
+ s"@${formatFile}_${name} = external alias ${toLLVM(info)}, ${toLLVM(info)}* @${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, 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 with templates $templates");
+ }
+ case x => x
+ }
+
+ 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)"
+ }
+
+ private def defineFunctions(input: List[(Expr.Func, Env)], lambdaMode: Boolean, templated: Boolean): String = {
+ input.map{ case (function, upperScope) => {
+ 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;
+
+ val fname = fNameSignature(info)
+ val args = info.args.map(x => s"${Type.toLLVM(x.varType)} %Input.${x.name}").mkString(", ")
+ 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"
+ 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)}
+ //TODO might want to handle name shadowing here
+
+ 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 = ""
+
+ 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)}** %__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" +
+ s"store $var_llvm ${varc.last()}, $var_llvm* ${var_name}\n"
+ }
+ }.mkString("")
+
+ newEnv = newEnv + ("__current_function__" -> Variable(s"%__current_function__", closure_type))
+
+ body += lambda_entry
+ }
+
+ //body += "%dummy = alloca i32\n"
+ body += convert(function.body, newEnv)._1
+
+ if (info.retType == Type.Undefined()) body += "ret void\n"
+ if (info.name == file_prefix+"_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], 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
+ 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");
+ }
+ createTemplateFunctionInstance(name, argInputTypes, templates)
+
+ //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, templates)) => {
+ 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")
+ };
+ }
+ }
+ 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) = {
+ 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, func.templates, env)
+ }
+ case None => props.find(x=>x.name == func.name) match {
+ case Some(_) => {
+ //Type.Function(_,_)
+ 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, closure_type@Type.Closure(func_type@Type.Function(argTypes, retType), closure_env), closure_loc) => {
+ val argRet = args.map(arg => convertLoc(arg, env))
+ 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 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)
+ }
+ case (_, x, _) => throw new Exception(s"Can not call variable of type $x");
+ }
+
+ 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")
+ }
+ }
+ 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;
+ var toString = value.replace("\n", "\\0A") + "\\00"
+ toString = toString.replace("\u0000", "\\00")
+
+ 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 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"
+ }
+
+ 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, ptr $arrLoc, i32 0, i32 1\n"
+ val arrStructLoc = varc.last()
+ 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
+ }
+
+ 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" //${arraySizeFromType(arrType)}
+ (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()}, align ${arraySizeFromType(arrType)}\n"
+ ret
+ }
+ }
+ def getArraySize(arr: Expr, env: Env): (String, Type) = convertLoc(arr, env) match {
+ case (code, Type.Array(arrType), loc) => {
+ val Tsig = "i32"
+ 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()}, align 4\n"
+ (ret, Type.Num())
+ }
+ case (_, varType, _) => throw new Exception(s"trying to access variable ${arr} as an array, has type $varType")
+ }
+
+ def defineArray(sizeLoc: String, setElemType:Type, defaultValues: List[Expr], env: Env): (String, Type) = {
+ var elemType: Type = setElemType;
+ 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")
+ converted
+ })
+
+ val array_elem_size = arraySizeFromType(elemType);
+ val Tsig = Type.toLLVM(elemType)
+ val arrTC = s"%Type.array.$Tsig"
+ 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*\n"
+ 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" //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 4\n"
+ ret += s"${arrStructPointer} = getelementptr $arrTC, $arrTC* $structLoc, i32 0, i32 1\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)
+ }}.mkString;
+
+ //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 {
+ case Type.Undefined() => 8
+ case Type.Character() => 1
+ case Type.Num() => 4
+ case Type.NumFloat() => 8
+ case Type.Function(_,_) => 8
+ case Type.T(_) => 8
+ case _ => 8
+ }
+
+ def lookup(tofind: String, env: Env): (String, Variable) = {
+ 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 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, loc: String, varType: Type, env: Env) : Env = {
+ if(env.contains(name)) throw new Exception(s"variable \"${name}\" already defined")
+ env + (name -> Variable(loc, varType))
+ }
+
+ def convertLoc(input: Expr, env: Env): (String, Type, String) = {
+ 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();
+ val c = varc.counter
+ val ret = convert(input, env)._2
+ varc.unpause();
+ varc.counter = c;
+ ret
+ }
+
+
+ def isTemplateFunction(input: Expr.Func): Boolean = {
+ //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
+ }
+
+ 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 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)),
+ 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 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] = {
+ 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.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) {
+ 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 _ => 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
+ }
+ applyFunctionToUnknownCaseClass(x, param_func)
+ }
+ }
+
+ 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))
+ })
+
+ _class.getConstructors()(0).newInstance(fields:_*).asInstanceOf[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 => println(s"Warning: T$i was not replaced");t//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());
+ }
+ 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(), 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)), 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\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)
+ converted._2 match {
+ 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(_,_,_,_) => 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";
+
+ 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")
+ }
+ }
+
+}
+
+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/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/app/src/main/scala/scala/Main.scala b/app/src/main/scala/scala/Main.scala
deleted file mode 100644
index f7c7812..0000000
--- a/app/src/main/scala/scala/Main.scala
+++ /dev/null
@@ -1,38 +0,0 @@
-package scala
-
-import java.io.{File, FileWriter}
-import scala.io.Source
-
-
-object Main extends App {
- var inputFile = "toCompile.txt";
- if (args.length > 0) {
- inputFile = args(0)
- }
- 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 writeToFile(input: String, directoryPath: String, filename: String): Unit = {
- val directory = new File(directoryPath);
- if (!directory.exists()) directory.mkdir();
-
- val fileWriter = new FileWriter(new File(directoryPath+filename))
- fileWriter.write(input)
- fileWriter.close()
- }
- def readFile(directoryPath: String, filename: String): String = {
- val source = Source.fromFile(new File(directoryPath+filename))
- val codetxt = source.mkString
- source.close()
- codetxt
- }
-}
-
-
-
diff --git a/app/src/main/scala/scala/Parser.scala b/app/src/main/scala/scala/Parser.scala
deleted file mode 100644
index 834bef0..0000000
--- a/app/src/main/scala/scala/Parser.scala
+++ /dev/null
@@ -1,302 +0,0 @@
-package scala
-
-import fastparse.JavaWhitespace._
-import fastparse._
-
-import scala.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 => {
- var func: List[Expr.Func] = List()
- var intf: List[Expr.DefineInterface] = List()
- var enum: List[Expr.DefineEnum] = 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
- }
- Expr.TopLevel(func, intf, enum)
- })
-
- def function[_: P]: P[Expr.Func] = P("def " ~/ ident ~ "(" ~/ functionArgs ~ ")" ~/ typeDef.? ~ block).map {
- case (name, args, retType, body) => {
- val ret = retType match {
- case Some(typ) => typ
- case None => Type.Undefined()
- }
- Expr.Func(name.name, args, ret, body)
- }
- }
-
- /*
- 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 " ~/ ident ~ "{" ~/ objValue ~ function.rep ~ "}").map(props =>
- Expr.DefineInterface(props._1.name, props._2.map(x => InputVar(x.name, x.varType)), props._3.toList)
- )
-
- 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 " ~ 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 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 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) |
- 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())
- }
-
- 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)
- case (".", Expr.Ident(ident), "(", args: List[Expr]) => Expr.CallObjFunc(acc, Expr.CallF(ident, args))
- case x => throw new ParseException(s"bad var access: $x");
- })
- }
-
- 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 {
- case (variable, op, value) => {
- val opType = op match {
- case "=" => value
- case "+=" => Expr.Plus(variable, value)
- case "-=" => Expr.Minus(variable, value)
- case "*=" => Expr.Mult(variable, value)
- case "/=" => Expr.Div(variable, value)
- }
- (variable, opType) match {
- case (id: Expr.Ident, value) => Expr.SetVal(id, value)
- case (Expr.GetProperty(expr, ident), value) => Expr.SetInterfaceProp(expr, ident, value)
- case (Expr.GetArray(arr, index), value) => Expr.SetArray(arr, index, value)
- case x => throw new ParseException(s"bad var set: $x");
- }
-
- }
- }
-
- /*
- 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 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 returnsArray[_: P]: P[Expr] = P(ident)
-
- 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 ~ "{" ~/ 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 typeArray[_: P]: P[Type] = P("array" ~/ "[" ~ typeBase ~ "]").map(x => Type.Array(x))
-
- 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 {
- case "int" => Type.Num();
- case "char" => Type.Character();
- case "float" => Type.NumFloat();
- case "bool" => Type.Bool();
- case "string" => Type.Array(Type.Character());
- case "void" => Type.Undefined();
- case "T1" => Type.T1();
- }
-
- def parens[_: P] = P("(" ~/ (binOp | prefixExpr) ~ ")")
-
- 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 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))
-
- 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 parseBinOpList(initial: Expr, rest: List[(String, Expr)]): Expr = {
- rest.foldLeft(initial) {
- case (left, (operator, right)) => operator match {
- case "+" => Expr.Plus(left, right)
- case "-" => Expr.Minus(left, right)
- case "*" => Expr.Mult(left, right)
- case "/" => Expr.Div(left, right)
- case "++" => Expr.ConcatArray(left, right)
- }
- }
- }
-
- 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 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 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))
- case "<" => Expr.LessThan(left, right)
- case ">" => Expr.MoreThan(left, right)
- case "<=" => Expr.Not(Expr.MoreThan(left, right))
- case ">=" => Expr.Not(Expr.LessThan(left, right))
- }
- }
-
- 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 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) => {
- 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 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 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 char[_: P]: P[Expr.Character] = P("'" ~/ AnyChar.! ~/ "'").map(x => Expr.Character(x.charAt(0)));
-
- def constant[_: P]: P[Expr] = P(trueC | falseC)
-
- def trueC[_: P]: P[Expr.True] = P("true").map(_ => Expr.True())
-
- 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 throwException[_: P]: P[Expr.ThrowException] = P("throw" ~/ "exception").map(x => Expr.ThrowException())
-
- 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")
-
- def checkForReservedKeyword(input: Expr.Ident): Unit = {
- if (reservedKeywords.contains(input.name)) throw new ParseException(s"${input.name} is a reserved keyword");
- }
-
- def parseInput(input: String): Expr = {
- val parsed = fastparse.parse(input, topLevel(_), verboseFailures = true);
- parsed match {
- case Parsed.Success(expr, n) => expr;
- case t: Parsed.Failure => {
- println(t.trace(true).longAggregateMsg); throw new ParseException("parsing fail");
- }
- case _ => throw new ParseException("parsing fail")
- }
- }
-}
diff --git a/app/src/main/scala/scala/ToAssembly.scala b/app/src/main/scala/scala/ToAssembly.scala
deleted file mode 100644
index de4cda5..0000000
--- a/app/src/main/scala/scala/ToAssembly.scala
+++ /dev/null
@@ -1,650 +0,0 @@
-package scala
-
-import scala.Type.{UserType, shortS}
-
-object ToAssembly {
- val defaultReg = List("rax", "rdi", "rsi", "rdx", "rcx", "r8", "r9", "r10", "r11")
- def convertMain(input: Expr): String = {
- /*
- 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;
- input match { case x: Expr.TopLevel => {
- declareFunctions(x);
- declareInterfaces(x);
- declareEnums(x)
- converted += defineFunctions(x.functions);
- converted += defineFunctions(x.interfaces.flatMap(intf=>
- intf.functions.map(func=>Expr.Func(intf.name + "_" + func.name, func.argNames, func.retType, func.body)))
- );
- }}
-
- //converted += convert(input, defaultReg, Map() )._1;
- 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
- }
- 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 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 {
- 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.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)
- }
- }
- 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.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"
- 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";
- (ret, Type.Interface(intf.args, intf.funcs))
- }
- 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.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, 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 => throw new Exception(s"no such function")
- }
- }
- 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.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.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 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)
- 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
- };
- 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); ""
- 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"
- 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"
- 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.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 None => free + "leave\nret\n";
- }
-
- }
- case Expr.ThrowException() => "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.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 += 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);
- (leftout._2, rightout._2) match {
- case (Type.Bool(), Type.Bool()) if !numeric => ;
- case (Type.Num(), Type.Num()) => ;
- case (Type.NumFloat(), Type.NumFloat()) => ;
- 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"
- }
- 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 = {
- 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))
- }
- 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
- }
- //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(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 => {
- 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"
- 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, 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
- }
- 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 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")
- 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"
- }
- 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.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 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 = {
- //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 lookup(tofind: String, env: Env): (String, Variable) = {
- val ret = lookupOffset(tofind, env)
- (s"[rbp-${ret.pointer}]", ret)
- }
- def lookupOffset(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 = {
- 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))
- }
-
- 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 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 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 printInterp(toPrint: Expr, env: Env): String = {
- val converted = convert(toPrint, defaultReg, 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)
-}
-
diff --git a/app/src/main/scala/test/TestExample.scala b/app/src/main/scala/test/TestExample.scala
deleted file mode 100644
index 3975df2..0000000
--- a/app/src/main/scala/test/TestExample.scala
+++ /dev/null
@@ -1,139 +0,0 @@
-import veritas.PoSharpScript
-import veritas.Veritas.Test
-
-import scala.Parser.ParseException
-import scala.language.postfixOps
-
-package test {
-
- @Test
- class TestExample {
- def runTest2(): (Boolean, String) = {
- new PoSharpScript(
- """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;
- }""")
- .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;
- }""")
- .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;
- }
- """)
- .ShouldBe("5")
- .Run()
- }
-
- def runTest6(): (Boolean, String) = {
- new PoSharpScript("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);}")
- .ShouldThrow(new ParseException(""))
- }
- def runTestBig(): (Boolean, String) = {
- new 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()
- }
- }
-}
diff --git a/app/src/main/scala/veritas/Veritas.scala b/app/src/main/scala/veritas/Veritas.scala
deleted file mode 100644
index 7be222d..0000000
--- a/app/src/main/scala/veritas/Veritas.scala
+++ /dev/null
@@ -1,208 +0,0 @@
-package veritas
-
-import org.reflections.Reflections
-import org.reflections.scanners.Scanners.TypesAnnotated
-import org.reflections.util.ConfigurationBuilder
-
-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 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
-
- def main(args: Array[String]): Unit = {
- time(() => RunTests())
- }
-
- /**
- * Times the passed runnable in ms.
- *
- * @param task The task to execute
- */
- def time(task: Runnable): Unit = {
- val now = System.currentTimeMillis
-
- task.run()
-
- val timeElapsed = System.currentTimeMillis - now
- println(s"Elapsed time: ${timeElapsed}ms")
- }
-
- /**
- * 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 = {
- val pool = Executors.newFixedThreadPool(numOfThreads)
-
- var exitCode = 0
- val out = new StringBuilder
-
- // reflection stuff
- val reflections = new Reflections(new ConfigurationBuilder()
- .forPackage("test")
- .setScanners(TypesAnnotated))
-
- // Get all annotated types from package test
- val res = reflections
- .getStore
- .get("TypesAnnotated")
- .get("scala.reflect.ScalaSignature")
- .toArray
- .filter(el => el.asInstanceOf[String].contains("test."))
- .map(el => el.asInstanceOf[String])
-
- // Get the class and instantiate it
- res.foreach(c => {
- val testClass = ScalaClassLoader(getClass.getClassLoader).tryToInitializeClass(c)
- var lastMethodName = ""
-
- def runTest(instance: AnyRef, el: Method) = {
- // 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
- }
- } 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))
- }
- }
-
- try {
- val instance = ScalaClassLoader(getClass.getClassLoader).create(c)
-
- // Run all tests in the class
- testClass.get.getMethods.filter(m =>
- m.getName.toLowerCase().contains("test")) // Filter out non-test methods
- .grouped(chunkSize) // Group in chunks
- .foreach(chunk => {
- pool.execute(() => {
- chunk.foreach(runTest(instance, _))
- })
- })
- } catch {
- case e: Exception =>
- println(s"$RED[ERROR]$RESET Could not instantiate $c\nException was: $e")
- }
- })
-
- pool.shutdown()
- pool.awaitTermination(5, TimeUnit.MINUTES)
- println(out)
-
- // Delete all files created by writeToFile and the tests
- new File("compiled")
- .listFiles
- .filter(_.isFile)
- .filter(_.getName.contains("test"))
- .foreach(el => el.delete())
-
- System.exit(exitCode)
- }
-
- def GetOutput(input: String, fileName: String): String = {
- 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 {""}).!!
-
- 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 {}
-}
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
new file mode 100644
index 0000000..9b29234
--- /dev/null
+++ b/build-llvm.sh
@@ -0,0 +1,57 @@
+#!/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)
+ 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
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..05bfdf0
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,15 @@
+plugins {
+ scala
+ application
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ implementation("org.scala-lang:scala3-library_3:3.3.1")
+ implementation("com.google.guava:guava:31.1-jre")
+ implementation("com.lihaoyi:fastparse_3:3.0.2")
+ //implementation("co.blocke:scala-reflection_3:2.0.0")
+}
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/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
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/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/Guide.md b/docs/Guide.md
index 6fc63f8..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
{
@@ -48,6 +48,7 @@ def main(): int {
* bool
* float
* array[*type*]
+* void (return nothing for functions)
### Floats
@@ -189,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/examples.txt b/docs/examples.txt
index a24b130..24075bc 100644
--- a/docs/examples.txt
+++ b/docs/examples.txt
@@ -50,32 +50,14 @@ object Dynamic {
}
}
+//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));
+ };
+ };
-//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;
+ l(5);
}
\ 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/docs/tasklist.md b/docs/tasklist.md
new file mode 100644
index 0000000..51fa050
--- /dev/null
+++ b/docs/tasklist.md
@@ -0,0 +1,39 @@
+## LLVM migration
+
+* lambdas
+
+## 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
+
+
+* 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)
+* 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
+* Allow selecting main function/file
+
+* infer self in object functions
+* 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
+* nested arrays
+* 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~~
+* ~~bug when comment at top of file~~
+* ~~allow list of . calls~~
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..ee158f2
--- /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
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
diff --git a/gradlew b/gradlew
old mode 100644
new mode 100755
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
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();
+ }
+}
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/settings.gradle b/settings.gradle
deleted file mode 100644
index 1633742..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')
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/tasklist.md b/tasklist.md
deleted file mode 100644
index 01b3975..0000000
--- a/tasklist.md
+++ /dev/null
@@ -1,21 +0,0 @@
-##To do
-
-* ~~booleans~~
-* infer self in object functions
-* static variables
-* add prinf
-* Prevent array deferencing (point to a pointer that points to array)
-* 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
-* optimise string to char array conversion
-
-
-* ~~change functions names to consider their object~~
-* ~~make self auto-pass~~
-* ~~bug when comment at top of file~~
-* ~~allow list of . calls~~
\ No newline at end of file
diff --git a/toCompile.txt b/toCompile.txt
deleted file mode 100644
index ddbc2f5..0000000
--- a/toCompile.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-def main(): int {
- print("hello world!");
-}
\ No newline at end of file
diff --git a/toast.yml b/toast.yml
new file mode 100644
index 0000000..de2a093
--- /dev/null
+++ b/toast.yml
@@ -0,0 +1,37 @@
+image: antoniosbarotsis/posharp-veritas
+command_prefix: set -e # Make Bash fail fast.
+
+tasks:
+
+ 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
+ description: "Performs a `gradle build`. Only runs in the CI environment to save time."
+ environment:
+ CI: ''
+ excluded_input_paths:
+ - 'build'
+ input_paths:
+ - '.'
+
+ test:
+ cache: false
+ command: |
+ chmod +x build-llvm.sh
+ dos2unix build-llvm.sh
+
+ ./gradlew runCoverage $args --no-daemon
+ dependencies:
+ - build
+ description: "Runs the tests and generates a coverage report."
+ environment:
+ args: ''
+ input_paths:
+ - '.'
+ output_paths:
+ - coverage.json
diff --git a/veritas/README.md b/veritas/README.md
new file mode 100644
index 0000000..3613949
--- /dev/null
+++ b/veritas/README.md
@@ -0,0 +1,79 @@
+# 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
+
+> [!WARNING]
+> The tests are no longer discovered via the annotation, instead there is a hardcoded list at the top of
+> [`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:
+
+- 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`](./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.
+
+A simple test might look like this;
+
+```scala
+@Test
+class test.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 test.TestExample {
+ def runTest2(): (Boolean, String) =
+ PoSharpScript("""def main(): int {
+ val a = 5;
+ print(a);
+ return 0;
+ }""")
+ .ShouldBe("5")
+ .Run()
+}
+```
+
+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
+
+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`.
+If you want to also generate the CodeCov JSON, run `gradle runCoverage -PextraArgs="export"`
+
+
+## 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
diff --git a/veritas/build.gradle.kts b/veritas/build.gradle.kts
new file mode 100644
index 0000000..3040a1c
--- /dev/null
+++ b/veritas/build.gradle.kts
@@ -0,0 +1,40 @@
+plugins {
+ scala
+ application
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ implementation("org.scala-lang:scala3-library_3:3.3.1")
+ implementation("org.reflections:reflections:0.10.2")
+ implementation(project(":app"))
+}
+
+application {
+ mainClass.set("core.Veritas")
+}
+
+task("runTests", JavaExec::class) {
+ mainClass.set("core.Veritas")
+ classpath = sourceSets["main"].runtimeClasspath
+
+ shouldRunAfter(":app:build")
+}
+
+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.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
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/veritas/src/main/scala/core/Coverage.scala b/veritas/src/main/scala/core/Coverage.scala
new file mode 100644
index 0000000..4583d78
--- /dev/null
+++ b/veritas/src/main/scala/core/Coverage.scala
@@ -0,0 +1,182 @@
+package core
+
+import org.reflections.Reflections
+import posharp.Expr
+
+import java.nio.file.{Files, Path}
+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
+ * the tests.
+ */
+object Coverage {
+ 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()
+ private var percentage = -1.0
+
+ /**
+ * 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[ExprUsages] = {
+ 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 => ExprUsages(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]).asScala.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.
+ *
+ * @param _expression True exports a CodeCov JSON report
+ * @return Map[ClassName, TimesUsed]
+ */
+ def CalculateCoverage(_expression: Boolean = false): Map[String, Int] = {
+ val coverages = SumCoverages(exprs)
+
+ val res = GetAllExprCaseClasses()
+ .groupBy(identity)
+ .map(el => el._1 -> 0)
+ .filterNot(el => redundantExprs.contains(el._1))
+
+ percentage = ((coverages.size / GetAllExprCaseClasses().size.toDouble) * 100).round.toDouble
+
+ println(s"==== Covered $percentage% of Expr case classes ====\n")
+
+ val output = ListMap.from((res ++ coverages).toSeq.sortBy(_._2))
+
+ if (_expression)
+ CreateCodeCovReport(output)
+
+ output
+ }
+
+ /**
+ * 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[ExprUsages]]): Map[String, Int] = {
+ args
+ .flatten
+ .map(el => (el.Expression, el.TimesUsed))
+ .groupBy(_._1)
+ .map(el => el._2.reduce((op, x) => (op._1, op._2 + x._2)))
+ }
+
+ /**
+ * 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
+ }
+
+ /**
+ * 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[ExprUsagesLine] = List()
+
+ 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
+ // you never know.
+ val objStart = txt.asScala.indexWhere(_.contains("object Expr"))
+
+ cov.foreach(el => output = output :+
+ ExprUsagesLine(el._1, el._2,
+ txt.asScala
+ .drop(objStart)
+ .indexWhere(line => ExtractClassName(line) == el._1) + objStart + 1
+ ))
+
+ 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
+
+ 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.asJava.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 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
diff --git a/veritas/src/main/scala/core/FileHelpers.scala b/veritas/src/main/scala/core/FileHelpers.scala
new file mode 100644
index 0000000..873e273
--- /dev/null
+++ b/veritas/src/main/scala/core/FileHelpers.scala
@@ -0,0 +1,59 @@
+package core
+
+import posharp.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
new file mode 100644
index 0000000..2addade
--- /dev/null
+++ b/veritas/src/main/scala/core/PoSharp.scala
@@ -0,0 +1,82 @@
+package core
+
+import scala.language.implicitConversions
+import scala.util.{Failure, Success}
+
+protected trait IPoSharp {
+ /**
+ * 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.
+ */
+ 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) = {
+ 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")
+ }
+ }
+
+ /**
+ * Compile, run the code snippet and check assertions.
+ *
+ * @return (Passed/Failed, Failure debug message)
+ */
+ def Run(): (Boolean, String) = {
+ Veritas.Compile(code) match {
+ case Success(value) =>
+ val output = FileHelpers.GetOutput(value, FileHelpers.getTestName)
+
+ if (expected == output) {
+ (true, expected)
+ } else {
+ (false, s"Expected $expected, was $output")
+ }
+ case Failure(exception) =>
+ println(s"Compilation failed with $exception")
+ throw exception
+ }
+
+ }
+ }
+
+ /**
+ * 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/veritas/src/main/scala/core/Veritas.scala b/veritas/src/main/scala/core/Veritas.scala
new file mode 100644
index 0000000..296a102
--- /dev/null
+++ b/veritas/src/main/scala/core/Veritas.scala
@@ -0,0 +1,181 @@
+package core
+
+import core.FileHelpers.deleteTestArtifacts
+import org.reflections.Reflections
+import org.reflections.scanners.Scanners.TypesAnnotated
+import org.reflections.util.ConfigurationBuilder
+
+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.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
+ 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
+
+ time(() => exitCode = RunTests())
+
+ System.exit(exitCode)
+ }
+
+ /**
+ * Times the passed runnable in ms.
+ *
+ * @param task The task to execute
+ */
+ def time(task: Runnable): Unit = {
+ val now = System.currentTimeMillis
+
+ task.run()
+
+ val timeElapsed = System.currentTimeMillis - now
+ println(s"Elapsed time: ${timeElapsed}ms")
+ }
+
+ /**
+ * 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(): Int = {
+ val pool = Executors.newFixedThreadPool(numOfThreads)
+
+ var exitCode = 0
+ val out = new mutable.StringBuilder
+ out.append('\n')
+
+ // reflection stuff
+ val reflections = new Reflections(new ConfigurationBuilder()
+ .forPackage("test")
+ .setScanners(TypesAnnotated))
+
+ println()
+
+ // Get the class and instantiate it
+ CLASSES_TO_TEST.foreach(c => {
+ val testClass = Class.forName(c).getConstructor().newInstance().getClass
+
+ var lastMethodName = ""
+
+ def runTest(instance: AnyRef, tests: Array[Method]): Unit = {
+ // Put output here until all tests are done to avoid using synchronized
+ val chunkedOut = new mutable.StringBuilder
+
+ // Catches invalid tests (say main is missing from the code snippet)
+ tests.foreach(el => {
+ // Catches invalid tests (say main is missing from the code snippet)
+ try {
+ lastMethodName = el.getName
+
+ 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 {
+ chunkedOut.append(s"$RED[FAILED]$RESET: ${el.getName} | $actual\n")
+ exitCode = 1
+ }
+ } catch {
+ 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
+ }
+ })
+
+ // Add to actual string builder
+ this.synchronized(out.append(chunkedOut.toString))
+ }
+
+ try {
+ val instance = Class.forName(c).getConstructor().newInstance().asInstanceOf[AnyRef]
+
+ // Run all tests in the class
+ 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))
+ })
+ } catch {
+ case e: Exception =>
+ println(s"$RED[ERROR]$RESET Could not instantiate $c\nException was: $e")
+ }
+ })
+
+ pool.shutdown()
+ pool.awaitTermination(5, TimeUnit.MINUTES)
+ println(out)
+ println()
+
+ if (calculateCoverage)
+ cov.CalculateCoverage(exportCoverage)
+
+ deleteTestArtifacts()
+
+ exitCode
+ }
+
+ /**
+ * Parses and compiles the code to asm
+ *
+ * @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, "file_name")
+ val something = new ToAssembly("file_name")
+ something.declarationPass(parsed)
+
+ if (calculateCoverage)
+ cov.AddCoverage(parsed)
+
+ this.synchronized(Success(something.convertMain(parsed, Map[String, Expr.TopLevel]())))
+ } catch {
+ case e: Exception => Failure(e)
+ }
+ }
+
+ class Test extends scala.annotation.ConstantAnnotation {}
+
+ case class InvalidReturnTypeException(msg: String) extends RuntimeException(msg)
+}
diff --git a/veritas/src/main/scala/test/TestExample.scala b/veritas/src/main/scala/test/TestExample.scala
new file mode 100644
index 0000000..fe1ba83
--- /dev/null
+++ b/veritas/src/main/scala/test/TestExample.scala
@@ -0,0 +1,335 @@
+package test
+
+import core.PoSharp.PoSharpImplicit
+import core.Veritas.Test
+import posharp.Parser.ParseException
+
+import scala.language.postfixOps
+
+
+@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 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 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 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 runTestGenericInterface1(): (Boolean, String) =
+ """object Test[T1] {
+ | toPrint: T1;
+ | def Constructor(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) = {
+ """
+ object Dynamic {
+ size: int;
+ allocated: int;
+ arr: array[char];
+
+ def Constructor(self: Dynamic): Dynamic {
+ self.arr = array[char][8];
+ self.allocated = 8;
+ self.size = 0;
+ return self;
+ }
+ def Constructor(self: Dynamic, arr: array[char]): Dynamic {
+ self.Constructor();
+ 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: 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 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;) {
+ 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;
+ 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 main(): int {
+
+ val a = new Dynamic();
+ a.push(array('a', 'b', 'c'));
+ val b = new Dynamic(array('d', 'e', 'f'));
+ print(a+b);
+ }
+ """
+ .ShouldBe("abcdef")
+ .Run()
+ }
+
+ def runTestGenericInterfaceBig1(): (Boolean, String) = {
+ """
+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 __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()
+ }
+}