kmp openharmony 括号匹配与表达式合法性检测
本文介绍了一个基于Kotlin Multiplatform和OpenHarmony的括号匹配检测工具。该工具能够检测()、[]、{}三种括号的成对出现、类型匹配以及多余或未关闭的情况,并提供错误位置、上下文片段和统计信息。通过ArkTS单页面板在OpenHarmony设备上可视化展示结果。核心算法使用栈结构进行括号匹配检查,并设计了BracketStackEntry和BracketCheckRes

在实际开发中,我们几乎每天都在和「括号」打交道:函数调用、数组下标、泛型、JSON、正则表达式……
一旦括号少写一个、多写一个、或者类型对不上,就可能导致编译错误、运行异常甚至安全漏洞。
本案例基于 Kotlin Multiplatform(KMP)与 OpenHarmony,实现了一个 括号匹配与表达式合法性检测 小工具:
- 支持
(),[],{}三种括号类型; - 检测括号是否成对出现、是否类型匹配、是否存在多余的右括号或未关闭的左括号;
- 给出首个错误的位置、上下文片段以及各类括号统计信息;
- 通过 ArkTS 单页面板在 OpenHarmony 设备上可视化展示检测结果。
一、问题背景:为什么要做括号匹配检测?
典型场景:
- 粘贴一段复杂 SQL/JSON/代码片段,想快速确认括号是否匹配;
- 在线配置表达式(如路由规则、限流规则)时,需要前端快速校验表达式合法性;
- 简单的语法高亮 / Lint 工具,需要一个轻量级的括号检测引擎。
与完整的语法解析器相比,「括号匹配检测」是一个更轻量、更通用的基础能力,可以作为表达式解析前的一道快速过滤。
二、Kotlin 括号匹配检测引擎
1. 接口设计:bracketValidatorAnalyzer
在 App.kt 中,我们通过 @JsExport 暴露了一个简单易用的分析入口:
@JsExport
fun bracketValidatorAnalyzer(inputData: String): String {
val sanitized = inputData.trim()
if (sanitized.isEmpty()) {
return "❌ 输入为空,请输入需要检查的表达式(支持 (), [], {} 三种括号)"
}
val result = checkBrackets(sanitized)
val builder = StringBuilder()
builder.appendLine("🔍 括号匹配与表达式合法性检测报告")
builder.appendLine("━━━━━━━━━━━━━━━━━━━━━━━━━━")
builder.appendLine("表达式长度: ${sanitized.length}")
builder.appendLine()
if (result.isValid) {
builder.appendLine("✅ 括号匹配正确,未发现结构性错误。")
} else {
builder.appendLine("❌ 括号匹配存在问题:")
builder.appendLine(" 错误类型: ${result.errorMessage}")
if (result.errorIndex >= 0) {
builder.appendLine(" 首个错误位置索引: ${result.errorIndex}")
val context = buildErrorContext(sanitized, result.errorIndex)
builder.appendLine(" 错误上下文: $context")
}
}
builder.appendLine()
builder.appendLine("📊 括号统计信息")
builder.appendLine(" '(' 出现次数: ${result.countRoundOpen},')' 出现次数: ${result.countRoundClose}")
builder.appendLine(" '[' 出现次数: ${result.countSquareOpen},']' 出现次数: ${result.countSquareClose}")
builder.appendLine(" '{' 出现次数: ${result.countCurlyOpen},'}' 出现次数: ${result.countCurlyClose}")
if (result.unclosedStack.isNotEmpty()) {
builder.appendLine()
builder.appendLine("📌 未关闭的左括号栈(从栈底到栈顶):")
result.unclosedStack.forEach { entry ->
builder.appendLine(" '${entry.bracket}' @ 索引 ${entry.index}")
}
}
return builder.toString().trim()
}
输出结果以纯文本报告形式呈现,便于在 ArkTS 中直接显示。
2. 数据结构与算法核心
2.1 栈元素与检测结果结构体
private data class BracketStackEntry(
val bracket: Char,
val index: Int
)
private data class BracketCheckResult(
val isValid: Boolean,
val errorIndex: Int,
val errorMessage: String,
val countRoundOpen: Int,
val countRoundClose: Int,
val countSquareOpen: Int,
val countSquareClose: Int,
val countCurlyOpen: Int,
val countCurlyClose: Int,
val unclosedStack: List<BracketStackEntry>
)
BracketStackEntry 记录左括号字符及其索引位置,便于错误报告;BracketCheckResult 则汇总了整个检测过程的结果与统计信息。
2.2 核心检测流程:checkBrackets
private fun checkBrackets(expr: String): BracketCheckResult {
val stack = ArrayDeque<BracketStackEntry>()
var countRoundOpen = 0
var countRoundClose = 0
var countSquareOpen = 0
var countSquareClose = 0
var countCurlyOpen = 0
var countCurlyClose = 0
fun isPair(left: Char, right: Char): Boolean {
return (left == '(' && right == ')') ||
(left == '[' && right == ']') ||
(left == '{' && right == '}')
}
for (i in expr.indices) {
val c = expr[i]
when (c) {
'(' -> {
countRoundOpen++
stack.addLast(BracketStackEntry(c, i))
}
')' -> {
countRoundClose++
if (stack.isEmpty()) {
return BracketCheckResult(
isValid = false,
errorIndex = i,
errorMessage = "遇到多余的右括号 ')',在没有对应左括号的情况下关闭",
countRoundOpen = countRoundOpen,
countRoundClose = countRoundClose,
countSquareOpen = countSquareOpen,
countSquareClose = countSquareClose,
countCurlyOpen = countCurlyOpen,
countCurlyClose = countCurlyClose,
unclosedStack = stack.toList()
)
}
val top = stack.removeLast()
if (!isPair(top.bracket, c)) {
return BracketCheckResult(
isValid = false,
errorIndex = i,
errorMessage = "右括号 ')' 与索引 ${top.index} 处的 '${top.bracket}' 类型不匹配",
countRoundOpen = countRoundOpen,
countRoundClose = countRoundClose,
countSquareOpen = countSquareOpen,
countSquareClose = countSquareClose,
countCurlyOpen = countCurlyOpen,
countCurlyClose = countCurlyClose,
unclosedStack = stack.toList()
)
}
}
'[' -> {
countSquareOpen++
stack.addLast(BracketStackEntry(c, i))
}
']' -> {
countSquareClose++
if (stack.isEmpty()) {
return BracketCheckResult(
isValid = false,
errorIndex = i,
errorMessage = "遇到多余的右括号 ']',在没有对应左括号的情况下关闭",
countRoundOpen = countRoundOpen,
countRoundClose = countRoundClose,
countSquareOpen = countSquareOpen,
countSquareClose = countSquareClose,
countCurlyOpen = countCurlyOpen,
countCurlyClose = countCurlyClose,
unclosedStack = stack.toList()
)
}
val top = stack.removeLast()
if (!isPair(top.bracket, c)) {
return BracketCheckResult(
isValid = false,
errorIndex = i,
errorMessage = "右括号 ']' 与索引 ${top.index} 处的 '${top.bracket}' 类型不匹配",
countRoundOpen = countRoundOpen,
countRoundClose = countRoundClose,
countSquareOpen = countSquareOpen,
countSquareClose = countSquareClose,
countCurlyOpen = countCurlyOpen,
countCurlyClose = countCurlyClose,
unclosedStack = stack.toList()
)
}
}
'{' -> {
countCurlyOpen++
stack.addLast(BracketStackEntry(c, i))
}
'}' -> {
countCurlyClose++
if (stack.isEmpty()) {
return BracketCheckResult(
isValid = false,
errorIndex = i,
errorMessage = "遇到多余的右括号 '}',在没有对应左括号的情况下关闭",
countRoundOpen = countRoundOpen,
countRoundClose = countRoundClose,
countSquareOpen = countSquareOpen,
countSquareClose = countSquareClose,
countCurlyOpen = countCurlyOpen,
countCurlyClose = countCurlyClose,
unclosedStack = stack.toList()
)
}
val top = stack.removeLast()
if (!isPair(top.bracket, c)) {
return BracketCheckResult(
isValid = false,
errorIndex = i,
errorMessage = "右括号 '}' 与索引 ${top.index} 处的 '${top.bracket}' 类型不匹配",
countRoundOpen = countRoundOpen,
countRoundClose = countRoundClose,
countSquareOpen = countSquareOpen,
countSquareClose = countSquareClose,
countCurlyOpen = countCurlyOpen,
countCurlyClose = countCurlyClose,
unclosedStack = stack.toList()
)
}
}
else -> {
// 其他字符忽略
}
}
}
if (stack.isNotEmpty()) {
val top = stack.last()
val msg = "存在未关闭的左括号 '${top.bracket}',起始索引 ${top.index}"
return BracketCheckResult(
isValid = false,
errorIndex = top.index,
errorMessage = msg,
countRoundOpen = countRoundOpen,
countRoundClose = countRoundClose,
countSquareOpen = countSquareOpen,
countSquareClose = countSquareClose,
countCurlyOpen = countCurlyOpen,
countCurlyClose = countCurlyClose,
unclosedStack = stack.toList()
)
}
return BracketCheckResult(
isValid = true,
errorIndex = -1,
errorMessage = "无错误",
countRoundOpen = countRoundOpen,
countRoundClose = countRoundClose,
countSquareOpen = countSquareOpen,
countSquareClose = countSquareClose,
countCurlyOpen = countCurlyOpen,
countCurlyClose = countCurlyClose,
unclosedStack = emptyList()
)
}
时间复杂度:
- 单次遍历表达式,栈操作均为 O(1),总时间复杂度为 O(n);
- 适合在前端或轻量级工具中高频调用。
3. 错误上下文构造
为了更直观地展示错误位置,我们为首个错误索引构造一段「上下文 + 指针」:
private fun buildErrorContext(expr: String, index: Int, radius: Int = 10): String {
if (index < 0 || index >= expr.length) return ""
val start = (index - radius).coerceAtLeast(0)
val end = (index + radius + 1).coerceAtMost(expr.length)
val snippet = expr.substring(start, end)
val pointerPos = index - start
val pointerLine = buildString {
repeat(pointerPos) { append(' ') }
append('^')
}
return "\n $snippet\n $pointerLine"
}
这样在报告中可以看到类似输出:
错误上下文:
(a + b] * c
^
既方便人眼定位,又不会太冗长。
三、JavaScript / ArkTS 集成
@JsExport 让 bracketValidatorAnalyzer 自动出现在 JS 模块 hellokjs.js 中,同时在 hellokjs.d.ts 中也有声明:
export declare function bracketValidatorAnalyzer(inputData: string): string;
在 ArkTS 侧,你可以这样导入并使用:
import { bracketValidatorAnalyzer } from './hellokjs';
// ...
this.result = bracketValidatorAnalyzer(this.inputData);
前端只需将文本表达式传给该函数,就能拿到完整的检测报告字符串并展示在 UI 中。
四、ArkTS 页面布局建议
如果将该案例挂在首页(Index 页面),可以采用如下布局风格:
- 顶部标题:「括号匹配与表达式合法性检测」;
- 中间输入区域:
- 大号
TextArea输入表达式(支持多行); - 右上角显示当前表达式长度;
- 大号
- 操作区域:
- 「▶ 运行括号检测」按钮;
- 「↻ 恢复示例表达式」按钮;
- 底部结果区域:
- 显示检测报告(合法/不合法、错误类型、错误上下文、括号统计)。
配合现有的深蓝/紫色数据面板主题,可以很容易实现一个既「开发者工具风」又「设计统一」的小工具页面。
五、应用场景
-
在线表达式编辑器预检查
- 在用户提交复杂规则或表达式前,先做括号匹配校验,减少后端解析错误。
-
轻量级 Lint 工具
- 对 JSON、SQL、正则表达式等配置进行快速括号合法性检测。
-
教学示例与算法练习
- 展示栈(Stack)在括号匹配问题中的经典用法,适合作为数据结构与算法教学案例。
-
代码片段校验小工具
- 在设备端提供一个简单的「表达式/代码片段检查器」,辅助开发调试。
六、总结
本案例从最经典的「括号匹配」问题出发,展示了如何在 KMP + OpenHarmony 上实现一个实用的表达式合法性检测工具:
- 使用栈结构线性扫描表达式,时间复杂度 O(n);
- 支持多种括号类型与错误场景(多余右括号、未关闭左括号、类型不匹配);
- 通过详细的文本报告与错误上下文,提供良好的可读性与定位能力;
- 借助
@JsExport与 ArkTS UI,将算法能力自然融入 OpenHarmony 设备应用中。
与前面那些「时间序列」「任务依赖」等案例相比,本篇提供了一个 更偏语法/编译器风格 的小算法示例,为你的 KMP + OpenHarmony 算法案例库再增加一个不同维度的实战案例。***
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐


所有评论(0)