几何、扫描线、模拟与解析 · HTML
数学 / 逻辑形态
在位置 i 依据字符类别选择 token 规则;规则消费一个非空最长前缀并返回 (type,value,next_i)。
识别信号
- 输入是字符流而非已分词对象
- 空白、数字、标识符与符号规则不同
- 最长匹配影响边界
- 后续语法依赖 token 类型
- 需要报告非法字符位置
容易误判的相似信号
| Signal | Why it is different |
|---|
| 仅按单字符 split | 多字符数字、转义或一元符号会被错误切开。 |
| 分词直接解决嵌套语法 | tokenization 只识别词;括号优先级仍需 parser/stack。 |
核心不变量
循环前 s[:i] 已被完整且无重叠地切成合法 token;下一规则从 i 消费至少一字符或明确报错。
状态、转移与处理顺序
| Item | Definition |
|---|
| State | 指针 i、token 列表、当前字符类别,以及可选的引号/转义模式。 |
| Transition | 跳过空白;数字或标识符按同类最长扩张;已知符号单独输出;未知字符报错。 |
| Frontier / order | i 是已解析前缀与未知后缀的唯一边界,任何分支都必须推进它。 |
Python 骨架
def tokenize(s):
tokens, i = [], 0
while i < len(s):
ch = s[i]
if ch.isspace():
i += 1
elif ch.isdigit():
j = i + 1
while j < len(s) and s[j].isdigit(): j += 1
tokens.append(('INT', int(s[i:j])))
i = j
elif ch.isalpha() or ch == '_':
j = i + 1
while j < len(s) and (s[j].isalnum() or s[j] == '_'): j += 1
tokens.append(('NAME', s[i:j]))
i = j
elif ch in '+-*/()':
tokens.append((ch, ch)); i += 1
else:
raise ValueError(f'unexpected character at {i}: {ch!r}')
return tokens
正确性思路
- 每条规则只从当前 i 开始,故 token 不重叠。
- 数字与标识符循环消费该类别的最长连续前缀。
- 所有分支推进 i 或抛错,因此终止且不静默跳过字符。
- 归纳得成功返回时 token 拼接(除空白)与输入一致。
复杂度
| Time | Space | Parameters |
|---|
| O(n) | O(k) 输出 | n 为字符数,k 为 token 数 |
边界条件
- 空输入返回空 token
- 末尾恰结束一个多字符 token
- 一元负号通常留给 parser 判定
- Unicode 字母类别是否允许
- 引号与转义需额外状态
错误模式
| Pattern | Why it fails / fix |
|---|
| 某分支不推进 i | 会无限循环。 |
| 负号并入所有数字 | 减法与一元负号需语法上下文区分。 |
| 未知字符静默忽略 | 会让错误位置漂移并产生伪合法结果。 |
选择与排除规则
- 先词法分层,再处理嵌套语法。
- 每个规则保证消费非空前缀。
- 最长匹配冲突时规定优先级。
- 错误中保留字符位置便于调试。
相关模板
| Relation | Template | Boundary |
|---|
| 词法前置 | 括号匹配栈 | 定界符可能出现在字符串字面量中,需先正确 tokenize。 |
| 状态基础 | 显式模拟 / 状态机 | 引号、注释、转义形成模式状态 |
| 语法后继 | 栈式状态模拟 | token 需按括号或运算符解析 |
原创微型例子
| 环节 | 内容 |
|---|
| Result | 得到 NAME、+、INT、*、(、NAME、-、INT、) token。 |
| Scenario | 输入 sum_2 + 17*(x-3)。 |
| Walkthrough | 标识符与整数用最长连续规则;符号逐个消费,空白被明确跳过。 |
Recall prompts
我的补充