wp-lang 0.3.1

WPL language crate with AST, parser, evaluator, builtins, and generators.
Documentation
# 分隔符模式(Sep Pattern)

本文档介绍 WPL 中 `{...}` 分隔符模式语法,用于在字段分隔位置使用通配符和特殊匹配进行灵活切分。

适用于快捷分隔符(`\,`、`\s` 等)无法满足的复杂场景,例如"跳过任意内容直到关键字""空白 + 特定前缀"等。

提示:如果你的分隔需求可以用固定字符串满足,优先使用快捷分隔符(参考 [WPL 基础 - 分隔符优先级与合并](./01-wpl_basics.md#分隔符优先级与合并sep))。

---

## 📚 文档导航

| 主题 | 内容 |
|------|------|
| [**基本语法**]#基本语法 | `{...}` 写法、通配符、转义字符 |
| [**语法表**]#语法表 | 所有支持的记法一览 |
| [**通配符 `*``?`**]#通配符--与- | 非贪婪匹配、单字符匹配 |
| [**空白匹配**]#空白匹配-s-与-h | `\s` 连续空白、`\h` 水平空白 |
| [**保留标记 `()`**]#保留标记-preserve | 匹配但不消费,留给下一阶段 |
| [**使用约束**]#使用约束 | `*``()` 的限制 |
| [**实战示例**]#实战示例 | 常见场景与完整规则 |

---

## 基本语法

在字段的分隔符位置使用花括号 `{...}` 包裹模式表达式:

```wpl
# 快捷分隔符
chars\,

# 模式分隔符
chars{*=}
```

模式分隔符与快捷分隔符遵循相同的优先级规则(字段级 > 组级 > 上游)。

---

## 语法表

| 记法 | 说明 | 示例 |
|------|------|------|
| `a` | 字面字符 `a` | `{abc}` 匹配 `"abc"` |
| `*` | 零或多个任意字符(**非贪婪**,最短匹配) | `{*=}` 匹配到第一个 `=` |
| `?` | 恰好一个任意字符 | `{field?:}` 匹配 `"fieldA:"` |
| `\0` | 空字节 | |
| `\n` | 换行 | |
| `\t` | 制表符 | |
| `\r` | 回车 | |
| `\s` | 一个或多个连续空白 `[ \t\r\n]+` | `{\s=}` 跳过空白后匹配 `=` |
| `\h` | 一个或多个水平空白 `[ \t]+` | `{\h:\h}` 匹配 `" : "` |
| `\\` `\*` `\?` `\{` `\}` `\(` `\)` | 字面转义 | `{\*}` 匹配字面 `*` |
| `(...)` | 匹配但不消费(仅限模式末尾) | `{*(key=)}` 保留 `key=` |

> 注意:`\s``\h``{}` 模式内部匹配**一个或多个**连续空白;`{}` 之外的 `\s` 沿用原有语义,旧配置不受影响。

---

## 通配符 `*``?`

### `*` — 非贪婪匹配

`*` 匹配零或多个任意字符,采用**最短匹配**策略。

```
输入:a=b=c
模式:{*=}
匹配:a=          ← 找到第一个 "=" 即停止,而非 "a=b="
```

**典型场景:** 消费到某个关键字符。

```wpl
# 匹配到第一个等号
chars{*=}

# 匹配到第一个冒号加空格
chars{*:\s}
```

### `?` — 单字符匹配

`?` 恰好匹配一个任意字符。

```
输入:field1: value
模式:{field?:\s}
匹配:field1:         ← "?" 匹配了 "1"
```

**约束:** 模式中 `*` 最多出现**一次**,超过则报配置错误。`?` 无数量限制。

---

## 空白匹配 `\s``\h`

### `\s` — 连续空白

匹配一个或多个空白字符(空格、制表符、回车、换行)。

```
输入:key   =value
模式:{\s=}
匹配:   =             ← 三个空格 + "="
```

### `\h` — 水平空白

匹配一个或多个水平空白字符(仅空格和制表符,不含换行)。

```
输入:name    :    value
模式:{\h:\h}
匹配:    :             ← 制表/空格 + ":" + 制表/空格
```

### `\S``\H`

与上面相反:

| 记法 | 说明 |
|------|------|
| `\S` | 一个或多个连续**非空白**字符 |
| `\H` | 一个或多个连续**非水平空白**字符 |

---

## 保留标记(Preserve)

`()` 包裹的内容参与匹配以确认分隔位置,但**不会从输入流中消费**。下一阶段从 `()` 内容的起始处继续读取。

```
输入:hello  key=value
模式:{*\s(key=)}

消费:hello            ← 被截断,作为当前字段的值
保留:key=value        ← 留在输入流中,下一个字段从这里开始
```

### 使用方式

```wpl
# 消费到 "command=" 之前,保留 "command=" 给下一个字段
chars{*(command=)}

# 消费任意内容 + 空白区域,保留 "next" 给下一个字段
chars{*\s(next)}
```

### 约束

- `()` 只能出现在模式**末尾**`{*(key=)}` 合法,`{(key)*=}` 不合法。
- `()` 不允许嵌套。
- `()` 内允许字面量、`\s` `\h` `\0` `\n` `\t` `\r``?` 及转义字符。
- `()`**不允许 `*`**——保留部分必须是确定长度。

---

## 使用约束

| 约束 | 说明 |
|------|------|
| `*` 最多一个 | 整个模式中 `*` 最多出现一次,多于一个报错 |
| `()` 仅末尾 | 保留标记只能出现在模式的最后 |
| `()` 内无 `*` | 保留段不允许不确定长度的通配 |
| `()` 无嵌套 | 不允许 `((...))` |
| 不可与 ups_val 混用 | 同时配置 `{}` 与次级结束符时,解析阶段报错 |

---

## 实战示例

### 场景 1:Key=Value 风格日志

日志格式中,字段以 `key=value` 排列,每个 value 后面跟空白和下一个 key。

```
输入:src=192.168.1.1 dst=10.0.0.1 action=accept proto=TCP
```

```wpl
rule kv_log {
  (
    ip{*\s(dst=)}:src,
    ip{*\s(action=)}:dst,
    chars{*\s(proto=)}:action,
    chars:proto
  )
}
# 输出:src=192.168.1.1, dst=10.0.0.1, action=accept, proto=TCP
```

说明:每个字段使用 `{*\s(next_key=)}` 消费到下一个 key 之前的空白,同时保留下一个 key 给后续字段。

### 场景 2:分隔符中包含空白

日志中字段之间用 ` | `(空格 + 竖线 + 空格)分隔。

```
输入:192.168.1.1 | admin | 2024-01-01 10:00:00
```

```wpl
rule pipe_sep {
  (ip:client, chars:user, time:ts){\h|\h}
}
# 输出:client=192.168.1.1, user=admin, ts=2024-01-01 10:00:00
```

### 场景 3:匹配到特定关键字

消费到 `command=` 出现的位置。

```
输入:user=admin role=root command=ls -la
```

```wpl
rule cmd_log {
  (
    chars{*(command=)}:prefix,
    chars:command
  )
}
# prefix = "user=admin role=root "
# command = "ls -la"(前面 "command=" 被 consume_sep 消费)
```

### 场景 4:字段名后跟变化字符

某些日志的字段名格式为 `fieldN:`,N 是一个可变字符。

```
输入:field1: hello field2: world
```

```wpl
rule field_var {
  (chars{field?:\s}:f1, chars:f2)
}
# f1 = "hello", f2 = "world"
```

### 场景 5:纯字面量模式

当 `{}` 内不含通配符时,等价于快捷字面量分隔符,但可以表达多字符序列。

```wpl
# 以下两种写法效果相同
chars{::}
# 等价于
chars\:\:
```

### 场景 6:结合管道函数

模式分隔符可与字段级管道组合使用。

```wpl
rule combined {
  (
    chars{*\s(src=)}:header,
    kvarr(\s):payload
  ) |(take src)
}
```

---

## 与快捷分隔符的对比

| 特性 | 快捷分隔符 | 模式分隔符 |
|------|-----------|-----------|
| 语法 | `\,` `\;` `\s`| `{...}` |
| 匹配能力 | 固定字符串 | 通配符、空白区域、保留标记 |
| 性能 | 最优(memchr) | 接近(纯字面退化为 memchr) |
| 适用场景 | 分隔符是确定字符 | 分隔逻辑含变化部分 |
| 优先级 | 与字段/组级一致 | 与字段/组级一致 |

**选择建议:**
- 分隔符是固定字符(逗号、空格、竖线等)→ 使用快捷分隔符
- 分隔符包含空白区域、需要跳过到关键字、需要保留部分内容 → 使用模式分隔符

---

## 常见问题

### Q: `{...}` 内的 `\s` 和外面的 `\s` 一样吗?

不完全一样。`{}` 内的 `\s` 匹配**一个或多个**连续空白字符;`{}` 外面的 `\s` 沿用原有语义(表示空格分隔符)。旧配置不受影响。

### Q: 可以在组级使用模式分隔符吗?

可以。与快捷分隔符一样,模式分隔符支持字段级和组级:

```wpl
# 字段级
(chars{*=}, digit)

# 组级
(chars, digit){*=}
```

### Q: `*` 匹配到行尾怎么办?

如果 `*` 后续没有其他匹配内容,`*` 会匹配到输入的末尾。如果没找到后续内容的匹配,则该模式整体不匹配,字段将读取到行尾。

### Q: 可以只用 `()` 不带 `*` 吗?

可以。`()` 不依赖 `*`,例如 `{\s(key=)}` 表示:匹配空白区域后确认 `key=` 存在,消费空白部分,保留 `key=`。

---

## 相关资源

- WPL 基础:[01-wpl_basics.md]./01-wpl_basics.md — 字段、分组、分隔符基础
- 核心概念:[02-core-concepts.md]./02-core-concepts.md — 分隔符优先级详解
- 管道函数:[03-wpl_pipe_functions.md]./03-wpl_pipe_functions.md — 字段级管道
- 语言参考:[04-language-reference.md]./04-language-reference.md — 完整类型列表