diff --git a/.github/workflows/unit_test.yml b/.github/workflows/unit_test.yml
index 53d616a5..2743a2a3 100644
--- a/.github/workflows/unit_test.yml
+++ b/.github/workflows/unit_test.yml
@@ -19,24 +19,24 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
- - name: Set up JDK 17
- uses: actions/setup-java@v2
- with:
- java-version: '17'
- distribution: 'adopt'
- - name: Grant execute permission for gradlew
- run: chmod +x gradlew
- - name: Build with Gradle
- uses: gradle/gradle-build-action@v2
- with:
- arguments: test -DrunExecutionTest=false # ignore execution test due to local file accessing might causing unexpected issues
- - name: Publish Test Report
- uses: mikepenz/action-junit-report@v2
- if: always() # always run even if the previous step fails
- with:
- report_paths: '**/build/test-results/test/TEST-*.xml'
- - name: Upload test report
- uses: codecov/codecov-action@v2
- with:
- files: "build/coverage/coverage.xml"
+ - uses: actions/checkout@v2
+ - name: Set up JDK 17
+ uses: actions/setup-java@v2
+ with:
+ java-version: '17'
+ distribution: 'adopt'
+ - name: Grant execute permission for gradlew
+ run: chmod +x gradlew
+ - name: Build with Gradle
+ uses: gradle/gradle-build-action@v2
+ with:
+ arguments: test -DrunExecutionTest=false # ignore execution test due to local file accessing might causing unexpected issues
+ - name: Publish Test Report
+ uses: mikepenz/action-junit-report@v2
+ if: always() # always run even if the previous step fails
+ with:
+ report_paths: '**/build/test-results/test/TEST-*.xml'
+ - name: Upload test report
+ uses: codecov/codecov-action@v2
+ with:
+ files: "build/coverage/coverage.xml"
diff --git a/.github/workflows/weekly_release.yml b/.github/workflows/weekly_release.yml
index a803b9e9..730b5c91 100644
--- a/.github/workflows/weekly_release.yml
+++ b/.github/workflows/weekly_release.yml
@@ -5,11 +5,11 @@
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
-name: CASC Weekly Release
+name: CASC Weekly Release
on:
schedule:
- - cron: "0 0 * * 1"
+ - cron: "0 0 * * 1"
workflow_dispatch:
jobs:
@@ -18,45 +18,45 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
- - name: Get current date
- id: date
- run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
- - name: Set up JDK 17
- uses: actions/setup-java@v2
- with:
- java-version: '17'
- distribution: 'adopt'
- - name: Grant execute permission for gradlew
- run: chmod +x gradlew
- - name: Build with Gradle
- run: ./gradlew build
- - name: Jar built files with Gradle
- run: ./gradlew jar
- - name: Create release
- uses: actions/create-release@v1
- id: create_release
- with:
- draft: false
- prerelease: false
- release_name: ${{ steps.version.outputs.version }}
- tag_name: ${{ steps.date.outputs.date }}
- env:
- GITHUB_TOKEN: ${{ github.token }}
- - name: Upload Jar file to release
- uses: actions/upload-release-asset@v1
- env:
- GITHUB_TOKEN: ${{ github.token }}
- with:
- upload_url: ${{ steps.create_release.outputs.upload_url }}
- asset_path: ./build/libs/CASC-0.0.1.jar
- asset_name: CASC-Weekly-${{ steps.date.outputs.date }}.jar
- asset_content_type: application/zip
- - name: Publish Jar file
- uses: actions/upload-artifact@v2.3.1
- with:
- # Artifact name
- name: CASC-Weekly-${{ steps.date.outputs.date }}.jar
- # A file, directory or wildcard pattern that describes what to upload
- path: ./build/libs/CASC-0.0.1.jar
- # The desired behavior if no files are found using the provided path.
+ - uses: actions/checkout@v2
+ - name: Get current date
+ id: date
+ run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
+ - name: Set up JDK 17
+ uses: actions/setup-java@v2
+ with:
+ java-version: '17'
+ distribution: 'adopt'
+ - name: Grant execute permission for gradlew
+ run: chmod +x gradlew
+ - name: Build with Gradle
+ run: ./gradlew build
+ - name: Jar built files with Gradle
+ run: ./gradlew jar
+ - name: Create release
+ uses: actions/create-release@v1
+ id: create_release
+ with:
+ draft: false
+ prerelease: false
+ release_name: ${{ steps.version.outputs.version }}
+ tag_name: ${{ steps.date.outputs.date }}
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+ - name: Upload Jar file to release
+ uses: actions/upload-release-asset@v1
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+ with:
+ upload_url: ${{ steps.create_release.outputs.upload_url }}
+ asset_path: ./build/libs/CASC-0.0.1.jar
+ asset_name: CASC-Weekly-${{ steps.date.outputs.date }}.jar
+ asset_content_type: application/zip
+ - name: Publish Jar file
+ uses: actions/upload-artifact@v2.3.1
+ with:
+ # Artifact name
+ name: CASC-Weekly-${{ steps.date.outputs.date }}.jar
+ # A file, directory or wildcard pattern that describes what to upload
+ path: ./build/libs/CASC-0.0.1.jar
+ # The desired behavior if no files are found using the provided path.
diff --git a/TODO.md b/TODO.md
index 171eefe7..5be86201 100644
--- a/TODO.md
+++ b/TODO.md
@@ -1,8 +1,11 @@
## TODO List
+
``There is no precedence for this list, all tasks are currently invloving.``
To see release-based TODOs, checkout GitHub repository's project
+
### Category
+
#### Language Spec
##### TO BE INCLUDED IN UNIT TEST
@@ -30,18 +33,26 @@ To see release-based TODOs, checkout GitHub repository's project
##### IMPLEMENTATION IN PROGRESS
- [ ] loops
-- - [x] for loop (Java loop)[1](#f1)
-- - [ ] range for loop[2](#f2)
-- - [ ] while loop
-- - [ ] do-while loop
+-
+ - [x] for loop (Java loop)[1](#f1)
+-
+ - [ ] range for loop[2](#f2)
+-
+ - [ ] while loop
+-
+ - [ ] do-while loop
- [ ] label
- [ ] generic
- [ ] .class
- [ ] inheritance
-- - [x] class
-- - [ ] abstract class
-- - [ ] interface
-- - [x] `super` call
+-
+ - [x] class
+-
+ - [ ] abstract class
+-
+ - [ ] interface
+-
+ - [x] `super` call
### Compiler Unit
@@ -49,5 +60,6 @@ To see release-based TODOs, checkout GitHub repository's project
- [ ] more customizable compilation arguments(?)
#### FOOTNOTES
+
1. Most likely will remove after range for loop is done. Due to syntax parsing collision. [↩](#a1)
2. Requires inheritance fully supported. [↩](#a2)
diff --git a/src/main/kotlin/org/casc/lang/asm/BytecodeClassLoader.kt b/src/main/kotlin/org/casc/lang/asm/BytecodeClassLoader.kt
index a06b6f09..457485f8 100644
--- a/src/main/kotlin/org/casc/lang/asm/BytecodeClassLoader.kt
+++ b/src/main/kotlin/org/casc/lang/asm/BytecodeClassLoader.kt
@@ -1,8 +1,7 @@
package org.casc.lang.asm
-import java.net.URLClassLoader
-import java.lang.ClassNotFoundException
import java.net.URL
+import java.net.URLClassLoader
class BytecodeClassLoader(urls: Array?, parent: ClassLoader?) : URLClassLoader(urls, parent) {
fun defineClass(className: String?, bytecode: ByteArray): Class<*> {
diff --git a/src/main/kotlin/org/casc/lang/asm/CommonClassWriter.kt b/src/main/kotlin/org/casc/lang/asm/CommonClassWriter.kt
index 3a3b8050..5d6f589c 100644
--- a/src/main/kotlin/org/casc/lang/asm/CommonClassWriter.kt
+++ b/src/main/kotlin/org/casc/lang/asm/CommonClassWriter.kt
@@ -6,4 +6,10 @@ class CommonClassWriter(flags: Int, private val classLoader: ClassLoader) : Clas
override fun getClassLoader(): ClassLoader {
return classLoader
}
+
+ override fun getCommonSuperClass(type1: String?, type2: String?): String {
+ // search common type from Table
+
+ return super.getCommonSuperClass(type1, type2)
+ }
}
\ No newline at end of file
diff --git a/src/main/kotlin/org/casc/lang/ast/ClassInstance.kt b/src/main/kotlin/org/casc/lang/ast/ClassInstance.kt
index 028b2b0a..99b769c3 100644
--- a/src/main/kotlin/org/casc/lang/ast/ClassInstance.kt
+++ b/src/main/kotlin/org/casc/lang/ast/ClassInstance.kt
@@ -1,6 +1,5 @@
package org.casc.lang.ast
-import org.casc.lang.table.HasFlag
import org.casc.lang.table.Reference
import org.casc.lang.utils.getOrElse
import org.objectweb.asm.Opcodes
@@ -8,22 +7,17 @@ import org.objectweb.asm.Opcodes
data class ClassInstance(
override val packageReference: Reference?,
val accessorToken: Token?,
- val abstrToken: Token?, // Unused
+ val abstrToken: Token?,
val mutKeyword: Token?,
- val classKeyword: Token?,
+ val classKeyword: Token,
override val typeReference: Reference,
override val fields: List,
override val accessor: Accessor = Accessor.fromString(accessorToken?.literal)
-) : TypeInstance(), HasFlag {
+) : TypeInstance() {
override val flag: Int by lazy {
var flag = Opcodes.ACC_SUPER
flag += accessor.access
- flag += abstrToken.getOrElse(Opcodes.ACC_ABSTRACT)
- flag += mutKeyword.getOrElse(0, Opcodes.ACC_FINAL)
+ flag += abstrToken.getOrElse(Opcodes.ACC_ABSTRACT, mutKeyword.getOrElse(0, Opcodes.ACC_FINAL))
flag
}
-
- val parentClassReference: Reference by lazy {
- impl?.parentClassReference ?: Reference.OBJECT_TYPE_REFERENCE
- }
}
diff --git a/src/main/kotlin/org/casc/lang/ast/CompanionBlock.kt b/src/main/kotlin/org/casc/lang/ast/CompanionBlock.kt
new file mode 100644
index 00000000..40be370f
--- /dev/null
+++ b/src/main/kotlin/org/casc/lang/ast/CompanionBlock.kt
@@ -0,0 +1,3 @@
+package org.casc.lang.ast
+
+data class CompanionBlock(val compKeyword: Token, val statements: List)
diff --git a/src/main/kotlin/org/casc/lang/ast/Constructor.kt b/src/main/kotlin/org/casc/lang/ast/Constructor.kt
index 4b2dafdd..79b8e142 100644
--- a/src/main/kotlin/org/casc/lang/ast/Constructor.kt
+++ b/src/main/kotlin/org/casc/lang/ast/Constructor.kt
@@ -6,7 +6,7 @@ data class Constructor(
val ownerReference: Reference?,
var parentReference: Reference?,
val accessorToken: Token?,
- val newKeyword: Token?,
+ val newKeyword: Token,
val parameters: List,
val statements: List,
val superKeyword: Token?,
@@ -34,6 +34,7 @@ data class Constructor(
ownerType,
companion = true,
mutable = false,
+ abstract = false,
accessor,
"",
parameterTypes.mapNotNull { it },
diff --git a/src/main/kotlin/org/casc/lang/ast/Expression.kt b/src/main/kotlin/org/casc/lang/ast/Expression.kt
index bea6f05a..1fe6758e 100644
--- a/src/main/kotlin/org/casc/lang/ast/Expression.kt
+++ b/src/main/kotlin/org/casc/lang/ast/Expression.kt
@@ -5,7 +5,6 @@ import org.casc.lang.table.PrimitiveType
import org.casc.lang.table.Reference
import org.casc.lang.table.Type
import org.objectweb.asm.Opcodes
-import org.objectweb.asm.TypeReference
sealed class Expression {
companion object {
diff --git a/src/main/kotlin/org/casc/lang/ast/File.kt b/src/main/kotlin/org/casc/lang/ast/File.kt
index 67cf48c1..1ebede00 100644
--- a/src/main/kotlin/org/casc/lang/ast/File.kt
+++ b/src/main/kotlin/org/casc/lang/ast/File.kt
@@ -2,7 +2,12 @@ package org.casc.lang.ast
import org.casc.lang.table.Reference
-data class File(val path: String, val relativeFilePath: String, val usages: List, var typeInstance: TypeInstance) {
+data class File(
+ val path: String,
+ val relativeFilePath: String,
+ val usages: List,
+ var typeInstance: TypeInstance
+) {
val fileName: String by lazy {
relativeFilePath.split('\\', '/').last()
}
diff --git a/src/main/kotlin/org/casc/lang/ast/Function.kt b/src/main/kotlin/org/casc/lang/ast/Function.kt
index 2e0eaa85..2807aa44 100644
--- a/src/main/kotlin/org/casc/lang/ast/Function.kt
+++ b/src/main/kotlin/org/casc/lang/ast/Function.kt
@@ -3,7 +3,6 @@ package org.casc.lang.ast
import org.casc.lang.table.*
import org.casc.lang.utils.getOrElse
import org.objectweb.asm.Opcodes
-import java.lang.reflect.Modifier
data class Function(
val ownerReference: Reference?,
@@ -11,6 +10,7 @@ data class Function(
val ovrdKeyword: Token?,
val abstrKeyword: Token?,
val mutKeyword: Token?,
+ val fnKeyword: Token,
val selfKeyword: Token?, // determine whether function is companion
val name: Token?,
val parameters: List,
@@ -26,7 +26,7 @@ data class Function(
parameterTypes?.fold("") { s, type ->
s + type?.descriptor
}
- })${returnType?.descriptor}"
+ })${returnType?.descriptor ?: "V"}"
override val flag: Int by lazy {
var flag = accessor.access
flag += selfKeyword.getOrElse(0, Opcodes.ACC_STATIC)
@@ -35,6 +35,7 @@ data class Function(
flag += mutKeyword.getOrElse(0, Opcodes.ACC_FINAL)
flag
}
+ var abstract: Boolean = abstrKeyword != null
override fun asSignature() =
FunctionSignature(
@@ -42,6 +43,7 @@ data class Function(
ownerType,
selfKeyword == null,
mutKeyword != null,
+ abstrKeyword != null,
accessor,
name?.literal ?: "",
parameterTypes!!.mapNotNull { it },
diff --git a/src/main/kotlin/org/casc/lang/ast/Impl.kt b/src/main/kotlin/org/casc/lang/ast/Impl.kt
index 74eac57e..e33982d5 100644
--- a/src/main/kotlin/org/casc/lang/ast/Impl.kt
+++ b/src/main/kotlin/org/casc/lang/ast/Impl.kt
@@ -5,7 +5,7 @@ import org.casc.lang.table.Reference
data class Impl(
val implKeyword: Token,
val parentClassReference: Reference?,
- var companionBlock: List,
+ var companionBlock: CompanionBlock?,
var constructors: List,
var functions: List
)
diff --git a/src/main/kotlin/org/casc/lang/ast/Statement.kt b/src/main/kotlin/org/casc/lang/ast/Statement.kt
index cbd4c281..d16e3c57 100644
--- a/src/main/kotlin/org/casc/lang/ast/Statement.kt
+++ b/src/main/kotlin/org/casc/lang/ast/Statement.kt
@@ -11,7 +11,8 @@ sealed class Statement {
val expressions: List,
var indexes: MutableList = mutableListOf(),
override val pos: Position? = Position.fromMultipleAndExtend(
- *variables.firstOrNull()?.toList()?.map { it?.pos }?.toTypedArray() ?: arrayOf())
+ *variables.firstOrNull()?.toList()?.map { it?.pos }?.toTypedArray() ?: arrayOf()
+ )
?.extend(expressions.lastOrNull()?.pos)
) : Statement()
diff --git a/src/main/kotlin/org/casc/lang/ast/TraitImpl.kt b/src/main/kotlin/org/casc/lang/ast/TraitImpl.kt
index 456d125f..16847014 100644
--- a/src/main/kotlin/org/casc/lang/ast/TraitImpl.kt
+++ b/src/main/kotlin/org/casc/lang/ast/TraitImpl.kt
@@ -1,3 +1,5 @@
package org.casc.lang.ast
-data class TraitImpl(val implKeyword: Token, val functions: List)
+import org.casc.lang.table.Reference
+
+data class TraitImpl(val implKeyword: Token, val implementedTraitReference: Reference, val functions: List)
diff --git a/src/main/kotlin/org/casc/lang/ast/TraitInstance.kt b/src/main/kotlin/org/casc/lang/ast/TraitInstance.kt
index 418a44ce..0fca0e2a 100644
--- a/src/main/kotlin/org/casc/lang/ast/TraitInstance.kt
+++ b/src/main/kotlin/org/casc/lang/ast/TraitInstance.kt
@@ -1,6 +1,5 @@
package org.casc.lang.ast
-import org.casc.lang.table.HasFlag
import org.casc.lang.table.Reference
import org.objectweb.asm.Opcodes
@@ -11,7 +10,7 @@ data class TraitInstance(
override val typeReference: Reference,
override val fields: List,
override val accessor: Accessor = Accessor.fromString(accessorToken?.literal)
-) : TypeInstance(), HasFlag {
+) : TypeInstance() {
override val flag: Int by lazy {
accessor.access + Opcodes.ACC_ABSTRACT + Opcodes.ACC_INTERFACE
}
diff --git a/src/main/kotlin/org/casc/lang/ast/TypeInstance.kt b/src/main/kotlin/org/casc/lang/ast/TypeInstance.kt
index 60540c77..baa8c464 100644
--- a/src/main/kotlin/org/casc/lang/ast/TypeInstance.kt
+++ b/src/main/kotlin/org/casc/lang/ast/TypeInstance.kt
@@ -1,8 +1,9 @@
package org.casc.lang.ast
+import org.casc.lang.table.HasFlag
import org.casc.lang.table.Reference
-sealed class TypeInstance {
+sealed class TypeInstance : HasFlag {
abstract val packageReference: Reference?
abstract val typeReference: Reference
abstract val fields: List
@@ -14,4 +15,12 @@ sealed class TypeInstance {
val reference: Reference by lazy {
Reference(packageReference?.fullQualifiedPath, typeReference.fullQualifiedPath)
}
+
+ val parentClassReference: Reference by lazy {
+ impl?.parentClassReference ?: Reference.OBJECT_TYPE_REFERENCE
+ }
+
+ val traitClassReferences: Array by lazy {
+ traitImpls?.map(TraitImpl::implementedTraitReference)?.toTypedArray() ?: arrayOf()
+ }
}
\ No newline at end of file
diff --git a/src/main/kotlin/org/casc/lang/checker/Checker.kt b/src/main/kotlin/org/casc/lang/checker/Checker.kt
index 016cb5d2..dfa28cca 100644
--- a/src/main/kotlin/org/casc/lang/checker/Checker.kt
+++ b/src/main/kotlin/org/casc/lang/checker/Checker.kt
@@ -60,18 +60,18 @@ class Checker(private val preference: AbstractPreference) {
if (parentClassType != null) {
if (parentClassType !is ClassType) {
reports += Error(
- clazz.parentClassReference.pos ?: clazz.classKeyword?.pos,
+ clazz.parentClassReference.pos,
"Cannot inherit from non-class type ${parentClassType.asCASCStyle()}"
)
} else if (parentClassType != ClassType.OBJECT_TYPE && !parentClassType.mutable) {
reports += Error(
- clazz.parentClassReference.pos ?: clazz.classKeyword?.pos,
+ clazz.parentClassReference.pos,
"Cannot inherit from final class ${parentClassType.asCASCStyle()}",
"Add `mut` to class ${parentClassType.asCASCStyle()}"
)
} else if (parentClassType.type(preference)?.let { Modifier.isFinal(it.modifiers) } == true) {
reports += Error(
- clazz.parentClassReference.pos ?: clazz.classKeyword?.pos,
+ clazz.parentClassReference.pos,
"Cannot inherit from final class ${parentClassType.asCASCStyle()}",
"Add `mut` to class ${parentClassType.asCASCStyle()}"
)
@@ -96,6 +96,14 @@ class Checker(private val preference: AbstractPreference) {
}
}
+ if (clazz.traitImpls != null) {
+ clazz.traitImpls!!.forEach {
+ it.functions.forEach { function ->
+ checkFunction(function, classScope, it.implementedTraitReference)
+ }
+ }
+ }
+
return classScope
}
@@ -133,6 +141,14 @@ class Checker(private val preference: AbstractPreference) {
}
}
+ if (trait.traitImpls != null) {
+ trait.traitImpls!!.forEach {
+ it.functions.forEach { function ->
+ checkFunction(function, traitScope, it.implementedTraitReference)
+ }
+ }
+ }
+
return traitScope
}
@@ -198,7 +214,9 @@ class Checker(private val preference: AbstractPreference) {
if (topScope.usages.find { usage -> usage.fullQualifiedPath == reference.fullQualifiedPath } != null) {
// Using an already used package or class
reports += Warning(
- reference.pos, "${reference.asCASCStyle()} is already used in this context", "Consider removing this usage"
+ reference.pos,
+ "${reference.asCASCStyle()} is already used in this context",
+ "Consider removing this usage"
)
}
@@ -246,7 +264,7 @@ class Checker(private val preference: AbstractPreference) {
if (clazz.impl != null) {
val impl = clazz.impl!!
- impl.companionBlock.forEach {
+ impl.companionBlock?.statements?.forEach {
checkStatement(it, companionBlockScope, PrimitiveType.Unit)
}
@@ -267,6 +285,23 @@ class Checker(private val preference: AbstractPreference) {
}
}
}
+
+ if (clazz.traitImpls != null) {
+ for (traitImpl in clazz.traitImpls!!) {
+ traitImpl.functions.forEach {
+ if (it.statements != null) {
+ checkFunctionBody(it, Scope(classScope, isCompScope = it.selfKeyword == null))
+
+ if (!checkControlFlow(it.statements, it.returnType)) {
+ // Not all code path returns value
+ reports += Error(
+ it.name?.pos, "Not all code path returns value"
+ )
+ }
+ }
+ }
+ }
+ }
}
private fun checkTrait(file: File, trait: TraitInstance, traitScope: Scope) {
@@ -281,7 +316,7 @@ class Checker(private val preference: AbstractPreference) {
if (trait.impl != null) {
val impl = trait.impl!!
- impl.companionBlock.forEach {
+ impl.companionBlock?.statements?.forEach {
checkStatement(it, companionBlockScope, PrimitiveType.Unit)
}
@@ -298,6 +333,23 @@ class Checker(private val preference: AbstractPreference) {
}
}
}
+
+ if (trait.traitImpls != null) {
+ for (traitImpl in trait.traitImpls!!) {
+ traitImpl.functions.forEach {
+ if (it.statements != null) {
+ checkFunctionBody(it, Scope(traitScope, isCompScope = it.selfKeyword == null))
+
+ if (!checkControlFlow(it.statements, it.returnType)) {
+ // Not all code path returns value
+ reports += Error(
+ it.name?.pos, "Not all code path returns value"
+ )
+ }
+ }
+ }
+ }
+ }
}
private fun checkField(field: Field, scope: Scope): Field {
@@ -364,12 +416,19 @@ class Checker(private val preference: AbstractPreference) {
}
}
- if (validationPass) scope.registerSignature(constructor)
+ if (validationPass && !scope.registerSignature(constructor)) {
+ // Duplicate constructors
+ reports += Error(
+ constructor.newKeyword.pos,
+ "Constructor `new`(${DisplayFactory.getParametersTypePretty(constructor.parameters)}) has already declared in same context",
+ "Try modify parameters' type"
+ )
+ }
return constructor
}
- private fun checkFunction(function: Function, scope: Scope): Function {
+ private fun checkFunction(function: Function, scope: Scope, traitReference: Reference? = null): Function {
val localScope = Scope(scope)
checkIdentifierIsKeyword(function.name?.literal, function.name?.pos)
@@ -420,7 +479,7 @@ class Checker(private val preference: AbstractPreference) {
// Check is overriding parent function
val parentFunction =
- scope.findSignature(scope.parentClassPath, function.name!!.literal, function.parameterTypes ?: listOf())
+ scope.findSignature(traitReference ?: scope.parentClassPath, function.name!!.literal, function.parameterTypes ?: listOf())
if (parentFunction != null) {
if (function.ovrdKeyword == null) {
@@ -462,7 +521,14 @@ class Checker(private val preference: AbstractPreference) {
)
}
- if (validationPass) scope.registerSignature(function)
+ if (validationPass && !scope.registerSignature(function)) {
+ // Duplicate functions
+ reports += Error(
+ function.name.pos,
+ "Function ${function.name.literal}(${DisplayFactory.getParametersTypePretty(function.parameters)}) has already declared in same context",
+ "Try rename this function or modify parameters' type"
+ )
+ }
return function
}
@@ -479,7 +545,7 @@ class Checker(private val preference: AbstractPreference) {
if (superCallSignature == null) {
// No super call match
reports += Error(
- constructor.newKeyword?.pos, "Cannot find matched super call `super`(${
+ constructor.newKeyword.pos, "Cannot find matched super call `super`(${
constructor.parentConstructorArgumentsTypes.mapNotNull { it?.typeName }.joinToString()
})"
)
@@ -493,7 +559,7 @@ class Checker(private val preference: AbstractPreference) {
if (thisCallSignature == null) {
// No super call match
reports += Error(
- constructor.newKeyword?.pos, "Cannot find matched super call `this`(${
+ constructor.newKeyword.pos, "Cannot find matched super call `this`(${
constructor.parentConstructorArgumentsTypes.mapNotNull { it?.typeName }.joinToString()
})"
)
@@ -502,7 +568,7 @@ class Checker(private val preference: AbstractPreference) {
if (scope.parentClassPath != Reference.OBJECT_TYPE_REFERENCE && scope.parentClassPath != null) {
// Requires `super` call
reports += Error(
- constructor.newKeyword?.pos,
+ constructor.newKeyword.pos,
"Class ${scope.typeReference} extends class ${scope.parentClassPath} but doesn't `super` any parent class' constructor",
"Add `super` call after constructor declaration"
)
@@ -511,6 +577,7 @@ class Checker(private val preference: AbstractPreference) {
ClassType.OBJECT_TYPE,
companion = true,
mutable = false,
+ abstract = false,
Accessor.Pub,
"",
listOf(),
@@ -648,10 +715,12 @@ class Checker(private val preference: AbstractPreference) {
}
}
is BlockStatement -> {
+ val blockScope = Scope(scope)
+
statement.statements.forEachIndexed { i, it ->
checkStatement(
it!!,
- Scope(scope),
+ blockScope,
returnType,
if (i == statement.statements.size - 1) retainLastExpression else false
)
@@ -960,7 +1029,7 @@ class Checker(private val preference: AbstractPreference) {
// Check function call expression's context, e.g companion context
val ownerReference = expression.ownerReference ?: previousType?.getReference() ?: scope.typeReference
val functionSignature = scope.findSignature(
- ownerReference, expression.name!!.literal, argumentTypes
+ ownerReference, expression.name!!.literal, argumentTypes, allowAbstract = false
)
if (functionSignature == null) {
@@ -1316,17 +1385,21 @@ class Checker(private val preference: AbstractPreference) {
)
} else expression.type = targetType
} else if (originalType is ArrayType) {
- if (targetType is PrimitiveType) {
- reports += Error(
- expression.targetTypeReference?.pos,
- "Cannot cast array type `${originalType.asCASCStyle()}` into primitive type `${targetType.asCASCStyle()}`"
- )
- } else if (targetType is ClassType) {
- reports += Error(
- expression.targetTypeReference?.pos,
- "Cannot cast array type `${originalType.asCASCStyle()}` into class type `${targetType.asCASCStyle()}`"
- )
- } else expression.type = targetType
+ when (targetType) {
+ is PrimitiveType -> {
+ reports += Error(
+ expression.targetTypeReference?.pos,
+ "Cannot cast array type `${originalType.asCASCStyle()}` into primitive type `${targetType.asCASCStyle()}`"
+ )
+ }
+ is ClassType -> {
+ reports += Error(
+ expression.targetTypeReference?.pos,
+ "Cannot cast array type `${originalType.asCASCStyle()}` into class type `${targetType.asCASCStyle()}`"
+ )
+ }
+ else -> expression.type = targetType
+ }
}
expression.type
diff --git a/src/main/kotlin/org/casc/lang/compilation/AbstractPreference.kt b/src/main/kotlin/org/casc/lang/compilation/AbstractPreference.kt
index 69aed56b..e7e4122c 100644
--- a/src/main/kotlin/org/casc/lang/compilation/AbstractPreference.kt
+++ b/src/main/kotlin/org/casc/lang/compilation/AbstractPreference.kt
@@ -2,11 +2,13 @@ package org.casc.lang.compilation
import org.casc.lang.asm.BytecodeClassLoader
import java.io.File
-import java.net.URLClassLoader
abstract class AbstractPreference {
abstract var enableColor: Boolean
- open var classLoader: BytecodeClassLoader = BytecodeClassLoader(arrayOf(File(System.getProperty("user.dir"), "out").toURI().toURL()), ClassLoader.getSystemClassLoader())
+ open var classLoader: BytecodeClassLoader = BytecodeClassLoader(
+ arrayOf(File(System.getProperty("user.dir"), "out").toURI().toURL()),
+ ClassLoader.getSystemClassLoader()
+ )
abstract var sourceFile: File?
abstract var enableTiming: Boolean
var outputDir: File = File(System.getProperty("user.dir"), "out")
diff --git a/src/main/kotlin/org/casc/lang/compilation/Compilation.kt b/src/main/kotlin/org/casc/lang/compilation/Compilation.kt
index b21aee13..58d9520d 100644
--- a/src/main/kotlin/org/casc/lang/compilation/Compilation.kt
+++ b/src/main/kotlin/org/casc/lang/compilation/Compilation.kt
@@ -2,16 +2,16 @@ package org.casc.lang.compilation
import com.diogonunes.jcolor.AnsiFormat
import com.diogonunes.jcolor.Attribute
-import org.casc.lang.ast.ClassInstance
-import org.casc.lang.ast.File
-import org.casc.lang.ast.Token
+import org.casc.lang.ast.*
import org.casc.lang.checker.Checker
import org.casc.lang.emitter.Emitter
import org.casc.lang.lexer.Lexer
import org.casc.lang.parser.Parser
+import org.casc.lang.table.Reference
import org.casc.lang.table.Scope
import org.casc.lang.table.Table
import java.io.BufferedReader
+import java.util.*
import kotlin.time.DurationUnit
import kotlin.time.ExperimentalTime
@@ -25,7 +25,7 @@ class Compilation(private val preference: AbstractPreference) {
fun compile() {
var panic = false
- val sourceFile = preference.sourceFile!!.absoluteFile!!
+ val sourceFile = preference.sourceFile!!.absoluteFile
measureTime("Compilation") {
if (sourceFile.isDirectory) {
@@ -41,7 +41,7 @@ class Compilation(private val preference: AbstractPreference) {
val source = cascFile.readLines()
val relativeFilePath = cascFile.toRelativeString(sourceFile)
val compilationUnit =
- CompilationFileUnit(cascFile.name, source, relativeFilePath, cascFile.path)
+ CompilationFileUnit(cascFile.name, source, cascFile.path, relativeFilePath)
// Unit I: Lexer
/**
@@ -56,6 +56,8 @@ class Compilation(private val preference: AbstractPreference) {
}
}
+ compilationUnits.sortBy { it.relativePath }
+
compilationUnits.printReports()
if (compilationUnits.anyError()) {
@@ -87,7 +89,7 @@ class Compilation(private val preference: AbstractPreference) {
return@measureTime
}
- measureTime("Check (Prelude)") {
+ measureTime("Check (Prelude)") checkPrelude@{
// Caches class for dummy type checking, used in declaration checking
Table.cachedClasses += compilationUnits.map { it.file.typeInstance.reference to it.file }
@@ -108,48 +110,87 @@ class Compilation(private val preference: AbstractPreference) {
compilationUnit.scope = classScope
}
- compilationUnits.printReports()
-
if (compilationUnits.anyError()) {
panic = true
- return@measureTime
+ return@checkPrelude
}
- Table.cachedClasses.clear()
-
- // Define temporary class through bytecode for ASM library to process (See [getClassLoader][org.casc.lang.asm.CommonClassWriter])
- val creationQueue = LinkedHashSet()
+ val traitQueue = mutableListOf()
+ val classQueue = mutableListOf()
val declarations = compilationUnits.map(CompilationFileUnit::file)
- fun addToQueue(file: File) {
- val typeInstance = file.typeInstance
+ for ((file, compilationUnit) in declarations.zip(compilationUnits)) {
+ // Check parent class has no cyclic inheritance
+ val (_, _, _, typeInstance) = file
- if (typeInstance is ClassInstance && typeInstance.impl?.parentClassReference != null) {
- val parentClazzFile =
- declarations.find { it.typeInstance.reference.fullQualifiedPath == typeInstance.impl!!.parentClassReference!!.fullQualifiedPath }
- if (parentClazzFile != null)
- addToQueue(parentClazzFile)
+ when (typeInstance) {
+ is ClassInstance ->
+ classQueue += typeInstance.reference
+ is TraitInstance ->
+ traitQueue += typeInstance.reference
+ }
+
+
+ if (typeInstance is ClassInstance) {
+ val parentClassReferenceSet = hashSetOf()
+ var currentTypeInstance = typeInstance
+
+ while (currentTypeInstance.impl != null && currentTypeInstance.impl!!.parentClassReference != null) {
+ currentTypeInstance =
+ Table.findTypeInstance(currentTypeInstance.impl!!.parentClassReference!!)
+ ?: break
+
+ if (!parentClassReferenceSet.add(currentTypeInstance.typeReference)) {
+ // Cyclic inheritance
+ compilationUnit.reports += Error(
+ typeInstance.impl!!.parentClassReference!!.pos,
+ "Circular inheritance is forbidden",
+ "Class ${typeInstance.reference.asCASCStyle()} inherits class ${currentTypeInstance.reference.asCASCStyle()} but class ${currentTypeInstance.reference.asCASCStyle()} also inherits class ${typeInstance.reference.asCASCStyle()}"
+ )
+ break
+ } else classQueue.add(0, currentTypeInstance.reference)
+ }
}
if (typeInstance.traitImpls != null) {
- // TODO: implement traitImpls precaching
+ val currentTraitQueue =
+ LinkedList(typeInstance.traitImpls!!.map(TraitImpl::implementedTraitReference))
+
+ while (currentTraitQueue.isNotEmpty()) {
+ val trait = currentTraitQueue.remove()
+ traitQueue.add(0, trait)
+ Table.findFile(trait)!!.typeInstance.traitImpls?.map(TraitImpl::implementedTraitReference)
+ ?.let(currentTraitQueue::addAll)
+ }
}
+ }
- creationQueue.add(file.typeInstance.reference.fullQualifiedPath)
+ if (compilationUnits.anyError()) {
+ panic = true
+ return@checkPrelude
}
- declarations.forEach(::addToQueue)
+ // Define temporary class through bytecode for ASM library to process (See [getClassLoader][org.casc.lang.asm.CommonClassWriter])
+ for (reference in traitQueue.distinct()) {
+ val cachedFile = Table.findFile(reference)!!
+ val bytecode = Emitter(preference, true).emit(cachedFile)
- for (name in creationQueue) {
- val cachedFile =
- declarations.find { it.typeInstance.reference.fullQualifiedPath == name }!!
+ preference.classLoader.defineClass(reference.fullQualifiedPath, bytecode)
+ }
+ for (reference in classQueue.distinct()) {
+ val cachedFile = Table.findFile(reference)!!
val bytecode = Emitter(preference, true).emit(cachedFile)
- preference.classLoader.defineClass(name, bytecode)
+ preference.classLoader.defineClass(reference.fullQualifiedPath, bytecode)
}
+ }
- Table.cachedClasses += declarations.map { it.typeInstance.reference to it }
+ compilationUnits.printReports()
+
+ if (compilationUnits.anyError()) {
+ panic = true
+ return@measureTime
}
measureTime("Check (Main)") {
diff --git a/src/main/kotlin/org/casc/lang/compilation/CompilationFileUnit.kt b/src/main/kotlin/org/casc/lang/compilation/CompilationFileUnit.kt
index a2354909..297819ff 100644
--- a/src/main/kotlin/org/casc/lang/compilation/CompilationFileUnit.kt
+++ b/src/main/kotlin/org/casc/lang/compilation/CompilationFileUnit.kt
@@ -4,7 +4,12 @@ import org.casc.lang.ast.File
import org.casc.lang.ast.Token
import org.casc.lang.table.Scope
-data class CompilationFileUnit(val fileName: String, val source: List, val filePath: String, val relativePath: String) {
+data class CompilationFileUnit(
+ val fileName: String,
+ val source: List,
+ val filePath: String,
+ val relativePath: String
+) {
var tokens: List = listOf()
var reports: List = mutableListOf()
lateinit var file: File
diff --git a/src/main/kotlin/org/casc/lang/compilation/GlobalPreference.kt b/src/main/kotlin/org/casc/lang/compilation/GlobalPreference.kt
index 27f0b5db..1a4286bb 100644
--- a/src/main/kotlin/org/casc/lang/compilation/GlobalPreference.kt
+++ b/src/main/kotlin/org/casc/lang/compilation/GlobalPreference.kt
@@ -1,8 +1,6 @@
package org.casc.lang.compilation
-import org.casc.lang.asm.BytecodeClassLoader
import java.io.File
-import java.net.URLClassLoader
object GlobalPreference : AbstractPreference() {
override var enableColor: Boolean = true
diff --git a/src/main/kotlin/org/casc/lang/compilation/Report.kt b/src/main/kotlin/org/casc/lang/compilation/Report.kt
index 4d6ca93e..f7b3aaaa 100644
--- a/src/main/kotlin/org/casc/lang/compilation/Report.kt
+++ b/src/main/kotlin/org/casc/lang/compilation/Report.kt
@@ -110,18 +110,18 @@ sealed class Report {
}
) else "^".repeat(end - start)
- if (hint != null) {
- finalMessage += if (preference.enableColor) Ansi.colorize(
+ finalMessage += if (hint != null) {
+ if (preference.enableColor) Ansi.colorize(
"= hint: $hint\n", reportAttribute[when (this) {
is Warning -> 0
is Error -> 1
}]
) else "= hint: $hint\n"
} else {
- finalMessage += "\n"
+ "\n"
}
- if (lineNumber < source.lastIndex) {
+ if (lineNumber <= source.lastIndex) {
finalMessage += "${
if (preference.enableColor) Ansi.colorize(
"${lineNumber + 1} |",
diff --git a/src/main/kotlin/org/casc/lang/emitter/Emitter.kt b/src/main/kotlin/org/casc/lang/emitter/Emitter.kt
index 855cbd56..ebf24230 100644
--- a/src/main/kotlin/org/casc/lang/emitter/Emitter.kt
+++ b/src/main/kotlin/org/casc/lang/emitter/Emitter.kt
@@ -10,7 +10,6 @@ import org.objectweb.asm.*
import java.lang.invoke.CallSite
import java.lang.invoke.MethodHandles
import java.lang.invoke.MethodType
-import java.util.OptionalDouble
import java.io.File as JFile
class Emitter(private val preference: AbstractPreference, private val declarationOnly: Boolean) {
@@ -18,10 +17,7 @@ class Emitter(private val preference: AbstractPreference, private val declaratio
emitFile(file)
private fun emitFile(file: File): ByteArray {
- val bytecode = when (val typeInstance = file.typeInstance) {
- is ClassInstance -> emitClass(typeInstance, file.fileName)
- is TraitInstance -> emitTrait(typeInstance, file.fileName)
- }
+ val bytecode = emitTypeInstance(file.typeInstance, file.fileName)
val outFile = JFile(preference.outputDir, "/${file.typeInstance.typeReference.className}.class")
if (!preference.noEmit) {
@@ -37,50 +33,25 @@ class Emitter(private val preference: AbstractPreference, private val declaratio
return bytecode
}
- private fun emitClass(clazz: ClassInstance, sourceFile: String): ByteArray {
+ private fun emitTypeInstance(typeInstance: TypeInstance, sourceFile: String): ByteArray {
val classWriter =
CommonClassWriter(ClassWriter.COMPUTE_FRAMES + ClassWriter.COMPUTE_MAXS, preference.classLoader)
classWriter.visit(
Opcodes.V1_8,
- clazz.flag,
- clazz.reference.internalName(),
+ typeInstance.flag,
+ typeInstance.reference.internalName(),
null,
- clazz.impl?.parentClassReference?.fullQualifiedPath ?: Reference.OBJECT_TYPE_REFERENCE.internalName(),
- null
+ typeInstance.parentClassReference.internalName(),
+ typeInstance.traitClassReferences.map(Reference::internalName).toTypedArray()
)
classWriter.visitSource(sourceFile, null)
if (!declarationOnly) {
- clazz.fields.forEach {
- emitField(classWriter, it)
- }
-
- if (clazz.impl != null) {
- val impl = clazz.impl!!
-
- if (impl.companionBlock.isNotEmpty()) {
- val methodVisitor = classWriter.visitMethod(Opcodes.ACC_STATIC, "", "()V", null, null)
-
- methodVisitor.visitCode()
-
- impl.companionBlock.forEach {
- emitStatement(methodVisitor, it)
- }
-
- methodVisitor.visitInsn(Opcodes.RETURN)
- methodVisitor.visitMaxs(-1, -1)
- methodVisitor.visitEnd()
- }
-
- impl.constructors.forEach {
- emitConstructor(classWriter, it)
- }
-
- impl.functions.forEach {
- emitFunction(classWriter, it)
- }
+ when (typeInstance) {
+ is ClassInstance -> emitClass(classWriter, typeInstance)
+ is TraitInstance -> emitTrait(classWriter, typeInstance)
}
}
@@ -89,50 +60,48 @@ class Emitter(private val preference: AbstractPreference, private val declaratio
return classWriter.toByteArray()
}
- private fun emitTrait(trait: TraitInstance, sourceFile: String): ByteArray {
- val classWriter =
- CommonClassWriter(ClassWriter.COMPUTE_FRAMES + ClassWriter.COMPUTE_MAXS, preference.classLoader)
+ private fun emitClass(classWriter: ClassWriter, clazz: ClassInstance) {
+ clazz.fields.forEach {
+ emitField(classWriter, it)
+ }
- classWriter.visit(
- Opcodes.V1_8,
- trait.flag,
- trait.reference.internalName(),
- null,
- trait.impl?.parentClassReference?.fullQualifiedPath ?: Reference.OBJECT_TYPE_REFERENCE.internalName(),
- null
- )
+ clazz.impl?.let { emitImpl(classWriter, it) }
+ clazz.traitImpls?.let { for (traitImpl in it) for (function in traitImpl.functions) emitFunction(classWriter, function) }
+ }
- classWriter.visitSource(sourceFile, null)
+ private fun emitTrait(classWriter: ClassWriter, trait: TraitInstance) {
+ trait.fields.forEach {
+ emitField(classWriter, it)
+ }
- if (!declarationOnly) {
- trait.fields.forEach {
- emitField(classWriter, it)
- }
+ trait.impl?.let { emitImpl(classWriter, it) }
+ trait.traitImpls?.let { for (traitImpl in it) for (function in traitImpl.functions) emitFunction(classWriter, function) }
+ }
- if (trait.impl != null) {
- val impl = trait.impl!!
+ private fun emitImpl(classWriter: ClassWriter, impl: Impl) {
+ val companionBlock = impl.companionBlock
- if (impl.companionBlock.isNotEmpty()) {
- val methodVisitor = classWriter.visitMethod(Opcodes.ACC_STATIC, "", "()V", null, null)
+ if (companionBlock != null && companionBlock.statements.isNotEmpty()) {
+ val methodVisitor = classWriter.visitMethod(Opcodes.ACC_STATIC, "", "()V", null, null)
- methodVisitor.visitCode()
+ methodVisitor.visitCode()
- impl.companionBlock.forEach {
- emitStatement(methodVisitor, it)
- }
+ companionBlock.statements.forEach {
+ emitStatement(methodVisitor, it)
+ }
- methodVisitor.visitInsn(Opcodes.RETURN)
- methodVisitor.visitMaxs(-1, -1)
- methodVisitor.visitEnd()
- }
+ methodVisitor.visitInsn(Opcodes.RETURN)
+ methodVisitor.visitMaxs(-1, -1)
+ methodVisitor.visitEnd()
+ }
- impl.functions.forEach {
- emitFunction(classWriter, it)
- }
- }
+ impl.functions.forEach {
+ emitFunction(classWriter, it)
}
- return classWriter.toByteArray()
+ impl.constructors.forEach {
+ emitConstructor(classWriter, it)
+ }
}
private fun emitField(classWriter: ClassWriter, field: Field) {
@@ -403,8 +372,8 @@ class Emitter(private val preference: AbstractPreference, private val declaratio
// Use INVOKEVIRTUAL instead
methodVisitor.visitMethodInsn(
when {
- expression.superCall -> Opcodes.INVOKESPECIAL
functionSignature.ownerType?.isTrait == true -> Opcodes.INVOKEINTERFACE
+ expression.superCall -> Opcodes.INVOKESPECIAL
else -> Opcodes.INVOKEVIRTUAL
},
functionSignature.ownerReference.internalName(),
diff --git a/src/main/kotlin/org/casc/lang/lexer/Lexer.kt b/src/main/kotlin/org/casc/lang/lexer/Lexer.kt
index 1c7bca0c..0902708d 100644
--- a/src/main/kotlin/org/casc/lang/lexer/Lexer.kt
+++ b/src/main/kotlin/org/casc/lang/lexer/Lexer.kt
@@ -318,7 +318,7 @@ class Lexer(private val preference: AbstractPreference) {
for (i in 0 until 4) {
val current = peek()
- if (current.digitToIntOrNull() != null || current in 'a' .. 'f' || current in 'A'..'F') {
+ if (current.digitToIntOrNull() != null || current in 'a'..'f' || current in 'A'..'F') {
unicodeHexBuilder += peekInc()
} else {
reports += Error(
@@ -418,7 +418,7 @@ class Lexer(private val preference: AbstractPreference) {
val current = peek()
- if (current.digitToIntOrNull() != null || current in 'a' .. 'f' || current in 'A'..'F') {
+ if (current.digitToIntOrNull() != null || current in 'a'..'f' || current in 'A'..'F') {
unicodeHexBuilder += peekInc()
} else if (peek() == '\'') {
reports += Error(
diff --git a/src/main/kotlin/org/casc/lang/parser/Parser.kt b/src/main/kotlin/org/casc/lang/parser/Parser.kt
index 1b47f36c..23f9fb2b 100644
--- a/src/main/kotlin/org/casc/lang/parser/Parser.kt
+++ b/src/main/kotlin/org/casc/lang/parser/Parser.kt
@@ -19,7 +19,6 @@ class Parser(private val preference: AbstractPreference) {
private lateinit var tokens: List
fun parse(path: String, relativeFilePath: String, tokens: List): Pair, File?> {
- pos = 0
this.tokens = tokens
val file = parseFile(path, relativeFilePath)
@@ -33,17 +32,20 @@ class Parser(private val preference: AbstractPreference) {
private fun hasNext(): Boolean = tokens.size > pos
private fun assertUntil(type: TokenType): Token? {
- while (hasNext()) {
+ do {
val token = assert(type)
if (token != null) return token
- }
+ } while ((hasNext()))
return null
}
private fun assert(type: TokenType): Token? = when {
- !hasNext() -> null
+ !hasNext() -> {
+ reports.reportUnexpectedToken(type, last()?.pos, "Reached last token")
+ null
+ }
tokens[pos].type == type -> tokens[pos++]
else -> {
reports.reportUnexpectedToken(type, tokens[pos++])
@@ -52,21 +54,26 @@ class Parser(private val preference: AbstractPreference) {
}
private fun assertUntil(predicate: (Token) -> Boolean): Token? {
- while (hasNext()) {
+ do {
val token = assert(predicate)
if (token != null) return token
- }
+ } while ((hasNext()))
return null
}
private fun assert(predicate: (Token) -> Boolean): Token? = when {
- !hasNext() -> null
+ !hasNext() -> {
+ reports += Error(
+ last()?.pos, "Expected token but got nothing", "Reached last token"
+ )
+ null
+ }
predicate(tokens[pos]) -> tokens[pos++]
else -> {
reports += Error(
- tokens[pos].pos, "Unexpected token ${tokens[pos++].type}, expected predicate $predicate"
+ tokens[pos].pos, "Unexpected token ${tokens[pos++].type}"
)
null
}
@@ -129,7 +136,7 @@ class Parser(private val preference: AbstractPreference) {
val usages = mutableListOf()
val typeInstances = mutableMapOf()
val majorImpls = mutableMapOf()
- val traitImpls = mutableMapOf>()
+ val traitImpls = mutableMapOf>()
while (hasNext()) {
if (peekIf(Token::isUseKeyword)) {
@@ -225,10 +232,79 @@ class Parser(private val preference: AbstractPreference) {
val ownerReference = parseTypeSymbol()
if (peekIf(Token::isForKeyword)) {
- // TODO
+ consume() // `for` keyword
+
+ val implementedTraitReference = parseTypeSymbol()
+
+ var functions: List? = null
+
+ if (peekIf(TokenType.OpenBrace)) {
+ consume() // open brace
+
+ val (fns, ctors, compBlock) = parseFunctions(ownerReference, null)
+
+ if (ctors.isNotEmpty()) {
+ // Constructor declarations in trait implementation
+ for (constructor in ctors) {
+ reports += Error(
+ constructor.newKeyword.pos,
+ "Constructor cannot be declared in trait implementation",
+ "Remove this constructor declaration"
+ )
+ }
+ }
+
+ if (compBlock != null) {
+ // Companion blocks in trait implementation
+ reports += Error(
+ compBlock.compKeyword.pos,
+ "Companion block is forbidden in trait implementation",
+ "Remove this companion block"
+ )
+ }
+
+ // All functions in trait implementation must be override functions
+ for (function in fns) {
+ if (function.ovrdKeyword == null) {
+ // Function does not attempt to override trait functions
+ reports += Error(
+ function.name?.pos,
+ "Function a"
+ )
+ }
+ }
+
+ functions = fns
+
+ assertUntil(TokenType.CloseBrace)
+ }
+
+ val traitImpl = TraitImpl(
+ implKeyword,
+ implementedTraitReference,
+ functions ?: listOf()
+ )
+
+ if (!typeInstances.containsKey(ownerReference)) {
+ // Unknown type implementation
+ reports += Error(
+ ownerReference.pos, "Unknown trait implementation for class ${ownerReference.asCASCStyle()}"
+ )
+ } else if (traitImpls[ownerReference]?.find { it.implementedTraitReference == implementedTraitReference } != null) {
+ // Implementation duplication
+ reports += Error(
+ ownerReference.pos,
+ "Duplicated trait implementation for class ${ownerReference.asCASCStyle()}",
+ "Consider remove this trait implementation"
+ )
+ } else {
+ if (traitImpls[ownerReference] == null)
+ traitImpls[ownerReference] = mutableListOf()
+
+ traitImpls[ownerReference]!! += traitImpl
+ }
} else {
// Common implementation
-
val parentClassReference = if (peekIf(TokenType.Colon)) {
consume() // colon
parseTypeSymbol()
@@ -236,7 +312,7 @@ class Parser(private val preference: AbstractPreference) {
var functions: List? = null
var constructors: List? = null
- var companionBlock: List? = null
+ var companionBlock: CompanionBlock? = null
if (peekIf(TokenType.OpenBrace)) {
consume() // open brace
@@ -255,7 +331,7 @@ class Parser(private val preference: AbstractPreference) {
val impl = Impl(
implKeyword,
parentClassReference,
- companionBlock ?: listOf(),
+ companionBlock,
constructors ?: listOf(),
functions ?: listOf()
)
@@ -336,7 +412,7 @@ class Parser(private val preference: AbstractPreference) {
// Illegal constructor declaration for trait instance's implementation
for (constructor in it.constructors) {
reports += Error(
- constructor.newKeyword?.pos,
+ constructor.newKeyword.pos,
"Trait cannot have constructors",
"Remove this constructor declaration"
)
@@ -736,16 +812,10 @@ class Parser(private val preference: AbstractPreference) {
private fun parseFunctions(
classReference: Reference?, parentReference: Reference?
- ): Triple, List, List> {
- var firstCompKeyword: Token? = null
- var companionBlock: List? = null
- val functions = object : MutableObjectSet() {
- override fun isDuplicate(a: Function, b: Function): Boolean =
- a.name?.literal == b.name?.literal && a.parameters == b.parameters
- }
- val constructors = object : MutableObjectSet() {
- override fun isDuplicate(a: Constructor, b: Constructor): Boolean = a.parameters == b.parameters
- }
+ ): Triple, List, CompanionBlock?> {
+ var companionBlock: CompanionBlock? = null
+ val functions = mutableListOf()
+ val constructors = mutableListOf()
while (hasNext()) {
if (peekIf(TokenType.CloseBrace)) break
@@ -777,25 +847,27 @@ class Parser(private val preference: AbstractPreference) {
)
}
- val compKeyword = next()
+ val compKeyword = next()!!
assertUntil(TokenType.OpenBrace)
if (companionBlock != null) {
// Companion block already declared
reports += Error(
- compKeyword?.pos,
+ compKeyword.pos,
"Companion block already declared",
"Try merge companion blocks together"
)
reports += Error(
- firstCompKeyword?.pos,
+ companionBlock.compKeyword.pos,
"Companion block already declared",
"Merge to this companion block"
)
- } else firstCompKeyword = compKeyword
+ }
+
+ val statements = parseStatements(true)
- companionBlock = parseStatements(true)
+ companionBlock = CompanionBlock(compKeyword, statements)
assertUntil(TokenType.CloseBrace)
} else if (peekIf(Token::isNewKeyword)) {
@@ -814,7 +886,7 @@ class Parser(private val preference: AbstractPreference) {
)
}
- val newKeyword = next() // `new` keyword
+ val newKeyword = next()!! // `new` keyword
val (parameterSelfKeyword, parameters) = parseParameters()
@@ -864,21 +936,23 @@ class Parser(private val preference: AbstractPreference) {
superCallArguments
)
- if (!constructors.add(constructor)) {
- // Duplicate constructors
- reports += Error(
- newKeyword?.pos,
- "Constructor `new`(${DisplayFactory.getParametersTypePretty(parameters)}) has already declared in same context",
- "Try modify parameters' type"
- )
- }
+ constructors += constructor
} else if (peekIf(Token::isFnKeyword)) {
// Function declaration
- consume() // `fn` keyword
+ val fnKeyword = next()!!
val name = assertUntil(TokenType.Identifier)
val (parameterSelfKeyword, parameters) = parseParameters()
+ if (parameterSelfKeyword == null && mutable != null) {
+ // Companion function cannot be mutable
+ reports += Error(
+ mutable.pos,
+ "Companion function cannot be mutable",
+ "Remove this `mut` keyword"
+ )
+ }
+
val returnType = if (peekIf(TokenType.Colon)) {
consume()
parseComplexTypeSymbol()
@@ -898,6 +972,7 @@ class Parser(private val preference: AbstractPreference) {
ovrd,
abstr,
mutable,
+ fnKeyword,
parameterSelfKeyword,
name,
parameters,
@@ -905,18 +980,11 @@ class Parser(private val preference: AbstractPreference) {
statements,
)
- if (!functions.add(function)) {
- // Duplicate functions
- reports += Error(
- name?.pos,
- "Function ${name?.literal}(${DisplayFactory.getParametersTypePretty(parameters)}) has already declared in same context",
- "Try rename this function or modify parameters' type"
- )
- }
+ functions += function
} else if (peekIf(TokenType.CloseBrace)) break
}
- return Triple(functions.toList(), constructors.toList(), companionBlock ?: listOf())
+ return Triple(functions.toList(), constructors.toList(), companionBlock)
}
/**
diff --git a/src/main/kotlin/org/casc/lang/parser/reporter.kt b/src/main/kotlin/org/casc/lang/parser/reporter.kt
index c24a5f68..224881ab 100644
--- a/src/main/kotlin/org/casc/lang/parser/reporter.kt
+++ b/src/main/kotlin/org/casc/lang/parser/reporter.kt
@@ -1,22 +1,34 @@
package org.casc.lang.parser
+import org.casc.lang.ast.Position
import org.casc.lang.ast.Token
import org.casc.lang.ast.TokenType
import org.casc.lang.compilation.Error
import org.casc.lang.compilation.Report
-internal fun MutableList.reportUnexpectedToken(expected: TokenType, got: Token) =
+internal fun MutableList.reportUnexpectedToken(expected: TokenType, got: Token, hint: String? = null) =
this.add(
Error(
got.pos,
- "Unexpected token ${got.type}, expected token $expected"
+ "Unexpected token ${got.type}, expected token $expected",
+ hint
)
)
-internal fun MutableList.reportUnexpectedToken(got: Token) =
+internal fun MutableList.reportUnexpectedToken(expected: TokenType, pos: Position?, hint: String? = null) =
+ this.add(
+ Error(
+ pos,
+ "Expected token $expected, but got nothing",
+ hint
+ )
+ )
+
+internal fun MutableList.reportUnexpectedToken(got: Token, hint: String? = null) =
this.add(
Error(
got.pos,
- "Unexpected token ${got.type}"
+ "Unexpected token ${got.type}",
+ hint
)
)
\ No newline at end of file
diff --git a/src/main/kotlin/org/casc/lang/process.kt b/src/main/kotlin/org/casc/lang/process.kt
index 05b6fcc7..11a2ff54 100644
--- a/src/main/kotlin/org/casc/lang/process.kt
+++ b/src/main/kotlin/org/casc/lang/process.kt
@@ -1,6 +1,5 @@
package org.casc.lang
-import io.github.classgraph.ClassGraph
import org.casc.lang.compilation.AbstractPreference
import org.casc.lang.compilation.Compilation
import org.casc.lang.compilation.Error
@@ -87,7 +86,9 @@ enum class CommandType(val helpMessage: String) {
preference.outputDir = outputFolder
true
} else {
- Error("Output path `$outputFolderPath` is not a directory").printReport(preference)
+ Error("Output path `$outputFolderPath` is not a directory").printReport(
+ preference
+ )
false
}
} else {
diff --git a/src/main/kotlin/org/casc/lang/table/ClassType.kt b/src/main/kotlin/org/casc/lang/table/ClassType.kt
index 808578d0..0284390a 100644
--- a/src/main/kotlin/org/casc/lang/table/ClassType.kt
+++ b/src/main/kotlin/org/casc/lang/table/ClassType.kt
@@ -2,7 +2,6 @@ package org.casc.lang.table
import org.casc.lang.ast.Accessor
import org.casc.lang.compilation.AbstractPreference
-import org.casc.lang.compilation.GlobalPreference
import org.objectweb.asm.Opcodes
import java.lang.reflect.Modifier
@@ -28,7 +27,7 @@ data class ClassType(
)
override fun type(preference: AbstractPreference): Class<*>? = try {
- preference.classLoader?.loadClass(typeName)
+ preference.classLoader.loadClass(typeName)
} catch (e: Exception) {
null
}
diff --git a/src/main/kotlin/org/casc/lang/table/FunctionSignature.kt b/src/main/kotlin/org/casc/lang/table/FunctionSignature.kt
index 0b7f5ae8..4cd4777d 100644
--- a/src/main/kotlin/org/casc/lang/table/FunctionSignature.kt
+++ b/src/main/kotlin/org/casc/lang/table/FunctionSignature.kt
@@ -1,19 +1,35 @@
package org.casc.lang.table
import org.casc.lang.ast.Accessor
+import org.casc.lang.compilation.AbstractPreference
import org.casc.lang.utils.getOrElse
import org.objectweb.asm.Opcodes
+import java.lang.reflect.Method
+import java.lang.reflect.Modifier
data class FunctionSignature(
val ownerReference: Reference,
val ownerType: ClassType?,
val companion: Boolean,
val mutable: Boolean,
+ val abstract: Boolean,
override val accessor: Accessor,
val name: String,
val parameters: List,
val returnType: Type
) : HasDescriptor, HasFlag, HasAccessor {
+ constructor(ownerClass: Class<*>, function: Method, preference: AbstractPreference) : this(
+ Reference(ownerClass),
+ TypeUtil.asType(ownerClass, preference) as ClassType,
+ Modifier.isStatic(function.modifiers),
+ !Modifier.isFinal(function.modifiers),
+ Modifier.isAbstract(function.modifiers),
+ Accessor.fromModifier(function.modifiers),
+ function.name,
+ function.parameterTypes.map { TypeUtil.asType(it, preference)!! },
+ TypeUtil.asType(function.returnType, preference)!!
+ )
+
override val descriptor: String =
if (name == "") {
// Constructor
@@ -24,4 +40,26 @@ data class FunctionSignature(
}
override val flag: Int =
companion.getOrElse(Opcodes.ACC_STATIC) + mutable.getOrElse(0, Opcodes.ACC_FINAL) + accessor.access
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as FunctionSignature
+
+ if (ownerReference != other.ownerReference) return false
+ if (companion != other.companion) return false
+ if (name != other.name) return false
+ if (descriptor != other.descriptor) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = ownerReference.hashCode()
+ result = 31 * result + companion.hashCode()
+ result = 31 * result + name.hashCode()
+ result = 31 * result + descriptor.hashCode()
+ return result
+ }
}
diff --git a/src/main/kotlin/org/casc/lang/table/HasFlag.kt b/src/main/kotlin/org/casc/lang/table/HasFlag.kt
index 125fc09f..5dddb52a 100644
--- a/src/main/kotlin/org/casc/lang/table/HasFlag.kt
+++ b/src/main/kotlin/org/casc/lang/table/HasFlag.kt
@@ -1,7 +1,6 @@
package org.casc.lang.table
import org.casc.lang.ast.Accessor
-import org.casc.lang.utils.getOrElse
import org.objectweb.asm.Opcodes
interface HasFlag {
diff --git a/src/main/kotlin/org/casc/lang/table/Scope.kt b/src/main/kotlin/org/casc/lang/table/Scope.kt
index 9618b577..557889d7 100644
--- a/src/main/kotlin/org/casc/lang/table/Scope.kt
+++ b/src/main/kotlin/org/casc/lang/table/Scope.kt
@@ -1,10 +1,15 @@
package org.casc.lang.table
import io.github.classgraph.ClassGraph
-import org.casc.lang.ast.*
+import org.casc.lang.ast.Accessor
+import org.casc.lang.ast.ClassInstance
+import org.casc.lang.ast.Field
+import org.casc.lang.ast.HasSignature
import org.casc.lang.compilation.AbstractPreference
import org.casc.lang.utils.getOrElse
+import java.lang.reflect.Method
import java.lang.reflect.Modifier
+import java.util.*
data class Scope(
val preference: AbstractPreference,
@@ -51,14 +56,10 @@ data class Scope(
init {
if (usages.isEmpty()) {
// It was just initialized, add classes under package java.lang
- val classes = ClassGraph()
- .acceptPackagesNonRecursive("java.lang")
- .overrideClassLoaders(ClassLoader.getSystemClassLoader())
- .scan()
+ val classes = ClassGraph().acceptPackagesNonRecursive("java.lang")
+ .overrideClassLoaders(ClassLoader.getSystemClassLoader()).scan()
-
- for (classInfo in classes.allStandardClasses)
- usages += Reference(classInfo.loadClass().name)
+ for (classInfo in classes.allStandardClasses) usages += Reference(classInfo.loadClass().name)
}
}
@@ -109,25 +110,30 @@ data class Scope(
// Function Signatures //
//=======================================//
- fun registerSignature(signatureObject: HasSignature) {
- signatures += signatureObject.asSignature()
- }
+ fun registerSignature(signatureObject: HasSignature): Boolean = signatures.add(signatureObject.asSignature())
private fun findSignatureInSameType(functionName: String, argumentTypes: List): FunctionSignature? =
signatures.find {
- it.name == functionName &&
- it.parameters.size == argumentTypes.size &&
- argumentTypes
- .zip(it.parameters)
- .all { (l, r) ->
- canCast(l, r)
- }
+ it.name == functionName && it.parameters.size == argumentTypes.size && argumentTypes.zip(it.parameters)
+ .all { (l, r) ->
+ canCast(l, r)
+ }
}
- fun findSignature(ownerPath: Reference?, functionName: String, argumentTypes: List): FunctionSignature? {
+ /**
+ * Searches functions with the following searching order:
+ * 1. Current scope if:
+ * - argument `ownerPath` is null
+ * - argument `ownerPath`'s reference is same as current scope's typeReference
+ * 2. Type symbol table / JVM Class Loader:
+ * - always search implemented trait (interface) functions first
+ * - if previous step does not return available function signature, search parent class
+ */
+ fun findSignature(
+ ownerPath: Reference?, functionName: String, argumentTypes: List, searchTraitOnly: Boolean = false, allowAbstract: Boolean = true
+ ): FunctionSignature? {
if (ownerPath == null) return findSignatureInSameType(
- functionName,
- argumentTypes
+ functionName, argumentTypes
)
val reference = findReference(ownerPath)
@@ -136,36 +142,48 @@ data class Scope(
val typeInstance = Table.findTypeInstance(reference)
if (typeInstance != null) {
- return if (typeInstance is ClassInstance && functionName == "") {
- typeInstance.impl?.constructors?.find {
- it.parameterTypes.size == argumentTypes.size &&
- argumentTypes
- .zip(it.parameterTypes)
- .all { (l, r) ->
- canCast(l, r)
- }
- }?.asSignature() ?: findSignature(typeInstance.parentClassReference, functionName, argumentTypes)
- } else {
- // TODO: Find functions from implemented traits
- typeInstance.impl?.functions?.find {
- it.name?.literal == functionName &&
- it.parameterTypes?.size == argumentTypes.size &&
- argumentTypes
- .zip(it.parameterTypes!!)
- .all { (l, r) ->
- canCast(l, r)
- }
- }?.let(HasSignature::asSignature) ?: when (typeInstance) {
- is ClassInstance -> findSignature(
- typeInstance.parentClassReference,
- functionName,
- argumentTypes
- )
- is TraitInstance -> null
+ if (typeInstance is ClassInstance && functionName == "") {
+ return typeInstance.impl?.constructors?.find {
+ it.parameterTypes.size == argumentTypes.size && argumentTypes.zip(it.parameterTypes).all { (l, r) ->
+ canCast(l, r)
+ }
+ }?.asSignature()
+ }
+ // TODO: Find functions from implemented traits
+ var functionSignature = typeInstance.impl?.functions?.filter { if (!allowAbstract) !it.abstract else true }?.find {
+ it.name?.literal == functionName && it.parameterTypes?.size == argumentTypes.size && argumentTypes.zip(
+ it.parameterTypes!!
+ ).all { (l, r) ->
+ canCast(l, r)
+ }
+ }?.let(HasSignature::asSignature)
+
+ if (functionSignature != null) return functionSignature
+
+ // Search trait function first
+ if (typeInstance.traitImpls != null) {
+ for (traitImpl in typeInstance.traitImpls!!) {
+ functionSignature =
+ findSignature(traitImpl.implementedTraitReference, functionName, argumentTypes, allowAbstract)
+
+ if (functionSignature != null) return functionSignature
}
}
+
+ if (searchTraitOnly) return null
+
+ // Search function in parent class
+ return if (typeInstance.impl != null) {
+ findSignature(
+ typeInstance.impl!!.parentClassReference ?: Reference.OBJECT_TYPE_REFERENCE,
+ functionName,
+ argumentTypes,
+ allowAbstract
+ )
+ } else null
}
+
val ownerType = findType(reference)
if (ownerType != null) {
@@ -173,66 +191,72 @@ data class Scope(
if (functionName == "") {
// Constructor
- try {
- val (ownerClazz, argumentClasses) = retrieveExecutableInfo(ownerType, argTypes)
- val constructors = ownerClazz.constructors.filter { it.parameters.size == argumentClasses.size }
-
- for (constructor in constructors) {
- if (constructor.parameterTypes.zip(argumentClasses).all { (l, r) -> l.isAssignableFrom(r) }) {
- return FunctionSignature(
- Reference(ownerClazz),
- TypeUtil.asType(ownerClazz, preference) as ClassType,
- companion = true,
- mutable = false,
- Accessor.fromModifier(constructor.modifiers),
- functionName,
- constructor.parameterTypes.map { TypeUtil.asType(it, preference)!! },
- ownerType
- )
- }
+ val (ownerClazz, argumentClasses) = retrieveExecutableInfo(ownerType, argTypes)
+ val constructors = ownerClazz.constructors.filter { it.parameters.size == argumentClasses.size }
+
+ for (constructor in constructors) {
+ if (constructor.parameterTypes.zip(argumentClasses).all { (l, r) -> l.isAssignableFrom(r) }) {
+ return FunctionSignature(
+ Reference(ownerClazz),
+ TypeUtil.asType(ownerClazz, preference) as ClassType,
+ companion = true,
+ mutable = false,
+ abstract = false,
+ Accessor.fromModifier(constructor.modifiers),
+ functionName,
+ constructor.parameterTypes.map { TypeUtil.asType(it, preference)!! },
+ ownerType
+ )
}
- } catch (_: Exception) {
}
} else {
// Function
- try {
- var (ownerClazz, argumentClasses) =
- if (ownerPath == typeReference) retrieveExecutableInfo(
- findType(parentClassPath) ?: ClassType(Any::class.java), argTypes
- )
- else retrieveExecutableInfo(ownerType, argTypes)
- var signature: FunctionSignature? = null
-
- while (ownerClazz != Any::class.java) {
- try {
- val functions =
- ownerClazz.declaredMethods.filter { it.name == functionName && it.parameters.size == argumentClasses.size }
-
- for (function in functions) {
- if (function.parameterTypes.zip(argumentClasses)
- .all { (l, r) -> l.isAssignableFrom(r) }
- ) {
- signature = FunctionSignature(
- Reference(ownerClazz),
- TypeUtil.asType(ownerClazz, preference) as ClassType,
- Modifier.isStatic(function.modifiers),
- Modifier.isFinal(function.modifiers),
- Accessor.fromModifier(function.modifiers),
- functionName,
- function.parameterTypes.map { TypeUtil.asType(it, preference)!! },
- TypeUtil.asType(function.returnType, preference)!!
- )
- }
- }
-
- break
- } catch (e: Throwable) {
- ownerClazz = ownerClazz.superclass
+ var (ownerClazz, argumentClasses) = if (ownerPath == typeReference) retrieveExecutableInfo(
+ findType(parentClassPath) ?: ClassType(Any::class.java), argTypes
+ )
+ else retrieveExecutableInfo(ownerType, argTypes)
+
+ var functions = filterFunction(ownerClazz, functionName, argumentClasses, allowAbstract)
+
+ for (function in functions) {
+ if (isSameFunction(function, argumentClasses)) {
+ return FunctionSignature(ownerClazz, function, preference)
+ }
+ }
+
+ // Search trait function first (BFS)
+ val traits = LinkedList(ownerClazz.interfaces.toList())
+
+ while (traits.isNotEmpty()) {
+ val trait = traits.remove()
+ functions = filterFunction(trait, functionName, argumentClasses, allowAbstract)
+
+ for (function in functions) {
+ if (isSameFunction(function, argumentClasses)) {
+ return FunctionSignature(trait, function, preference)
}
}
- return signature
- } catch (_: Throwable) {
+ traits += trait.interfaces
+ }
+
+ if (searchTraitOnly) return null
+
+ if (ownerClazz.superclass == null) return null
+
+ ownerClazz = ownerClazz.superclass
+
+ while (true) {
+ functions = filterFunction(ownerClazz, functionName, argumentClasses, allowAbstract)
+
+ for (function in functions) {
+ if (isSameFunction(function, argumentClasses)) {
+ return FunctionSignature(ownerClazz, function, preference)
+ }
+ }
+
+ if (ownerClazz.superclass == null) return null
+ else ownerClazz = ownerClazz.superclass
}
}
}
@@ -240,6 +264,24 @@ data class Scope(
return null
}
+ private fun filterFunction(
+ ownerClass: Class<*>,
+ functionName: String,
+ argumentClasses: Array>,
+ allowAbstract: Boolean
+ ): List =
+ ownerClass.declaredMethods.filter { it.name == functionName && it.parameters.size == argumentClasses.size && if (!allowAbstract) !Modifier.isAbstract(it.modifiers) else true }
+
+ private fun isSameFunction(function: Method, argumentClasses: Array>): Boolean {
+ for ((i, parameterType) in function.parameterTypes.withIndex()) {
+ if (!parameterType.isAssignableFrom(argumentClasses[i])) {
+ return false
+ }
+ }
+
+ return true
+ }
+
//=======================================//
// Local Variables //
//=======================================//
@@ -253,11 +295,7 @@ data class Scope(
else lastIndex + 1
}
val variable = Variable(
- mutable,
- variableName,
- type,
- index,
- scopeDepth
+ mutable, variableName, type, index, scopeDepth
)
if (variables.contains(variable)) return false
@@ -287,17 +325,12 @@ data class Scope(
fun findType(reference: Reference?): Type? = when (reference) {
null -> null
typeReference -> ClassType(
- typeReference.fullQualifiedPath,
- parentClassPath?.fullQualifiedPath,
- accessor,
- mutable,
- isTrait
+ typeReference.fullQualifiedPath, parentClassPath?.fullQualifiedPath, accessor, mutable, isTrait
)
else -> TypeUtil.asType(findReference(reference), preference)
}
- fun canCast(from: Type?, to: Type?): Boolean =
- TypeUtil.canCast(from, to, preference)
+ fun canCast(from: Type?, to: Type?): Boolean = TypeUtil.canCast(from, to, preference)
/**
* Find reference (either shortened, aliased, or full-qualified) from usage, return itself if not found
@@ -312,18 +345,15 @@ data class Scope(
childReference == null -> false
parentReference == childReference -> false
childReference == typeReference -> isChildType(
- findType(parentReference),
- findType(parentClassPath)
+ findType(parentReference), findType(parentClassPath)
)
else -> isChildType(findType(parentReference), findType(childReference))
}
- fun isChildType(parentReference: Reference?): Boolean =
- isChildType(parentReference, typeReference)
+ fun isChildType(parentReference: Reference?): Boolean = isChildType(parentReference, typeReference)
private fun retrieveExecutableInfo(
- ownerType: Type,
- argumentTypes: List
+ ownerType: Type, argumentTypes: List
): Pair, Array>> =
ownerType.type(preference)!! to argumentTypes.mapNotNull { it.type(preference) }.toTypedArray()
}
diff --git a/src/main/kotlin/org/casc/lang/table/Table.kt b/src/main/kotlin/org/casc/lang/table/Table.kt
index 290cc86c..2ce0f63d 100644
--- a/src/main/kotlin/org/casc/lang/table/Table.kt
+++ b/src/main/kotlin/org/casc/lang/table/Table.kt
@@ -19,12 +19,12 @@ object Table {
/**
* By given full qualified class path (including package path) will return a ClassType if exists or null
*/
- fun findType(classReference: Reference): ClassType? =
- cachedClasses[classReference]?.let {
+ fun findType(typeReference: Reference): ClassType? =
+ cachedClasses[typeReference]?.let {
val typeInstance = it.typeInstance
ClassType(
- classReference.fullQualifiedPath,
+ typeReference.fullQualifiedPath,
if (typeInstance is ClassInstance && typeInstance.packageReference != null)
typeInstance.packageReference!!.fullQualifiedPath
else Reference.OBJECT_TYPE_REFERENCE.fullQualifiedPath,
@@ -35,8 +35,11 @@ object Table {
)
}
- fun findTypeInstance(classReference: Reference): TypeInstance? =
- cachedClasses[classReference]?.typeInstance
+ fun findTypeInstance(typeReference: Reference): TypeInstance? =
+ cachedClasses[typeReference]?.typeInstance
+
+ fun findFile(typeReference: Reference): File? =
+ cachedClasses[typeReference]
fun hasClass(classReference: Reference): Boolean =
cachedClasses.containsKey(classReference)
diff --git a/src/main/kotlin/org/casc/lang/table/TypeUtil.kt b/src/main/kotlin/org/casc/lang/table/TypeUtil.kt
index 0056fab1..136a5605 100644
--- a/src/main/kotlin/org/casc/lang/table/TypeUtil.kt
+++ b/src/main/kotlin/org/casc/lang/table/TypeUtil.kt
@@ -40,8 +40,7 @@ object TypeUtil {
name.substring(name.length - 2) == "[]" -> {
val baseType = asType(Reference(name.substring(0 until name.length - 2)), preference)
- if (baseType == null) null
- else ArrayType(baseType)
+ baseType?.let(::ArrayType)
}
else -> null
}
diff --git a/src/main/kotlin/org/casc/lang/table/Variable.kt b/src/main/kotlin/org/casc/lang/table/Variable.kt
index 1b87bcff..dfcd3988 100644
--- a/src/main/kotlin/org/casc/lang/table/Variable.kt
+++ b/src/main/kotlin/org/casc/lang/table/Variable.kt
@@ -3,8 +3,25 @@ package org.casc.lang.table
/**
* Refers to function's local variable, its type might be null since its value could be null.
*/
-data class Variable(val mutable: Boolean, val name: String, var type: Type?, val index: Int, val declaredScopeDepth: Int) {
- override fun equals(other: Any?): Boolean =
- if (other !is Variable) false
- else other.name == name
+data class Variable(
+ val mutable: Boolean,
+ val name: String,
+ var type: Type?,
+ val index: Int,
+ val declaredScopeDepth: Int
+) {
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as Variable
+
+ if (name != other.name) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return name.hashCode()
+ }
}
diff --git a/src/test/kotlin/CompilationTest.kt b/src/test/kotlin/CompilationTest.kt
index 1cf35d5b..968e70c3 100644
--- a/src/test/kotlin/CompilationTest.kt
+++ b/src/test/kotlin/CompilationTest.kt
@@ -21,7 +21,7 @@ class CompilationTest {
System.setOut(printStream)
val localPreference = LocalPreference(enableColor = false, noEmit = true)
- fileMap?.get("casc")?.forEach {
+ fileMap["casc"]?.forEach {
localPreference.sourceFile = it
val compilation = Compilation(localPreference)
compilation.compile()
@@ -47,4 +47,43 @@ class CompilationTest {
return tests
}
+
+ @TestFactory
+ fun testPreludeChecker(): List {
+ val tests = mutableListOf()
+ val outputStream = ByteArrayOutputStream()
+ val printStream = PrintStream(outputStream)
+ val fileMap = File(Compilation::class.java.classLoader.getResource("preludeCheck")!!.file)
+ .listFiles()!!
+ .toList()
+
+ System.setOut(printStream)
+ val localPreference = LocalPreference(enableColor = false, noEmit = true)
+
+ fileMap.filter(File::isDirectory).forEach {
+ localPreference.sourceFile = it
+ val compilation = Compilation(localPreference)
+ compilation.compile()
+
+ System.out.flush()
+
+ val output = outputStream.toString().trim()
+
+ val outFile = fileMap.find { file -> file.name == "${it.nameWithoutExtension}.out" }
+
+ tests += if (outFile == null) {
+ DynamicTest.dynamicTest(it.name) {
+ Assertions.assertEquals("", output)
+ }
+ } else {
+ DynamicTest.dynamicTest(it.name) {
+ Assertions.assertEquals(outFile.readText(Charsets.UTF_8).trim().replace("\r", ""), output)
+ }
+ }
+
+ outputStream.reset()
+ }
+
+ return tests
+ }
}
\ No newline at end of file
diff --git a/src/test/resources/parse/AbstractTrait.casc b/src/test/resources/parse/AbstractTrait.casc
new file mode 100644
index 00000000..2b61dcf4
--- /dev/null
+++ b/src/test/resources/parse/AbstractTrait.casc
@@ -0,0 +1 @@
+abstr trait AbstractTrait
\ No newline at end of file
diff --git a/src/test/resources/parse/AbstractTrait.out b/src/test/resources/parse/AbstractTrait.out
new file mode 100644
index 00000000..97e4eed5
--- /dev/null
+++ b/src/test/resources/parse/AbstractTrait.out
@@ -0,0 +1,4 @@
+error: Cannot declare trait instance with `abstr` keyword, trait is implicitly abstract
+--> ./AbstractTrait.casc:1:0
+1 | abstr trait AbstractTrait
+ ^^^^^= hint: Remove this `abstr` keyword
\ No newline at end of file
diff --git a/src/test/resources/parse/DuplicateMut.casc b/src/test/resources/parse/DuplicateMut.casc
index 2fc0e29f..714aed5a 100644
--- a/src/test/resources/parse/DuplicateMut.casc
+++ b/src/test/resources/parse/DuplicateMut.casc
@@ -1,5 +1,5 @@
mut mut class DuplicateMut
impl DuplicateMut {
- mut fn a1() {}
+ mut mut fn a1(self) {}
}
\ No newline at end of file
diff --git a/src/test/resources/parse/DuplicateMut.out b/src/test/resources/parse/DuplicateMut.out
index 6f0b3c3f..6fe595e5 100644
--- a/src/test/resources/parse/DuplicateMut.out
+++ b/src/test/resources/parse/DuplicateMut.out
@@ -7,4 +7,16 @@ error: Duplicate `mut` keyword
--> ./DuplicateMut.casc:1:4
1 | mut mut class DuplicateMut
^^^= hint: Duplicate here
-2 |
\ No newline at end of file
+2 |
+error: Duplicate `mut` keyword
+--> ./DuplicateMut.casc:4:4
+3 | impl DuplicateMut {
+4 | mut mut fn a1(self) {}
+ ^^^= hint: Encountered first one here
+5 | }
+error: Duplicate `mut` keyword
+--> ./DuplicateMut.casc:4:8
+3 | impl DuplicateMut {
+4 | mut mut fn a1(self) {}
+ ^^^= hint: Duplicate here
+5 | }
\ No newline at end of file
diff --git a/src/test/resources/parse/DuplicateOvrd.out b/src/test/resources/parse/DuplicateOvrd.out
index 0bb92d15..53296b5f 100644
--- a/src/test/resources/parse/DuplicateOvrd.out
+++ b/src/test/resources/parse/DuplicateOvrd.out
@@ -3,8 +3,10 @@ error: Duplicate `ovrd` keyword
3 | impl DuplicateOvrd {
4 | ovrd ovrd fn a1() {}
^^^^= hint: Encountered first one here
+5 | }
error: Duplicate `ovrd` keyword
--> ./DuplicateOvrd.casc:4:9
3 | impl DuplicateOvrd {
4 | ovrd ovrd fn a1() {}
- ^^^^= hint: Duplicate here
\ No newline at end of file
+ ^^^^= hint: Duplicate here
+5 | }
\ No newline at end of file
diff --git a/src/test/resources/parse/IncompleteImpl.casc b/src/test/resources/parse/IncompleteImpl.casc
new file mode 100644
index 00000000..36bfe73e
--- /dev/null
+++ b/src/test/resources/parse/IncompleteImpl.casc
@@ -0,0 +1,3 @@
+class IncompleteImpl
+
+impl IncompleteImpl {
\ No newline at end of file
diff --git a/src/test/resources/parse/IncompleteImpl.out b/src/test/resources/parse/IncompleteImpl.out
new file mode 100644
index 00000000..f05ae1e1
--- /dev/null
+++ b/src/test/resources/parse/IncompleteImpl.out
@@ -0,0 +1,5 @@
+error: Expected token CloseBrace, but got nothing
+--> ./IncompleteImpl.casc:3:20
+2 |
+3 | impl IncompleteImpl {
+ ^= hint: Reached last token
\ No newline at end of file
diff --git a/src/test/resources/parse/InvalidConstructor.casc b/src/test/resources/parse/InvalidConstructor.casc
index bef42b18..c46e254c 100644
--- a/src/test/resources/parse/InvalidConstructor.casc
+++ b/src/test/resources/parse/InvalidConstructor.casc
@@ -2,8 +2,4 @@ class InvalidConstructor
impl InvalidConstructor {
new() : pub()
-
- new(a: i32)
-
- new(b: i32)
}
\ No newline at end of file
diff --git a/src/test/resources/parse/InvalidConstructor.out b/src/test/resources/parse/InvalidConstructor.out
index f4db0378..6b183ea6 100644
--- a/src/test/resources/parse/InvalidConstructor.out
+++ b/src/test/resources/parse/InvalidConstructor.out
@@ -3,9 +3,4 @@ error: Unexpected token, expected `super` or `self` keyword
3 | impl InvalidConstructor {
4 | new() : pub()
^^^
-5 |
-error: Constructor `new`(i32) has already declared in same context
---> ./InvalidConstructor.casc:8:4
-7 |
-8 | new(b: i32)
- ^^^= hint: Try modify parameters' type
\ No newline at end of file
+5 | }
\ No newline at end of file
diff --git a/src/test/resources/parse/InvalidModifier.casc b/src/test/resources/parse/InvalidModifier.casc
index 980476ea..93fa576f 100644
--- a/src/test/resources/parse/InvalidModifier.casc
+++ b/src/test/resources/parse/InvalidModifier.casc
@@ -6,4 +6,6 @@ impl InvalidModifier {
}
mut ovrd new() {}
+
+ mut fn a1() {}
}
\ No newline at end of file
diff --git a/src/test/resources/parse/InvalidModifier.out b/src/test/resources/parse/InvalidModifier.out
index 67aafc32..51ad19cc 100644
--- a/src/test/resources/parse/InvalidModifier.out
+++ b/src/test/resources/parse/InvalidModifier.out
@@ -32,13 +32,22 @@ error: Cannot declare `ovrd` keyword after `mut` keyword
7 |
8 | mut ovrd new() {}
^^^= hint: `mut` keyword here
+9 |
error: Cannot declare constructor with `ovrd` keyword
--> ./InvalidModifier.casc:8:8
7 |
8 | mut ovrd new() {}
^^^^= hint: Remove this `ovrd` keyword
+9 |
error: Cannot declare constructor with `mut` keyword
--> ./InvalidModifier.casc:8:4
7 |
8 | mut ovrd new() {}
- ^^^= hint: Remove this `mut` keyword
\ No newline at end of file
+ ^^^= hint: Remove this `mut` keyword
+9 |
+error: Companion function cannot be mutable
+--> ./InvalidModifier.casc:10:4
+9 |
+10 | mut fn a1() {}
+ ^^^= hint: Remove this `mut` keyword
+11 | }
\ No newline at end of file
diff --git a/src/test/resources/parse/InvalidModifierSequence.casc b/src/test/resources/parse/InvalidModifierSequence.casc
index ebca3bef..2442ea06 100644
--- a/src/test/resources/parse/InvalidModifierSequence.casc
+++ b/src/test/resources/parse/InvalidModifierSequence.casc
@@ -1,7 +1,7 @@
mut priv class InvalidModifierSequence
impl InvalidModifierSequence {
- mut ovrd fn a1() {}
+ mut ovrd fn a1(self) {}
ovrd priv fn a2() {}
}
\ No newline at end of file
diff --git a/src/test/resources/parse/InvalidModifierSequence.out b/src/test/resources/parse/InvalidModifierSequence.out
index b8d55fde..ebc79bbc 100644
--- a/src/test/resources/parse/InvalidModifierSequence.out
+++ b/src/test/resources/parse/InvalidModifierSequence.out
@@ -6,11 +6,12 @@ error: Cannot declare access modifier after `mut` keyword
error: Cannot declare `ovrd` keyword after `mut` keyword
--> ./InvalidModifierSequence.casc:4:4
3 | impl InvalidModifierSequence {
-4 | mut ovrd fn a1() {}
+4 | mut ovrd fn a1(self) {}
^^^= hint: `mut` keyword here
5 |
error: Cannot declare access modifier after `ovrd` keyword
--> ./InvalidModifierSequence.casc:6:4
5 |
6 | ovrd priv fn a2() {}
- ^^^^= hint: `ovrd` keyword here
\ No newline at end of file
+ ^^^^= hint: `ovrd` keyword here
+7 | }
\ No newline at end of file
diff --git a/src/test/resources/parse/InvalidSelf.out b/src/test/resources/parse/InvalidSelf.out
index cd837470..0ca33f50 100644
--- a/src/test/resources/parse/InvalidSelf.out
+++ b/src/test/resources/parse/InvalidSelf.out
@@ -14,4 +14,5 @@ error: `self` can only be at the start of parameters
--> ./InvalidSelf.casc:8:18
7 |
8 | fn a1(i: i32, self) {}
- ^^^^= hint: move `self` to the start of parameters
\ No newline at end of file
+ ^^^^= hint: move `self` to the start of parameters
+9 | }
\ No newline at end of file
diff --git a/src/test/resources/parse/MutableCompanionFunction.casc b/src/test/resources/parse/MutableCompanionFunction.casc
new file mode 100644
index 00000000..878466e2
--- /dev/null
+++ b/src/test/resources/parse/MutableCompanionFunction.casc
@@ -0,0 +1,5 @@
+class MutableCompanionFunction
+
+impl MutableCompanionFunction {
+ mut fn a1() {}
+}
\ No newline at end of file
diff --git a/src/test/resources/parse/MutableCompanionFunction.out b/src/test/resources/parse/MutableCompanionFunction.out
new file mode 100644
index 00000000..4dd32af1
--- /dev/null
+++ b/src/test/resources/parse/MutableCompanionFunction.out
@@ -0,0 +1,6 @@
+error: Companion function cannot be mutable
+--> ./MutableCompanionFunction.casc:4:4
+3 | impl MutableCompanionFunction {
+4 | mut fn a1() {}
+ ^^^= hint: Remove this `mut` keyword
+5 | }
\ No newline at end of file
diff --git a/src/test/resources/parse/MutableTrait.casc b/src/test/resources/parse/MutableTrait.casc
new file mode 100644
index 00000000..91138572
--- /dev/null
+++ b/src/test/resources/parse/MutableTrait.casc
@@ -0,0 +1 @@
+mut trait MutableTrait
\ No newline at end of file
diff --git a/src/test/resources/parse/MutableTrait.out b/src/test/resources/parse/MutableTrait.out
new file mode 100644
index 00000000..dc3e796d
--- /dev/null
+++ b/src/test/resources/parse/MutableTrait.out
@@ -0,0 +1,4 @@
+error: Cannot declare trait instance with `mut` keyword, trait is implicitly mutable
+--> ./MutableTrait.casc:1:0
+1 | mut trait MutableTrait
+ ^^^= hint: Remove this `mut` keyword
\ No newline at end of file
diff --git a/src/test/resources/parse/NonCompanionTraitField.casc b/src/test/resources/parse/NonCompanionTraitField.casc
new file mode 100644
index 00000000..fe8d5fa3
--- /dev/null
+++ b/src/test/resources/parse/NonCompanionTraitField.casc
@@ -0,0 +1,3 @@
+trait NonCompanionTraitField {
+ a: i32
+}
\ No newline at end of file
diff --git a/src/test/resources/parse/NonCompanionTraitField.out b/src/test/resources/parse/NonCompanionTraitField.out
new file mode 100644
index 00000000..16b114be
--- /dev/null
+++ b/src/test/resources/parse/NonCompanionTraitField.out
@@ -0,0 +1,6 @@
+error: Cannot declare non-companion field a in trait
+--> ./NonCompanionTraitField.casc:2:4
+1 | trait NonCompanionTraitField {
+2 | a: i32
+ ^= hint: Declare this field in companion block
+3 | }
\ No newline at end of file
diff --git a/src/test/resources/parse/RedundantModifier.out b/src/test/resources/parse/RedundantModifier.out
index b63f1027..c05e3330 100644
--- a/src/test/resources/parse/RedundantModifier.out
+++ b/src/test/resources/parse/RedundantModifier.out
@@ -13,4 +13,5 @@ error: Redundant access modifier `pub`, `pub` is declared under the hood by comp
--> ./RedundantModifier.casc:6:4
5 |
6 | pub fn a1() {}
- ^^^= hint: Remove this access modifier `pub`
\ No newline at end of file
+ ^^^= hint: Remove this access modifier `pub`
+7 | }
\ No newline at end of file
diff --git a/src/test/resources/preludeCheck/circularInheritance.out b/src/test/resources/preludeCheck/circularInheritance.out
new file mode 100644
index 00000000..bfcd05cd
--- /dev/null
+++ b/src/test/resources/preludeCheck/circularInheritance.out
@@ -0,0 +1,10 @@
+error: Circular inheritance is forbidden
+--> ClassA.casc:3:14
+2 |
+3 | impl ClassA : ClassB
+ ^^^^^^= hint: Class ClassA inherits class ClassB but class ClassB also inherits class ClassA
+error: Circular inheritance is forbidden
+--> ClassB.casc:3:14
+2 |
+3 | impl ClassB : ClassA
+ ^^^^^^= hint: Class ClassB inherits class ClassA but class ClassA also inherits class ClassB
\ No newline at end of file
diff --git a/src/test/resources/preludeCheck/circularInheritance/ClassA.casc b/src/test/resources/preludeCheck/circularInheritance/ClassA.casc
new file mode 100644
index 00000000..ec0e9c46
--- /dev/null
+++ b/src/test/resources/preludeCheck/circularInheritance/ClassA.casc
@@ -0,0 +1,3 @@
+mut class ClassA
+
+impl ClassA : ClassB
\ No newline at end of file
diff --git a/src/test/resources/preludeCheck/circularInheritance/ClassB.casc b/src/test/resources/preludeCheck/circularInheritance/ClassB.casc
new file mode 100644
index 00000000..db2b6aaf
--- /dev/null
+++ b/src/test/resources/preludeCheck/circularInheritance/ClassB.casc
@@ -0,0 +1,3 @@
+mut class ClassB
+
+impl ClassB : ClassA
\ No newline at end of file
diff --git a/src/test/resources/preludeCheck/functionSignatureDuplicate.out b/src/test/resources/preludeCheck/functionSignatureDuplicate.out
new file mode 100644
index 00000000..a84db95b
--- /dev/null
+++ b/src/test/resources/preludeCheck/functionSignatureDuplicate.out
@@ -0,0 +1,18 @@
+error: Constructor `new`(i32, i32) has already declared in same context
+--> Class.casc:6:4
+5 |
+6 | new(x: i32, y: i32) {}
+ ^^^= hint: Try modify parameters' type
+7 |
+error: Function a(i32, i32) has already declared in same context
+--> Class.casc:10:7
+9 |
+10 | fn a(x: i32, y: i32) {}
+ ^= hint: Try rename this function or modify parameters' type
+11 |
+error: Function a(i32, i32) has already declared in same context
+--> Class.casc:14:7
+13 |
+14 | fn a(self, x: i32, y: i32) {}
+ ^= hint: Try rename this function or modify parameters' type
+15 | }
\ No newline at end of file
diff --git a/src/test/resources/preludeCheck/functionSignatureDuplicate/Class.casc b/src/test/resources/preludeCheck/functionSignatureDuplicate/Class.casc
new file mode 100644
index 00000000..4b2516a9
--- /dev/null
+++ b/src/test/resources/preludeCheck/functionSignatureDuplicate/Class.casc
@@ -0,0 +1,15 @@
+class Class
+
+impl Class {
+ new(a: i32, b: i32) {}
+
+ new(x: i32, y: i32) {}
+
+ fn a(a: i32, b: i32) {}
+
+ fn a(x: i32, y: i32) {}
+
+ fn a(self, a: i32, b: i32) {}
+
+ fn a(self, x: i32, y: i32) {}
+}
\ No newline at end of file
diff --git a/src/test/resources/validate/project/Animal.casc b/src/test/resources/validate/project/Animal.casc
index 05bf0e7a..a35e3f8b 100644
--- a/src/test/resources/validate/project/Animal.casc
+++ b/src/test/resources/validate/project/Animal.casc
@@ -1,18 +1,6 @@
trait Animal {
- comp {
- b: i32
- }
}
impl Animal {
- fn main(args: [str]) {
- System.out.println("Hello World")
- a1()
- }
-
- fn a1() {}
-
- fn a2(self)
-
- fn a4(self) {}
+ fn roar(self)
}
\ No newline at end of file
diff --git a/src/test/resources/validate/project/Atom.casc b/src/test/resources/validate/project/Atom.casc
index 7b80dc6c..63c738bf 100644
--- a/src/test/resources/validate/project/Atom.casc
+++ b/src/test/resources/validate/project/Atom.casc
@@ -7,4 +7,8 @@ impl Atom {
mut fn hello(self) {
java::lang::System.out.println("Atomic Powah!")
}
+}
+
+impl Atom for Animal {
+ ovrd fn roar(self) {}
}
\ No newline at end of file
diff --git a/src/test/resources/validate/project/Matter.casc b/src/test/resources/validate/project/Matter.casc
new file mode 100644
index 00000000..3d2ddd92
--- /dev/null
+++ b/src/test/resources/validate/project/Matter.casc
@@ -0,0 +1 @@
+abstr class Matter
\ No newline at end of file
diff --git a/src/test/resources/validate/project/Rock.casc b/src/test/resources/validate/project/Rock.casc
index a51ac9e2..ae4ceb01 100644
--- a/src/test/resources/validate/project/Rock.casc
+++ b/src/test/resources/validate/project/Rock.casc
@@ -21,7 +21,7 @@ impl Rock: Atom {
} else {
new Rock("")
}
- a.hello()
+ a.roar()
}
mut fn lol(self): i32 {