# aardio 模式匹配快速入门  

简单的模式匹配示例：

```aardio
var str = string.match("字符串","这里是模式串")   
```

模式串有两个基本的元素：

1. 子模式 - 指定匹配的数据
2. 运算符 - 指定匹配的规则
  
## 一. 子模式 

子模式是模式串里的最小匹配单位，用于表示字符或某一类的字符（或连续的字符序列）。
  
### 1. 字面值
  
字面值指的是 "a" 表示 "a",  "b" 表示 "b "，字面意思是什么就表示什么，这个不难理解。  

```aardio
var str = string.match("abcd","abc")   
```

### 2. 任意字符
  
- `.` 匹配任意单字节字符（ 与正则表达式类似 ）。 
- `:` 匹配任意多字节字符( 例如中文字符，正则表达式都是用单个点表示  )  。
  
### 3. 预定义的字符类  
  
正则表达式兼容的字符类：

`\n` 换行符 
`\r` 回车符
`\w` 字母和数字 
`\s` 空白符
`\d` 数字
`\f` 换页符 '\x0c'
`\v` 匹配一个垂直制表符。等价于 '\x0b' 
`\t` 制表符 

其他字符类：

`\a` 字母  
`\c` 控制字符  
`\i` 是否ASCII字符( 字节码 < 0x80 )  
`\l` 小写字母  
`\p` 标点字符  
`\u` 大写字母  
`\x` 十六进制数字（正则表达式里用于16进制字符前缀，即`\xhh`）  
`\z` 表示 `'\0'  `

大写表示反义( 与正则表达式相同 )，例如`\D`表示不是数字的字符。  

```aardio
import console;

var str = string.match("12345678","\d")  
console.log( str ) //显示 1

console.pause();
```

### 4. 自定义的字符集合

例如`[abcd]`表示字符是 abcd 其中一个，或者`[a-z]`表示 a 到 z 的任意小写字母字符。  

```aardio
//还是返回 1
var str = string.match("12345678","[1230-9]")   
```

