Skip to content

Commit

Permalink
feat: 🎸 add antlr bridge basic implementations
Browse files Browse the repository at this point in the history
  • Loading branch information
Xanonymous-GitHub committed Aug 4, 2024
1 parent 828ec83 commit 55dcd59
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 1 deletion.
9 changes: 9 additions & 0 deletions antlr-bridge/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
dependencies {
implementation(project(":core"))
implementation(libs.antlr)
implementation(libs.kotlin.stdlib)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.coroutines.core.jvm)
testImplementation(libs.kotlin.test)
testImplementation(libs.kotlin.test.junit5)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package tw.xcc.gumtree.antlrBridge

import java.io.File

private fun File.isFileSafeToRead(): Boolean = this.exists() && this.isFile && this.canRead()

private fun File.isNotSymbolicLink(): Boolean = !this.toPath().toRealPath().equals(this.canonicalFile.toPath())

private fun File.isPathSecure(allowedDirectory: String): Boolean {
val canonicalFilePath = this.canonicalPath
val canonicalAllowedDirectory = File(allowedDirectory).canonicalPath
return canonicalFilePath.startsWith(canonicalAllowedDirectory)
}

private fun File.isFileSizeAcceptable(maxSizeInBytes: Long): Boolean = this.length() <= maxSizeInBytes

private fun File.isFileTypeAllowed(allowedExtensions: Set<String>): Boolean {
val fileExtension = this.extension
return fileExtension in allowedExtensions
}

internal fun File.isValidToRead(
maxSizeInBytes: Long,
allowedDirectory: String,
allowedExtensions: Set<String>
): Boolean =
isFileSafeToRead() &&
isPathSecure(allowedDirectory) &&
isNotSymbolicLink() &&
isFileSizeAcceptable(maxSizeInBytes) &&
isFileTypeAllowed(allowedExtensions)
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package tw.xcc.gumtree.antlrBridge

import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.withContext
import org.antlr.v4.runtime.CharStream
import org.antlr.v4.runtime.CharStreams
import org.antlr.v4.runtime.ParserRuleContext
import org.antlr.v4.runtime.Token
import org.antlr.v4.runtime.Vocabulary
import org.antlr.v4.runtime.tree.Tree
import tw.xcc.gumtree.model.GumTree
import tw.xcc.gumtree.model.TreeType
import java.io.File
import java.io.InputStream
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract

class GumTreeConverter(private val vocabulary: Vocabulary) {
private fun fileStreamOf(path: String): InputStream {
val file = File(path)
val isValidToRead =
file.isValidToRead(
maxSizeInBytes = DEFAULT_ALLOWED_MAX_FILE_SIZE_IN_BYTE,
allowedDirectory = file.parent,
allowedExtensions = setOf(file.extension)
)

if (!isValidToRead) {
throw IllegalArgumentException("File is not valid to read")
}

return file.inputStream()
}

private fun createSingleGumTreeNodeFrom(token: Token): GumTree =
GumTree(
GumTree.Info(
type = TreeType(vocabulary.getSymbolicName(token.type) ?: "<UNKNOWN[${token.type}]>"),
text = token.toString(),
line = token.line,
posOfLine = token.charPositionInLine
)
)

private suspend fun buildWholeGumTreeFrom(antlrTree: Tree): GumTree? =
coroutineScope {
val self =
when {
antlrTree is Token -> createSingleGumTreeNodeFrom(antlrTree)
else -> null
} ?: return@coroutineScope null

with(antlrTree) {
val buildChildJobs = mutableListOf<Deferred<GumTree?>>()
for (childIdx in 0 until childCount) {
buildChildJobs.add(
async { buildWholeGumTreeFrom(getChild(childIdx)) }
)
}
self.setChildrenTo(
buildChildJobs.awaitAll().filterNotNull()
)
}

return@coroutineScope self
}

@OptIn(ExperimentalContracts::class)
suspend fun convertFrom(
filePath: String,
parseTreeCreation: (CharStream) -> ParserRuleContext
): GumTree? {
contract {
callsInPlace(parseTreeCreation, InvocationKind.AT_MOST_ONCE)
}

return coroutineScope {
val charStream =
withContext(Dispatchers.IO) {
val inputStream = fileStreamOf(filePath)
CharStreams.fromStream(inputStream)
}

val firstGrammarEntry = parseTreeCreation(charStream)
buildWholeGumTreeFrom(firstGrammarEntry)
}
}

companion object {
private const val DEFAULT_ALLOWED_MAX_FILE_SIZE_IN_BYTE = 1L * 1024 * 1024 * 1024 // 1GB
}
}
3 changes: 2 additions & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ dependencyResolutionManagement {
}
}

include(":core")
include(":core")
include(":antlr-bridge")

0 comments on commit 55dcd59

Please sign in to comment.