实现正则表达式的想法很早就有,各种原因导致没有做,最近花了点时间先实现了几个简单的正则语法,分别是concatenation、alternation和closure,其他语法及metacharacter等有时间了有想法了之后再扩展。

这三种基本的语法分别是对应这样的:

concatenation: abc    表示匹配字符串abc

alternation: abc|def   表示匹配字符串abc或者def

closure: a*               表示匹配零个到多个a构成的字符串

我们知道正则表达式最终需要转换成自动机才能用来匹配字符串,我实现的正则通过如下几个步骤把正则表达式转换成自动机:

正则表达式->Parse成AST->生成边(字符)集合->生成NFA->NFA subset construction->转换成DFA->DFA minimization

最后用DFA minimization之后构造的自动机来匹配字符串。

正则语法的分析

一个正则表达式写出来,要让这个正则表达式匹配字符串等操作之前,我们先需要从正则表达式中提取需要的信息并在正则语法错误的时候提示错误,这个过程自然少不了parser。一个parser通常是从一个lexer里面获取一个token,而正则表达式的token都是字符,那么lexer不需要做任何的分词操作,只需要简单的把字符返回给parser即可。

那三种基本的正则语法对应的BNF为:

re ::= alter

re_base ::= char | char_range | '(' re ')'

alter ::= alter_base alter_end

alter_base ::= concat

alter_end ::= '|' alter_base alter_end | epsilon

concat ::= concat_base concat_end

concat_base ::= re_base | closure

concat_end ::= concat_base concat_end | epsilon

closure ::= re_base '*'

这个parser分析了正则表达式之后产生AST,AST的node类型为:class ASTNode

{

public:

ACCEPT_VISITOR() = 0;

virtual ~ASTNode() { }

};

class CharNode : public ASTNode

{

public:

explicit CharNode(int c) : c_(c) { }

ACCEPT_VISITOR();

int c_;

};

class CharRangeNode : public ASTNode

{

public:

struct Range

{

int first_;

int last_;

explicit Range(int first = 0, int last = 0)

: first_(first), last_(last)

{

}

};

CharRangeNode() { }

void AddRange(int first, int last)

{

ranges_.push_back(Range(first, last));

}

void AddChar(int c)

{

chars_.push_back(c);

}

ACCEPT_VISITOR();

std::vector ranges_;

std::vector chars_;

};

class ConcatenationNode : public ASTNode

{

public:

void AddNode(std::unique_ptr node)

{

nodes_.push_back(std::move(node));

}

ACCEPT_VISITOR();

std::vector<:unique_ptr>> nodes_;

};

class AlternationNode : public ASTNode

{

public:

void AddNode(std::unique_ptr node)

{

nodes_.push_back(std::move(node));

}

ACCEPT_VISITOR();

std::vector<:unique_ptr>> nodes_;

};

class ClosureNode : public ASTNode

{

public:

explicit ClosureNode(std::unique_ptr node)

: node_(std::move(node))

{

}

ACCEPT_VISITOR();

std::unique_ptr node_;

};

其中ASTNode作为AST的基类,并提供接口实现Visitor模式访问ASTNode类型。

字符(边)集的构造

AST构造好了之后,需要把AST转换成NFA。语法中有[a-zA-Z0-9]这种字符区间表示法,我们可以用最简单原始的方法转换,就是把区间中的每个字符都转化成相应的一条边(NFA中的边),这样一来会导致字符区间越大,对应边的数量会越多,使得对应的NFA也越大。因此,我们需要构造区间字符集合来减少边的数量。

比如正则表达式是:a[x-z]|[a-z]*e

那么我们希望对应的字符集合是这样:[a-a] [b-d] [e-e] [f-w] [x-z]

这需要构造一个字符集,每次插入一个区间的时候,把新插入的区间与已存在的区间进行分割,初始时已存在的区间集为空,那么正则表达式a[x-z]|[a-z]*e的划分步骤如下:

已存在区间集合{},插入[a-a],得到{[a-a]}

已存在区间集合{[a-a]},插入[x-z],得到{[a-a], [x-z]}

已存在区间集合{[a-a], [x-z]},插入[a-z],得到{[a-a], [b-w], [x-z]}

已存在区间集合{[a-a], [b-w], [x-z]},插入[e-e],得到{[a-a], [b-d], [e-e], [f-w], [x-z]}

这个区间构造完成了之后,还需要在后面转换成NFA边的时候,根据字符区间查询出在这个集合中,由哪几个区间构成,比如:

查询区间[a-a],得到[a-a]

查询区间[x-z],得到[x-z]

查询区间[a-z],得到区间[a-a] [b-d] [e-e] [f-w] [x-z]

在转换成NFA时,集合中的每个区间都对应一条边,这样相对于每个字符对应一条边,边的数量不会太多。

有了这么一个集合构造的类之后,把正则的AST中的字符信息提取出来构造出这么个集合即可,这样只需要写个visitor就完成了:class EdgeSetConstructorVisitor : public Visitor

{

public:

explicit EdgeSetConstructorVisitor(EdgeSet *edge_set)

: edge_set_(edge_set)

{

}

EdgeSetConstructorVisitor(const EdgeSetConstructorVisitor &) = delete;

void operator = (const EdgeSetConstructorVisitor &) = delete;

VISIT_NODE(CharNode);

VISIT_NODE(CharRangeNode);

VISIT_NODE(ConcatenationNode);

VISIT_NODE(AlternationNode);

VISIT_NODE(ClosureNode);

private:

EdgeSet *edge_set_;

};

