在这里插入图片描述

在实际开发中,我们几乎每天都在和「括号」打交道:函数调用、数组下标、泛型、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 集成

@JsExportbracketValidatorAnalyzer 自动出现在 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 输入表达式(支持多行);
    • 右上角显示当前表达式长度;
  • 操作区域:
    • 「▶ 运行括号检测」按钮;
    • 「↻ 恢复示例表达式」按钮;
  • 底部结果区域:
    • 显示检测报告(合法/不合法、错误类型、错误上下文、括号统计)。

配合现有的深蓝/紫色数据面板主题,可以很容易实现一个既「开发者工具风」又「设计统一」的小工具页面。


五、应用场景

  1. 在线表达式编辑器预检查

    • 在用户提交复杂规则或表达式前,先做括号匹配校验,减少后端解析错误。
  2. 轻量级 Lint 工具

    • 对 JSON、SQL、正则表达式等配置进行快速括号合法性检测。
  3. 教学示例与算法练习

    • 展示栈(Stack)在括号匹配问题中的经典用法,适合作为数据结构与算法教学案例。
  4. 代码片段校验小工具

    • 在设备端提供一个简单的「表达式/代码片段检查器」,辅助开发调试。

六、总结

本案例从最经典的「括号匹配」问题出发,展示了如何在 KMP + OpenHarmony 上实现一个实用的表达式合法性检测工具:

  1. 使用栈结构线性扫描表达式,时间复杂度 O(n);
  2. 支持多种括号类型与错误场景(多余右括号、未关闭左括号、类型不匹配);
  3. 通过详细的文本报告与错误上下文,提供良好的可读性与定位能力;
  4. 借助 @JsExport 与 ArkTS UI,将算法能力自然融入 OpenHarmony 设备应用中。

与前面那些「时间序列」「任务依赖」等案例相比,本篇提供了一个 更偏语法/编译器风格 的小算法示例,为你的 KMP + OpenHarmony 算法案例库再增加一个不同维度的实战案例。***
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