diff --git a/src/main/kotlin/org/casc/lang/ast/ClassInstance.kt b/src/main/kotlin/org/casc/lang/ast/ClassInstance.kt index 6f9a8c28..028b2b0a 100644 --- a/src/main/kotlin/org/casc/lang/ast/ClassInstance.kt +++ b/src/main/kotlin/org/casc/lang/ast/ClassInstance.kt @@ -8,15 +8,17 @@ import org.objectweb.asm.Opcodes data class ClassInstance( override val packageReference: Reference?, val accessorToken: Token?, + val abstrToken: Token?, // Unused val mutKeyword: Token?, val classKeyword: Token?, override val typeReference: Reference, override val fields: List, override val accessor: Accessor = Accessor.fromString(accessorToken?.literal) -) : TypeInstance(packageReference, typeReference), HasFlag { +) : TypeInstance(), HasFlag { 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 } diff --git a/src/main/kotlin/org/casc/lang/ast/Constructor.kt b/src/main/kotlin/org/casc/lang/ast/Constructor.kt index 201b50f9..4b2dafdd 100644 --- a/src/main/kotlin/org/casc/lang/ast/Constructor.kt +++ b/src/main/kotlin/org/casc/lang/ast/Constructor.kt @@ -12,8 +12,8 @@ data class Constructor( val superKeyword: Token?, val selfKeyword: Token?, val parentConstructorArguments: List, - var ownerType: Type? = null, - var parentType: Type? = null, + var ownerType: ClassType? = null, + var parentType: ClassType? = null, val accessor: Accessor = Accessor.fromString(accessorToken?.literal), var parameterTypes: List = listOf(), var parentConstructorArgumentsTypes: List = listOf(), @@ -31,6 +31,7 @@ data class Constructor( override fun asSignature(): FunctionSignature = FunctionSignature( ownerReference!!, + ownerType, companion = true, mutable = false, accessor, diff --git a/src/main/kotlin/org/casc/lang/ast/Field.kt b/src/main/kotlin/org/casc/lang/ast/Field.kt index f49b5553..87beb574 100644 --- a/src/main/kotlin/org/casc/lang/ast/Field.kt +++ b/src/main/kotlin/org/casc/lang/ast/Field.kt @@ -7,6 +7,7 @@ import org.objectweb.asm.Opcodes data class Field( val ownerReference: Reference?, val accessorToken: Token?, + val abstrKeyword: Token?, val mutKeyword: Token?, val compKeyword: Token?, val name: Token?, @@ -16,8 +17,13 @@ data class Field( ) : HasDescriptor, HasFlag { override val descriptor: String get() = type?.descriptor ?: "" - override val flag: Int = - mutKeyword.getOrElse(0, Opcodes.ACC_FINAL) + accessor.access + compKeyword.getOrElse(Opcodes.ACC_STATIC) + override val flag: Int by lazy { + var flag = accessor.access + flag += compKeyword.getOrElse(Opcodes.ACC_STATIC) + flag += abstrKeyword.getOrElse(Opcodes.ACC_ABSTRACT) + flag += mutKeyword.getOrElse(0, Opcodes.ACC_FINAL) + flag + } fun asClassField(): TypeField = TypeField( diff --git a/src/main/kotlin/org/casc/lang/ast/Function.kt b/src/main/kotlin/org/casc/lang/ast/Function.kt index ce11d5c0..2e0eaa85 100644 --- a/src/main/kotlin/org/casc/lang/ast/Function.kt +++ b/src/main/kotlin/org/casc/lang/ast/Function.kt @@ -3,19 +3,21 @@ 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?, val accessorToken: Token?, val ovrdKeyword: Token?, + val abstrKeyword: Token?, val mutKeyword: Token?, val selfKeyword: Token?, // determine whether function is companion val name: Token?, val parameters: List, val returnTypeReference: Reference?, - val statements: List, + val statements: List?, val accessor: Accessor = Accessor.fromString(accessorToken?.literal), - var ownerType: Type? = null, + var ownerType: ClassType? = null, var parameterTypes: List? = listOf(), var returnType: Type? = null ) : Method(), HasDescriptor, HasFlag, HasSignature { @@ -25,12 +27,19 @@ data class Function( s + type?.descriptor } })${returnType?.descriptor}" - override val flag: Int = - mutKeyword.getOrElse(0, Opcodes.ACC_FINAL) + accessor.access + selfKeyword.getOrElse(0, Opcodes.ACC_STATIC) + override val flag: Int by lazy { + var flag = accessor.access + flag += selfKeyword.getOrElse(0, Opcodes.ACC_STATIC) + flag += statements.getOrElse(abstrKeyword.getOrElse(Opcodes.ACC_ABSTRACT), Opcodes.ACC_ABSTRACT) + if (ownerType?.isTrait == false) + flag += mutKeyword.getOrElse(0, Opcodes.ACC_FINAL) + flag + } override fun asSignature() = FunctionSignature( ownerReference!!, + ownerType, selfKeyword == null, mutKeyword != null, accessor, diff --git a/src/main/kotlin/org/casc/lang/ast/Token.kt b/src/main/kotlin/org/casc/lang/ast/Token.kt index 36002d8d..0bb6db26 100644 --- a/src/main/kotlin/org/casc/lang/ast/Token.kt +++ b/src/main/kotlin/org/casc/lang/ast/Token.kt @@ -5,8 +5,8 @@ data class Token(var literal: String, val type: TokenType, val pos: Position) { companion object { val keywords = arrayOf( - "package", "use", "class", "impl", "comp", "pub", "prot", "intl", "priv", "true", "false", "null", - "mut", "fn", "if", "else", "for", "return", "as", "is", "new", "self", "super", "ovrd" + "package", "use", "class", "trait", "impl", "comp", "pub", "prot", "intl", "priv", "true", "false", "null", + "mut", "fn", "if", "else", "for", "return", "as", "is", "new", "self", "super", "ovrd", "abstr" ) } @@ -19,6 +19,9 @@ data class Token(var literal: String, val type: TokenType, val pos: Position) { fun isClassKeyword(): Boolean = literal == "class" + fun isTraitKeyword(): Boolean = + literal == "trait" + fun isImplKeyword(): Boolean = literal == "impl" @@ -61,6 +64,9 @@ data class Token(var literal: String, val type: TokenType, val pos: Position) { fun isOvrdKeyword(): Boolean = literal == "ovrd" + fun isAbstrKeyword(): Boolean = + literal == "abstr" + fun isAccessorKeyword(): Boolean = Accessor.validKeywords.contains(literal) } diff --git a/src/main/kotlin/org/casc/lang/ast/TraitInstance.kt b/src/main/kotlin/org/casc/lang/ast/TraitInstance.kt new file mode 100644 index 00000000..418a44ce --- /dev/null +++ b/src/main/kotlin/org/casc/lang/ast/TraitInstance.kt @@ -0,0 +1,18 @@ +package org.casc.lang.ast + +import org.casc.lang.table.HasFlag +import org.casc.lang.table.Reference +import org.objectweb.asm.Opcodes + +data class TraitInstance( + override val packageReference: Reference?, + val accessorToken: Token?, + val traitKeyword: Token?, + override val typeReference: Reference, + override val fields: List, + override val accessor: Accessor = Accessor.fromString(accessorToken?.literal) +) : TypeInstance(), HasFlag { + 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 28741b9e..60540c77 100644 --- a/src/main/kotlin/org/casc/lang/ast/TypeInstance.kt +++ b/src/main/kotlin/org/casc/lang/ast/TypeInstance.kt @@ -2,7 +2,9 @@ package org.casc.lang.ast import org.casc.lang.table.Reference -sealed class TypeInstance(open val packageReference: Reference?, open val typeReference: Reference) { +sealed class TypeInstance { + abstract val packageReference: Reference? + abstract val typeReference: Reference abstract val fields: List var impl: Impl? = null var traitImpls: List? = null diff --git a/src/main/kotlin/org/casc/lang/checker/Checker.kt b/src/main/kotlin/org/casc/lang/checker/Checker.kt index 7d4b42a4..016cb5d2 100644 --- a/src/main/kotlin/org/casc/lang/checker/Checker.kt +++ b/src/main/kotlin/org/casc/lang/checker/Checker.kt @@ -25,6 +25,7 @@ class Checker(private val preference: AbstractPreference) { fun checkDeclaration(file: File): Pair, Scope> { val scope = when (val typeInstance = file.typeInstance) { is ClassInstance -> checkClassDeclaration(typeInstance, file.path) + is TraitInstance -> checkTraitDeclaration(typeInstance, file.path) } return reports.toList() to scope @@ -98,10 +99,47 @@ class Checker(private val preference: AbstractPreference) { return classScope } - fun check(file: File, classScope: Scope): Pair, File> { - val checkedFile = checkFile(file, classScope) + private fun checkTraitDeclaration(trait: TraitInstance, filePath: String): Scope { + topScope = Scope(preference, companion = false, mutable = false, Accessor.Pub, trait.reference, isTrait = true) - return reports.toList() to checkedFile + val traitScope = Scope( + topScope, companion = false, // TODO: Allow (nested) class has `comp` + mutable = true, accessor = trait.accessor, classPath = trait.packageReference + ) + + if (trait.packageReference != null) { + val packagePath = trait.packageReference.fullQualifiedPath.replace('.', '/') + + if (!JFile(filePath).parentFile.toPath().endsWith(packagePath)) { + reports += Error( + trait.packageReference.pos, + "Package path mismatch", + "Try rename parent folders' name or rename package name" + ) + } + } + + checkIdentifierIsKeyword(trait.typeReference.fullQualifiedPath, trait.typeReference.pos) + + trait.fields.forEach { + checkField(it, traitScope) + } + + if (trait.impl != null) { + val impl = trait.impl!! + + impl.functions.forEach { + checkFunction(it, traitScope) + } + } + + return traitScope + } + + fun check(file: File, classScope: Scope): List { + checkFile(file, classScope) + + return reports.toList() } private fun checkIdentifierIsKeyword(literal: String?, position: Position? = null, isVariable: Boolean = false) { @@ -119,7 +157,7 @@ class Checker(private val preference: AbstractPreference) { Accessor.Pub -> {} Accessor.Prot -> { val accessible = targetOwnerClass.type(preference)?.isAssignableFrom( - currentScope.findType(currentScope.classReference)?.type(preference) ?: Any::class.java + currentScope.findType(currentScope.typeReference)?.type(preference) ?: Any::class.java ).getOrElse() if (!accessible) { @@ -129,7 +167,7 @@ class Checker(private val preference: AbstractPreference) { } } Accessor.Intl -> { - val accessible = targetOwnerClass.isSamePackage(currentScope.findType(currentScope.classReference)) + val accessible = targetOwnerClass.isSamePackage(currentScope.findType(currentScope.typeReference)) if (!accessible) { reports += Error( @@ -145,65 +183,63 @@ class Checker(private val preference: AbstractPreference) { } } - private fun checkFile(file: File, classScope: Scope): File { + private fun checkFile(file: File, typeInstanceScope: Scope) { when (val typeInstance = file.typeInstance) { - is ClassInstance -> checkClass(file, typeInstance, classScope) + is ClassInstance -> checkClass(file, typeInstance, typeInstanceScope) + is TraitInstance -> checkTrait(file, typeInstance, typeInstanceScope) } - - return file } - private fun checkClass(file: File, clazz: ClassInstance, classScope: Scope) { - topScope = Scope(preference, companion = false, mutable = false, Accessor.Pub, clazz.reference) - - file.usages.map { - it.tokens.forEach { token -> - checkIdentifierIsKeyword(token?.literal, token?.pos) - } - - if (topScope.usages.find { usage -> usage.fullQualifiedPath == it.fullQualifiedPath } != null) { - // Using an already used package or class - reports += Warning( - it.pos, "${it.asCASCStyle()} is already used in this context", "Consider removing this usage" - ) - } + private fun checkUsage(reference: Reference, scope: Scope) { + reference.tokens.forEach { token -> + checkIdentifierIsKeyword(token?.literal, token?.pos) + } - if (it.className == "*") { - if (TypeUtil.asType(it, preference) != null) { - // The full-qualified path represents a class name but a package name - // TODO: This should refers to nested class usage + 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" + ) + } - listOf() + if (reference.className == "*") { + if (TypeUtil.asType(reference, preference) != null) { + // The full-qualified path represents a class name but a package name + // TODO: This should refers to nested class usage + } else { + val results = mutableListOf() + + if (reference.fullQualifiedPath == "java.lang") { + // Already added by CASC internally + reports += Warning( + reference.pos, + "package `java.lang` has been added by compiler under the hood", + "Consider remove this usage" + ) } else { - val results = mutableListOf() - - if (it.fullQualifiedPath == "java.lang") { - // Already added by CASC internally - reports += Warning( - it.pos, - "package `java.lang` has been added by compiler under the hood", - "Consider remove this usage" - ) - } else { - val classes = ClassGraph().acceptPackagesNonRecursive(it.fullQualifiedPath) - .overrideClassLoaders(ClassLoader.getSystemClassLoader()).scan() + val classes = ClassGraph().acceptPackagesNonRecursive(reference.fullQualifiedPath) + .overrideClassLoaders(ClassLoader.getSystemClassLoader()).scan() - for (classInfo in classes.allStandardClasses) results += Reference(classInfo.loadClass().name) - } - - results + for (classInfo in classes.allStandardClasses) results += Reference(classInfo.loadClass().name) } - } else { - val type = TypeUtil.asType(it, preference) - if (type == null) { - reports.reportUnknownTypeSymbol(it) - - listOf() - } else listOf(it) + scope.usages += results } - }.flatten().forEach(classScope.usages::add) + } else { + val type = TypeUtil.asType(reference, preference) + + if (type != null) scope.usages += reference + else reports.reportUnknownTypeSymbol(reference) + } + } + + private fun checkClass(file: File, clazz: ClassInstance, classScope: Scope) { + topScope = Scope(preference, companion = false, mutable = false, Accessor.Pub, clazz.reference) + + file.usages.forEach { + checkUsage(it, classScope) + } val companionBlockScope = Scope(classScope) @@ -219,13 +255,46 @@ class Checker(private val preference: AbstractPreference) { } impl.functions.forEach { - checkFunctionBody(it, Scope(classScope, isCompScope = it.selfKeyword == null)) + 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" - ) + 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) { + topScope = Scope(preference, companion = false, mutable = false, Accessor.Pub, trait.reference, isTrait = true) + + file.usages.forEach { + checkUsage(it, traitScope) + } + + val companionBlockScope = Scope(traitScope) + + if (trait.impl != null) { + val impl = trait.impl!! + + impl.companionBlock.forEach { + checkStatement(it, companionBlockScope, PrimitiveType.Unit) + } + + impl.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" + ) + } } } } @@ -249,7 +318,7 @@ class Checker(private val preference: AbstractPreference) { // Validate types first then register it to scope // Check if parameter has duplicate names val localScope = Scope(scope) - localScope.registerVariable(true, "self", TypeUtil.asType(localScope.classReference, preference)) + localScope.registerVariable(true, "self", TypeUtil.asType(localScope.typeReference, preference)) val duplicateParameters = constructor.parameters.groupingBy { checkIdentifierIsKeyword(it.name?.literal, it.name?.pos) @@ -281,8 +350,8 @@ class Checker(private val preference: AbstractPreference) { if (constructor.parentReference == null) constructor.parentReference = Reference.OBJECT_TYPE_REFERENCE - constructor.ownerType = findType(constructor.ownerReference, localScope) - constructor.parentType = findType(constructor.parentReference, localScope) + constructor.ownerType = findType(constructor.ownerReference, localScope) as ClassType + constructor.parentType = findType(constructor.parentReference, localScope) as ClassType constructor.parentConstructorArgumentsTypes = constructor.parentConstructorArguments.mapNotNull { if (it == null) null @@ -347,7 +416,7 @@ class Checker(private val preference: AbstractPreference) { null } else type } - function.ownerType = findType(function.ownerReference, scope) + function.ownerType = findType(function.ownerReference, scope) as ClassType // Owner class must be class type // Check is overriding parent function val parentFunction = @@ -399,7 +468,7 @@ class Checker(private val preference: AbstractPreference) { } private fun checkConstructorBody(constructor: Constructor, scope: Scope) { - scope.registerVariable(true, "self", TypeUtil.asType(scope.classReference, preference)) + scope.registerVariable(true, "self", TypeUtil.asType(scope.typeReference, preference)) if (constructor.superKeyword != null) { // `super` call @@ -434,11 +503,12 @@ class Checker(private val preference: AbstractPreference) { // Requires `super` call reports += Error( constructor.newKeyword?.pos, - "Class ${scope.classReference} extends class ${scope.parentClassPath} but doesn't `super` any parent class' constructor", + "Class ${scope.typeReference} extends class ${scope.parentClassPath} but doesn't `super` any parent class' constructor", "Add `super` call after constructor declaration" ) } else constructor.parentConstructorSignature = FunctionSignature( Reference.OBJECT_TYPE_REFERENCE, + ClassType.OBJECT_TYPE, companion = true, mutable = false, Accessor.Pub, @@ -459,14 +529,14 @@ class Checker(private val preference: AbstractPreference) { private fun checkFunctionBody(function: Function, scope: Scope) { if (function.selfKeyword != null) { - scope.registerVariable(true, "self", TypeUtil.asType(scope.classReference, preference)) + scope.registerVariable(true, "self", TypeUtil.asType(scope.typeReference, preference)) } function.parameters.forEachIndexed { i, parameter -> scope.registerVariable(false, parameter.name!!.literal, function.parameterTypes?.get(i)) } - function.statements.forEach { + function.statements!!.forEach { checkStatement(it, scope, function.returnType ?: PrimitiveType.Unit) } } @@ -679,7 +749,7 @@ class Checker(private val preference: AbstractPreference) { ) } - if (!field.companion && field.ownerReference != scope.classReference && field.ownerReference != scope.parentClassPath) { + if (!field.companion && field.ownerReference != scope.typeReference && field.ownerReference != scope.parentClassPath) { reports += Error( expression.leftExpression.pos, "Cannot access non-companion field $name from other context" @@ -712,7 +782,7 @@ class Checker(private val preference: AbstractPreference) { if (variable == null) { // Lookup local field - checkFieldAssignment(scope.findField(scope.classReference, name)) + checkFieldAssignment(scope.findField(scope.typeReference, name)) } else { if (!variable.mutable) { reports += Error( @@ -789,7 +859,7 @@ class Checker(private val preference: AbstractPreference) { ) } else { checkCompanionAccessibility(field) - if (ownerReference != scope.classReference) checkAccessible( + if (ownerReference != scope.typeReference) checkAccessible( scope, expression.name.pos, field, @@ -814,7 +884,7 @@ class Checker(private val preference: AbstractPreference) { ) } else { checkCompanionAccessibility(field) - if (field.ownerReference != scope.classReference) checkAccessible( + if (field.ownerReference != scope.typeReference) checkAccessible( scope, expression.name.pos, field, @@ -832,7 +902,7 @@ class Checker(private val preference: AbstractPreference) { ) } else { expression.type = when (expression.name.literal) { - "self" -> scope.findType(scope.classReference) + "self" -> scope.findType(scope.typeReference) "super" -> scope.findType(scope.parentClassPath) else -> null } @@ -888,7 +958,7 @@ class Checker(private val preference: AbstractPreference) { true // Check function call expression's context, e.g companion context - val ownerReference = expression.ownerReference ?: previousType?.getReference() ?: scope.classReference + val ownerReference = expression.ownerReference ?: previousType?.getReference() ?: scope.typeReference val functionSignature = scope.findSignature( ownerReference, expression.name!!.literal, argumentTypes ) @@ -901,7 +971,7 @@ class Checker(private val preference: AbstractPreference) { }) does not exist in current context" ) } else { - if (ownerReference != scope.classReference) checkAccessible( + if (ownerReference != scope.typeReference) checkAccessible( scope, expression.name.pos, functionSignature, @@ -934,7 +1004,7 @@ class Checker(private val preference: AbstractPreference) { ) } } else if (scope.isChildType( - functionSignature.ownerReference, previousType?.getReference() ?: scope.classReference + functionSignature.ownerReference, previousType?.getReference() ?: scope.typeReference ) ) expression.superCall = true } @@ -963,7 +1033,7 @@ class Checker(private val preference: AbstractPreference) { }) does not exist" ) } else { - if (expression.constructorOwnerReference != scope.classReference) checkAccessible( + if (expression.constructorOwnerReference != scope.typeReference) checkAccessible( scope, expression.pos, signature, diff --git a/src/main/kotlin/org/casc/lang/compilation/Compilation.kt b/src/main/kotlin/org/casc/lang/compilation/Compilation.kt index 76f88f7a..b21aee13 100644 --- a/src/main/kotlin/org/casc/lang/compilation/Compilation.kt +++ b/src/main/kotlin/org/casc/lang/compilation/Compilation.kt @@ -129,10 +129,13 @@ class Compilation(private val preference: AbstractPreference) { declarations.find { it.typeInstance.reference.fullQualifiedPath == typeInstance.impl!!.parentClassReference!!.fullQualifiedPath } if (parentClazzFile != null) addToQueue(parentClazzFile) - creationQueue.add(file.typeInstance.reference.fullQualifiedPath) - } else { - creationQueue.add(file.typeInstance.reference.fullQualifiedPath) } + + if (typeInstance.traitImpls != null) { + // TODO: implement traitImpls precaching + } + + creationQueue.add(file.typeInstance.reference.fullQualifiedPath) } declarations.forEach(::addToQueue) @@ -156,9 +159,8 @@ class Compilation(private val preference: AbstractPreference) { * Check all AST declaration's body's validity */ val checker = Checker(preference) - val (checkReports, checkResult) = checker.check(compilationUnit.file, compilationUnit.scope) + val checkReports = checker.check(compilationUnit.file, compilationUnit.scope) - compilationUnit.file = checkResult compilationUnit.reports = checkReports } } @@ -256,9 +258,8 @@ class Compilation(private val preference: AbstractPreference) { } measureTime("Check (Main)") { - val (bodyReports, finalFile) = checker.check(file!!, scope!!) + val bodyReports = checker.check(file!!, scope!!) - file = finalFile reports = bodyReports } diff --git a/src/main/kotlin/org/casc/lang/emitter/Emitter.kt b/src/main/kotlin/org/casc/lang/emitter/Emitter.kt index 9557bc39..855cbd56 100644 --- a/src/main/kotlin/org/casc/lang/emitter/Emitter.kt +++ b/src/main/kotlin/org/casc/lang/emitter/Emitter.kt @@ -10,6 +10,7 @@ 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,7 +19,8 @@ class Emitter(private val preference: AbstractPreference, private val declaratio private fun emitFile(file: File): ByteArray { val bytecode = when (val typeInstance = file.typeInstance) { - is ClassInstance -> emitClass(file.path, typeInstance) + is ClassInstance -> emitClass(typeInstance, file.fileName) + is TraitInstance -> emitTrait(typeInstance, file.fileName) } val outFile = JFile(preference.outputDir, "/${file.typeInstance.typeReference.className}.class") @@ -35,7 +37,7 @@ class Emitter(private val preference: AbstractPreference, private val declaratio return bytecode } - private fun emitClass(sourceFile: String, clazz: ClassInstance): ByteArray { + private fun emitClass(clazz: ClassInstance, sourceFile: String): ByteArray { val classWriter = CommonClassWriter(ClassWriter.COMPUTE_FRAMES + ClassWriter.COMPUTE_MAXS, preference.classLoader) @@ -64,7 +66,7 @@ class Emitter(private val preference: AbstractPreference, private val declaratio methodVisitor.visitCode() impl.companionBlock.forEach { - emitStatement(methodVisitor, it!!) + emitStatement(methodVisitor, it) } methodVisitor.visitInsn(Opcodes.RETURN) @@ -87,6 +89,52 @@ 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) + + classWriter.visit( + Opcodes.V1_8, + trait.flag, + trait.reference.internalName(), + null, + trait.impl?.parentClassReference?.fullQualifiedPath ?: Reference.OBJECT_TYPE_REFERENCE.internalName(), + null + ) + + classWriter.visitSource(sourceFile, null) + + if (!declarationOnly) { + trait.fields.forEach { + emitField(classWriter, it) + } + + if (trait.impl != null) { + val impl = trait.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.functions.forEach { + emitFunction(classWriter, it) + } + } + } + + return classWriter.toByteArray() + } + private fun emitField(classWriter: ClassWriter, field: Field) { val classField = field.asClassField() @@ -137,17 +185,19 @@ class Emitter(private val preference: AbstractPreference, private val declaratio null ) - methodVisitor.visitCode() + if (function.statements != null) { + methodVisitor.visitCode() - function.statements.forEach { - emitStatement(methodVisitor, it) - } + function.statements.forEach { + emitStatement(methodVisitor, it) + } - if (function.returnType == PrimitiveType.Unit) - methodVisitor.visitInsn(Opcodes.RETURN) + if (function.returnType == PrimitiveType.Unit) + methodVisitor.visitInsn(Opcodes.RETURN) - methodVisitor.visitMaxs(-1, -1) - methodVisitor.visitEnd() + methodVisitor.visitMaxs(-1, -1) + methodVisitor.visitEnd() + } } private fun emitStatement(methodVisitor: MethodVisitor, statement: Statement) { @@ -347,16 +397,20 @@ class Emitter(private val preference: AbstractPreference, private val declaratio functionSignature.ownerReference.internalName(), functionSignature.name, functionSignature.descriptor, - false // TODO: Support interface function calling + functionSignature.ownerType?.isTrait == true ) } else { // Use INVOKEVIRTUAL instead methodVisitor.visitMethodInsn( - if (expression.superCall) Opcodes.INVOKESPECIAL else Opcodes.INVOKEVIRTUAL, + when { + expression.superCall -> Opcodes.INVOKESPECIAL + functionSignature.ownerType?.isTrait == true -> Opcodes.INVOKEINTERFACE + else -> Opcodes.INVOKEVIRTUAL + }, functionSignature.ownerReference.internalName(), functionSignature.name, functionSignature.descriptor, - false // TODO: Support interface function calling + functionSignature.ownerType?.isTrait == true ) } } diff --git a/src/main/kotlin/org/casc/lang/parser/Parser.kt b/src/main/kotlin/org/casc/lang/parser/Parser.kt index e7a73ca2..1b47f36c 100644 --- a/src/main/kotlin/org/casc/lang/parser/Parser.kt +++ b/src/main/kotlin/org/casc/lang/parser/Parser.kt @@ -9,6 +9,7 @@ import org.casc.lang.compilation.Warning import org.casc.lang.table.HasFlag import org.casc.lang.table.Reference import org.casc.lang.utils.MutableObjectSet +import org.casc.lang.utils.Tuple4 import org.objectweb.asm.Opcodes import java.io.File as JFile @@ -137,13 +138,14 @@ class Parser(private val preference: AbstractPreference) { continue } - val (accessor, _, mutable) = parseModifiers(ovrdKeyword = false) { it.isClassKeyword() || it.isImplKeyword() } + val (accessor, abstr, _, mutable) = parseModifiers(ovrdKeyword = false) { it.isClassKeyword() || it.isTraitKeyword() || it.isImplKeyword() } if (peekIf(Token::isClassKeyword)) { + // Class declaration val classKeyword = next()!! val classReference = parseTypeSymbol() val fields = if (peekIf(TokenType.OpenBrace)) { - assertUntil(TokenType.OpenBrace) + consume() // open brace val fields = parseFields(classReference) assertUntil(TokenType.CloseBrace) fields @@ -154,7 +156,7 @@ class Parser(private val preference: AbstractPreference) { if (classReference.fullQualifiedPath.isEmpty()) continue val classInstance = - ClassInstance(packageReference, accessor, mutable, classKeyword, classReference, fields) + ClassInstance(packageReference, accessor, abstr, mutable, classKeyword, classReference, fields) if (typeInstances.containsKey(classReference)) { // Class declaration duplication @@ -164,6 +166,60 @@ class Parser(private val preference: AbstractPreference) { "Consider remove this class declaration" ) } else typeInstances[classReference] = classInstance + } else if (peekIf(Token::isTraitKeyword)) { + // Trait declaration + if (abstr != null) { + // Trait with `abstr` keyword + reports += Error( + abstr.pos, + "Cannot declare trait instance with `abstr` keyword, trait is implicitly abstract", + "Remove this `abstr` keyword" + ) + } + + if (mutable != null) { + // Trait with `mut` keyword + reports += Error( + mutable.pos, + "Cannot declare trait instance with `mut` keyword, trait is implicitly mutable", + "Remove this `mut` keyword" + ) + } + + val traitKeyword = next()!! + val traitReference = parseTypeSymbol() + val fields = if (peekIf(TokenType.OpenBrace)) { + consume() // open brace + val fields = parseFields(traitReference) + assertUntil(TokenType.CloseBrace) + fields + } else listOf() + + for (field in fields) { + if (field.compKeyword == null) { + // non-companion field in trait + reports += Error( + field.name?.pos, + "Cannot declare non-companion field ${field.name?.literal ?: ""} in trait", + "Declare this field in companion block" + ) + } + } + + // Missing class reference, most likely caused by token missing + if (traitReference.fullQualifiedPath.isEmpty()) continue + + val traitInstance = + TraitInstance(packageReference, accessor, traitKeyword, traitReference, fields) + + if (typeInstances.containsKey(traitReference)) { + // Trait declaration duplication + reports += Error( + traitReference.pos, + "Duplicated trait declaration for class ${traitReference.asCASCStyle()}", + "Consider remove this trait declaration" + ) + } else typeInstances[traitReference] = traitInstance } else if (peekIf(Token::isImplKeyword)) { val implKeyword = next()!! val ownerReference = parseTypeSymbol() @@ -218,9 +274,7 @@ class Parser(private val preference: AbstractPreference) { ) } else majorImpls[ownerReference] = impl } - } else if (hasNext()) { - reports.reportUnexpectedToken(next()!!) - } else break + } } // Bind major type instance @@ -247,6 +301,60 @@ class Parser(private val preference: AbstractPreference) { // Bind implementations to type instances for ((typeInstanceReference, typeInstanceEntry) in typeInstances) { majorImpls[typeInstanceReference]?.let { + when (typeInstanceEntry) { + is ClassInstance -> { + for (function in it.functions) { + if (function.statements == null && (typeInstanceEntry.abstrToken == null || function.abstrKeyword == null)) { + // Function body missing while function is not declared with `abstr` keyword + reports += Error( + function.name?.pos, + "Function body must be implemented when both class instance and function didn't declared with `abstr` keyword", + "Add function body" + ) + } + + if (function.selfKeyword == null && function.statements == null) { + // Companion function body missing + reports += Error( + function.name?.pos, + "Companion function must have function body" + ) + } + } + } + is TraitInstance -> { + if (it.parentClassReference != null) { + // Illegal inheritance for trait instance's implementation + reports += Error( + it.parentClassReference.pos, + "Trait cannot inherit type instance by major implementation", + "Replace `:` (colon) with `for` keyword" + ) + } + + if (it.constructors.isNotEmpty()) { + // Illegal constructor declaration for trait instance's implementation + for (constructor in it.constructors) { + reports += Error( + constructor.newKeyword?.pos, + "Trait cannot have constructors", + "Remove this constructor declaration" + ) + } + } + + for (function in it.functions) { + if (function.selfKeyword == null && function.statements == null) { + // Companion function body missing + reports += Error( + function.name?.pos, + "Companion function must have function body" + ) + } + } + } + } + typeInstanceEntry.impl = it } @@ -376,18 +484,23 @@ class Parser(private val preference: AbstractPreference) { /** * parseModifiers follows the following modifier sequence: - * (`pub`#1 / `prot` / `intl` / `priv`)? (`ovrd`)? (`mut`)? + * (`pub`#1 / `prot` / `intl` / `priv`)? (`ovrd`)? (`abstr`)? (`mut`)? + * + * Notice that `abstr` and `mut` keywords are conflicted, when conflicted, sequence error will be muted, instead, + * generates a conflicted error * * #1: Will generate a warning by default. */ private fun parseModifiers( accessModifier: Boolean = true, - mutableKeyword: Boolean = true, ovrdKeyword: Boolean = true, + abstrKeyword: Boolean = true, + mutableKeyword: Boolean = true, forbidPubAccessor: Boolean = true, terminator: (Token) -> Boolean - ): Triple { + ): Tuple4 { var accessor: Token? = null + var abstr: Token? = null var ovrd: Token? = null var mutable: Token? = null @@ -407,6 +520,11 @@ class Parser(private val preference: AbstractPreference) { ) } + if (abstrKeyword && abstr != null) { + reports += Error( + abstr.pos, "Cannot declare access modifier after `abstr` keyword", "`abstr` keyword here" + ) + } if (ovrdKeyword && ovrd != null) { reports += Error( ovrd.pos, "Cannot declare access modifier after `ovrd` keyword", "`ovrd` keyword here" @@ -433,6 +551,34 @@ class Parser(private val preference: AbstractPreference) { "Remove this access modifier `${token.literal}`" ) } + } else if (token.isAbstrKeyword()) { + if (abstrKeyword) { + if (abstr != null) { + reports += Error( + abstr.pos, "Duplicate `abstr` keyword", "Encountered first one here" + ) + reports += Error( + token.pos, "Duplicate `abstr` keyword", "Duplicate here" + ) + } + + if (mutableKeyword && mutable != null) { + // Conflicted modifiers + reports += Error( + mutable.pos, + "Cannot declare `abstr` keyword while `mut` keyword was declared", + "`mut` keyword here" + ) + } + + abstr = token + } else { + reports += Error( + token.pos, + "Cannot declare `abstr` keyword in current context", + "Remove this `abstr` keyword" + ) + } } else if (token.isOvrdKeyword()) { if (ovrdKeyword) { if (ovrd != null) { @@ -444,6 +590,11 @@ class Parser(private val preference: AbstractPreference) { ) } + if (abstrKeyword && abstr != null) { + reports += Error( + abstr.pos, "Cannot declare `ovrd` keyword after `abstr` keyword", "`abstr` keyword here" + ) + } if (mutableKeyword && mutable != null) { reports += Error( mutable.pos, "Cannot declare `ovrd` keyword after `mut` keyword", "`mut` keyword here" @@ -467,6 +618,15 @@ class Parser(private val preference: AbstractPreference) { ) } + if (abstrKeyword && abstr != null) { + // Conflicted modifiers + reports += Error( + abstr.pos, + "Cannot declare `mut` keyword while `abstr` keyword was declared", + "`abstr` keyword here" + ) + } + mutable = token } else { reports += Error( @@ -484,13 +644,14 @@ class Parser(private val preference: AbstractPreference) { } } - return Triple(accessor, ovrd, mutable) + return Tuple4(accessor, abstr, ovrd, mutable) } private fun parseFields( classReference: Reference?, compKeyword: Token? = null ): List { var accessorToken: Token? = null + var abstrToken: Token? = null var mutKeyword: Token? = null var compScopeDeclared = false val usedFlags = mutableSetOf(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL) // Default flag is pub (final) @@ -530,16 +691,18 @@ class Parser(private val preference: AbstractPreference) { // Scoped-modified fields // (`pub`#1 / `prot` / `intl` / `priv`)? (`mut`)? : // Assume it's accessor keyword or mut keyword - val (accessor, _, mutable) = parseModifiers( + val (accessor, abstr, _, mutable) = parseModifiers( ovrdKeyword = false, forbidPubAccessor = false ) { it.type == TokenType.Colon } accessorToken = accessor + abstrToken = abstr mutKeyword = mutable assertUntil(TokenType.Colon) - val currentFlag = HasFlag.getFlag(Accessor.fromString(accessorToken?.literal), mutKeyword != null) + val currentFlag = + HasFlag.getFlag(Accessor.fromString(accessorToken?.literal), abstrToken != null, mutKeyword != null) if (!usedFlags.add(currentFlag)) { reports += Error( @@ -555,7 +718,7 @@ class Parser(private val preference: AbstractPreference) { val typeReference = parseComplexTypeSymbol() val field = Field( - classReference, accessorToken, mutKeyword, compKeyword, name, typeReference + classReference, accessorToken, abstrToken, mutKeyword, compKeyword, name, typeReference ) if (!fields.add(field)) { @@ -587,7 +750,7 @@ class Parser(private val preference: AbstractPreference) { while (hasNext()) { if (peekIf(TokenType.CloseBrace)) break - val (accessor, ovrd, mutable) = parseModifiers { it.isNewKeyword() || it.isFnKeyword() || it.isCompKeyword() || it.type == TokenType.CloseBrace } + val (accessor, abstr, ovrd, mutable) = parseModifiers { it.isNewKeyword() || it.isFnKeyword() || it.isCompKeyword() || it.type == TokenType.CloseBrace } if (peekIf(Token::isCompKeyword)) { // Companion block @@ -721,14 +884,19 @@ class Parser(private val preference: AbstractPreference) { parseComplexTypeSymbol() } else null - assertUntil(TokenType.OpenBrace) - val statements = parseStatements(parameterSelfKeyword != null) - assertUntil(TokenType.CloseBrace) + val statements = if (peekIf(TokenType.OpenBrace)) { + consume() // open brace + val statements = parseStatements(parameterSelfKeyword != null) + assertUntil(TokenType.CloseBrace) + + statements + } else null val function = Function( classReference, accessor, ovrd, + abstr, mutable, parameterSelfKeyword, name, diff --git a/src/main/kotlin/org/casc/lang/table/ClassType.kt b/src/main/kotlin/org/casc/lang/table/ClassType.kt index 4fa16750..808578d0 100644 --- a/src/main/kotlin/org/casc/lang/table/ClassType.kt +++ b/src/main/kotlin/org/casc/lang/table/ClassType.kt @@ -11,6 +11,7 @@ data class ClassType( val parentClassName: String?, override val accessor: Accessor, val mutable: Boolean, + val isTrait: Boolean, override val internalName: String = typeName.replace('.', '/'), override val descriptor: String = "L$internalName;" ) : Type, HasAccessor { @@ -22,7 +23,8 @@ data class ClassType( clazz.typeName, clazz.superclass?.typeName, Accessor.fromModifier(clazz.modifiers), - Modifier.isFinal(clazz.modifiers) + Modifier.isFinal(clazz.modifiers), + clazz.isInterface ) override fun type(preference: AbstractPreference): Class<*>? = try { diff --git a/src/main/kotlin/org/casc/lang/table/FunctionSignature.kt b/src/main/kotlin/org/casc/lang/table/FunctionSignature.kt index 61ee8440..0b7f5ae8 100644 --- a/src/main/kotlin/org/casc/lang/table/FunctionSignature.kt +++ b/src/main/kotlin/org/casc/lang/table/FunctionSignature.kt @@ -6,6 +6,7 @@ import org.objectweb.asm.Opcodes data class FunctionSignature( val ownerReference: Reference, + val ownerType: ClassType?, val companion: Boolean, val mutable: Boolean, override val accessor: Accessor, diff --git a/src/main/kotlin/org/casc/lang/table/HasFlag.kt b/src/main/kotlin/org/casc/lang/table/HasFlag.kt index 3eb0b816..125fc09f 100644 --- a/src/main/kotlin/org/casc/lang/table/HasFlag.kt +++ b/src/main/kotlin/org/casc/lang/table/HasFlag.kt @@ -6,8 +6,8 @@ import org.objectweb.asm.Opcodes interface HasFlag { companion object { - fun getFlag(accessor: Accessor, mutable: Boolean): Int = - accessor.access + mutable.getOrElse(0, Opcodes.ACC_FINAL) + fun getFlag(accessor: Accessor, abstr: Boolean, mutable: Boolean): Int = + accessor.access + if (abstr) Opcodes.ACC_ABSTRACT else if (!mutable) Opcodes.ACC_FINAL else 0 } val flag: Int diff --git a/src/main/kotlin/org/casc/lang/table/Scope.kt b/src/main/kotlin/org/casc/lang/table/Scope.kt index c923d175..9618b577 100644 --- a/src/main/kotlin/org/casc/lang/table/Scope.kt +++ b/src/main/kotlin/org/casc/lang/table/Scope.kt @@ -1,10 +1,7 @@ package org.casc.lang.table import io.github.classgraph.ClassGraph -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.ast.* import org.casc.lang.compilation.AbstractPreference import org.casc.lang.utils.getOrElse import java.lang.reflect.Modifier @@ -14,7 +11,8 @@ data class Scope( var companion: Boolean, var mutable: Boolean, var accessor: Accessor, - var classReference: Reference, + var typeReference: Reference, + var isTrait: Boolean = false, var parentClassPath: Reference? = null, var usages: MutableSet = mutableSetOf(), var fields: MutableSet = mutableSetOf(), @@ -30,6 +28,7 @@ data class Scope( mutable: Boolean? = null, accessor: Accessor? = null, classPath: Reference? = null, + isTrait: Boolean? = null, isCompScope: Boolean = false, isLoopScope: Boolean = false, ) : this( @@ -37,7 +36,8 @@ data class Scope( companion ?: parent.companion, mutable ?: parent.mutable, accessor ?: parent.accessor, - classPath ?: parent.classReference, + classPath ?: parent.typeReference, + isTrait ?: parent.isTrait, parent.parentClassPath, parent.usages.toMutableSet(), parent.fields.toMutableSet(), @@ -71,7 +71,7 @@ data class Scope( } fun findField(ownerPath: Reference?, fieldName: String): TypeField? { - if (ownerPath == null || ownerPath == classReference) return fields.find { it.name == fieldName } + if (ownerPath == null || ownerPath == typeReference) return fields.find { it.name == fieldName } // Find function signature from cached classes first val ownerClass = Table.findTypeInstance(ownerPath) @@ -146,6 +146,7 @@ data class Scope( } }?.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 && @@ -160,6 +161,7 @@ data class Scope( functionName, argumentTypes ) + is TraitInstance -> null } } } @@ -179,6 +181,7 @@ data class Scope( 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), @@ -194,7 +197,7 @@ data class Scope( // Function try { var (ownerClazz, argumentClasses) = - if (ownerPath == classReference) retrieveExecutableInfo( + if (ownerPath == typeReference) retrieveExecutableInfo( findType(parentClassPath) ?: ClassType(Any::class.java), argTypes ) else retrieveExecutableInfo(ownerType, argTypes) @@ -211,6 +214,7 @@ data class Scope( ) { signature = FunctionSignature( Reference(ownerClazz), + TypeUtil.asType(ownerClazz, preference) as ClassType, Modifier.isStatic(function.modifiers), Modifier.isFinal(function.modifiers), Accessor.fromModifier(function.modifiers), @@ -264,7 +268,7 @@ data class Scope( fun findVariable(variableName: String): Variable? = if (!isCompScope && (variableName == "self" || variableName == "super")) { when (variableName) { - "self" -> Variable(true, "self", findType(classReference), 0, scopeDepth) + "self" -> Variable(true, "self", findType(typeReference), 0, scopeDepth) "super" -> Variable(true, "super", findType(parentClassPath), 0, scopeDepth) else -> null // Should not happen } @@ -282,11 +286,12 @@ data class Scope( fun findType(reference: Reference?): Type? = when (reference) { null -> null - classReference -> ClassType( - classReference.fullQualifiedPath, + typeReference -> ClassType( + typeReference.fullQualifiedPath, parentClassPath?.fullQualifiedPath, accessor, - mutable + mutable, + isTrait ) else -> TypeUtil.asType(findReference(reference), preference) } @@ -306,7 +311,7 @@ data class Scope( fun isChildType(parentReference: Reference?, childReference: Reference?): Boolean = when { childReference == null -> false parentReference == childReference -> false - childReference == classReference -> isChildType( + childReference == typeReference -> isChildType( findType(parentReference), findType(parentClassPath) ) @@ -314,7 +319,7 @@ data class Scope( } fun isChildType(parentReference: Reference?): Boolean = - isChildType(parentReference, classReference) + isChildType(parentReference, typeReference) private fun retrieveExecutableInfo( ownerType: Type, diff --git a/src/main/kotlin/org/casc/lang/table/Table.kt b/src/main/kotlin/org/casc/lang/table/Table.kt index 49815304..290cc86c 100644 --- a/src/main/kotlin/org/casc/lang/table/Table.kt +++ b/src/main/kotlin/org/casc/lang/table/Table.kt @@ -3,6 +3,7 @@ package org.casc.lang.table import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import org.casc.lang.ast.ClassInstance import org.casc.lang.ast.File +import org.casc.lang.ast.TraitInstance import org.casc.lang.ast.TypeInstance /* @@ -29,7 +30,8 @@ object Table { else Reference.OBJECT_TYPE_REFERENCE.fullQualifiedPath, it.typeInstance.accessor, if (typeInstance is ClassInstance) typeInstance.mutKeyword != null - else false + else false, + typeInstance is TraitInstance ) } diff --git a/src/main/kotlin/org/casc/lang/utils/tuples.kt b/src/main/kotlin/org/casc/lang/utils/tuples.kt new file mode 100644 index 00000000..1642cd90 --- /dev/null +++ b/src/main/kotlin/org/casc/lang/utils/tuples.kt @@ -0,0 +1,3 @@ +package org.casc.lang.utils + +data class Tuple4(val v1: T1, val v2: T2, val v3: T3, val v4: T4) \ No newline at end of file diff --git a/src/test/resources/validate/project/Animal.casc b/src/test/resources/validate/project/Animal.casc new file mode 100644 index 00000000..05bf0e7a --- /dev/null +++ b/src/test/resources/validate/project/Animal.casc @@ -0,0 +1,18 @@ +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) {} +} \ No newline at end of file