精华内容
下载资源
问答
  • lex

    2018-04-06 16:27:42
    lex(flex)&yacc(bison) 转载 2016年05月28日 20:41:54 标签:
    • 文法分析用Flex(Lex):将数据分隔成一个个的标记token (标示符identifiers,关键字keywords,数字numbers, 中括号brackets, 大括号braces, 等等etc.)
    • 语法分析用Bison(Yacc): 在分析标记的时候生成抽象语法树. Bison 将会做掉几乎所有的这些工作, 我们定义好我们的抽象语法树就OK了.
    • 组装用LLVM: 这里我们将遍历我们的抽象语法树,并未每一个节点生成字节/机器码。 这听起来似乎很疯狂,但是这几乎就是最简单的 一步了.
    • sudo apt-get install flex bison
    • flex -h && bison -h 进行测试
    • cd ~/shellscript/bin
    • gedit lexya
    • 将执行后面每一个例子时一连串的命令放进去
    • chmod u+x lexya 就像下面这样
    • ~/shellscript/bin/lexya 针对草木鱼(四五六七)和汇编版本的一个脚本

    flex&yacc 相关知识

    flex&yacc IBM1
    yacc 基本用法-矮油

    flex

    所使用的语法是普通而古老的正则表达式语法。有一些扩展。其中一个扩展是,您可以为通用的模式命名。
    您可以在 lex 程序的第一部分中第一个 %% 之前为其中 的一部分定义名称:

    • DIGIT [0-9]
    • ALPHA [a-zA-Z]
    • ALNUM [0-9a-zA-Z]
    • IDENT [0-9a-zA-Z_]
      然后,您可以在规则部分将其名称放置在大括号内来反向引用它们:
    • ({ALPHA}|_){IDENT}* { return IDENTIFIER; }

    yacc

    有关 union 标志(token)绑定到YYSTYPE的某个域 nonassoc

    yacc 基本用法-矮油

    union 标志(token)绑定到YYSTYPE的某个域

    对于操作符,可以定义%left和%right:%left表示左相关(left-associative),%right表示右相关(right-associative)。可以定义多组%left或%right,在后面定义的组有更高的优先级。如:

    • %left ‘+’ ‘-‘
    • %left ‘*’ ‘/’
      上面定义的乘法和除法比加法和减法有更高的优先级。

    改变YYSTYPE的类型。如这样定义TTSTYPE:

    %union
    {
         int iValue; /* integer value */
         char sIndex; /* symbol table index */
         nodeType *nPtr; /* node pointer */
    };
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    则生成的头文件中的内容是:

    typedef union
    {
         int iValue;      /* integer value */
         char sIndex;    /* symbol table index */
         nodeType *nPtr; /* node pointer */
    } YYSTYPE;
    extern YYSTYPE yylval;
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    可以把标志(token)绑定到YYSTYPE的某个域。如:

    %token <iValue> INTEGER
    %type <nPtr> expr
    • 1
    • 2

    把expr绑定到nPtr,把INTEGER绑定到iValue。yacc处理时会做转换。如:

    • expr: INTEGER { $$ = con($1); }
      转换结果为:
    • yylval.nPtr = con(yyvsp[0].iValue);
      其中yyvsp[0]是值栈(value stack)当前的头部。

    Bison中默认将所有的语义值都定义为int类型,可以通过定义宏YYSTYPE来改变值的类型。如果有多个值类型,则需要通过在Bison声明中使用%union列举出所有的类型
    ,然后为每个符号定义相对的类型,终结符使用%token,非终结符使用%type来定义。

    nonassoc

    定义一元减号符有更高的优先级的方法:

    %left GE LE EQ NE '>' '<'
    %left '+' '-'
    %left '*'
    %nonassoc UMINUS
    • 1
    • 2
    • 3
    • 4

    %nonassoc的含义是没有结合性。它一般与%prec结合使用表示该操作有同样的优先级。如:
    expr: ‘-’ expr %prec UMINUS { $$ = node(UMINUS, 1, $2); }
    表示该操作的优先级与UMINUS相同,在上面的定义中,UMINUS的优先级高于其他操作符,所以该操作的优先级也高于其他操作符计算。

    .c和 .h 的关系

    点击 .c与.h联系区别
    因为 #include “xx.h” 这个宏其实际意思就是把当前这一行删掉,把 xx.h 中的内容原封不动的插入在当前行的位置。
    由于想写这些函数声明的地方非常多(每一个调用 xx.c 中函数的地方,都要在使用前声明一下子),所以用 #include “xx.h”
    这个宏就简化了许多行代码——让预处理器自己替换好了。也就是说,xx.h 其实只是让需要写 xx.c 中函数声明的地方调用(可以少写几行字),
    至于 include 这个 .h 文件是谁,是 .h 还是 .c,还是与这个 .h 同名的 .c,都没有任何必然关系。
    编译器不会自动把 .h 文件里面的东西跟同名的.c文件绑定在一起
    在编译阶段只是生成各自的.o文件.这个阶段不和其它的文件发生任何的关系. 而include这个预处理指令发生在预处理阶段(早先编译阶段,只是编译器的一个前驱处理程序).

    安装

    sudo apt-get install flex bison LLVM

    yacc & flex

    yacc,flex快速入门
    点击  一系列 yacc, flex

    flex 的使用

    点击 Lex和Yacc应用方法(一).初识Lex
    exfirst.l 存 flex 型的文件

    %{
    #include "stdio.h"
    %}
    %%
    [\n]                  ;
    [0-9]+                printf("Int     : %s\n",yytext);
    [0-9]*\.[0-9]+        printf("Float   : %s\n",yytext);
    [a-zA-Z][a-zA-Z0-9]*  printf("Var     : %s\n",yytext);
    [\+\-\*\/\%]          printf("Op      : %s\n",yytext);
    .                     printf("Unknown : %c\n",yytext[0]);
    %%
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在命令行下执行命令flex解析,会自动生成lex.yy.c文件:

    • flex exfirst.l
    • flex -o a.c exfirst.l
      进行编译生成parser可执行程序:
    • cc -o parser lex.yy.c -ll
    • cc lex.yy.c -o parser -ll
    • $ gcc -o a a.c -ll
      [注意:如果不加-ll链结选项,cc编译时会出现错误。当编译时不带-ll选项时,是必须加入main函数和yywrap]

    file.txt 存自己的定义的语言程序

    title
    i=1+3.9;
    a3=909/6
    bcd=4%9-333
    
    • 1
    • 2
    • 3
    • 4
    • 5

    用 a 执行 文件

    • $ ./a < file.txt

    小结
    1.定义Lex描述文件
    2.通过lex,flex工具解析成lex.yy.c文件(a.c)
    3.使用cc编译lex.yy.c(a.c)生成可执行程序a

    yacc 的使用

    点击 Lex和Yacc应用方法(二).再识

    “移进-归约”冲突(shift-reduce conflict) ,在yacc中产生这种冲突时,会继续移进。
    “归约-归约”冲突
    YYSTYPE 定义了用来将值从 lexer 拷贝到解析器或者 Yacc 的 yylval (另一个 Yacc 变量)的类型。 默认的类型是 int。

    简单计算器 

    • lexya_a.l
    %{       //C 和 Lex 的全局声明
    #include <stdlib.h>
    void yyerror(char *);
    #include "lexya_a.tab.h"
    %}
    %%       //标记声明, 包括模式(C 代码)
    [0-9]+       { yylval = atoi(yytext); return INTEGER; }
    [-+*/\n]     return *yytext;
    /*向yacc的分析栈返回运算符标记,系统保留的0-255此时便有了作用,内容栈为空。*/
    [/t]         ;/* 去除空格 */
    .            yyerror("无效字符");
    %%       //补充的C函数
    int yywrap(void) {
      return 1;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • lexya_a.y

    %{  //定义段,引用,C 与 Yacc 的声明

    #include <stdlib.h>

    include <stdio.h>

    int yylex(void);  //yylex的返回值类型是整型,可以用于返回标记。
    void yyerror(char *);
    %} //预定义,
    %token INTEGER   //终结符(中的一种-命名标记: 这些由 %token 标识符来定义)
    %left ‘+’ ‘-‘
    %left ‘*’ ‘/’ //最后列出的定义拥有最高的优先权
    %%
    program:
    program expr ‘\n’ { printf(“%d\n”, $2); }
    |
    ; /* 先理解expr,再递归理解program。首先program可以为空,也可以用单单的expr加下“/n”回车符组成,结合起来看program定义的就是多个表达式组成的文件内容 */
    expr:
    INTEGER {

    </span> = <span class="hljs-variable">$1</span>; }
    | expr <span class="hljs-string">'*'</span> expr { <span class="hljs-variable">
    = $1; } | expr '*' expr { </span> = <span class="hljs-variable">1</span><spanclass="hljsvariable"> * 3</span>; } | expr <span class="hljs-string">'/'</span> expr { <span class="hljs-variable">
    </span> = <span class="hljs-variable">$1</span> / <span class="hljs-variable">$3</span>; }
    | expr <span class="hljs-string">'+'</span> expr { <span class="hljs-variable">
    = $1 / $3; } | expr '+' expr { </span> = <span class="hljs-variable">1</span>+<spanclass="hljsvariable"> + 3</span>; } | expr <span class="hljs-string">'-'</span> expr { <span class="hljs-variable">$$ = 1</span><spanclass="hljsvariable"> - 3; }
    ; /* 可以由单个INTEGER值组成,也可以有多个INTERGER和运算符组合组成。以表达式“1+4/2*3-0”为例,1 4 2 3 都是expr,就是expr+expr/expr*expr-expr说到底最后还是个expr。递归思想正好与之相反,逆推下去会发现expr这个规则标记能表示所有的数值运算表达式 */
    %%
    void yyerror(char *s) {
    printf(“%s\n”, s);
    }
    int main(void) {
    yyparse();
    return 0;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    • bison -d lexya_a.y 参数 -d 将头文件和.c文件分离
    • lex lexya_a.l
    • cc -o parser *.c
    • ./parser 输入计算式,回车会显示运行结果
    • bison -d lexya_a.y 编译后会产生 lexya_a.tab.c lexya_a.tab.h
    • lex文件lexya_a.l中头声明已包括了 lexya_a.tab.h。这两个文件是典型的互相协作的示例
    • 详细分析见 草木瓜系列的第二篇

    计算器升级版 ()运算符 变量保存 lex && yacc

    包含+,-,*,/四项操作,且支持()运算符,其中对值可以进行变量保存,并打印出内部的分析信息。

    • lexya_b.l
    %{
    #include <stdlib.h>
    #include "lexya_b.tab.h"
    void yyerror(char *);
    void add_buff(char *);
    extern char sBuff[10][20];
    extern int iX;
    extern int iY;
    %}
    %%
    [a-z]         { yylval = *yytext; add_buff(yytext); return VAR; }
    [0-9]+        { yylval = atoi(yytext); add_buff(yytext); return INT; }
    [-+()=*/]     { yylval = *yytext; add_buff(yytext); return *yytext; }
    [\n]          { yylval = *yytext; iY=0;iX++; return *yytext; }
    [\t]          ;
    .             yyerror("无效字符");
    %%
    void add_buff(char * buff) {
        sBuff[iX][iY]=*buff; iY++;
    }
    int yywrap(void) {
        return 1;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • lexya_b.y
    %{
    #include <stdlib.h>
    int yylex(void);
    void yyerror(char *);
    void debuginfo(char *, int *, char *);      //打印堆栈信息
    void printinfo();   //打印目前的分析语句
    int sMem[256];       //来存放变量
    char sBuff[10][20]={0};     //存放分析过的语句
    int iX=0;
    int iY=0;
    %}
    %token INT VAR
    %left '+' '-'
    %left '*' '/'
    %%
    program:
    program statement
    |
    ;
    statement:
    expr { printf("%d\n",$1); }
    | VAR '=' expr { debuginfo("=",yyvsp,"110"); sMem[$<span class="hljs-number">1</span>]=$3; }
    | statement '\n' { printf("--------------------------------\n\n"); }
    ;
    expr:
    INT { debuginfo("INT",yyvsp,"0"); $$ = $<span class="hljs-number">1</span>; }
    | VAR { debuginfo(<span class="hljs-string">"VAR"</span>,yyvsp,<span class="hljs-string">"1"</span>); $$ = sMem[$<span class="hljs-number">1</span>]; }
    | expr <span class="hljs-string">'*'</span> expr { debuginfo(<span class="hljs-string">"*"</span>,yyvsp,<span class="hljs-string">"010"</span>); $$ = $<span class="hljs-number">1</span> * $<span class="hljs-number">3</span>; }
    | expr <span class="hljs-string">'/'</span> expr { debuginfo(<span class="hljs-string">"/"</span>,yyvsp,<span class="hljs-string">"010"</span>); $$ = $<span class="hljs-number">1</span> / $<span class="hljs-number">3</span>; }
    | expr <span class="hljs-string">'+'</span> expr { debuginfo(<span class="hljs-string">"+"</span>,yyvsp,<span class="hljs-string">"010"</span>); $$ = $<span class="hljs-number">1</span> + $<span class="hljs-number">3</span>; }
    | expr <span class="hljs-string">'-'</span> expr { debuginfo(<span class="hljs-string">"-"</span>,yyvsp,<span class="hljs-string">"010"</span>); $$ = $<span class="hljs-number">1</span> - $<span class="hljs-number">3</span>; }
    | <span class="hljs-string">'('</span> expr <span class="hljs-string">')'</span>   { debuginfo(<span class="hljs-string">"()"</span>,yyvsp,<span class="hljs-string">"101"</span>); $$ = $2; }
    ;
    %%
    void debuginfo(char * info,int * vsp, char * mark) {
        printf("--RULE: %s \n", info);
        int i=0;
        int ilen=strlen(mark);
        for(i=0;i>=1-ilen;i--) {
        if(mark[ilen+i-1]=='1')
            printf("$%d %d %c \n", i+ilen, vsp[i], vsp[i]);
        else
            printf("$%d %d \n", i+ilen, vsp[i]);
        }
        printinfo();
    }
    void printinfo() {
        int i=0;
        printf("--STATEMENT: \n");
    
        if(iY==0)
            printf("%s \n",sBuff[iX-1]);
        else
            printf("%s \n",sBuff[iX]);
        printf("\n");
    }
    void yyerror(char *s) {
        printf("%s\n", s);
    }
    int main(void) {
        yyparse();
        return 0;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • input
    a=4+2*(3-2-1)+6
    b=1-10/(6+4)+8
    c=a-b
    a
    b
    c
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 这里只是做了一些扩充变化:
      1.增加了全局数组sMem来存放变量,不过变量名有限制,只支持单字符。
      2.增加了全局数组sBuff存放分析过的语句
      3.增加debuginfo打印堆栈信息
      4.增加printinfo打印目前的分析语句
    • 要进行内部分析,就需要剖析生成的c文件,对程序(parser)进行跟踪调试。
    • (注:Lex编译时加上d参数,会在程序解释时输出详细的调试信息。如:lex -d lexya_$1.l)
    • 通过本示例再加上实际对lexya_b.tab.c的分析理解,会对lex,yacc理论有更进一步的理解。

    增加支持的变量字符数

    • userdef.h
    typedef struct {
        int iValue;  //存数值
        char sMark[10];  //存代表的字符,如aa ,bb
    } varIndex;
    varIndex strMem[256];  //字符数组
    • 1
    • 2
    • 3
    • 4
    • 5
    • lexya_c.l
    %{
    #include <stdlib.h>
    #include "userdef.h"
    #include "lexya_c.tab.h"
    
    void yyerror(char *);
    void add_buff(char *);  //加入 lex 存放分析过的源代码 
    void add_var(char *);
    
    extern char sBuff[10][20];   //lex 存放分析过的源代码
    extern int iBuffX;  //lex 目前分析到的行数
    extern int iBuffY;  //lex 目前分析到的某行第几个
    
    extern varIndex strMem[256];
    extern int iMaxIndex;  //当前所有VAR的个数
    extern int iCurIndex;   //目前字符指针(值)
    %}
    %%
    [a-zA-Z][a-zA-Z0-9]*         { add_var(yytext); yylval = iCurIndex; add_buff(yytext);   return VAR; }
    [0-9]+        { yylval = atoi(yytext); add_buff(yytext); return INT;   }
    [-+()=*/]     { yylval = *yytext; add_buff(yytext); return *yytext; }
    [\n]          { yylval = *yytext; iBuffY=0;iBuffX++; return *yytext; }
    [\t]          ;
    .             yyerror("无效字符");
    %%
    void add_buff(char * buff) {
        strcat(sBuff[iBuffX],buff);
        iBuffY+=strlen(buff);
    }
    void add_var(char *mark) {
        if(iMaxIndex==0){
            strcpy(strMem[0].sMark,mark);
            iMaxIndex++;
            iCurIndex=0;
            return;
        }
        int i;
        for(i=0;i<=iMaxIndex-1;i++) {
            if(strcmp(strMem[i].sMark,mark)==0) {
                iCurIndex=i;
                return;
            }
        }
        strcpy(strMem[iMaxIndex].sMark,mark);
        iCurIndex=iMaxIndex;
        iMaxIndex++;
    }
    int yywrap(void) {
        return 1;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • lexya_c.y
    %{
    #include <stdlib.h>
    #include "userdef.h"
    int yylex(void);
    void yyerror(char *);
    void debug_info(char *, int *, char *);
    void stm_info();
    
    extern varIndex strMem[256];
    
    int iMaxIndex=0;
    int iCurIndex=0;
    
    char sBuff[10][20]={0};
    int iBuffX=0;
    int iBuffY=0;
    
    %}
    %token INT VAR
    %left '+' '-'
    %left '*' '/'
    %%
    program:
    program statement
    |
    ;
    statement:
    expr { printf("%d\n",$1); }
    | VAR '=' expr { debug_info("=",yyvsp,"210"); strMem[$<span class="hljs-number">1</span>].iValue=$3;   }
    | statement '\n' { printf("--------------------------------\n\n"); }
    ;
    expr:
    INT { debug_info("INT",yyvsp,"0"); $$ = $<span class="hljs-number">1</span>;   }
    | VAR { debug_info(<span class="hljs-string">"VAR"</span>,yyvsp,<span class="hljs-string">"2"</span>); $$ =   strMem[$<span class="hljs-number">1</span>].iValue; }
    | expr <span class="hljs-string">'*'</span> expr { debug_info(<span class="hljs-string">"*"</span>,yyvsp,<span class="hljs-string">"010"</span>); $$ = $<span class="hljs-number">1</span> * $<span class="hljs-number">3</span>; }
    | expr <span class="hljs-string">'/'</span> expr { debug_info(<span class="hljs-string">"/"</span>,yyvsp,<span class="hljs-string">"010"</span>); $$ = $<span class="hljs-number">1</span> / $<span class="hljs-number">3</span>; }
    | expr <span class="hljs-string">'+'</span> expr { debug_info(<span class="hljs-string">"+"</span>,yyvsp,<span class="hljs-string">"010"</span>); $$ = $<span class="hljs-number">1</span> + $<span class="hljs-number">3</span>; }
    | expr <span class="hljs-string">'-'</span> expr { debug_info(<span class="hljs-string">"-"</span>,yyvsp,<span class="hljs-string">"010"</span>); $$ = $<span class="hljs-number">1</span> - $<span class="hljs-number">3</span>; }
    | <span class="hljs-string">'('</span> expr <span class="hljs-string">')'</span>   { debug_info(<span class="hljs-string">"()"</span>,yyvsp,<span class="hljs-string">"101"</span>); $$ = $2;      }
    ;
    %%
    void debug_info(char * info,int * vsp, char * mark) {
        printf("--RULE: %s \n", info);
        int i=0;
        int ilen=strlen(mark);
        for(i=0;i>=1-ilen;i--) {
            switch(mark[ilen+i-1]){
                case '1':
                    printf("$%d %d %c \n", i+ilen, vsp[i], vsp[i]);
                    break;
                case '0':
                    printf("$%d %d \n", i+ilen, vsp[i]);
                    break;
                case '2':
                    printf("$%d %s %d\n", i+ilen, strMem[vsp[i]].sMark, strMem[vsp[i]].iValue);
                    break;
            }
        }
        stm_info();
    }
    void stm_info() {
        int i=0;
        printf("--STATEMENT: \n");
        if(iBuffY==0)
            printf("%s \n",sBuff[iBuffX-1]);
        else
            printf("%s \n",sBuff[iBuffX]);
        printf("\n");
    }
    void yyerror(char *s) {
        printf("%s\n", s);
    }
    int main(void) {
        yyparse();
        return 0;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • input
    aa=4+2*(3-2-1)+6
    bb=1-10/(6+4)+8
    cd=aa-bb
    aa
    bb
    cd
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • bison -d lexya_c.y
    • lex lexya_c.l
    • gcc -g -o parser lex.yy.c lexya_c.tab.c 参数-g是调试选项
    • ./parser < input

    yyvsp 从内容栈中弹出的东西
    yylval 对应内容栈
    VAR INT + 之类的对应分析栈
    内容栈 分析栈 同进(lex到yacc) 同出(规约)

    展开全文
  • Lex

    2011-04-28 21:20:00
    流程是这样的: lex lex_source.l //生成lex.yy.c c语言文件 cc -c lex.yy.c //生成lex.yy.o 中间语言文件 cc lex.yy.o -o -ll main //生成main 机器语言文件即可执行文件 这里-ll不能省略-l表示外部链接l表示...

    /

    目录

    一.Lex基础

    二.Lex注意事项

     

     

     

     

    ///

     

    一.Lex基础

     

    1.Lex可以用来分析文件。 lex在分析文件时遵循两个规则 1)同一个词只匹配一次 2)同时满足多条规则时取满足长度最长的那个规则 

     

    2.编译流程是这样的: 

       lex lex_source.l   //生成lex.yy.c c语言文件

       cc -c lex.yy.c       //生成lex.yy.o 中间语言文件

       cc lex.yy.o -o -ll main //生成main 机器语言文件即可执行文件 这里-ll不能省略-l表示外部链接l表示库名为libl.a(或libl.so)

      

       是这样使用的:

        main < file 

     

    3.lex_file_name.l文件一般是这样的:

    //1.定义部分

    %{

    #include<stdio.o>

    int global_var;

     

    %}

    //2.规则部分

    %%

     

    [0-9] { //do sth like print;

                //do sth else;

              }

     

    [a-z] {

     

              }

     

    %%

    //3.自定义函数部分

    int main()

    {

        yytex();

        

        //do sth

     

       return 1;

    }

     

    4.一个具体的例子main.l是这样的

     

     

    5.一个配套的Makefile文件可以是这样的

     

     

      

     

     

     

     

    二.Lex注意事项

     

    1.虽然有很多规则你都定义好了,但不要忘了 . 规则的处理,虽然我们通常什么都不做 ; ,但是不做也是处理的方式之一啊!

     

    2.file.l文件里也就是lex文件里main函数可以没有,lex编译器会帮你生成(带有yylex()的main函数),麻烦就是你不能做一些总结性陈词,但你在自定义过程定义的过程还是可以在第二部分规则后面的行为部分调用,所以问题不大。 但是如果你定义了main函数,就一定要调用yylex(),这是本质啊本质,然后你再加上你的总结性陈词。

     

    3.yylex()结束时调用yywrap(),若yywrap()返回1,则yylex()结束;若yywrap()返回0,则yylex()继续处理下一个文件,所以yylex()可能的样子是:

     

     4. regular expressions中 . 是不包括/n以外的所有字符的代表

     

     

     

     

     

     

    展开全文
  • LEX应用举例 LEX

    2010-03-24 19:36:53
    LEX应用举例 LEX应用举例 LEX应用举例 LEX应用举例
  • lex issue

    2020-12-08 19:20:34
    lex fileio.l "fileio.l":line 54: Error: Too many transitions Try using %a num make[2]: *** [lex.yy.c] Error 1 </code></pre> <p>i'm using posix compatible lex ...
  • AWS Lex

    2021-01-12 14:17:08
    t find any issues or code relating to AWS Lex: http://docs.aws.amazon.com/lex/latest/dg/API_Reference.html. It'd be great to be able to define entire bots as code instead of configuration through ...
  • LEX 教程

    2019-09-28 16:51:53
    LEX简介 LEX是LEXical compiler 的缩写,是UNIX环境下非常著名的工具软件,其主要功能是根据LEX源程序生成一个用C语言描述的词法分析程序(scanner)。 LEX源程序是词法分析程序的规格说明文件,文件名约定为lex.l,...

    LEX简介

    LEX是LEXical compiler 的缩写,是UNIX环境下非常著名的工具软件,其主要功能是根据LEX源程序生成一个用C语言描述的词法分析程序(scanner)。

    LEX源程序是词法分析程序的规格说明文件,文件名约定为lex.l,经过LEX编译程序的编译,生成一个C语言程序lex.yy.c。若用C语言作为编译程序的实现语言,则lex.yy.c可以和其他源文件一起编译,生成编译程序的目标程序。若用C语言编译程序对lex.yy.c进行单独编译,可生成目标文件lex.yy.o,或直接生成可执行程序a.out。目标文件lex.yy.o作为编译程序目标代码的组成部分,可以和其他高级语言或汇编语言产生的目标代码连接。词法分析程序a.out运行时,可以将输入的字符串转换成相应的记号序列。

    下图展示了LEX生成词法分析程序的流程:

    LEX生成词法分析程序的流程

    LEX源程序的结构

    LEX程序的组成部分:

    1. 声明
      声明部分包括变量的声明,符号常量的声明和正则表达式定义。
      正规定义中定义的名字可以出现在翻译规则的正规表达式中。
      希望出现在lex,yy.c中的C语言声明语句用符号"%{"和“%}”括起来,如
    %{
    	#include<stdio.h>
    	#include<stdlib.h>
    	#include<string.h>
    	#include<ctype.h>
    	#include"y.tab.h"
    	typedef char * YYSTYPE
    	char * yylval
    %}
    

    下面是关于名字delim,ws,letter, digit以及标识符 id的正规定义,这些名字都可以在翻译规则部分的正规表达式中出现。

    dilim [\t\n]
    ws {dilim}+
    letter [A-Za-z]
    digit [0-9]
    id {letter}({letter}|{digit})*
    
    1. 翻译规则
      翻译规则部分是由正规表达式和相应的动作组成的具有如下形式的语句序列:
    p1		{action 1}
    p2		{action 2}
    p3		{action 3}
    p4		{action 4}
    

    其中pi是正规表达式,描述一种记号的模式;动作i是用C语言描述的程序段,表示当一个符号匹配模式pi时,词法分析程序应该做得动作

    关于LEX源程序中的正规表达式的使用,参考python中的re模块不难理解

    这里应该注意LEX解决冲突的两点策略:

    • 根据规则定义的先后顺序进行匹配
      解决了例子中关键字和标识符的冲突
    • 最长匹配原则
      解决了例子中诸如"<"和“<=”的冲突

    也就是说,词法分析程序依此尝试每一条规则,尽可能地匹配最长的输入符号串,并且排在前面的规则的优先级高于排在后面的规则的优先级。如果有一些内容不匹配任何规则,LEX将其拷贝到标准输出。

    1. 辅助过程

    对翻译规则的补充。翻译规则部分中某些动作需要调用的 过程或函数,如果不是C语言的库函数,则要在此给出具体的定义。这些过程或函数也可以在另一个程序文件中定义,然后和词法分析程序链接在一起即可。

    各个部分由“%%”隔开

    展开全文
  • lex 命令

    2019-07-28 08:06:23
    用途 生成一个与输入流的简单语法...lex 命令读取 File 或标准输入,生成 C 语言程序并将它写到一个名为 lex.yy.c 的文件中。这个文件,lex.yy.c ,是一个兼容的 C 语言的程序。一个 C++ 编译器也能够编译 lex ...

    用途

    生成一个与输入流的简单语法分析相匹配的 C 或 C++ 语言程序。

    语法

    lex [ -C ] [ -t ] [ -v| -n ] [ File... ]

    描述

    lex 命令读取 File 或标准输入,生成 C 语言程序并将它写到一个名为 lex.yy.c 的文件中。这个文件,lex.yy.c ,是一个兼容的 C 语言的程序。一个 C++ 编译器也能够编译 lex 命令的输出。-C 标志将输出文件重命名为 lex.yy.C 供 C++ 编译器使用。

    lex 命令生成的 C++ 程序可使用 STDIO 或 IOSTREAMS。如果在 C++ 编译中,cpp 定义 _CPP_IOSTREAMS 是真,程序为所有 I/O 使用 IOSTREAMS。否则,使用 STDIO。

    lex 命令使用包含在 File 中的规则和操作来生成一个程序,lex.yy.c,这个程序可用 cc 命令编译。这个编译过的 lex.yy.c 然后能接受输入,将输入分成为由在 File 文件中的规则定义的逻辑片,并运行包含在 File 文件中的操作的程序片断。

    这个生成的程序是一个称为 yylex 的 C 语言函数。lex 命令将 yylex 函数存储在一个名为 lex.yy.c 的文件中。可单独用 yylex 函数来识别简单的一个单词的输入,或能用它和其他 C 语言程序一起来执行更困难的输入分析函数。例如,您能用 lex 命令来生成一个程序。这个程序能在将输入流发送到一个由 yacc 命令生成的解析器程序之前简化输入流。

    yylex 函数用称为有限自动机的程序结构来分析输入流。这个结构在一个时间允许程序仅在一个状态(或条件)下退出。允许有有限个数目的状态。在 File 中的规则确定程序怎样从一个状态移动到另一个状态。

    如果不指定一个 Filelex 命令读取标准输入。它将多个文件作为一个单个的文件对待。

    注: 由于 lex 命令为中间和输出文件使用固定的名称,您可仅有一个由 lex 在给定目录中生成的程序。

    lex 规范文件

    输入文件文件包含三部分:定义规则用户子例程。每部分必须用仅含定界符 %%(双百分号)的行和其他部分分开。格式是:

    下面描述了各自的用途和格式。

    定义

    如果想在您的规则中应用变量,必须在这个部分定义它们。变量组成左边的列,它们的定义组成右边的列。例如,如果想定义 D 作为数字,应该这样写:

    D   [0-9]

    您可用一个在 {} (大括号)内围住变量名的规则部分定义的变量。

    {D}

    在以空格开头或由 %{, %} 定界符行中括住的定义部分中的行被复制到 lex.yy.c 文件。能用这个构造声明 C 语言变量用在 lex 操作或包含头文件,例如:

    %{
    #include <math.h>
    int count;
    %}

    这些行也可出现在规则部分的开头部分,仅在第一个 %% 定界符之后,但它们不应当用在规则部分的其他地方。如果这行在 File 的定义部分,lex 命令将它复制到 lex.yy.c 文件的外部声明部分。如果这行出现在规则部分,在第一个规则前,lex 命令将它复制到 lex.yy.c 文件的 yylex 子例程的本地声明部分。那些行不能在第一个规则后出现。

    lex 外部的类型,yytext,能通过在定义部分指定以下之一来设置为以空结束的字符数组(缺省)或者是以空结束字符串的指针:

    %array    (缺省) %pointer

    在定义部分,可为生成的有限状态机设置表的大小。缺省大小对小程序足够大。可能想为更复杂的程序设置更大的大小。

    %an 转变数是 n(缺省 5000)
    %en 语法分析树节点数是 n(缺省 2000)
    %hn 多字节字符输出槽数(缺省 0)
    %kn 压缩字符类数(缺省 1000)
    %mn 多字节字符类输出槽数(缺省 0)
    %nn 状态数是 n(缺省 2500)
    %on 输出槽数(缺省 5000,最小 257)
    %pn 位置数是 n(缺省 5000)
    %vp 在由 %h%m 控制的散列表中的空槽百分比(缺省 20,范围 0 <= P < 100)
    %zn 多字节字符类输出槽数(缺省 0)

    如果多字节字符出现在扩展的正则表达式字符串中,可能需要用 %o 参数复位输出数组大小(可能的数组大小在 10,000 到 20,000 的范围内)。这个复位反映相对于单字节字符数大得多的字符数。

    如果多字节字符出现在一个扩展的正则表达式中,必须用 %h%m 参数设置多字节散列表大小为一个比包含在 lex 文件中的多字节字符总数更大的大小。

    如果没有多字节字符出现在扩展的规则表达式中,但是您想 '.' 来匹配多字节字符,必须设置 %z 大于零。类似的,对逆字符类(例如,[^abc])来匹配多字节字符,必须设置 %h%m 大于零。

    当用多字节字符时,lex.yy.c 文件必须用 -qmbcs 编译选项来编译。

    规则

    一旦定义了条件,就可写规则部分。它包含由 yylex 子例程来匹配的字符串和表达式,和当匹配时要执行的 C 命令。需要这一部分,这一部分必须由定界符 %%(双百分号)开头,不论是否有一个定义部分。lex 命令不识别没有定界符的规则。

    在这个部分,左边列包含扩展正则表达式形式的模式。这些表达式可由在到 yylex 子例程的输入文件中被识别。右边的列包含一个当这个模式被识别时执行的 C 程序段,称为一个操作

    当词法分析程序发现一个扩展的正则表达式的匹配,词法分析程序执行与那个扩展正则表达式相关联的操作。

    模式可包含扩展的字符。如果多字节语言环境在系统中安装,模式也可包含属于安装代码集一部分的多字节字符。

    列由跳格或空格分开。例如,如果想搜索关键字为 KEY 的文件,可输入如下内容:

    (KEY) printf ("found KEY");

    如果在 File 文件中包含这个规则,yylex 词法分析程序匹配模式 KEY 并运行 printf 子例程。

    每个模式可有一个对应操作,既,当一个模式匹配时,一个 C 命令来执行。每个语句必须以 ;(分号)结束。如果在一个操作中用多于一条的语句,必须将它们包含在 { } (大括号)中。如果有个用户子例程部分,第二个定界符 %%,必须跟着这个规则部分。如果没有一个指定操作的模式匹配,词法分析程序将在不更改输入模式的情况下将之复制到输出。

    yylex 词法分析程序在匹配一个输入流中的一个字符串时,在它执行规则部分的任何命令前,它会将这个匹配的字符串复制到一个外部字符数组(或指向字符串的指针),yytext。类似的,外部的 int,yyleng,被设置为以字节表示的匹配字符串的长度(因此,多字节字符的大小大于 1)。

    如想获得如何形成扩展正则表达式的信息,请参阅在 《AIX V6.1 通用编程概念:编写并调试程序》 中的『lex 命令中的扩展正则表达式』

    用户子例程

    lex 库定义下列子例程作为能在 lex 规范文件的规则部分用的宏。

    input yyin 读取字节。
    unput 在读取后替换一个字节。
    output 写一个输出字节到 yyout
    winput yyin 读取多字节字符。
    wunput 在读取后替换一个多字节字符。
    woutput 写一个多字节输出字符到 yyout
    yysetlocale 调用 setlocale (LC_ALL " " ); 子例程来确定当前语言环境。

    winputwunputwoutput 宏被定义来使用在 lex.yy.c 文件中编码的 yywinputyywunputyywoutput 子例程。为了兼容性,那些 yy 子例程顺序地使用 inputunputoutput 子例程用完全多字节字符来读取、替换和写必要的字节数。

    能通过为在用户子例程部分的例程写自己的代码来覆盖那些宏。但是如果写自己的,必须如下那样在定义部分取消那些宏的定义:

    %{
    #undef input
    #undef unput
    #undef unput
    #undef output
    #undef winput
    #undef wunput
    #undef woutput
    #undef yysetlocale
    %}

    lex.yy.c 中没有 main 子例程,因为 lex 库包含 main 子例程,而这个子例程调用 yylex 词法分析程序和由 yylex()File 结束处调用的 yywrap 子例程。因此,如果在用户子例程部分不包含 main()yywrap() 或两者都不包含,当编译 lex.yy.c 时,必须在 ll 调用 lex 库的地方输入 cclex.yy.c-ll

    lex 命令生成的外部名称都以 yy 开始,象在 yyinyyout yylexyytext 中那样。

    有限状态机

    有限状态机的缺省骨架在 /usr/ccs/lib/lex/ncform 中定义。用户可通过设置一个环境变量 LEXER=PATH. 使用一个个人配置的有限状态机。PATH 变量指定用户定义的有限状态机路径和文件名。lex 命令为变量检查环境,如果它被设置,那么用补充的路径。

    在表达式中放置空格

    一般的,空格或跳格结束一个规则,接着结束定义一个规则的表达式。然而,可在 " "(引号)内包括空格和跳格字符来在表达式中包含它们。用引号括住没有在 [ ] (括号)集合中的表达式中的所有空格。

    其他特殊字符

    lex 程序识别许多正常的 C 语言特殊字符。这些字符序列是:

    序列 含义
    \a 提醒
    \b 退格
    \f 反馈表单
    \n 换行符(在表达式中不用实际的换行符。)
    \r 返回
    \t 跳格
    \v 纵向跳格
    \\ 反斜杠
    \digits 通过由 digits 指定 1、2、3 位的八进制整数表示的带编码的字符。
    \xdigits 通过由 digits 指定的十六进制字符的序列表示的带编码的字符。
    \c c 不是上面列出的字符的情况下,表示这个 c 字符未改变。
     注:lex 规则中不使用 \0\x0

    当在一个表达式中用这些特殊字符,不必将它们括到引号中。除了在 《AIX V6.1 通用编程概念:编写并调试程序》 中的『lex 命令中的扩展正则表达式』中描述的特殊字符和运算符符号,所有字符总是一个文本字符。

    匹配规则

    当多于一个表达式可匹配当前输入,lex 命令先选择最长的匹配。当几个规则匹配相同数目的字符,lex 命令选择先出现的那个。例如,如果规则

    integer    keyword action...;
    [a-z]+ identifier action...;

    以这个顺序给出,integers 是输入单词,lex 匹配输入作为一个标识,因为 [a-z]+ 匹配八个字符然而 integer 仅匹配七个字符。然而,输入是 integer,两个规则匹配七个字符。lex 选择这个关键字规则因为它先出现。一个更短的输入,如 int,不匹配整数表达式,所以 lex 选择标识规则。

    用通配符匹配一个字符串

    因为 lex 先选择最长的匹配,所以不使用包含像 .* 的表达式。例如:

    '.*'

    可能象是一个在单引号中识别一个字符串的好方法。然而,词法分析程序读取源头,来查找一个远的单引号来完成长匹配。如果带这样规则的词法分析规则得到以下输入:

    'first' quoted string here, 'second' here

    它匹配:

    'first' quoted string here, 'second'

    为了发现更短的字符串,firstsecond,使用以下规则:

    '[^'\n]*'

    这个规则在 'first' 后停止。

    这个类型的错误不是远到达,因为 .(句点)运算符不匹配换行字符。因此,像 .*(句号,星号)的表达式在当前行停止。不要试图用像 [.\n]+ 这样的表达式来使它失败。词法分析程序试图读取整个输入文件,并且发生一个内部缓冲区溢出。

    在字符串中查找字符串

    lex 程序分割输入流同时不搜索每个表达式的所有可能匹配。每个字符计算一次且仅一次。例如,计算 shehe 在输入文本中的出现次数,尝试以下规则:

    she         s++
    he h++
    \n |. ;

    在这里最后两个规则忽略除了 heshe 的所有东西。然而,因为 she 包含 helex 识别包含在 she 中的 he 的情况。

    为覆盖这个选择,用 REJECT 操作。这个伪指令告诉 lex 转到下一个规则。lex 然后在第一个规则被执行前调整输入指针的位置到它在的地方,并执行第二个选择规则。例如,计算包含 he 的实例,用以下规则:

    she                 {s++;REJECT;}
    he {h++;REJECT;}
    \n |. ;

    在计算完 she 的发生次数,lex 拒绝输入流然后计算 he 的发生次数。因为在这种情况下,she 包含 he 但反之不然,可在 he 上省略 REJECT 操作。在其他情况下,确定哪个输入字符在两个类中可能较困难。

    总之,无论何时 lex 的目的不是分割输入流而是检测在输入中的某些项的所有示例,REJECT 总是有用的,并且这些项的实例可交迭或互相包含。

    标志

    -C 生成 lex.yy.C 文件而不是 lex.yy.c 以和 C++ 编译器一起使用。为得到 I/O 流库,使用宏 _CPP_IOSTREAMS
    -n 禁止统计摘要。当为有限状态机设置自己的表的大小时,如果您不选该标志,lex 命令自动生成这个摘要。
    -t lex.yy.c 到标准输出而不是到一个文件。
    -v 提供一个生成的有限状态机统计的一行摘要。

    退出状态

    此命令返回以下退出值:

    0 成功完成。
    >0 有错误发生。

    示例

    1.  lexcommands 文件提取 lex 指令,并在 lex.yy.c 中放置输出,用下列命令:
      lex lexcommands
    2.  创建一个 lex 程序,它将大写转换为小写,删除行尾空格,并用一个空格代替多个空格,在 lex 命令文件中包括下列内容:
      %%
      [A-Z] putchar(yytext[0]+ 'a'-'A');
      [ ]+$ ;
      [ ]+ putchar(' ');

    文件

    /usr/ccs/lib/libl.a 包含运行时库。
    /usr/ccs/lib/lex/ncform 定义一个有限状态机。

    转载于:https://www.cnblogs.com/hnrainll/archive/2011/04/12/2014055.html

    展开全文
  • 语法lex [ -C ] [ -t ] [ -v| -n ] [ File... ]描述lex 命令读取 File 或标准输入,生成 C 语言程序并将它写到一个名为 lex.yy.c 的文件中。这个文件,lex.yy.c ,是一个兼容的 C 语言的程序。一个 C++ 编译器也能够...
  • Lex编程

    千次阅读 2018-01-22 17:20:40
    LexAndYacc.zip Lex编程入门 Lex编程入门 LexLexical Analyzar 描述文件的结构介绍 定义部分 规则部分 规则部分的正则表达式 规则部分的优先级 规则部分的使用变量 用户子程序部分 LexLexical Analyzar 一些...
  • Lex/Yacc 初识Lex

    千次阅读 2016-06-10 23:32:28
    因工作需要接触了一下Lex和Yacc,个人感觉挺有趣的,所以就写下来了。 Lex是Lexical的缩写,大概就可以理解为词汇提取。 Yacc是Yet another compiler compiler, 可以翻译为“还有另一个编译器的编译器”,挺拗口的...
  • Lex是由美国Bell实验室M.Lesk等人用C语言开发的一种词法分析器自动生成工具,它提供一种供开发者编写词法规则(正规式等)的语言(Lex语言)以及这种语言的翻译器(这种翻译器将Lex语言编写的规则翻译成为C语言程序)。Lex...
  • 介绍如何使用lex和yacc(用于生成词法分析器和解析器的工具)构造编译器。
  • lex简介

    2017-02-26 13:15:23
    LexLex 简介 常规表达式正则表达式re ...Lex与C是强耦合的,一个.l文件通过公用的LEX程序来传递,并生成c的输出文件。这些文件被编译为词法分析器的可执行版本。 lex把每个扫描出来的单词叫统统叫做token,
  • LEX使用

    千次阅读 2017-02-05 11:31:04
    一、Lex的基本原理和使用方法 Lex的基本工作原理为:由正规式生成NFA,将NFA变换成DFA,DFA经化简后,模拟生成词法分析器。 其中正规式由开发者使用Lex语言编写,其余部分由Lex翻译器完成.翻译器将Lex源程序翻译成一...
  • Laravel开发-lex

    2019-08-28 06:54:08
    Laravel开发-lex 游戏货币管理包。
  • lex与yacc.pdf

    2020-08-05 10:27:44
    这是关于编译原理, 词法分析lex, 与语法分析yacc的相关文档,高清版,但是这只有该书的前5章 第一章 lex与yacc 第二章 lex使用 第三章 yacc使用 第四章 菜单生成与语言 第五章 SQL分析
  • 初识 lex

    2017-12-19 22:25:05
    一、背景 从零开始学习下lex和yacc 1. 基础 lex只有状态和状态转换,没有栈,善于模式匹配;yacc能处理带栈的FSA(有限状态机),更适合更复杂的任务。 模式匹配原语元字符匹配说明.任意字符( 除了换行)\n换行*0次或者...
  • Lex/Yacc Lex结合Yacc

    千次阅读 2016-06-11 00:15:11
    这次就来尝试lex结合yacc。 简单lex 先写个简单lex,如下: %{ #include "name.tab.h" #include #include extern char* yylval; %} char [A-Za-z] num [0-9] eq [=] name {char}+ age {num}+ %% {name} { ...
  • 什么是lex/flex? 学习LEX(FLEX)的语法结构,学会如何写LEX程序。 Lex是Lexical Compiler的缩写,是Unix环境下非常著名的工具。 实现对以小写字母ab结尾的字符串(只包含大小写字母)的识别,如Helloab和Goab。 注意...
  • lex视频播放器,解压安装使用,未破解版本可短期使用。
  • MacBook写lex

    2020-03-04 12:03:17
    Step1 brew install flex flex 是目前比较流行的lex开源包 Step2 lex文件的后缀是.l lex xxxx.l 这样就可以得到对应的.c文件
  • lex 编程

    2014-04-28 11:28:26
    lex 编程 lex编程分三步 1.以
  • 主要内容:lex源程序的结构+lex编译器的使用 一、lex源程序的结构 声明部分 %% 识别规则 %% 辅助函数 由三个部分构成,三个部分之间由两个%%进行分割。 识别规则是单词符号的识别规则,也是lex程序的主体部分。...
  • TypeError in lex

    2020-11-30 10:20:44
    <div><p>With the 3.6 update, I...line 893, in lex if '.' not in lextab: TypeError: argument of type 'module' is not iterable </code></pre>该提问来源于开源项目:dabeaz/ply</p></div>
  • Lex和Yacc.之Lex

    2014-04-16 15:50:45
    Lex和Yacc应用方法(一).初识Lex 分类: C++/C/C#2007-03-15 17:23 25533人阅读 评论(22) 收藏 举报 yacc正则表达式floatflexreferencecompiler  Lex和Yacc应用方法(一).初识Lex 草木瓜 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,362
精华内容 1,744
关键字:

lex