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 @@ -->

- +

[![Contributors][contributors-shield]][contributors-url] [![Issues][issues-shield]][issues-url] [![MIT License][license-shield]][license-url] [![LinkedIn][linkedin-shield]][linkedin-url] -[![Build and Test](https://github.com/pijuskri/Po-Sharp/actions/workflows/workflow.yml/badge.svg?branch=master)](https://github.com/pijuskri/Po-Sharp/actions/workflows/workflow.yml) +[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/pijuskri/Po-Sharp/workflow.yml?label=Build%20and%20Test&style=for-the-badge)](https://github.com/pijuskri/Po-Sharp/actions/workflows/workflow.yml)

Po#

- 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: + * + * + * 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() + } +}