参考：[模式语法 - 字符集合](../../library-guide/builtin/string/patterns.md#set)

### 5. 元序列

例如 `<a-zabcd>` 

元序列的语法与**自定义字符类**类似，但是**元序列**匹配的是一连串的字符**序列**。

```aardio
// 显示 1234
var str = string.match("12345678","<1230-9>")   
```

元序列可组合多个子模式并将其转换为一个新的子模式（可对其应用其他模式运算符），并且在内部可以支持大多数其他模式语法。元序列可以嵌套包含其他元序列，但不能在元序列内包含捕获组（可以引用捕获组）。元序列是非捕获分组，也是原子分组（内部不回溯），这两者在正则表达式中都属于性能优化分组，而元序列的速度更快（通常比正则表达式快数十倍）。

元序列具有原子性，在内部执行贪婪与激进的匹配规则，只要能匹配到内容就会尽力向后走，并且不会在元序列内执行回溯匹配。

参考：[模式语法 - 元序列](../../library-guide/builtin/string/patterns.md#metasequence)

## 二. 运算符
  
运算符是指定一个模式应当怎样去匹配或者重复匹配多少次的特殊符号。

运算符只能用于子模式，不能用于其他运算符，也不能用于捕获分组。

如果将『子模式』理解为编程语言里的操作数，那么『模式运算符』就相当于编程语言里的操作符。

运算符有很多种，而最常用的就是用来指定匹配次数的量词。  

### 量词
  
- `p{2,3}` 表示子模式 p 出现 2 到 3次  

```aardio
//匹配结果为 12345 
var str = string.match("12345678","\d{2,5}")   
```

- `p+`  表示子模式 p 出现 1 次到任意次数，等价于 `p{1,} `

```aardio
//匹配结果为 12345678 
var str = string.match("12345678","\d+")   
```
  
-  p* 表示a出现0次到任意次数，等价于 `p{0,}`

```aardio
//匹配结果还是 12345678 
var str = string.match("12345678","\d+\s*") //这里的"\s*"匹配零个或多个空格   
```

其他运算符请参考：[模式语法 - 运算符](../../library-guide/builtin/string/patterns.md#operators)

## 三. 捕获组与非捕获组 <a id="group" href="#group">&#x23;</a>

- aardio 模式匹配用 `<>` 包含非捕获组（也就是元序列）。
- aardio 模式匹配用 `()` 包含捕获组，但捕获组不是子模式，不能对其使用量词等模式运算符，这是 aardio 模式匹配与正则表达式的主要区别。

在 aardio 模式匹配语法里 `()` 包含的捕获组只有分组的作用，但捕获组本身不是模式更不是子模式。而 `<>` 包含的非捕获组则恰恰相反，非捕获组用于将其他模式或子模式组合为单个的子模式，可以对非捕获组使用量词等运算符，而且非捕获组还可以嵌套包含其他非捕获组。

捕获组记录匹配的内容，在模式串后面可以使用 `\1` 到  `\9` 反向引用之前捕获组的匹配结果。

对于[string.match](../../library-guide/builtin/string/matching.md#match) [string.gmatch](../../library-guide/builtin/string/matching.md#gmatch) 等模式匹配函数，每个捕获组都会增加一个返回的字符串值。如果没有任何捕获组，则第一个返回值为匹配的全部字符串，否则第一个返回值为第一个捕获组。

[string.replace](../../library-guide/builtin/string/matching.md#replace)  等替换函数的 @repl 参数指定为替换字符串时同样可以反向引用捕获组，如果 @repl 参数指定替换回调函数，则每个捕获组都会增加一个字符串参数。如果没有任何捕获组，则第一个回调参数为匹配的全部字符串，否则第一个回调参数为第一个捕获组。

## 四. 模式匹配应用实例

模式匹配对正则表达式进行了简化，保留基本语法，牺牲一些功能换取效率。  

### 最简单的模式串
  
例如模式串 `\d+` 包含两个部分：
  
- `\d` 表示数字。这里的`\d`还可以换成`[0-9]` ，它们的意思是一样的。`[0-9]` 等价于 `[0123456789]`。 **中括号表示自定义的一个字符集合，只要目标字符是其中的一个就匹配成功。**  
- 运算符  `+`  表示匹配一次或多次，也可以写为 `{1,}`  意思是一样的。  

`\d+` 前面的子模式 `\d` 指定匹配什么样的字符，后面运算符 `+` 指定怎样去匹配、匹配多少个字符。

### 模式匹配身份证号码
  
再例如身份证匹配的模式 `\d{15,18`}  用于匹配 15 到 18 个数字。  

1. 自定义字符集合
  
    但我们遇到的一个问题是，最后一位可能不是数值，也有一种可能是"x"，也就是说**可能是数值也可能是x**，那么我们就要改为：

    `\d{14,17}[\dx]`

    但是别人还有可能把 x 大写啊，所以继续修改为：

    `\d{14,17}[\dxX]`

2. 元序列
  
    身份证还不能是 16 位, 17 位数字。所以我们继续修改为：

    ```aardio
    var str = string.match("身份证号码","\d{14,14}<\d\d\d>*[\dxX]" )   
    ```
    
    上面的 `<\d\d\d>` 是一个[自定义的元序列（ metasequence ）](../../library-guide/builtin/string/patterns.md#metasequence)。 

    像这些，多动手折腾很快会明白，别人灌输给你的知识永远没有自已探索到的理解深刻。

3. 首尾锚点
  
    更进一步，19 位，20 位的数值都可能匹配成功，因为他只要匹配其中18位就成功了，这时候我们还要限定他前面后面都不能有其他的字符，这时就要指定首尾锚点，用 `^` 表示文本开始位置的锚点，用 `$` 表示文本结束位置的锚点。

    例如：  

    ```aardio
    var str = string.match("身份证号码","^\d{14,14}<\d\d\d>*[\dxX]$" )   
    ```

4. 跳过空白字符

    但是这样还有一个问题，如果身份证号码前后可能有空格怎么办呢？可以用 `\s` 表示所有空白字符，加上匹配零到多次的量词运算符就是`\s*`，再改进如下：  

    ```aardio
    var str = string.match("身份证号码","^\s*\d{14,14}<\d\d\d>*[\dxX]\s*$" )   
    ```

5. 边界断言

    如果身份证前后不仅仅是空格，还可能有别的字符，哪怎么办呢？  
    
    这里我们可以接触一个新的概念：[边界断言](../../library-guide/builtin/string/patterns.md#boundary)。

    边界断言是在子模式前面加一个表示否定的感叹号，表示从不匹配（左侧）到匹配（右侧）的边界。

    例如 `!\d` 表示不是数字到数字交界的位置，这是一个试探性的零宽匹配，匹配的仅仅是边界，消耗的字符串长度为 0。  

    加上边界断言后的代码如下：

    ```aardio
    import console; 
    
    var idNumber = "sfz612323198608110000fgd"  
    idNumber = string.match(idNumber,"!\d(\d{14}<\d\d\d>*[\dxX])![^\dxX]")  

    console.log(idNumber);
    console.pause(true);
    ```

    边界断言是一种左右双向的零宽断言（Zero-width Assertions）语法,  
    结合向左回顾断言（逻辑取反） + 向右预测断言以实现边界检测，在字符串首尾两端只检测有字符的内侧边界。

这些需要在**实践中嗑碰出来的知识，如流水无形，无一定之规**。  

## 五. 模式匹配中的那些括号

可以熟练使用模式匹配中的括号，表示你精通了模式匹配。

- `[ab]` 中括号创建自定义字符集合，用于匹配指定字符中的**任意一个**。  
- `<ab>` 尖括号创建元序列，匹配一连串的字符**序列**。  
- `p{2,3}` 大括号作为运算符指定子模式重复匹配的次数。
- `(p)`  而圆括号则对模式匹配结果进行分组，每增加一对圆括号，模式匹配函数就**多一个返回值**。
  
参考：

- [模式匹配语法](../../library-guide/builtin/string/patterns.md)
  
- [模式匹配函数](../../library-guide/builtin/string/matching.md) 
