如何用Java正则从字符串读取函数式命令?有哪些替代方案?
嵌套命令结构的解析方案
为什么正则表达式不适用
正则表达式基于有限状态机,只能处理非嵌套的结构化文本。对于你描述的多层嵌套{}结构(比如go(){do(){go}}),正则无法跟踪嵌套的层级深度,根本无法正确解析这种上下文无关的语法结构。所以别在正则上浪费时间,正经的解析得用语法分析方法。
推荐实现:递归下降解析器
这种嵌套结构属于上下文无关文法(CFG),递归下降解析器是最适合的轻量实现方式,手写成本不高,逻辑清晰,完全匹配你的需求。
步骤1:定义文法规则
先把你的命令结构转换成明确的文法规则,比如:
Program → 命令+ (多个命令组成程序) 命令 → 标识符 ( 参数列表? ) { 命令+ }? (命令可以带参数和可选的嵌套命令体) 参数列表 → 参数 (, 参数)* (多个参数用逗号分隔) 参数 → 布尔值 | 数字 | 命令 (参数可以是基本类型,也可以是嵌套的命令) 布尔值 → "true" | "false" 数字 → 整数或浮点数 标识符 → 字母下划线开头的字母数字下划线组合
步骤2:词法分析(Tokenizer)
先把输入字符串拆分成一个个最小的语法单元(Token),比如标识符、括号、逗号、布尔值、数字等。这一步可以用正则辅助拆分单个Token(注意不是解析整体嵌套)。
示例Python代码:
import re # 定义Token规则:(正则模式, Token类型) TOKEN_PATTERNS = [ (r'[a-zA-Z_][a-zA-Z0-9_]*', 'IDENTIFIER'), (r'true|false', 'BOOLEAN'), (r'\d+\.?\d*|\.\d+', 'NUMBER'), (r'\(', 'LPAREN'), (r'\)', 'RPAREN'), (r'\{', 'LBRACE'), (r'\}', 'RBRACE'), (r',', 'COMMA'), (r'\s+', None), # 忽略空白字符 ] def tokenize(input_str): tokens = [] pos = 0 input_len = len(input_str) while pos < input_len: match = None for pattern, token_type in TOKEN_PATTERNS: regex_match = re.match(pattern, input_str[pos:]) if regex_match: if token_type: tokens.append((token_type, regex_match.group(0))) pos += regex_match.end() break if not match: raise ValueError(f"无效字符在位置 {pos}: {input_str[pos]}") return tokens
步骤3:语法分析(Parser)
基于Token流,用递归的方式按照文法规则构建抽象语法树(AST),每个节点对应你说的继承自同一抽象类的类实例。
示例Python代码:
from abc import ABC, abstractmethod # 抽象命令类,所有命令都继承它 class BaseCommand(ABC): pass # 具体命令实现类 class Command(BaseCommand): def __init__(self, name: str, args: list, body: list[BaseCommand] = None): self.name = name self.args = args # 参数列表:元素可以是基本类型或BaseCommand实例 self.body = body # 命令体:嵌套的命令列表 class CommandParser: def __init__(self, tokens): self.tokens = tokens self.current_idx = 0 def _peek(self): """查看当前Token,不移动指针""" if self.current_idx < len(self.tokens): return self.tokens[self.current_idx] return None def _consume(self): """移动到下一个Token""" self.current_idx += 1 def parse_program(self) -> list[BaseCommand]: """解析整个程序,返回命令列表""" commands = [] while self._peek(): commands.append(self._parse_command()) return commands def _parse_command(self) -> Command: """解析单个命令""" # 解析命令名(标识符) if self._peek()[0] != 'IDENTIFIER': raise SyntaxError("命令必须以标识符开头") cmd_name = self._peek()[1] self._consume() # 解析左括号 if self._peek()[0] != 'LPAREN': raise SyntaxError(f"命令 {cmd_name} 后必须跟 (") self._consume() # 解析参数列表 args = [] if self._peek() and self._peek()[0] != 'RPAREN': args = self._parse_arg_list() # 解析右括号 if self._peek()[0] != 'RPAREN': raise SyntaxError("参数列表必须以 ) 结束") self._consume() # 解析可选的命令体 body = None if self._peek() and self._peek()[0] == 'LBRACE': self._consume() body = [] while self._peek() and self._peek()[0] != 'RBRACE': body.append(self._parse_command()) if not self._peek() or self._peek()[0] != 'RBRACE': raise SyntaxError("命令体必须以 } 结束") self._consume() return Command(cmd_name, args, body) def _parse_arg_list(self) -> list: """解析参数列表""" args = [self._parse_arg()] while self._peek() and self._peek()[0] == 'COMMA': self._consume() args.append(self._parse_arg()) return args def _parse_arg(self): """解析单个参数""" token = self._peek() if not token: raise SyntaxError("参数不能为空") if token[0] == 'BOOLEAN': self._consume() return token[1] == 'true' elif token[0] == 'NUMBER': self._consume() num_str = token[1] return float(num_str) if '.' in num_str else int(num_str) elif token[0] == 'IDENTIFIER': # 参数是嵌套命令,递归解析 return self._parse_command() else: raise SyntaxError(f"不支持的参数类型:{token}")
使用示例
input_str = "go(true, 4){do(), hello(){go(){}}}" tokens = tokenize(input_str) parser = CommandParser(tokens) command_tree = parser.parse_program() # 现在可以遍历command_tree,处理每个Command实例 for cmd in command_tree: print(f"命令名:{cmd.name},参数:{cmd.args}") if cmd.body: print(f"嵌套命令:{[c.name for c in cmd.body]}")
替代方案
如果不想手写解析器,可以用现成的工具降低开发成本:
- 解析器生成器:比如ANTLR(跨语言)、PLY(Python版Lex-Yacc),只需定义文法规则,工具会自动生成词法和语法分析器,适合更复杂的语法扩展。
- PEG解析库:比如Python的
lark、parsimonious,PEG文法比CFG更灵活,语法定义更简洁,直接写文法就能生成解析器,上手快。
内容的提问来源于stack exchange,提问作者Mehranjp73
相关产品推荐
相关产品推荐

