Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,15 +1,262 @@
package cc.unitmesh.agent.tool.impl

import cc.unitmesh.codegraph.model.*
import cc.unitmesh.codegraph.parser.CodeParser
import cc.unitmesh.codegraph.parser.ios.IosCodeParser
import cc.unitmesh.codegraph.parser.Language

/**
* Android implementation of CodeParser factory
* Uses the same JVM-based implementation as regular JVM
* Android implementation of CodeParser factory.
*
* Note: Android cannot access jvmMain code directly, so we provide a simplified
* regex-based implementation similar to iOS. For full TreeSitter functionality,
* consider using server-side parsing.
*/
actual fun createCodeParser(): CodeParser {
// Android uses JVM backend, but IosCodeParser is a fallback
// In practice, we should use JvmCodeParser but it's not accessible from androidMain
// For now, use the simplified iOS implementation
return IosCodeParser()
return AndroidCodeParser()
}

/**
* Simplified CodeParser for Android platform.
* Uses regex-based parsing to extract basic code structure information.
*/
private class AndroidCodeParser : CodeParser {

override suspend fun parseNodes(
sourceCode: String,
filePath: String,
language: Language
): List<CodeNode> {
return when (language) {
Language.JAVA, Language.KOTLIN -> parseOOPNodes(sourceCode, filePath, language)
Language.JAVASCRIPT, Language.TYPESCRIPT -> parseJSNodes(sourceCode, filePath, language)
Language.PYTHON -> parsePythonNodes(sourceCode, filePath, language)
else -> emptyList()
}
}

override suspend fun parseNodesAndRelationships(
sourceCode: String,
filePath: String,
language: Language
): Pair<List<CodeNode>, List<CodeRelationship>> {
val nodes = parseNodes(sourceCode, filePath, language)
val relationships = buildRelationships(nodes)
return Pair(nodes, relationships)
}

override suspend fun parseCodeGraph(
files: Map<String, String>,
language: Language
): CodeGraph {
val allNodes = mutableListOf<CodeNode>()
val allRelationships = mutableListOf<CodeRelationship>()

for ((filePath, sourceCode) in files) {
val (nodes, relationships) = parseNodesAndRelationships(sourceCode, filePath, language)
allNodes.addAll(nodes)
allRelationships.addAll(relationships)
}

return CodeGraph(
nodes = allNodes,
relationships = allRelationships,
metadata = mapOf(
"language" to language.name,
"fileCount" to files.size.toString(),
"platform" to "Android"
)
)
}

override suspend fun parseImports(
sourceCode: String,
filePath: String,
language: Language
): List<ImportInfo> {
return when (language) {
Language.JAVA, Language.KOTLIN -> extractJvmImports(sourceCode, filePath)
Language.PYTHON -> extractPythonImports(sourceCode, filePath)
Language.JAVASCRIPT, Language.TYPESCRIPT -> extractJsImports(sourceCode, filePath)
else -> emptyList()
}
}

private fun extractJvmImports(content: String, filePath: String): List<ImportInfo> {
val importRegex = Regex("""import\s+(static\s+)?([a-zA-Z_][\w.]*[\w*])""")
return importRegex.findAll(content).map { match ->
// Calculate actual line number from match position
val lineNumber = content.substring(0, match.range.first).count { it == '\n' } + 1
ImportInfo(
path = match.groupValues[2].removeSuffix(".*"),
type = ImportType.MODULE,
filePath = filePath,
startLine = lineNumber,
endLine = lineNumber
)
}.toList()
}

private fun extractPythonImports(content: String, filePath: String): List<ImportInfo> {
val imports = mutableListOf<ImportInfo>()

val fromImportRegex = Regex("""from\s+([\w.]+)\s+import""")
fromImportRegex.findAll(content).forEach { match ->
// Calculate actual line number from match position
val lineNumber = content.substring(0, match.range.first).count { it == '\n' } + 1
imports.add(ImportInfo(
path = match.groupValues[1],
type = ImportType.MODULE,
filePath = filePath,
startLine = lineNumber,
endLine = lineNumber
))
}

val importRegex = Regex("""^import\s+([\w.]+)""", RegexOption.MULTILINE)
importRegex.findAll(content).forEach { match ->
// Calculate actual line number from match position
val lineNumber = content.substring(0, match.range.first).count { it == '\n' } + 1
imports.add(ImportInfo(
path = match.groupValues[1],
type = ImportType.MODULE,
filePath = filePath,
startLine = lineNumber,
endLine = lineNumber
))
}

return imports
}

private fun extractJsImports(content: String, filePath: String): List<ImportInfo> {
val imports = mutableListOf<ImportInfo>()

val es6ImportRegex = Regex("""import\s+(?:.+\s+from\s+)?['"]([@\w./-]+)['"]""")
es6ImportRegex.findAll(content).forEach { match ->
// Calculate actual line number from match position
val lineNumber = content.substring(0, match.range.first).count { it == '\n' } + 1
imports.add(ImportInfo(
path = match.groupValues[1],
type = ImportType.MODULE,
filePath = filePath,
startLine = lineNumber,
endLine = lineNumber
))
}

return imports
}

private fun parseOOPNodes(
sourceCode: String,
filePath: String,
language: Language
): List<CodeNode> {
val nodes = mutableListOf<CodeNode>()
val lines = sourceCode.lines()
val packageName = extractPackageName(sourceCode)

val classPattern = Regex("""(class|interface|enum|object)\s+(\w+)""")

for ((index, line) in lines.withIndex()) {
val currentLine = index + 1

classPattern.find(line)?.let { match ->
val type = when (match.groupValues[1]) {
"class", "object" -> CodeElementType.CLASS
"interface" -> CodeElementType.INTERFACE
"enum" -> CodeElementType.ENUM
else -> CodeElementType.CLASS
}
val name = match.groupValues[2]
nodes.add(createCodeNode(name, type, packageName, filePath, currentLine, language))
}
}

return nodes
}

private fun parseJSNodes(
sourceCode: String,
filePath: String,
language: Language
): List<CodeNode> {
val nodes = mutableListOf<CodeNode>()
val lines = sourceCode.lines()

val classPattern = Regex("""class\s+(\w+)""")

for ((index, line) in lines.withIndex()) {
val currentLine = index + 1

classPattern.find(line)?.let { match ->
val name = match.groupValues[1]
nodes.add(createCodeNode(name, CodeElementType.CLASS, "", filePath, currentLine, language))
}
}

return nodes
}

private fun parsePythonNodes(
sourceCode: String,
filePath: String,
language: Language
): List<CodeNode> {
val nodes = mutableListOf<CodeNode>()
val lines = sourceCode.lines()

val classPattern = Regex("""class\s+(\w+)""")

for ((index, line) in lines.withIndex()) {
val currentLine = index + 1

classPattern.find(line)?.let { match ->
val name = match.groupValues[1]
nodes.add(createCodeNode(name, CodeElementType.CLASS, "", filePath, currentLine, language))
}
}

return nodes
}

private fun extractPackageName(sourceCode: String): String {
val packagePattern = Regex("""package\s+([\w.]+)""")
return packagePattern.find(sourceCode)?.groupValues?.get(1) ?: ""
}

private fun createCodeNode(
name: String,
type: CodeElementType,
packageName: String,
filePath: String,
startLine: Int,
language: Language
): CodeNode {
val qualifiedName = if (packageName.isNotEmpty()) "$packageName.$name" else name
// Use deterministic composite ID to avoid collisions
val id = "$filePath:$startLine:$qualifiedName"

return CodeNode(
id = id,
type = type,
name = name,
packageName = packageName,
filePath = filePath,
startLine = startLine,
// Approximate end line: regex parsing cannot determine actual end line,
// so we use a reasonable default. For accurate end lines, use TreeSitter-based parsing.
endLine = startLine + 10,
startColumn = 0,
endColumn = 0,
qualifiedName = qualifiedName,
content = "",
metadata = mapOf("language" to language.name, "platform" to "Android")
)
}

private fun buildRelationships(nodes: List<CodeNode>): List<CodeRelationship> {
// Simplified: no relationships for basic parsing
return emptyList()
}
}
Loading
Loading