From fa9ff80214fca1125364b5cc1607e70a295ab8d1 Mon Sep 17 00:00:00 2001 From: Kagura Date: Tue, 29 Oct 2024 23:03:45 +0800 Subject: [PATCH] finish --- .idea/uiDesigner.xml | 124 ++++++++++ build.gradle.kts | 2 +- src/main/kotlin/Main.kt | 226 +++++++++++++----- src/main/kotlin/core/CFunction.kt | 17 +- src/main/kotlin/core/GraphvizHelper.kt | 4 +- src/main/kotlin/core/SourceFile.kt | 13 +- src/main/kotlin/core/TraceTree.kt | 11 +- src/main/kotlin/ui/TextfieldWithLineNumber.kt | 154 ++++++++++++ src/main/kotlin/utils/DefaultCode.kt | 25 ++ src/main/kotlin/utils/OpenBrowser.kt | 23 ++ src/main/kotlin/utils/stringMagic.kt | 37 +++ 11 files changed, 552 insertions(+), 84 deletions(-) create mode 100644 .idea/uiDesigner.xml create mode 100644 src/main/kotlin/ui/TextfieldWithLineNumber.kt create mode 100644 src/main/kotlin/utils/DefaultCode.kt create mode 100644 src/main/kotlin/utils/OpenBrowser.kt create mode 100644 src/main/kotlin/utils/stringMagic.kt diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..2b63946 --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index e05d098..e23c92c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -28,7 +28,7 @@ compose.desktop { mainClass = "MainKt" nativeDistributions { - targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) + targetFormats(TargetFormat.Dmg, TargetFormat.Exe, TargetFormat.Deb) packageName = "VariableRelation" packageVersion = "1.0.0" } diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index 29e281e..3b43ee0 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -1,89 +1,183 @@ import androidx.compose.desktop.ui.tooling.preview.Preview -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material.Button -import androidx.compose.material.ButtonColors -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text -import androidx.compose.material.TextField -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.* +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.text.ExperimentalTextApi -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Window import androidx.compose.ui.window.application -import core.SourceFile +import core.* +import ui.TextfieldWithLineNumber +import utils.* -@OptIn(ExperimentalTextApi::class) @Composable @Preview fun App() { - var text by remember { mutableStateOf("") } - var isReady by remember { mutableStateOf(false) } var source: SourceFile? = null + val showResult = remember { mutableStateOf(false) } + var defineList by remember { mutableStateOf("") } + var useList by remember { mutableStateOf("") } + var traceTree by remember { mutableStateOf("") } + var funcTraceTree by remember { mutableStateOf("") } + var page by remember { mutableStateOf(0) } + var ttGraph by remember { mutableStateOf("") } + var ivGraph by remember { mutableStateOf("") } MaterialTheme { - Column( - modifier = Modifier.fillMaxSize() - .padding(horizontal = 10.dp, vertical = 5.dp), - horizontalAlignment = Alignment.Start, - verticalArrangement = Arrangement.Center - ) { - Text("请输入您的代码") - - TextField( - value = text, - onValueChange = { text = it }, - modifier = Modifier.fillMaxWidth(0.95f) - .weight(1f), - textStyle = TextStyle.Default.copy(fontFamily = FontFamily("monospace")) - ) - - Row( - modifier = Modifier.fillMaxWidth(0.9f) - .padding(vertical = 5.dp), - horizontalArrangement = Arrangement.SpaceEvenly, - verticalAlignment = Alignment.CenterVertically - ) { - Button( - modifier = Modifier.padding(vertical = 10.dp), - onClick = { - val s = SourceFile(text.toString()) - text = s.content - source = s - }, - ) { - Text("格式化") - } - - Button( - modifier = Modifier.padding(vertical = 10.dp), - onClick = { - source = SourceFile(text.toString()) - isReady = true - }, - enabled = !isReady - ) { - Text("分析") + when { + showResult.value -> { + Dialog(onDismissRequest = { + showResult.value = false + page = 0 + }) { + AlertDialog( + title = { + Text( + text = when (page) { + 0 -> "函数定义的变量" + 1 -> "函数内每条语句的定义和使用" + 2 -> "函数内每个变量 def-use 链" + 3 -> "函数间每个变量 def-use 链" + else -> "出错了" + }, + color = Color.Black, + fontWeight = FontWeight.Bold, + fontSize = 24.sp, + textAlign = TextAlign.Center + ) + }, + text = { + Text( + text = when (page) { + 0 -> defineList + 1 -> useList + 2 -> traceTree + 3 -> funcTraceTree + else -> "出错了(ง •̀_•́)ง" + }, + modifier = Modifier + .fillMaxSize() + .wrapContentHeight(), + textAlign = TextAlign.Start, + color = Color.Black + ) + }, + onDismissRequest = { + showResult.value = false + }, + confirmButton = { + TextButton( + onClick = { + page = (page + 1) % 4 + } + ) { + Text("下一个") + } + }, + dismissButton = { + TextButton( + onClick = { + if (page < 2){ + showResult.value = false + }else{ + if (page == 2){ + openGraph(ttGraph) + }else if (page == 3){ + openGraph(ivGraph) + } + } + } + ) { + Text( + if (page < 2){ + "关闭" + }else{ + "查看图像(在线)" + } + ) + } + }, + modifier = Modifier + .padding(5.dp) + .wrapContentSize(), + shape = RoundedCornerShape(20.dp), + backgroundColor = Color(0xffFAF0E6) + ) } } } + Surface( + modifier = Modifier.fillMaxSize() + .background(color = Color(0xFFFBFBFB)) + ) { + Column( + modifier = Modifier.fillMaxSize().padding(horizontal = 10.dp, vertical = 5.dp), + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.Center + ) { + Text("请输入您的代码") + TextfieldWithLineNumber( + modifier = Modifier.weight(1f).fillMaxWidth(), + ) { + if (it.isEmpty()) { + return@TextfieldWithLineNumber + } + val sf = SourceFile(it) + source = sf + try { + sf.parseFunction() + } catch (e: Exception) { + // 分析失败 + defineList = e.printStackTrace().toString() + return@TextfieldWithLineNumber + } + + defineList = + try { + val def = getDefineList(sf.getDef()) + def.ifEmpty { + "分析失败:可能输入有错误!" + } + } catch (e: Exception) { + e.printStackTrace().toString() + } + useList = try { + getUseList(sf.getUse()) + } catch (e: Exception) { + e.printStackTrace().toString() + } + val relations = mutableMapOf>() + val traceTreeStr = StringBuilder() + sf.functions.forEach { func -> + traceTreeStr.append(getTraceTreeString(func.getTraceTree().getStringRepr(),func.name)) + relations.putAll(parseRelation(func.name,func.getTraceTree().getStringRepr())) + } + traceTree = traceTreeStr.toString() + + funcTraceTree = + getInvokeTraceTreeString("main",sf,relations) + + ttGraph = generateGraph(relations) + ivGraph = sf.functions.getInvokeGraph("main") + + showResult.value = true + } + + } + } } } + fun main() = application { Window(onCloseRequest = ::exitApplication) { App() diff --git a/src/main/kotlin/core/CFunction.kt b/src/main/kotlin/core/CFunction.kt index 5c3bf42..d9ea148 100644 --- a/src/main/kotlin/core/CFunction.kt +++ b/src/main/kotlin/core/CFunction.kt @@ -127,16 +127,14 @@ fun List.find(name: String): CFunction? { * @param name: 从哪个函数开始查找 * @return 特制的 TraceTree */ -fun List.getInvokeTree(name: String): List { +fun List.getInvokeTree(name: String): Map> { // 先找到开始的函数 - val func = this.find(name) - if (func == null) { - return emptyList() - } + val func = this.find(name) ?: return emptyMap() val invokeTrees = mutableListOf() val args = mutableListOf() var funcName = "" + val resultMap = mutableMapOf>() func.cParser.sentenceList.forEach { sentence -> // 🌰: @@ -153,9 +151,12 @@ fun List.getInvokeTree(name: String): List { val tt = function.getTraceTree(info) invokeTrees.addAll(tt) } - args.clear() - funcName = "" + if (funcName != "") { + resultMap[funcName] = invokeTrees + args.clear() + funcName = "" + } } } - return invokeTrees + return resultMap } \ No newline at end of file diff --git a/src/main/kotlin/core/GraphvizHelper.kt b/src/main/kotlin/core/GraphvizHelper.kt index 25979a1..2194842 100644 --- a/src/main/kotlin/core/GraphvizHelper.kt +++ b/src/main/kotlin/core/GraphvizHelper.kt @@ -24,8 +24,8 @@ fun generateGraph(relations: Map>, crossLabelPaths: List< // 处理跨函数 for (path in crossLabelPaths) { val parts = path.split(":") - if (parts.size == 2) { - val subPath = parts[1].split("->") // 栗子: main:z->a->m + if (parts.size == 3) { + val subPath = parts[2].split("->") // 栗子: A:main:z->a->m if (subPath.size >= 2) { val fromNode = subPath[0] // 这里就是z val toNode = subPath[1] // 下一个就是 diff --git a/src/main/kotlin/core/SourceFile.kt b/src/main/kotlin/core/SourceFile.kt index 8b88c20..2d74c4c 100644 --- a/src/main/kotlin/core/SourceFile.kt +++ b/src/main/kotlin/core/SourceFile.kt @@ -4,6 +4,8 @@ package core import java.io.File import core.CLanguage.Companion.OPERATION.* +import utils.getDefineList +import utils.getUseList class SourceFile { companion object { @@ -20,7 +22,7 @@ class SourceFile { } val content: String - private var functions: List = emptyList() + var functions: List = emptyList() constructor(file: File) { if (!file.isFile) { @@ -52,8 +54,8 @@ class SourceFile { return result } - fun getUse(): List> { - val globalResult = mutableListOf>() + fun getUse(): Map> { + val globalResult = mutableMapOf>() functions.forEach { val funcResult = mutableListOf() @@ -97,7 +99,7 @@ class SourceFile { if (useCache.isNotEmpty()) { funcResult.add(useCache.toString()) } - globalResult.add(funcResult) + globalResult[it.name] = funcResult } return globalResult } @@ -120,4 +122,7 @@ fun main() { println(sourceFile.getUse()) println(generateGraph(relations)) println(funcs.getInvokeGraph("main")) + + println(getDefineList(sourceFile.getDef())) + println(getUseList(sourceFile.getUse())) } \ No newline at end of file diff --git a/src/main/kotlin/core/TraceTree.kt b/src/main/kotlin/core/TraceTree.kt index 514c60b..1a2558e 100644 --- a/src/main/kotlin/core/TraceTree.kt +++ b/src/main/kotlin/core/TraceTree.kt @@ -47,7 +47,12 @@ fun List.getStringRepr(): String { return result.toString() } -fun List.getFuncRepr(func: String): String { - val strs = this.getStringRepr() - return strs.split('\n').filter { it.startsWith(func) }.joinToString("\n") +fun Map>.getFuncRepr(func: String): String { + val result = StringBuilder() + this.forEach { (name, list) -> + result.append("$name:") + val listRepr = list.getStringRepr() + result.append(listRepr.split('\n').filter { it.startsWith(func) }.joinToString("\n")) + } + return result.toString() } \ No newline at end of file diff --git a/src/main/kotlin/ui/TextfieldWithLineNumber.kt b/src/main/kotlin/ui/TextfieldWithLineNumber.kt new file mode 100644 index 0000000..e558d16 --- /dev/null +++ b/src/main/kotlin/ui/TextfieldWithLineNumber.kt @@ -0,0 +1,154 @@ +package ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Card +import androidx.compose.material.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.ExperimentalTextApi +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import core.SourceFile +import utils.DefaultCode + +@OptIn(ExperimentalTextApi::class) +@Composable +fun TextfieldWithLineNumber( + modifier: Modifier = Modifier, + onSubmit: (String) -> Unit +) { + var linesText by remember { mutableIntStateOf(DefaultCode.hashCode()) } + var text by remember { mutableStateOf(DefaultCode.toString()) } + + val showFinishFormatDialog = remember { mutableStateOf(false) } + when { + showFinishFormatDialog.value -> { + FinishFormatDialog { + showFinishFormatDialog.value = false + } + } + } + + + val monospaceTextStyle = TextStyle.Default.copy( // 左右都是等宽字体 + fontFamily = FontFamily("monospace"), + fontSize = 16.sp + ) + + // 同步左右滚动进度 + val linesTextScroll = rememberScrollState() + val scriptTextScroll = rememberScrollState() + + LaunchedEffect(linesTextScroll.value) { + scriptTextScroll.scrollTo(linesTextScroll.value) + } + LaunchedEffect(scriptTextScroll.value) { + linesTextScroll.scrollTo(scriptTextScroll.value) + } + + Column(modifier = modifier) { + Row( + modifier = Modifier.fillMaxWidth() + .weight(1f) + .background(color = Color(0xFFfafafa)) + + ) { + // 左边是行号 + BasicTextField( + modifier = Modifier + .fillMaxHeight() + .width(12.dp * linesText.toString().length) + .verticalScroll(linesTextScroll), + value = IntRange(1, linesText).joinToString(separator = "\n"), + readOnly = true, // 不能去编辑行号那里 + textStyle = monospaceTextStyle.copy( + textAlign = TextAlign.End, + color = Color(0xff69b0c5) + ), + onValueChange = {}) + + Spacer(modifier = Modifier.width(10.dp)) + + // 输入内容的地方 + + BasicTextField( + modifier = Modifier + .fillMaxHeight() + .weight(1f) + .verticalScroll(scriptTextScroll), + value = text, + textStyle = monospaceTextStyle, + onValueChange = { textFieldValue -> + val nbLines = textFieldValue.count { it == '\n' } + 1 + if (nbLines != linesText) linesText = nbLines + text = textFieldValue.replace("\t"," ") // \t 显示不了 + }, + ) + + } + } + + Row( + modifier = Modifier.fillMaxWidth(0.9f) + .padding(vertical = 5.dp), + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically + ) { + Button( + modifier = Modifier.padding(vertical = 10.dp), + colors = ButtonDefaults.buttonColors(backgroundColor = Color(0xFFC6E7FF)), + onClick = { + val s = SourceFile(text) + text = s.content + showFinishFormatDialog.value = true + }, + ) { + Text("格式化") + } + + Button( + modifier = Modifier.padding(vertical = 10.dp), + colors = ButtonDefaults.buttonColors(backgroundColor = Color(0xFFD4F6FF)), + onClick = { onSubmit(text) }, + ) { + Text("分析") + } + } + +} + +@Composable +fun FinishFormatDialog(onDismissRequest: () -> Unit) { + Dialog(onDismissRequest = { onDismissRequest() }) { + Card( + modifier = Modifier + .height(130.dp) + .padding(16.dp), + shape = RoundedCornerShape(20.dp), + backgroundColor = Color(0xffFFE3E3) + ) { + Text( + text = "格式化成功", + modifier = Modifier + .fillMaxSize() + .wrapContentSize(Alignment.Center), + textAlign = TextAlign.Center, + ) + } + } +} + + diff --git a/src/main/kotlin/utils/DefaultCode.kt b/src/main/kotlin/utils/DefaultCode.kt new file mode 100644 index 0000000..a583eb1 --- /dev/null +++ b/src/main/kotlin/utils/DefaultCode.kt @@ -0,0 +1,25 @@ +package utils + +object DefaultCode { + override fun toString(): String { + return """ + int main(){ + int x=0,y =0; + int z =1; + printf("%d\n",z);//用户输入 + x= A(z); + y= x; + printf("%d\n",y);//main函数尾部 + } + + int A(int a){ + int m = a; + return m; + } + """.trimIndent() + } + + override fun hashCode(): Int { + return this.toString().count { it == '\n' } + 1 + } +} \ No newline at end of file diff --git a/src/main/kotlin/utils/OpenBrowser.kt b/src/main/kotlin/utils/OpenBrowser.kt new file mode 100644 index 0000000..57aa370 --- /dev/null +++ b/src/main/kotlin/utils/OpenBrowser.kt @@ -0,0 +1,23 @@ +package utils + +import java.awt.Desktop +import java.net.URI +import java.util.* + +fun openBrowser(uri: URI) { + val osName by lazy(LazyThreadSafetyMode.NONE) { System.getProperty("os.name").lowercase(Locale.getDefault()) } + val desktop = Desktop.getDesktop() + when { + Desktop.isDesktopSupported() && desktop.isSupported(Desktop.Action.BROWSE) -> desktop.browse(uri) + "mac" in osName -> Runtime.getRuntime().exec("open $uri") + "nix" in osName || "nux" in osName -> Runtime.getRuntime().exec("xdg-open $uri") + else -> desktop.browse(uri) + } +} + +fun openGraph(graph: String){ + val site = "https://dreampuf.github.io/GraphvizOnline/#" + val data = java.net.URLEncoder.encode(graph,"utf-8") + .replace("+","%20") + openBrowser(URI.create(site+data)) +} \ No newline at end of file diff --git a/src/main/kotlin/utils/stringMagic.kt b/src/main/kotlin/utils/stringMagic.kt new file mode 100644 index 0000000..bdda941 --- /dev/null +++ b/src/main/kotlin/utils/stringMagic.kt @@ -0,0 +1,37 @@ +package utils + +import core.* + +fun getDefineList(defList: Map>): String = +// Example: + // {main=[x, y, z], A=[a, m]} + defList.toString() + .replace("{", "") + .replace("}", "") // 解决左右括号 + .replace("=", ":def") + +fun getUseList(useList: Map>): String = +// Example: +// {main=[void, x-def, y-def, z-def, z-use, x-def;z-use, y-def;x-use, y-use], A=[a-def, m-def;a-use, m-use]} + useList.toString() + .replace("{}", "") // 如果是空就直接删了 + .replace("{", "") // 右括号留着 + .replace("=", ":{") // 换掉 + .replace("], ", "]}\n") // 保证不是最后一个 + .replace(", ", "],[") // 逗号后面有空格 + .replace(";", "、") + +fun getTraceTreeString(traceTreeStr: String, funcName: String): String { + val sb = StringBuilder("$funcName:\n") + traceTreeStr.split('\n').forEach { + sb.append(" ${it.trim()}\n") + } + return sb.toString() +} + +fun getInvokeTraceTreeString(funcName: String, sourceFile: SourceFile, relations: Map>): String { + val func = sourceFile.functions.find(funcName) ?: return "未找到 $funcName 函数" + val originPart = getTraceTreeString(func.getTraceTree().getStringRepr(), func.name) + val invokeTree = sourceFile.functions.getInvokeTree(funcName).getFuncRepr(funcName) + return originPart + "\n" + invokeTree +} \ No newline at end of file