编译原理 词法分析 Java代码实现
词法分析代码步骤
借助GraphViz
生成NFA,DFA,miniDFA等借助GraphViz工具
环境配置详情参见https://blog.csdn.net/qq_18208435/article/details/70464654
生成逆波兰表达式
对正则表达式添加操作符
如a(a|b)*b应该改为a·(a|b)*·b
给定输入字符串st,添加操作符,代码如下:
//假定字符串输入为小写字母。
for(int i = 0; i < st.length(); i++) {
if(i-1>=0) {
char t0 = st.charAt(i-1);
char t1 = st.charAt(i);
if((t0 >= 'a' && t0 <= 'z') && (t1 >= 'a' && t1 <= 'z')) {//ab->a·b
st.insert(i, "·");
}
else if(t0 == ')' && (t1 >= 'a' && t1 <= 'z')) {//)a->)·a
st.insert(i, "·");
}
else if((t0 >= 'a' && t0 <= 'z') && t1=='(') {//a(->a·(
st.insert(i, "·");
}
else if(t0 == '*' && (t1 >= 'a' && t1 <= 'z')) {//*a->*·a
st.insert(i,"·");
}
else if(t0 == '*' && t1 == '('){//*(->*·(
st.insert(i,"·");
}
}
}
生成逆波兰表达式
采用调度场算法
- 顺序扫描表达式,如果当前字符是字母(优先级为0的符号) ,则直接输出;如果当前字符为运算符或者括号(优先级不为 0的符号),则判断:
- 若当前运算符为’(’ ,直接入栈;
- 若为’)’ ,出栈并顺序输出运算符直到遇到第一个’(’ ,遇到的第一个 '('出栈但不输出;
- 若为其它,比较运算符栈栈顶元素与当前元素的优先级:
- 如果栈顶元素是’(’,当前元素直接入栈
- 如果栈顶元素优先级>=当前元素优先级,出栈并顺序输出运算符直到 栈顶元素优先级<当前元素优先级,然后当前元素入栈
- 如果栈顶元素优先级<当前元素优先级,当前元素直接入栈
- 重复上述操作直至表达式扫描完毕
- 顺序出栈并输出运算符直到栈元素为空
代码实现:
StringBuilder ans = new StringBuilder();
//char[] Table= {'*','·','|'};
Map<Character,Integer> map = new HashMap<Character,Integer>();//数字表示优先级
map.put('|', 1);
map.put('·', 2);
map.put('*', 3);
Stack<Character> stack = new Stack<>();
for(int i = 0; i < st.length(); i++) {
char t = st.charAt(i);
if(t >= 'a' && t <= 'z') {
ans.append(t);
}
else if(t == '(' || stack.empty()) {
stack.push(t);
}
else if(t == ')') {
char top = stack.pop();
while(top!='(') {
ans.append(top);
top = stack.pop();
}
}
else {
char top = stack.pop();
if(top == '(') {
stack.push(top);
stack.push(t);
}
else {
if(map.get(top) >= map.get(t)){
while(map.get(top) >= map.get(t)) {
ans.append(top);
if(!stack.empty()) {
top = stack.pop();
if(top == '(') {
stack.push(top);
stack.push(t);
break;
}
}
else {
stack.push(t);
break;
}
}
}
else {
stack.push(top);
stack.push(t);
}
}
}
}
while(!stack.empty()) {
ans.append(stack.pop());
}
生成NFA
采用存储边的思想,利用如下数据结构将每一条边存储:
public class Part{
public int s;//存储一条边的start
public int e;//存储一条边的end
public char op;//存储一条边上的op
public Part(int s,int e,char op) {
this.s = s;
this.e = e;
this.op = op;
}
}
通过处理逆波兰表达式得到最终的NFA,处理流程如下:
对你逆波兰表达式扫描
-
如果当前字符为字母,则增加一条新的边Part(num,num+1,op),并将该部分两端的节点(num,num+1)入栈
-
如果当前字符为’ · ',则从Stack中取出两条边,p1,p2。增加三条边:
new Part(p1.e,p2.s,'ε') new Part(num,p1.s,'ε') new Part(p2.e,++num,'ε')
并将该部分两端的节点(num-1,num)入栈
-
如果当前字符为’ | ',则从Stack中取出两条边,p1,p2。增加四条边:
v.add(new Part(num,p1.s,'ε')); v.add(new Part(num,p2.s,'ε')); ++num; v.add(new Part(p1.e,num,'ε')); v.add(new Part(p2.e,num,'ε'));
并将改部分两端的节点(num-1,num)入栈
-
如果当前字符为’ * ',则从Stack中取出一条边,p。增加四条边:
v.add(new Part(p.e,p.s,'ε')); v.add(new Part(num,p.s,'ε')); ++num; v.add(new Part(num-1,num,'ε')); v.add(new Part(p.e,num,'ε'));
并将该部分两端的节点(num-1,num)入栈
注:完成后stack中栈底的边p,p.s为整个NFA的start,p.e为整个NFA的end(终态)。
while(!stack.isEmpty()) {
p = stack.pop();
}
start = p.s;
end = p.e;
生成DFA
这部分比较思路上比较简单,主要是做一下几个方面的工作
-
求ε闭包(状态合并)
-
状态合并后标记终态(含NFA终态节点的DFA节点都是终态,可能不止一个)
注:含NFA初态节点的DFA节点为初态,只有一个
-
将合并后的节点重新编号,重新将DFA的边记录
代码实现思路为:
- 求NFA start节点的epsilon闭包
- 求闭包内节点通过不同操作符到达的节点的epsilon闭包,如果该节点是一个新的节点,则重复2操作直至没有新的节点产生
以下为关键代码实现:
//求start节点的epsilon闭包
public void GetIEpsilon(TreeSet<Integer> s,int start){
vis[start] = 1;
s.add(start);
for (Part part : v) {
if (part.s == start && part.op == 'ε') {
//s.add(v.get(i).e);
if (vis[part.e] == 0)
GetIEpsilon(s, part.e);
}
}
}
//求经过op操作符为a所到达的点的epsilon闭包,存入一个TreeSet
public void GetI(TreeSet<Integer> s,int start,char a){
for (Part part : v) {
if (part.s == start && part.op == a) {
s.add(part.e);
Arrays.fill(vis, 0);
GetIEpsilon(s, part.e);
}
}
}
//将含NFA的start和end节点标记出来,PointSet[i]为DFA的节点,集合内容为NFA的节点编号。
public void GetSE(){
System.out.println("start:"+ TurnNFA.start);
System.out.println("end:"+ TurnNFA.end);
for(int i = 0; i < setNum; i++){
if(pointSet[i].contains(TurnNFA.start)){
start.add(i+1);//只有一个
}
if(pointSet[i].contains(TurnNFA.end)){
end.add(i+1);
}
}
Iterator<Integer> itS = start.iterator();
Iterator<Integer> itE = end.iterator();
while(itS.hasNext()){
System.out.print(itS.next()+" ");
}
System.out.println();
while(itE.hasNext()){
System.out.print(itE.next()+" ");
}
System.out.println();
}
//求DFA的状态
while(visNum < setNum){
itOp = opSet.iterator();
while(itOp.hasNext()) {
itSet = pointSet[visNum].iterator();
char opNow = itOp.next();
while (itSet.hasNext()) {
GetI(pointSet[setNum],itSet.next(),opNow);
}
if(pointSet[setNum].size()!=0){
int flag = 0;
int pointSame=0;
for(int i = 0; i < setNum; i++){
if(pointSet[i].equals(pointSet[setNum])){
pointSame = i;
flag = 1;
break;
}
}
if(flag == 0){//没有相同的集合元素
edgeDFA.add(new Part(visNum+1,setNum+1,opNow));
setNum++;
pointSet[setNum] = new TreeSet<>();
}
else{//有集合相同的元素
edgeDFA.add(new Part(visNum+1,pointSame+1,opNow));
pointSet[setNum].clear();
}
}
}
visNum++;
}
生成miniDFA
这部分的实现思路没什么复杂的地方,关键在于找到相同的状态,合并。
关键代码
//state[x][y]二维数组表示x通过操作符‘a’+y到达的状态
//group[i]表示DFA的状态i对应的区号
while(true){
for (Part part : edge) {
state[part.s][part.op - 'a'] = groupId[part.e];
}
int Num = 1;
for(int i = 1; i <= setNum; i++){
int flag = 0;
for(int j = 1; j < i; j++){
if(cmp(state[j],state[i]) && groupId[i] == groupId[j]){
flag = 1;
newGroupId[i]=newGroupId[j];
break;
}
}
if(flag == 0) {
newGroupId[i] = Num;
Num++;
}
}
if(cmp(groupId,newGroupId))//旧区号和新区号相同,跳出。
break;
else{
groupId = Arrays.copyOf(newGroupId,newGroupId.length);
}
}
Code Origanization
//GraphViz.java
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
public class GraphViz{
private String runPath="";
private String dotPath="";
private String runOrder="";
private String dotCodeFile="dotcode.txt";
private String resultGif="dotGif";
private StringBuilder graph = new StringBuilder();
Runtime runtime=Runtime.getRuntime();
public void run() {
File file=new File(runPath);
file.mkdirs();
writeGraphToFile(graph.toString(), runPath);
creatOrder();
try {
runtime.exec(runOrder);
} catch (IOException e) {
e.printStackTrace();
}
}
public void creatOrder(){
runOrder+=dotPath+" ";
runOrder+=runPath;
runOrder+="\\"+dotCodeFile+" ";
runOrder+="-T png ";
runOrder+="-o ";
runOrder+=runPath;
runOrder+="\\"+resultGif+".png";
System.out.println(runOrder);
}
public void writeGraphToFile(String dotcode, String filename) {
try {
File file = new File(filename+"\\"+dotCodeFile);
if(!file.exists()){
file.createNewFile();
}
FileOutputStream fos = new FileOutputStream(file);
fos.write(dotcode.getBytes());
fos.close();
} catch (java.io.IOException ioe) {
ioe.printStackTrace();
}
}
public GraphViz(String runPath,String dotPath) {
this.runPath=runPath;
this.dotPath=dotPath;
}
public void add(String line) {
graph.append("\t"+line+"\n");
}
public void addln(String line,String factor) {
graph.append("\t"+line + "[label=\""+factor+"\"]"+"\n");
}
public void addln() {
graph.append('\n');
}
public void start_graph() {
graph.append("digraph G {\n") ;
}
public void end_graph() {
graph.append("}") ;
}
}
//ReversePolish.java
import java.util.*;
public class ReversePolish {
public StringBuilder s;
public ReversePolish(StringBuilder st)
{
for(int i = 0; i < st.length(); i++) {
if(i-1>=0) {
char t0 = st.charAt(i-1);
char t1 = st.charAt(i);
if((t0 >= 'a' && t0 <= 'z') && (t1 >= 'a' && t1 <= 'z')) {//ab
st.insert(i, "·");
}
else if(t0 == ')' && (t1 >= 'a' && t1 <= 'z')) {
st.insert(i, "·");
}
else if((t0 >= 'a' && t0 <= 'z') && t1=='(') {
st.insert(i, "·");
}
else if(t0 == '*' && (t1 >= 'a' && t1 <= 'z')) {
st.insert(i,"·");
}
else if(t0 == '*' && t1 == '('){
st.insert(i,"·");
}
}
}
System.out.println(st);
StringBuilder ans = new StringBuilder();
//char[] Table= {'*','·','|'};
Map<Character,Integer> map = new HashMap<Character,Integer>();
map.put('|', 1);
map.put('·', 2);
map.put('*', 3);
Stack<Character> stack = new Stack<>();
for(int i = 0; i < st.length(); i++) {
char t = st.charAt(i);
if(t >= 'a' && t <= 'z') {
ans.append(t);
}
else if(t == '(' || stack.empty()) {
stack.push(t);
}
else if(t == ')') {
char top = stack.pop();
while(top!='(') {
//System.out.println(top);
//System.out.print(stack+"\n");
ans.append(top);
top = stack.pop();
}
}
else {
//System.out.println(t);
char top = stack.pop();
if(top == '(') {
stack.push(top);
stack.push(t);
}
//System.out.println(a+"----"+b);
else {
if(map.get(top) >= map.get(t)){
while(map.get(top) >= map.get(t)) {
ans.append(top);
if(!stack.empty()) {
top = stack.pop();
if(top == '(') {
stack.push(top);
stack.push(t);
break;
}
}
else {
stack.push(t);
break;
}
}
}
else {
stack.push(top);
stack.push(t);
}
}
}
//System.out.print(stack+"\n");
//System.out.print(ans+"\n");
}
while(!stack.empty()) {
ans.append(stack.pop());
}
s = ans;
}
public static void main(String[] args) {
StringBuilder s = new StringBuilder();
s.append("((a*)*|(b|a))*cd*");
//System.out.println(s);
ReversePolish r = new ReversePolish(s);
System.out.println(r.s);
}
}
//Part.java
public class Part{
public int s;
public int e;
public char op;
public Part(int s,int e,char op) {
this.s = s;
this.e = e;
this.op = op;
}
}
//TurnNFA.java
import java.util.*;
public class TurnNFA {
public static ArrayList<Part> v;
public static int start;
public static int end;
public TurnNFA(StringBuilder s) {
v = new ArrayList<>(10000);
ArrayDeque<Part> stack = new ArrayDeque<>();
int num = 1;
for(int i = 0; i < s.length(); i++) {
char t = s.charAt(i);
if(t >= 'a' && t <= 'z'){
Part p = new Part(num,++num,t);
stack.push(p);
v.add(p);
++num;
}
else if(t == '·'){
Part p2 = stack.pop();
Part p1 = stack.pop();
v.add(new Part(p1.e,p2.s,'ε'));
v.add(new Part(num,p1.s,'ε'));
v.add(new Part(p2.e,++num,'ε'));
stack.push(new Part(num-1,num, ' '));
//stack.push(new Part(p1.s,p2.e,' '));
++num;
}
else if(t == '|'){
Part p2 = stack.pop();
Part p1 = stack.pop();
v.add(new Part(num,p1.s,'ε'));
v.add(new Part(num,p2.s,'ε'));
++num;
v.add(new Part(p1.e,num,'ε'));
v.add(new Part(p2.e,num,'ε'));
stack.push(new Part(num-1,num,' '));
++num;
}
else if(t == '*'){
Part p = stack.pop();
v.add(new Part(p.e,p.s,'ε'));
v.add(new Part(num,p.s,'ε'));
++num;
v.add(new Part(num-1,num,'ε'));
v.add(new Part(p.e,num,'ε'));
stack.push(new Part(num-1,num,' '));
++num;
}
}
Part p = new Part(0,0,' ');
while(!stack.isEmpty()) {
p = stack.pop();
}
start = p.s;
end = p.e;
drawNFA();
}
public void drawNFA(){
GraphViz gViz=new GraphViz(".\\src\\HomeWork1\\GraphNFA", "D:\\Grapgviz\\Graphviz\\bin\\dot.exe");
gViz.start_graph();
gViz.add("node [shape = doublecircle];"+end+";");
gViz.add("node [shape = circle];");
for (Part part : v) {
gViz.addln(part.s + "->" + part.e, "" + part.op);
}
gViz.addln("start"+"->"+start," ");
gViz.end_graph();
try {
gViz.run();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
StringBuilder s = new StringBuilder("a*ab·b·*·b·ba|*·");
TurnNFA t = new TurnNFA(s);
}
}
//TurnDFA.java
import java.util.*;
public class TurnDFA {
public static TreeSet<Integer> start;
public static TreeSet<Integer> end;//存储含有终态的节点
public static ArrayList<Part> v;
public static ArrayList<Part> edgeDFA;
public static TreeSet<Character> opSet;
public static TreeSet<Integer>[] pointSet;
public static int setNum = 0;
public static int[] vis;
public TurnDFA(StringBuilder s){
start = new TreeSet<>();
end = new TreeSet<>();
v = new ArrayList<>(10000);
opSet = new TreeSet<>();
edgeDFA = new ArrayList<>(10000);
vis = new int[1000];
Arrays.fill(vis,0);
TurnNFA tn = new TurnNFA(s);
TurnNFA.v.sort(new PartComparator());//按Part的start将边排序
v = new ArrayList<>(TurnNFA.v);
setNum = 0;//用于记录pointSet的数量
int visNum = 0;//用于记录已经访问的pointSet的数量
pointSet = new TreeSet[1000];
pointSet[0] = new TreeSet<>();
Arrays.fill(vis,0);//求Epsilon闭包前vis清零
GetIEpsilon(pointSet[0], TurnNFA.start);//得到start的Epsilon闭包
//start = 0;
setNum++;
pointSet[setNum] = new TreeSet<>();
System.out.println(pointSet[0]);
GetEdgeOp(opSet);
System.out.println(opSet);
Iterator<Integer> itSet;
Iterator<Character> itOp;
while(visNum < setNum){
itOp = opSet.iterator();
while(itOp.hasNext()) {
itSet = pointSet[visNum].iterator();
char opNow = itOp.next();
while (itSet.hasNext()) {
GetI(pointSet[setNum],itSet.next(),opNow);
}
//System.out.println(opNow+" "+pointSet[setNum]);
if(pointSet[setNum].size()!=0){
//edgeDFA.add(new Part(visNum+1,setNum+1,opNow));
int flag = 0;
int pointSame=0;
for(int i = 0; i < setNum; i++){
if(pointSet[i].equals(pointSet[setNum])){
pointSame = i;
flag = 1;
break;
}
}
if(flag == 0){//没有相同的集合元素
edgeDFA.add(new Part(visNum+1,setNum+1,opNow));
setNum++;
pointSet[setNum] = new TreeSet<>();
}
else{//有集合相同的元素
edgeDFA.add(new Part(visNum+1,pointSame+1,opNow));
pointSet[setNum].clear();
}
}
}
visNum++;
}
for(int i = 0; i < setNum; i++){//测试处,输出DFA每一个PointSet,后期注释
System.out.println(pointSet[i]);
}
for (Part part : edgeDFA) {//测试处,输出DFA的每一条边,后期注释
System.out.println(part.s + " " + part.e + " " + part.op);
}
System.out.println(setNum);//测试处,输出DFA的个数
GetSE();//
drawGraph();
}
public void GetI(TreeSet<Integer> s,int start,char a){
for (Part part : v) {
if (part.s == start && part.op == a) {
s.add(part.e);
Arrays.fill(vis, 0);
GetIEpsilon(s, part.e);
}
}
}
public void GetIEpsilon(TreeSet<Integer> s,int start){
vis[start] = 1;
s.add(start);
for (Part part : v) {
if (part.s == start && part.op == 'ε') {
//s.add(v.get(i).e);
if (vis[part.e] == 0)
GetIEpsilon(s, part.e);
}
}
}
public void GetEdgeOp(TreeSet<Character> s){
for (Part part : v) {
if (part.op != 'ε') {
s.add(part.op);
}
}
}
public void GetSE(){//获取含有start的节点集合以及含有end的节点集合
//TurnNFA tn = new TurnNFA(s);
System.out.println("start:"+ TurnNFA.start);
System.out.println("end:"+ TurnNFA.end);
for(int i = 0; i < setNum; i++){
if(pointSet[i].contains(TurnNFA.start)){
start.add(i+1);//只有一个
}
if(pointSet[i].contains(TurnNFA.end)){
end.add(i+1);
}
}
Iterator<Integer> itS = start.iterator();
Iterator<Integer> itE = end.iterator();
while(itS.hasNext()){
System.out.print(itS.next()+" ");
}
System.out.println();
while(itE.hasNext()){
System.out.print(itE.next()+" ");
}
System.out.println();
}
public static void main(String[] args) {
StringBuilder s = new StringBuilder("a*ab·b·*·b·ba|*·");
TurnDFA tn = new TurnDFA(s);
}
public void drawGraph(){
GraphViz gViz=new GraphViz(".\\src\\HomeWork1\\GraphDFA", "D:\\Grapgviz\\Graphviz\\bin\\dot.exe");
gViz.start_graph();
gViz.add("node [shape = doublecircle];");
for (Integer integer : end) {
gViz.add(integer.toString());
}
gViz.add(";");
gViz.add("node [shape = circle];");
for (Part part : edgeDFA) {
gViz.addln(part.s + "->" + part.e, "" + part.op);
}
Iterator<Integer> itStart = start.iterator();//只有一个
gViz.addln("start"+"->"+itStart.next()," ");
gViz.end_graph();
try {
gViz.run();
} catch (Exception e) {
e.printStackTrace();
}
}
}
class PartComparator implements Comparator<Part>{
public int compare(Part p1, Part p2){
return p1.s - p2.s;
}
}
//TurnMinDFA.java
import com.sun.source.tree.Tree;
import javax.swing.*;
import java.util.*;
public class TurnMinDFA {
public static ArrayList<Part> edge;
public static ArrayList<Part> minEdge;
public static ArrayList<Character> opList;
public static TreeSet<Integer> end;
public static int setNum;
public static int startPoint;
public static TreeSet<Integer> endPoint;
public TurnMinDFA(StringBuilder s){
TurnDFA t = new TurnDFA(s);
setNum = TurnDFA.setNum;
opList = new ArrayList<>(26);//一共26个英文字母
edge = new ArrayList<>(TurnDFA.edgeDFA);
minEdge = new ArrayList<>();
end = new TreeSet<>(TurnDFA.end);
opList.addAll(TurnDFA.opSet);
for (Character character : opList) {
System.out.println(character);
}
Iterator<Integer> itEnd;
System.out.println("setNum:"+setNum);
ArrayList<Integer>[] State = new ArrayList[setNum+1];
int[][] state = new int[setNum+1][];
for(int i = 1; i <= setNum; i++){
state[i] = new int[opList.size()];
Arrays.fill(state[i],0);//0代表epsilon
}
int[] newGroupId = new int[setNum+1];
int[] groupId = new int[setNum+1];
Arrays.fill(newGroupId,0);
for(int i = 1; i <= setNum; i++){//初始化
if(end.contains(i)){
groupId[i]=2;
}
else
groupId[i]=1;
}
while(true){
for (Part part : edge) {
state[part.s][part.op - 'a'] = groupId[part.e];
}
int Num = 1;
for(int i = 1; i <= setNum; i++){
int flag = 0;
for(int j = 1; j < i; j++){
if(cmp(state[j],state[i]) && groupId[i] == groupId[j]){
flag = 1;
newGroupId[i]=newGroupId[j];
break;
}
}
if(flag == 0) {
newGroupId[i] = Num;
Num++;
}
}
if(cmp(groupId,newGroupId))
break;
else{
groupId = Arrays.copyOf(newGroupId,newGroupId.length);
}
}
startPoint = groupId[1];
endPoint = new TreeSet<>();
itEnd = end.iterator();
while(itEnd.hasNext()){
endPoint.add(groupId[(int)itEnd.next()]);
}
System.out.println(startPoint+" "+endPoint);
//根据区号求存储miniDFA的边
for(int i = 1; i <= setNum; i++){
for(int j = 0; j < opList.size(); j++){
if(state[i][j]!=0) {
int flag = 0;
for (Part part : minEdge) {
if (groupId[i] == part.s && state[i][j] == part.e && (char) ('a' + j) == part.op) {
flag = 1;
break;
}
}
if(flag == 0)
minEdge.add(new Part(groupId[i], state[i][j], (char) ('a' + j)));
}
}
}
for (Part part : minEdge) {
System.out.println(part.s + " " + part.e + " " + part.op);
}
drawMinDFA(startPoint,endPoint);
}
public void drawMinDFA(int startPoint, TreeSet<Integer> endPoint){
GraphViz gViz=new GraphViz(".\\src\\HomeWork1\\GraphMinDFA", "D:\\Grapgviz\\Graphviz\\bin\\dot.exe");
gViz.start_graph();
gViz.add("rankdir = LR");
gViz.add("node [shape = doublecircle];");
for(int i: endPoint){
gViz.add(""+i);
}
gViz.add(";");
gViz.add("node [shape = circle];");
for(int i = 0; i < minEdge.size();i++)
{
System.out.println("Running"+i);
gViz.addln(minEdge.get(i).s+"->"+minEdge.get(i).e,""+minEdge.get(i).op);
}
gViz.addln("start"+"->"+startPoint," ");
gViz.end_graph();
try {
gViz.run();
} catch (Exception e) {
e.printStackTrace();
}
}
public boolean cmp(int[] a, int[] b){
int flag = 0;
if(a.length == b.length){
for(int i = 0; i < a.length; i++){
if(a[i]!=b[i]){
flag = 1;
break;
}
}
return flag == 0;
}
else{
return false;
}
}
public static void main(String[] args) {
String s0 = JOptionPane.showInputDialog("请输入表达式:");
StringBuilder s1 = new StringBuilder(s0);
ReversePolish r = new ReversePolish(s1);
//StringBuilder s = new StringBuilder("a*ab·b·*·b·ba|*·");
TurnMinDFA t = new TurnMinDFA(r.s);
}
}