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