边集合构造完成之后,下一步就是生成NFA了。

java正则表达式 分词_正则表达式实现(一)相关推荐

  1. java 正则表达式效验_正则表达式(Java版整理)

    基础 元字符 代码 说明 . 匹配除换行符以外的任意字符 \w 匹配字母或数字或下划线或汉字 \s 匹配任意的空白符 \d 匹配数字 ^ 匹配字符串的开始 $ 匹配字符串的结束 \b 匹配字符串的结束 ...

  2. python 正则表达式 前瞻_正则表达式 For Python

    Manarola 正则表达式有很多流派,也有很多的特性,不同的语言支持度也是不一样的.本篇文章是写Python中的正则表达式的用法的,介绍了一些可用特性,也指出了某些特性是不支持的. 本篇文章仅为学习 ...

  3. java正则表达式 分词_[Java]使用正则表达式实现分词

    手工分词稍嫌麻烦,不好维护,而利用正则表达式就利索多了.Java提供了java.util.regex.Matcher,java.util.regex.Pattern类来帮助我们实现此功能. 例一:以下 ...

  4. java 正则 关键字_正则表达式关键字

    在表达式中有特殊意义,需要添加 "\" 才能匹配该字符本身的字符汇总 字符 说明 ^ 匹配输入字符串的开始位置.要匹配 "^" 字符本身,请使用 "\ ...

  5. java正则表达式逗号_正则表达式只匹配逗号而不是括号?

    保罗,复活了这个问题,因为它有一个未提及的简单解决方案.(在进行正则表达式赏金任务研究时发现了您的问题.) 此外,现有解决方案还会检查逗号后是否没有括号,但这并不能保证它会嵌入括号中. 正则表达式非常 ...

  6. java 正则匹配_正则表达式真的很强大,可惜你不会写

    专注于Java领域优质技术,欢迎关注 本文旨在用最通俗的语言讲述最枯燥的基本知识 文章提纲: 元字符 重复限定符 分组 转义 条件或 区间 正则表达式在几乎所有语言中都可以使用,无论是前端的JavaS ...

  7. java 正则表达式 反向_正则表达式中的数量表示符、反向引用、零宽断言、以及java中的用法...

    在表示数量时,如果一个正则表达式X,后面没有加表示数量的符号,那就默认出现一次.如果指定需要出现n次,那就用{n},例如a{n},就是匹配a出现n次的.a{n,}表示a出现至少n次的,而这个时候就会默 ...

  8. java 完全匹配_正则表达式的完全匹配和部分匹配

    Java正则表达式有3中量词匹配模式: 1.贪婪量词: 先看整个字符串是否匹配,如果没有发现匹配,则去掉最后字符串中的最后一个字符,并再次尝试,如果还是没有发现匹配,那么,再次去掉最后一个字符串的最后 ...

  9. java限定符_正则表达式之限定符

    导读热词 我们知道正则表达式中的元字符一次一般只能匹配一个位置或一个字符,如果要匹配一个或 零个或多个字符的时候,则需要使用限定符了.限定符就是允许特定字符或字符集合自身重复出 现的次数.常用的限定符 ...

最新文章

  1. 浅显易懂 Makefile 入门 (08)— 默认 shell (/bin/sh)、命令回显、make参数(-n 只显示命令但不执行,-s 禁止所有回显)、单行命令、多行命令、并发执行
  2. Centos查找命令清单
  3. windows上搭建python+gvim开发环境
  4. mysql防注入pdo_mysql PDO和存储过程动态SQL注入
  5. LeetCode Construct Quad Tree(dfs)
  6. 蓝桥杯练习系统习题-算法训练2
  7. CentOS7 安装NFS SSH免密码登陆
  8. MySQL数据库-笔记05【查询练习题*25道(附解析)】
  9. 收集Linux常用命令
  10. 虚拟机linux gedit,Linux系统中把gedit改造成TextMate的方法
  11. 读caffe源码:gflags的使用
  12. python未知数的矩阵运算,机器学习的数学 之python矩阵运算
  13. 永磁同步电机市场现状及未来发展趋势
  14. matlab右上角星号怎么打出来,星号怎么打出来(教你怎么输入特殊符号)
  15. 使用阿里云ECS搭建Nextcloud私有云服务器
  16. Camera Hal OEM模块 ---- cmr_grab.c
  17. 《卸甲笔记》-单行函数对比之三
  18. mysql 事务机制
  19. 数字音频芯片--Digilent 公司PmodI2S芯片控制
  20. linux下硬盘拷贝

热门文章

  1. 风无定,人无常,人生如浮萍,聚散两茫茫——元组类型、字典类型的内置方法,第九天
  2. python 列表去重 保持顺序
  3. 数据结构之树与二叉树
  4. 一步步蚕食的意思_第800章 一步步蚕食
  5. 【进制运算】计算机的小任性——我说0代表正数,1代表负数,就是对的!
  6. CentOS系统安装
  7. Applet中签名与未签名代码的混合使用带来的问题
  8. 机器学习 基础理论 学习笔记 (6)异常值检测和处理
  9. 永中科技破产清算的疑问(三)
  10. vanish_3.0_ban