精华内容
下载资源
问答
  • 词法分析器c语言编写
    2022-02-24 17:23:15

    C 解析器

    描述C–语言的文法(grammar)规则如下:

    <程序>∷=void main() <语句块>
    <语句块>∷={<语句串>} 
    <语句串>∷=<语句串><语句>|
    <语句>∷=<赋值语句>|<输入语句>|<输出语句> 
    <赋值语句>∷=<标识符> = E;
    <标识符>∷=<字母>|_|<标识符><字母>|<标识符>_|<标识符><数字>
    <整数>∷=<数字>|<非0数字><整数串><数字>|<非0数字><数字>
    <整数串>∷=<整数串><数字>|<数字>
    <非0数字>∷=1|2|3|…|9
    <数字>∷=0|<非0数字>
    <字母>∷=a|b|c|…|z
    E∷=T|E+T 
    T∷=F|T*F
    F∷= (E)|<标识符>|整数
    <输入语句>∷=cin>><标识符>;
    <输出语句>∷=cout<<<标识符>;
    
                       +-------+                      +--------+
    -- source code --> | lexer | --> token stream --> | parser | --> assembly
                       +-------+                      +--------+
    

    构建词法分析器有两大类方法

    • 手写的语法分析器,以及手写的词法分析器
      • 清晰,但开发周期长
    • 用lex/yacc或类似的生成器构建
      • 可快速构建原型系统或小语言

    编译原理课程手写的词法分析器例子

    环境

    MAC下自带了Flex 和 Bosin 的编译环境

    $ gcc -v
    
    Configured with: --prefix=/Library/Developer/CommandLineTools/usr --with-gxx-include-dir=/Library/Developer/CommandLineTools/SDKs/MacOSX10.14.sdk/usr/include/c++/4.2.1
    Apple LLVM version 10.0.1 (clang-1001.0.46.3)
    Target: x86_64-apple-darwin18.5.0
    Thread model: posix
    InstalledDir: /Library/Developer/CommandLineTools/usr/bin
    
    

    其他的操作系统下需要自行配置环境(gcc, lex/flex, yacc/bison)

    • Lex - A Lexical Analyzer Generator
    • Yacc: Yet Another Compiler-Compiler
    • Flex, A fast scanner generator
    • Bison, The YACC-compatible Parser Generator

    关于lex,yacc,flex,bison,文档以及一些小例子可以查看:文档

    项目目录

    .
    ├── Makefile
    ├── README.md
    ├── doc.md
    ├── src
    │   ├── analyzer.tab.c
    │   ├── analyzer.tab.h
    │   ├── analyzer.y
    │   ├── node.h
    │   ├── parser
    │   ├── scanner.c
    │   ├── scanner.l
    │   ├── testPair
    │   └── testPair.l
    └── test
        ├── cacl.txt
        ├── in.txt
        ├── input.c--
        └── square.pl0
    

    其中 Makefile 如下:

    all:
    	gcc -g -o src/parser src/*.c
    	./src/parser < test/input.c--
    
    bison:
    	cd src/ && bison -d analyzer.y
    
    lex:
    	flex -o src/scanner.c src/scanner.l
    
    clean:
    	rm src/*.c
    
    pair:
    	flex -o src/testPair.c src/testPair.l
    	gcc -g -o src/testPair src/testPair.c -ll
    	./src/testPair < test/input.c--
    

    阮一峰的Makefile教程

    Makefile部分解释如下:

    1. 对lex语言描述的词法文件scanner.l
      使用flex转换得到c语言描述的词法文件, 默认输出为 lex.yy.c 我们重命名为: -o scanner.c
    2. 对bison语言描述的语法文件analyzer.y进行转换得到c语言描述的语法文件, 参数-d.h文件和.c文件分离, 默认输出为analyzer.tab.canalyzer.tab.h, 将analyzer.tab.hinclude进lex文件以进行下一步的联合编译
    3. 1,2中生成文件进行联合编译, 输出可执行程序parser, 对输入文件input.c--进行语法分析.
    4. 若lex文件没有main函数和yywrap,则在MAC上编译需添加 -ll参数, windows下用-lfl参数, 意思都是将flex库包含进去

    命令解释:

    
    $ make clean  // 清除所有生成的源文件
    $ make bison  // 使用bison生成语法解释源程序
    $ make lex    // 使用flex生成词法解释源程序
    $ make        // 编译生成目标解释程序
    
    $ make clean  // 清除所有生成的源文件
    $ make pair   // 测试词法的匹配
    

    初步实验: 对PL/0语言进行词法分析

    – 来自于官方文档 –

    待分析文件: square.pl0

    VAR x, squ;
    
    PRECEDURE square;
    BEGIN
    	squ:= x * x
    END;
    
    BEGIN
    	x := 1;
    	WHILE x <= 10 DO
    	BEGIN
    		CALL square;
    		1 squ;
    		x := x + 1
    	END
    END.
    

    scanner.l 符号识别分类:

    /* scanner for a toy Pascal-like language */
    
    %{
    /* need this for the call to atof() below */
    #include <math.h>
    %}
    
    DIGIT    [0-9]
    ID       [a-z][a-z0-9]*
    
    %%
    
    {DIGIT}+    {
                printf( "An integer: %s (%d)\n", yytext,
                        atoi( yytext ) );
                }
    
    {DIGIT}+"."{DIGIT}*        {
                printf( "A float: %s (%g)\n", yytext,
                        atof( yytext ) );
                }
    
    if|then|begin|end|procedure|function        {
                printf( "A keyword: %s\n", yytext );
                }
    
    {ID}        printf( "An identifier: %s\n", yytext );
    
    "+"|"-"|"*"|"/"   printf( "An operator: %s\n", yytext );
    
    "{"[^}\n]*"}"     /* eat up one-line comments */
    
    [ \t\n]+          /* eat up whitespace */
    
    .           printf( "Unrecognized character: %s\n", yytext );
    
    %%
    
    main( argc, argv )
    int argc;
    char **argv;
        {
        ++argv, --argc;  /* skip over program name */
        if ( argc > 0 )
                yyin = fopen( argv[0], "r" );
        else
                yyin = stdin;
    
        yylex();
        }
    %%
    

    执行命令:

    flex -o src/scanner.c src/scanner.l
    gcc -g -o src/parser src/scanner.c -ll
    ./src/parser < test/square.pl0
    

    分类后的部分结果如下:

    Unrecognized character: V
    Unrecognized character: A
    Unrecognized character: R
    An identifier: x
    Unrecognized character: ,
    An identifier: squ
    ...http://www.biyezuopin.vip
    ....
    

    成功识别了标识符xsqu


    二步实验: 对C–语言进行词法分析

    待分析文件: input.c--

    #include <stdio.h>
    
    void main() {
    
      double a_0;
      double b;
      double c;
      double d;
      
      cout << a_0;
    
      a_0 = 4 + 3 / 2 - 1 * 0;
      cout << a_0;
    
      b = 3;
      c = 3.14;
      d = b + c;
      cout << d;
    }
    

    描述词法

    正规文法转换成正则匹配式

    根据公式:

    右线性文法(X=aX|b)通解形式:X=a*b
    左线性文法(X=Xa|b)通解形式:X=ba*
    

    标识符转换过程如下:

    <标识符>∷=<字母>||<标识符><字母>|<标识符>|<标识符><数字>

    <标识符>∷=<标识符>(<字母>++<数字>)+<字母>+

    <标识符>∷=(<字母>|)(<字母>||<数字>)*

    ID         [_|A-Za-z]+(_|[A-Za-z]|[0-9])*
    

    整数转换过程如下

    联立:

    <整数>=<数字>|<非0数字><整数串><数字>|<非0数字><数字>
    <整数串>=<整数串><数字>|<数字>
    

    <整数>=<数字>|<非0数字><整数串><数字>|<非0数字><数字>
    <整数串>=<数字>+
    

    <整数>=<数字>|<非0数字><数字>+<数字>|<非0数字><数字>
    <数字>=0|<非0数字>
    

    INTEGER    [0-9]+
    

    flex文件由以下三个部分构成

    definitions
    %%
    rules
    %%
    user code
    

    由此构建出C–语言的相关词法(testPair.l):

    /* scanner for a toy C language */
    #include <stdlib.h>
    void yyerror(char *);
    %}
    http://www.biyezuopin.vip
    INTEGER    [0-9]+
    FLOAT      ([1-9][0-9]*)|0|([0-9]+\.[0-9]*)
    ID         [_|A-Za-z]+(_|[A-Za-z]|[0-9])*
    HD         #include<*.*>
    AT         \/\*(.*)\*\/
    DM         ,|;|\(|\)|\{|\}|\[|\]|\'|\"|<|>
    STR        \".*?\"|\'.*?\'
    %%
    [\n]                  {}
    {HD}                  { printf("Header\n"); }
    "main"                {
                            printf("Main:%s\n", yytext);
                          } 
    "cin"                 {
                            printf("Cin:%s\n", yytext);
                          } 
    "cout"                {
                            printf("Cout:%s\n", yytext);
                          } /*...*/
    {ID}                  {
                            printf("ID:%s\n", yytext);
                          }
    {INTEGER}             {
                            printf("int: %d\n", atoi(yytext));
                          }
    {FLOAT}               {
                            printf("double: %.10g\n", atof(yytext));
                          }
    [()<>=+\-/\*\;{}]     { printf("OP:%s\n", yytext); }
    [ \t]+                /* eat up whitespace */
    .                     printf( "Unrecognized character: %s\n", yytext );
    %%
    
    int yywrap(void) {
      return 1;
    }
    
    
    
    部分分析结果如下所示
    ```bash
    $ make pair
    flex -o src/testPair.c src/testPair.l
    gcc -g -o src/testPair src/testPair.c -ll
    ./src/testPair < test/input.c--
    Header
    ID:void
    Main:main
    OP:(
    OP:)
    OP:{
    ID:double
    ID:a_0
    ...
    

    描述文法

    语义信息

    符号的语义信息存储于全局变量 yylval 中,

    yylval = value;  /* Put value onto Bison stack. */
    return INT;      /* Return the type of the token. */
    
    %union {
      int intval;
      double val;
      symrec *tptr;
    }
    
    ...
    yylval.intval = value; /* Put value onto Bison stack. */
    return INT;          /* Return the type of the token. */
    ...http://www.biyezuopin.vip
    

    以上代码将符号压栈,并返回类型

    移进规约

    例:

    Bison的解析栈(parser stack)中已经移进(shift)了4个字符:
    1 + 5 * 3

    接下来遇到了另起一行的符号,根据以下公式将解析栈中的后3个字符进行规约(reduce)

    expr: expr '*' expr;
    

    解析栈中变成3个字符1 + 15

    自下而上的解析器将不断对读到的句子进行移进规约操作,直到输入序列只剩文法的开始符号

    向前看符号(Look-ahead tokens)

    look-ahead:读到字符但是不急着规约的行为
    

    若定义加法表达式(expr)和符号(term)如下:

    expr:     term '+' expr
            | term
            ;
    
    term:     '(' expr ')'
            | term '!'
            | NUMBER
            ;
    

    若解析栈中移进:1 + 2

    情况一:
    若接着遇到右括号“)”,则栈中三个符号要被规约为expr才能接着被规约为term, 但这些操作没有对应规则(缺了一个左括号不能完成规约)

    情况二:
    若接着遇到阶乘符号“!”,否则若栈中1+2先发生规约而没有向前看,则得到3!=6而不是1+2!=3

    向前看符号会被存储在yychar变量中

    另外在编写的时候还要注意:移进规约冲突,符号优先级,上下文相关优先级

    三步实验: 对C–语言进行语法分析(v1.0)

    程序: v1.0

    拿到一份待分析的代码文件,首先做语法分析,更细的再做词法分析;而程序员首先定义词法,再定义语法。

    待分析代码input.c--如下:

    #include <stdio.h>
    void main() {
      int a;
      cin >> a;
      a = a * 5 + 3.1415926536 - a / a;
      cout << x;
    }
    

    分析结果如下:

    ➜  bace01 git:(master)make bison
    cd src/ && bison -d analyzer.y
    analyzer.y: conflicts: 7 shift/reduce
    analyzer.y:52.5: warning: rule never reduced because of conflicts: stmt: /* empty */
    flex -o src/scanner.c src/scanner.l
    gcc -g -o src/parser src/*.c
    ./src/parser < test/input.c--
    Header
    定义INT变量
    语句串
    输入语句
    语句串
    赋值语句
    语句串
    输出语句
    语句串
    语句块
    函数
    

    V1.2

    程序: v1.2

    语义解析: 加减乘除, 输出语句, while 循环, if-else 
    

    待分析代码input.c--如下:

    #include <stdio.h>
    
    void main() {
    
      double a_0;
      double b;
      double c;
      double d;
      
      cout << a_0;
    
      a_0 = 4 + 3 / 2 - 1 * 0;
      cout << a_0;
    
      b = 3;
      c = 3.14;
      d = b + c;
      cout << d;
    
      int i = 0;
      while( i <= 10 ) {
        cout << i;
        i = i + 1;
      }
      cout << (i + 999);
    
      double e = d;
      while (e <= 20) {
        if(e <= 15) {
          cout << e;
        } 
        else {
          cout << 20;
        }
        e = e + 1;
      }
    }
    

    执行结果如下:

    ➜  bace01 git:(master) make clean
    rm src/*.c
    ➜  bace01 git:(master) make bison
    cd src/ && bison -d analyzer.y
    ➜  bace01 git:(master) make lex
    flex -o src/scanner.c src/scanner.l
    ➜  bace01 git:(master) make
    gcc -g -o src/parser src/scanner.c src/analyzer.tab.c
    ./src/parser < test/input.c--
    0
    5.5
    6.14
    0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    1010
    6.14
    7.14
    8.14
    9.14
    10.14
    11.14
    12.14
    13.14
    14.14
    20
    20
    20
    20
    20
    

    词法解析

    1. 对所有的词进行识别和分类, 在scanner.l中定义如下规则:

    lex语法中使用正则表达式识别文法, 编写正则表达式时, 可以使用在线工具测试匹配情况.

    方便起见, 在最初的版本中所有用于计算的数据类型都统一成double

    首先是一些正则表达式的定义(可以看做是宏定义), 写在definitions段:

    INTEGER    [0-9]+
    FLOAT      ([1-9][0-9]*)|0|([0-9]+\.[0-9]*)
    ID         [_|A-Za-z]+(_|[A-Za-z]|[0-9])*
    HD         #include<*.*>
    ...
    

    接着定义匹配到的词语所要进行的操作, 写在rule段:

    [\n]                  {
                            row_cnt ++; 
                          }
    {HD}                  {} /* 系统保留字单独定义 */
    "main"                return FUN;
                          return INT;
    "double"              return DOUBLE;
    "int"                 return INT;
    "void"                return VOID;
    "cin"                 return CIN; 
    ">>"                  return TO_RIGHT;
    ...
    
    {ID}                  {
                            varNode * vp = searchVarTab(yytext);
                            if (vp == 0) 
                              vp = insertVarTab(yytext);
                            yylval.tptr = vp;
                            return VAR;
                          } /* 每遇到一个标识符就存入变量表 */
    {INTEGER}             {
                            yylval.val = atof(yytext); 
                            return NUM;
                          }/*...*/
    {FLOAT}               {
                            yylval.val = atof(yytext); 
                            return NUM;
                          }
    [()<>=+\-/\*\;\{\}]   return *yytext;
    [ \t]+                /* eat up whitespace */
    .                     printf( "Unrecognized character: %s\n", yytext );
    

    大写字符表示终结符, 会被Bison生成器解析为宏定义, 这些大写字符定义在.y文件中, 用%token声明, 解析后的结果可以在*.tab.h文件中找到, 因此.l文件需要#include <*.tab.h>, 以将这张包含了全部宏定义的table包含进去, 以后遇到大写字母就替换成数字了:

    /* Tokens.  */
    #define NUM 258
    #define VAR 259
    #define FUN 260
    #define DEF 261
    #define INT 262
    ...
    

    遇到的以yy*开头的变量, 基本都是生成器自动定义的全局变量, 便于我们把值存在这些全局变量中, 在.l.y文件之间传递

    例如, 程序识别到的词会存储在char *yytext变量中, 可以将其赋值给YYSTYPE yylval变量, YYSTYPE默认是int类型, 当然我们可以自行定义为结构体类型, 以让程序理解更丰富的语义. YYSTYPE就是栈中的元素类型, 后续对这些元素进行移进规约操作.

    例如, 匹配到整数之后, 将char*转化成float类型, 赋值给全局变量yylval, 返回NUM替换成的数字, 告知语法识别程序.y, 识别到了类型为NUM的符号, 这个符号的值, 存储在yylval变量中, 若要使用可以在.y文件中通过$符号访问;

    定义一个全局变量表varNode * var_table, 每识别一个标识符ID, 在表中按名查找变量, 若是查询到变量则返回指向这个变量的指针, 否则将这个变量头插法插入到变量表中, 并赋初值为0;

    node.h中定义的变量表结构如下:

    /* 变量表中的结点 */
    typedef struct varNode
    {
      char *name;    /* 变量名称 */
      double value;  /* 变量值 */
      struct varNode *next;
    }varNode;
    
    extern varNode * var_table;
    extern varNode * insertVarTab ();
    extern varNode * searchVarTab ();
    

    语法解析

    对所有的语句, 以及语句中的子语句规定一个统一的结构, 以便用统一的接口进行解析.

    所有的元素因为具有统一的结构, 根据移进规约规则可以构造出一棵语法🌲, 解析程序边读取程序中的字符, 边识别, 边进行移进规约, 构成一棵语法树, 最后规约到只剩根结点;

    从根结点开始向下递归的进行语义解析;

    node.h中定义如下:

    typedef enum { TYPE_X, TYPE_VAR, TYPE_OP } NodeEnum;
    
    /* 操作符 */
    typedef struct opNode {
        int type; /* 操作符类型(macro) */
        int num;  /* 操作元个数        */
        struct NodeTag * node[1]; /* 操作元地址 可扩展 */
    } opNode;
    
    /* 一切元素(语句,变量,值,函数)都看成结点 */
    typedef struct NodeTag
    {
      NodeEnum type; /* 树结点类型 */
      union {
        double value;   /* 数值   */
        char *name;     /* 变量名 */
        opNode op;      /* 操作符 */
      };
    }Node;
    

    若遇到数字, 值存入double value;

    若遇到变量, 变量名存入name;

    若遇到操作符, 向opNode op中内容赋值;

    Node * setNum (double);           // 设置数字结点
    Node * setVar(varNode *);         // 设置变量结点
    Node * setOpr(int, int, ...);     // 设置操作符结点
    double executeNode(Node * p);     // 执行结点语句
    int setVarTable(char *, double);  // 赋值变量表
    void freeNode(Node *);            // 释放结点
    

    YYSTYPE 通过 union 声明为3种可选的类型, 分别对应数值, 变量表指针, 和 语法🌲指针

    %union {
      double     val;      /* 符号取值 */
      varNode  *tptr;      /* 变量表指针 */
      Node     *nptr;      /* 结点表表指针 */
    }
    

    声明终结符token, 算符, 非终结符%type<nptr>(语法🌲结点类型)

    后定义的优先规约, %left向左左结合规约, %right向右结合规约, %nonassoc没有结合性

    声明非终结符

    /* 终结符 */
    %token <val>  NUM
    %token <tptr> VAR
    %token FUN DEF INT DOUBLE VOID 
    %token TO_RIGHT TO_LEFT
    %token WHILE IF COUT CIN
    
    /* 算符 */
    %nonassoc IFX
    %nonassoc ELSE /* if-else嵌套中, else比if优先规约 */
    %left AND OR GE LE EQ NE '>' '<'
    %right '='
    %left '-' '+'
    %left '*' '/'
    %left NEG     /* 取负 */
    
    /* 非终结符*/
    %type <nptr> exp stmt stmt_list stmt_block
    

    从"细"到"粗", 声明规约原则:

    /* Grammar rules and actions follow */
    %%
    
    function: 
                VOID FUN '(' ')' stmt_block { 
                                              executeNode($5);
                                              freeNode($5);
                                            }
    ;
    
    stmt_block:
                '{' stmt_list '}' {
                                    $$ = $2;
                                  }
    ;
    
    stmt_list:
                  stmt            { $$ =$1; }
                | stmt_list stmt  {
                                    $$ = setOpr(';', 2, $1, $2);
                                  }
    ...
    stmt:
                 ';'                        { $$ = setOpr(';', 2, NULL, NULL);     }
                | DOUBLE VAR ';'            {
                                                $$ = setOpr(DEF, 1, setVar($2));
                                            }
                | INT VAR ';'               {
                                                $$ = setOpr(DEF, 1, setVar($2));
                                            }
    ...
    
    exp:         
                  NUM                 { $$ = setNum($1);                  }
                | VAR                 { $$ = setVar($1);                  }
                | exp '+' exp         { $$ = setOpr('+', 2, $1,  $3);     }
                | exp '-' exp         { $$ = setOpr('-', 2, $1,  $3);     }
    ...
    
    

    从"细"到"粗"都规约至Node *语法树结点, 分别是exp: 表达式, stmt: 语句, stmt_list: 语句串, stmt_list: 语句块, function: 函数

    举个例子

    exp:         
                  NUM                 { $$ = setNum($1);                  }
                | VAR                 { $$ = setVar($1);                  }
                | exp '+' exp         { $$ = setOpr('+', 2, $1,  $3);     }
    

    表达式可以由一个数字, 一个变量规约得到, 也可以由"表达式+表达式"这种组合规约得到.
    在下列表达式中
    exp: exp '+' exp { $$ = setOpr('+', 2, $1, $3); }
    $$: 非终结符exp 被一个符号结点赋值
    $1: 第一个符号exp
    $2: 第二个符号+
    $3: 第三个符号exp
    setOpr('+', 2, $1, $3);: 设置一个类型是+的符号结点, 将2个操作数, 存入该结点, 返回结点指针Node *给当前结点$$.

    边读边分析, 最后规约至语句块结点(stmt_block). 调用执行函数executeNode, 执行该结点, 根据不同的结点类型决定应该执行的语义操作:

    TYPE_X: 数值结点, 直接返回结点值(double)
    TYPE_VAR: 变量结点, 查询变量表var_table并返回结点值(double)
    TYPE_OP: 操作符结点, 根据操作符具体类型(根据操作符的宏定义结果)取操作数:op->node[?], 执行对应操作并返回结果(double)

    更多相关内容
  • 通过C语言编写一个词法分析器完成输入是字符串(或源程序文本文件),输出是源程序中各单词的字符串、起止位置、词的类别。附带实验报告。
  • C语言实现词法分析器

    2021-01-21 18:38:58
    用C或C++语言编写一个简单的词法分析程序,扫描C语言小子集的源程序,根据给定的词法规则,识别单词,填写相应的表。如果产生词法错误,则显示错误信息、位置,并试图从错误中恢复。简单的恢复方法是忽略该字符(或...
  • 编译原理c语言词法分析器的实现(使用c语言编写
  • 编译原理实验 词法分析器 语法分析器 完美代码 完美实验
  • 词法分析程序 用c语言编写 包含了各种功能 简单明了
  • 词法分析器c语言编写 如果我们的代码有明显的错误,那么我们就有动力去改进它。 但是,在某些时候,我们认为我们的代码“足够好”并继续前进。 通常,当我们认为改进现有代码的好处小于所需的工作时,就会发生这种...
    词法分析器c语言编写

    词法分析器c语言编写

    如果我们的代码有明显的错误,那么我们就有动力去改进它。 但是,在某些时候,我们认为我们的代码“足够好”并继续前进。

    通常,当我们认为改进现有代码的好处小于所需的工作时,就会发生这种情况。 当然,如果我们低估了投资回报,我们可能会打错电话,这会伤害我们。

    这就是发生在我身上的事情,因此我决定写这篇文章,以便您避免犯同样的错误。

    编写“良好”单元测试

    如果我们要编写“良好”的单元测试,则必须编写以下单元测试:

    • 只测试一件事。 一个好的单元测试只能由于一个原因而失败,并且只能断言一件事。
    • 被正确命名。 测试方法的名称必须揭示测试失败的原因。
    • 模拟外部依赖关系(和状态) 。 如果单元测试失败,我们将确切知道问题出在哪里。

    补充阅读:

    如果我们编写满足这些条件的单元测试,我们将编写好的单元测试。 对?

    我曾经这样认为。 现在我对此表示怀疑

    善意铺平地狱之路

    我从未见过一个决定编写糟糕的单元测试的软件开发人员。 如果开发人员正在编写单元测试,则他/她更有可能要编写好的单元测试。 但是,这并不意味着该开发人员编写的单元测试是好的。

    我想编写既易于阅读又易于维护的单元测试。 我什至写了一个教程,描述了如何编写干净的测试。 问题在于,本教程中给出的建议还不够好。 它可以帮助我们入门,但是并没有显示出兔子洞的真正深度。

    我的教程中描述的方法存在两个主要问题:

    命名标准FTW?

    如果我们使用Roy Osherove引入“命名标准”,则会注意到很难描述被测状态和预期行为。

    当我们为简单场景编写测试时,此命名标准非常有效。 问题在于,真正的软件并不简单。 通常,我们最终使用以下两个选项之一来命名测试方法:

    首先,如果我们尝试尽可能具体,那么我们的测试方法的方法名称就显得太过looooooooong。 最后,我们必须承认我们不能像我们想要的那样具体,因为方法名称会占用太多空间。

    其次,如果我们尝试使方法名称尽可能短,则方法名称将不会真正描述测试状态和预期行为。

    选择哪个选项并不重要,因为无论如何我们都会遇到以下问题:

    • 如果测试失败,则方法名称不一定描述要出错的方法。 我们可以使用自定义断言来解决此问题,但是它们不是免费的。
    • 很难对我们的测试涵盖的场景进行简要概述。

    以下是我们在“编写干净测试”教程中编写的测试方法的名称:

    • registerNewUserAccount_SocialSignInAndDuplicateEmail_ShouldThrowException()
    • registerNewUserAccount_SocialSignInAndDuplicateEmail_ShouldNotSaveNewUserAccount()
    • registerNewUserAccount_SocialSignInAndUniqueEmail_ShouldSaveNewUserAccountAndSetSignInProvider()
    • registerNewUserAccount_SocialSignInAndUniqueEmail_ShouldReturnCreatedUserAccount()
    • registerNewUserAccount_SocialSignInAnquequeEmail_ShouldNotCreateEncodedPasswordForUser()

    这些方法的名称不是很长,但是我们必须记住,编写这些单元测试是为了测试一种简单的注册方法。 当我使用这种命名约定为现实生活中的软件项目编写自动化测试时,最长的方法名称是我们最长的示例的两倍。

    那不是很干净或可读。 我们可以做得更好

    没有通用配置

    在本教程中,我们使单元测试变得更好了。 尽管如此,他们仍然遭受这样的事实,即没有“自然的”方式在不同的单元测试之间共享配置。

    这意味着我们的单元测试包含许多重复的代码,这些代码配置了我们的模拟对象并创建了在单元测试中使用的其他对象。

    同样,由于没有“自然”的方式表明某些常量仅与特定的测试方法相关,因此我们必须将所有常量添加到测试类的开头。

    我们的测试类的源代码如下(突出显示有问题的代码):

    import org.junit.Before;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.mockito.ArgumentCaptor;
    import org.mockito.Mock;
    import org.mockito.invocation.InvocationOnMock;
    import org.mockito.runners.MockitoJUnitRunner;
    import org.mockito.stubbing.Answer;
    import org.springframework.security.crypto.password.PasswordEncoder;
     
    import static com.googlecode.catchexception.CatchException.catchException;
    import static com.googlecode.catchexception.CatchException.caughtException;
    import static org.assertj.core.api.Assertions.assertThat;
    import static org.mockito.Matchers.isA;
    import static org.mockito.Mockito.never;
    import static org.mockito.Mockito.times;
    import static org.mockito.Mockito.verify;
    import static org.mockito.Mockito.verifyZeroInteractions;
    import static org.mockito.Mockito.when;
     
    @RunWith(MockitoJUnitRunner.class)
    public class RepositoryUserServiceTest {
     
        private static final String REGISTRATION_EMAIL_ADDRESS = "john.smith@gmail.com";
        private static final String REGISTRATION_FIRST_NAME = "John";
        private static final String REGISTRATION_LAST_NAME = "Smith";
        private static final SocialMediaService SOCIAL_SIGN_IN_PROVIDER = SocialMediaService.TWITTER;
     
        private RepositoryUserService registrationService;
     
        @Mock
        private PasswordEncoder passwordEncoder;
     
        @Mock
        private UserRepository repository;
     
        @Before
        public void setUp() {
            registrationService = new RepositoryUserService(passwordEncoder, repository);
        }
     
        @Test
        public void registerNewUserAccount_SocialSignInAndDuplicateEmail_ShouldThrowException() throws DuplicateEmailException {
            RegistrationForm registration = new RegistrationFormBuilder()
                    .email(REGISTRATION_EMAIL_ADDRESS)
                    .firstName(REGISTRATION_FIRST_NAME)
                    .lastName(REGISTRATION_LAST_NAME)
                    .isSocialSignInViaSignInProvider(SOCIAL_SIGN_IN_PROVIDER)
                    .build();
     
            when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(new User());
     
            catchException(registrationService).registerNewUserAccount(registration);
     
            assertThat(caughtException()).isExactlyInstanceOf(DuplicateEmailException.class);
        }
     
        @Test
        public void registerNewUserAccount_SocialSignInAndDuplicateEmail_ShouldNotSaveNewUserAccount() throws DuplicateEmailException {
            RegistrationForm registration = new RegistrationFormBuilder()
                    .email(REGISTRATION_EMAIL_ADDRESS)
                    .firstName(REGISTRATION_FIRST_NAME)
                    .lastName(REGISTRATION_LAST_NAME)
                    .isSocialSignInViaSignInProvider(SOCIAL_SIGN_IN_PROVIDER)
                    .build();
     
            when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(new User());
     
            catchException(registrationService).registerNewUserAccount(registration);
     
            verify(repository, never()).save(isA(User.class));
        }
     
        @Test
        public void registerNewUserAccount_SocialSignInAndUniqueEmail_
    ShouldSaveNewUserAccountAndSetSignInProvider() throws DuplicateEmailException {
            RegistrationForm registration = new RegistrationFormBuilder()
                    .email(REGISTRATION_EMAIL_ADDRESS)
                    .firstName(REGISTRATION_FIRST_NAME)
                    .lastName(REGISTRATION_LAST_NAME)
                    .isSocialSignInViaSignInProvider(SOCIAL_SIGN_IN_PROVIDER)
                    .build();
     
            when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(null);
     
            registrationService.registerNewUserAccount(registration);
     
            ArgumentCaptor<User> userAccountArgument = ArgumentCaptor.forClass(User.class);
            verify(repository, times(1)).save(userAccountArgument.capture());
     
            User createdUserAccount = userAccountArgument.getValue();
     
            assertThatUser(createdUserAccount)
                    .hasEmail(REGISTRATION_EMAIL_ADDRESS)
                    .hasFirstName(REGISTRATION_FIRST_NAME)
                    .hasLastName(REGISTRATION_LAST_NAME)
                    .isRegisteredUser()
                    .isRegisteredByUsingSignInProvider(SOCIAL_SIGN_IN_PROVIDER);
        }
     
     
        @Test
        public void registerNewUserAccount_SocialSignInAndUniqueEmail_ShouldReturnCreatedUserAccount() throws DuplicateEmailException {
            RegistrationForm registration = new RegistrationFormBuilder()
                    .email(REGISTRATION_EMAIL_ADDRESS)
                    .firstName(REGISTRATION_FIRST_NAME)
                    .lastName(REGISTRATION_LAST_NAME)
                    .isSocialSignInViaSignInProvider(SOCIAL_SIGN_IN_PROVIDER)
                    .build();
     
            when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(null);
     
            when(repository.save(isA(User.class))).thenAnswer(new Answer<User>() {
                @Override
                public User answer(InvocationOnMock invocation) throws Throwable {
                    Object[] arguments = invocation.getArguments();
                    return (User) arguments[0];
                }
            });
     
            User createdUserAccount = registrationService.registerNewUserAccount(registration);
     
            assertThatUser(createdUserAccount)
                    .hasEmail(REGISTRATION_EMAIL_ADDRESS)
                    .hasFirstName(REGISTRATION_FIRST_NAME)
                    .hasLastName(REGISTRATION_LAST_NAME)
                    .isRegisteredUser()
                    .isRegisteredByUsingSignInProvider(SOCIAL_SIGN_IN_PROVIDER);
        }
     
        @Test
        public void registerNewUserAccount_SocialSignInAnUniqueEmail_ShouldNotCreateEncodedPasswordForUser() throws DuplicateEmailException {
            RegistrationForm registration = new RegistrationFormBuilder()
                    .email(REGISTRATION_EMAIL_ADDRESS)
                    .firstName(REGISTRATION_FIRST_NAME)
                    .lastName(REGISTRATION_LAST_NAME)
                    .isSocialSignInViaSignInProvider(SOCIAL_SIGN_IN_PROVIDER)
                    .build();
     
            when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(null);
     
            registrationService.registerNewUserAccount(registration);
     
            verifyZeroInteractions(passwordEncoder);
        }
    }

    一些开发人员认为看起来像上面示例的单元测试足够干净。 我理解这种情绪,因为我曾经是其中之一。 但是,这些单元测试有三个问题:

    1. 该案的实质不是很清楚。 因为每种测试方法在调用被测试方法并验证预期结果之前都会进行自我配置,所以我们的测试方法变得比必要的更长。 这意味着我们不能只看一眼随机测试方法并弄清楚它要测试什么。
    2. 编写新的单元测试很慢。 因为每个单元测试都必须自行配置,所以向我们的测试套件中添加新的单元测试比它可能要慢得多。 另一个“意外”的缺点是,这种单元测试鼓励人们练习复制和粘贴编程
    3. 维持这些单元测试是一件痛苦的事情。 如果我们向注册表单添加新的必填字段,或者更改registerNewUserAccount()方法的实现,则必须对每个单元测试进行更改。 这些单元测试太脆弱了。

    换句话说,这些单元测试很难阅读,很难编写和维护。 我们必须做得更好

    概要

    这篇博客文章教会了我们四件事:

    • 即使我们认为我们正在编写好的单元测试,也不一定是正确的。
    • 如果由于必须更改许多单元测试而导致更改现有功能的速度很慢,那么我们就不会编写好的单元测试。
    • 如果由于我们必须向单元测试中添加大量重复代码而导致添加新功能的速度很慢,那么我们就不会编写好的单元测试。
    • 如果我们看不到单元测试所涵盖的情况,那么我们就没有编写好的单元测试。

    本教程的下一部分将回答这个非常相关的问题:

    如果我们现有的单元测试很烂,我们该如何解决?

    如果要编写干净的测试,则应阅读我的“编写干净的测试”教程

    翻译自: https://www.javacodegeeks.com/2015/03/writing-clean-tests-trouble-in-paradise.html

    词法分析器c语言编写

    展开全文
  • 词法分析器c语言编写 当我们向数据访问代码编写测试时,是否应该测试其公共API的每种方法? 一开始听起来很自然。 毕竟,如果我们不对所有内容进行测试,那么如何知道我们的代码可以按预期工作呢? 这个问题为我们...
    词法分析器c语言编写

    词法分析器c语言编写

    当我们向数据访问代码编写测试时,是否应该测试其公共API的每种方法?

    一开始听起来很自然。 毕竟,如果我们不对所有内容进行测试,那么如何知道我们的代码可以按预期工作呢?

    这个问题为我们提供了重要的线索:

    我们的代码

    我们应该只对自己的代码编写测试。

    什么是我们自己的代码?

    有时很难确定我们应该测试的代码。 这是因为我们的数据访问代码与将信息保存到使用的数据存储中或从中读取信息时所使用的库或框架紧密集成在一起。

    例如,如果我们要创建一个向Todo对象提供CRUD操作的Spring Data JPA存储库,则应创建一个扩展CrudRepository接口的接口。 TodoRepository接口的源代码如下所示:

    import org.springframework.data.repository.CrudRepository;
    
    public TodoRepository extends CrudRepository<Todo, Long> {
    
    }

    即使我们没有向存储库接口添加任何方法, CrudRepository接口也声明了许多可供使用我们存储库接口的类使用的方法。

    这些方法不是我们的代码,因为它们是由Spring Data团队实现和维护的。 我们只使用它们。

    另一方面,如果我们向存储库中添加自定义查询方法,情况将会改变。 假设我们必须找到标题等于给定搜索词的所有待办事项。 在将此查询方法添加到我们的存储库接口之后,其源代码如下所示:

    import org.springframework.data.repository.CrudRepository;
    import org.springframework.data.repository.query.Param;
    
    public TodoRepository extends CrudRepository<Todo, Long> {
    
    	@Query("SELECT t FROM Todo t where t.title=:searchTerm")
    	public List<Todo> search(@Param("searchTerm") String searchTerm)
    }

    可以很容易地断言该方法是我们自己的代码,这就是为什么我们应该对其进行测试。 但是,事实有点复杂。 即使JPQL查询是由我们编写的,Spring Data JPA也会提供将查询转发给使用过的JPA提供程序的代码。

    而且,我仍然认为该查询方法是我们自己的代码,因为其中最重要的部分是由我们编写的。

    如果要标识自己的数据访问代码,则必须找到每种方法的基本部分。 如果这部分是我们编写的,则应将该方法视为自己的代码。

    这一切都是显而易见的,更有趣的问题是:

    我们应该测试吗?

    我们的存储库接口为使用它的类提供了两种方法:

    1. 它提供了由CrudRepository接口声明的方法。
    2. 它提供了我们编写的查询方法。

    我们是否应该将集成测试编写到TodoRepository接口并测试所有这些方法?

    不,我们不应该这样做,因为

    1. CrudRepository接口声明的方法不是我们自己的代码。 这段代码是由Spring Data团队编写和维护的,他们已经确保它可以工作。 如果我们不相信他们的代码有效,那么我们就不应该使用它。
    2. 我们的应用程序可能有许多存储库接口,这些接口扩展了CrudRepository接口。 如果决定对CrudRepository接口声明的方法编写测试,则必须对所有存储库编写这些测试。 如果选择这种方式,我们将花费大量时间对其他人的代码编写测试,坦率地说,这样做是不值得的。
    3. 我们自己的代码可能是如此简单,以至于无法将测试写入我们的存储库。

    换句话说,我们应该集中精力寻找这个问题的答案:

    我们应该将集成测试写入我们的存储库方法(由我们编写的方法),还是只编写端到端测试?

    这个问题的答案取决于我们存储库方法的复杂性。 我知道复杂性是一个模糊的词,这就是为什么我们需要某种准则来帮助我们找到测试存储库方法的最佳方法的原因。

    做出此决定的一种方法是考虑测试每种可能情况所需的工作量。 这是有道理的,因为:

    1. 将集成测试写入单个存储库方法比将相同的测试写入使用存储库方法的功能所需的工作更少。
    2. 无论如何,我们都必须端对端地编写。

    这就是为什么最小化我们的投资(时间)和最大化我们的利润(测试覆盖率)的原因。 我们可以按照以下规则进行操作:

    • 如果我们只编写几个测试就可以测试所有可能的场景,那么我们就不应该浪费时间将集成测试写入我们的存储库方法。 我们应该编写端到端测试,以确保该功能按预期工作。
    • 如果我们需要编写多个测试,则应将集成测试编写到我们的存储库方法中,而仅编写一些端到端测试(烟雾测试)。

    概要

    这篇博客文章教会了我们两件事:

    • 我们不应该浪费时间将测试编写到其他人编写的数据访问框架(或库)中。 如果我们不信任该框架(或库),则不应使用它。
    • 有时我们也不应该对数据访问代码编写集成测试。 如果经过测试的代码足够简单(我们可以通过编写一些测试来涵盖所有情况),则应该通过编写端到端测试来对其进行测试。

    翻译自: https://www.javacodegeeks.com/2014/07/writing-tests-for-data-access-code-dont-test-the-framework.html

    词法分析器c语言编写

    展开全文
  • C语言编写一个PL/0词法分析器,为语法语义分析提供单词,使之能把输入的字符串形式的源程序分割成一个个单词符号传递给语法语义分析,并把分析结果(基本字,运算符,标识符,常数以及界符)输出。(含实验报告,cpp...
  • 中国矿业大学编译原理实践课程,C语言编译器词法分析器
  • 编译原理课程上要求自己完成一个简单的词法分析器C语言搞定,思路简单清晰。
  • c语言实现词法分析器

    千次阅读 多人点赞 2021-11-17 20:12:54
    词法分析器的功能:输入源程序,输出单词字符。单词字符一般可以分为下面五种。 (1)关键字 是由程序语言定义的具有固定意义的标识符。有时称这些标识符为保留字或者基本字。例如c语言中的int,char,define,strcut,...

    词法分析器的功能:输入源程序,输出单词字符。单词字符一般可以分为下面五种。
    (1)关键字 是由程序语言定义的具有固定意义的标识符。有时称这些标识符为保留字或者基本字。例如c语言中的int,char,define,strcut,double,if,else.等等
    (2)标识符 用来表示各种名字,如变量名,数组名,过程名。
    (3)常数 常数的类型一般有整形,实型,布尔型等
    (4)运算符 如+,-,*,/。
    (5)界符 如逗号,分号,括号,%,//,等。
    词法分析器的目标就是把程序分成一个一个单词。并给出单词符号的种类,以及种类值。
    代码实现:
    代码是贴的别人的,我在上面改善了一些实现的功能。自己发了三四个小时看明白了。自己完完全全写的确好多东西要学一下。比如一些文件的操作还不是特别的熟悉。等等呀!代码中还用到了结构体,我之前也做过一篇文章分析了,这里用结构体来实现关键字表,应该不是很难的。
    普通标识符的种类编码1
    无符号整数的种类编码2
    “int”,的种类编码3
    “char”,的种类编码4
    “float”,的种类编码5
    “main”,的种类编码6
    “double”,的种类编码7
    “case”, 的种类编码8
    “for”,的种类编码9
    “if”,的种类编码10
    “auto”,的种类编码11
    “else”,的种类编码12
    “do”,的种类编码13
    “while”,的种类编码14
    “void”,的种类编码15
    “static”, 的种类编码16
    “return”,的种类编码17
    “break”,的种类编码18
    “struct”,的种类编码19
    “const”,的种类编码20
    “union”,的种类编码21
    “switch”,的种类编码22
    “typedef”,的种类编码23
    "enum"的种类编码24
    ( 的种类编码25
    )的种类编码26
    [ 的种类编码27
    ] 的种类编码28
    ; 的种类编码29
    . 的种类编码30
    , 的种类编码31
    : 的种类编码32
    {的种类编码33
    } 的种类编码34
    % 的种类编码35
    " 的种类编码36
    \ 的种类编码37
    # 的种类编码38
    / 的种类编码39
    ++的种类编码41
    +=的种类编码42
    +的种类编码43
    –的种类编码44
    -=的种类编码45
    -的种类编码46
    *的种类编码47
    =的种类编码48
    >= 的种类编码49
    > 的种类编码50

    那个模块不是很清楚的话可以评论哈!!!

    /*附录源程序清单:*/
    
    #include<string.h>
    #include<stdio.h>
    #define   MAX 22             /*分析}表的最大容量*/
    #define   RES_MAX   10        /*关键字的最大长度*/
    #define   MAXBUF 255         /*缓冲区的大小*/
    
    char   ch =' ';             /*存放读入当前的输入字符*/
    int Line_NO;                /*纪录行号*/
    
    struct keywords    /*关键字*/
    {
    char lexptr[MAXBUF];
    int token;
    };
    struct keywords symtable[MAX];
    char str[MAX][10]={"int","char","float","main","double","case", "for","if","auto","else","do","while","void","static", "return","break","struct","const","union","switch","typedef","enum"};
    
    /*对关键字表进行初始化,div,mod,and,or也作为关键字处理*/
    /*最小的token是program:3,最大的token是or:24*/
    void init() 
    {           
    	int j;
    	for(j=0; j<MAX; j++)
    	{
    	   strcpy(symtable[j].lexptr,str[j]);
    	   symtable[j].token=j+3;   
    	}
    }
    
    /***************对关键字进行搜索**************/
    int Iskeyword(char * is_res){
       int i;
       for(i=0;i<MAX;i++){
         if((strcmp(symtable[i].lexptr,is_res))==0) break;
       }
       if(i<MAX) return   symtable[i].token;
       else return 0;
    }
    
    /*****************判断是否为字母*****************/
    int IsLetter(char c)
    {
       if(((c<='z')&&(c>='a'))||((c<='Z')&&(c>='A'))) return 1;
        else return 0;
    }
    
    /*************判断是否为数字**************/
    int IsDigit(char c){
       if(c>='0'&&c<='9') return 1;
       else return 0;
    }
    
    /***************分析程序**************/
    void analyse(FILE *fpin,FILE *fpout){
    	/* 输入缓冲区,存放一个单词符号 */
        char arr[MAXBUF];	
        int j=0;
        while((ch=fgetc(fpin))!=EOF){
        /*碰到空格、tab则跳过*/
        if(ch==' '||ch=='\t'){}
        else if(ch=='\n'){Line_NO++;}
    /*********************字符串的处理*************************/
       else if(IsLetter(ch)){
           while(IsLetter(ch)|IsDigit(ch)|ch=='_'){
    		   if((ch<='Z')&&(ch>='A'))     
    		   		ch=ch+32;   /*忽略大小写*/
                arr[j]=ch;
                j++;
                ch=fgetc(fpin);
            }
            /*输入指针回退一个字符*/
            fseek(fpin,-1L,SEEK_CUR);
            arr[j]='\0';
            j=0;
           if (Iskeyword(arr)){   /*如果是关键字*/
                fprintf(fpout,"%s\t\t%d\t\t关键字\n",arr,Iskeyword(arr));
            }else   
    			fprintf(fpout,"%s\t\t%d\t\t标识符\n",arr,1); /*普通标识符*/
    /*************************数字的处理****************************/      
       }else if(IsDigit(ch)){
    	int s=0;
          while(IsDigit(ch)|IsLetter(ch)){
    		if(IsLetter(ch)){
                arr[j]=ch;
                j++;
                ch=fgetc(fpin);
    			s=1;
    		}
    	 	else if(IsDigit(ch)){
    			arr[j]=ch;
                j++;
                ch=fgetc(fpin);
    		}		  
        }
            fseek(fpin,-1L,SEEK_CUR);
            arr[j]='\0';
            j=0;
    	if(s==0)
    		fprintf(fpout,"%s\t\t%d\t\t无符号整数\n",arr,2) ; 
    	else if(s==1)
    		fprintf(fpout,"%s\t\t%d\t\t错误\n",arr,3) ; 
          }else switch(ch){           
                case'+' :
                	ch=fgetc(fpin);
                	if(ch=='+'){
                		fprintf(fpout,"%s\t\t%d\t\t自加运算符\n","++",41);
                		break;
    				}
    				else if(ch=='='){
    					fprintf(fpout,"%s\t\t%d\t\t运算符\n","+=",42);
                		break;
    				}
                	else
                		fseek(fpin,-1L,SEEK_CUR);
    					fprintf(fpout,"%s\t\t%d\t\t运算符\n","+",43);
    				break;
                case'-' :
                	ch=fgetc(fpin);
                	if(ch=='-'){
                		fprintf(fpout,"%s\t\t%d\t\t自减运算符\n","--",44);
                		break;
    				}
    				else if(ch=='='){
    					fprintf(fpout,"%s\t\t%d\t\t运算符\n","-=",45);
                		break;
    				}
                	else
                		fseek(fpin,-1L,SEEK_CUR);
    					fprintf(fpout,"%s\t\t%d\t\t运算符\n","-",46);
    				break;
                case'*' :fprintf(fpout,"%s\t\t%d\t\t运算符\n","*",47);break;
                case'(' :fprintf(fpout,"%s\t\t%d\t\t分界符\n","(",25);break;
                case')' :fprintf(fpout,"%s\t\t%d\t\t分界符\n",")",26);break;
                case'[' :fprintf(fpout,"%s\t\t%d\t\t分界符\n","[",27);break;
                case']' :fprintf(fpout,"%s\t\t%d\t\t分界符\n","]",28);break;                      
                case';' :fprintf(fpout,"%s\t\t%d\t\t分界符\n",";",29);break;
                case'=' :fprintf(fpout,"%s\t\t%d\t\t运算符\n","=",48);break;
                case'.' :fprintf(fpout,"%s\t\t%d\t\t分界符\n",".",30);break;
                case',' :fprintf(fpout,"%s\t\t%d\t\t分界符\n",",",31);break;
    			case':' :fprintf(fpout,"%s\t\t%d\t\t分界符\n",":",32);break; 
    			case'{' :fprintf(fpout,"%s\t\t%d\t\t分界符\n","{",33);break;
    			case'}' :fprintf(fpout,"%s\t\t%d\t\t分界符\n","}",34);break; 
    			case'%' :fprintf(fpout,"%s\t\t%d\t\t分界符\n","%",35);break;
    			case'\"' :fprintf(fpout,"%s\t\t%d\t\t分界符\n","\"",36);break;
    			case'\\' :fprintf(fpout,"%s\t\t%d\t\t分界符\n","\\",37);break;  
    			case'#' :fprintf(fpout,"%s\t\t%d\t\t分界符\n","#",38);break;
                case'>' :{
    				ch=fgetc(fpin);
    		        if(ch=='=') 
    				 	fprintf(fpout,"%s\t\t%d\t\t运算符\n",">=",49);
    		        else {
    					fprintf(fpout,"%s\t\t%d\t\t运算符\n",">",50);
    					fseek(fpin,-1L,SEEK_CUR);
    				}
        		}break;
    
                case'<' :{
    				ch=fgetc(fpin);
    		        if(ch=='=') 
    				 	fprintf(fpout,"%s\t\t%d\t\t运算符\n","<=",51);
    		        else if(ch=='>') 
    				 	fprintf(fpout,"%s\t\t%d\n","<>",52);
    		        else{
    				 	fprintf(fpout,"%s\t\t%d\t\t运算符\n","<",53);
    				 	fseek(fpin,-1L,SEEK_CUR);}
                }break;
       /***************出现在/  /之间的全部作为注释部分处理*******************/  
                   case'/' :{
    			   		ch=fgetc(fpin);
    			   		if(ch=='/'){
                          	while(ch!='\n'){
    					 		ch=fgetc(fpin);
    					  	}
    					  }
    				    else if(ch=='*'){
                          	while(ch!='/'&&ch!=EOF){
    					 		ch=fgetc(fpin);
    					  	}
                          	if(ch==EOF) 
    					  		fprintf(fpout,"缺少一个'/'");}
    				    else {
    				   		fprintf(fpout,"%s\t\t%d\t\t运算符\n","/",39);
    				   		fseek(fpin,-1L,SEEK_CUR);
    						}
    				   	break;
                    }
       /***************非法字符*******************/                         
                   default :fprintf(fpout,"在第%d行无法识别的字符\t%c\n",Line_NO,ch);
            }
        }
    }
    /**********主程序中完成对输入输出文件的读写***********/
    int main(){
       char in_fn[25],out_fn[25];
       FILE * fpin,* fpout;  printf("<<<<<<<<<<<<<<<<<<WELCOME>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
       printf("..............词法分析程序实验......................\n");
       printf("\n");
       printf(".....目前共有词法程序范例:   1个   \n");
       printf(".....程序范例1:data.txt \n");
       printf(".....输入一个已有的程序:\n");
       scanf("%s",in_fn);
       printf(".....输入你想要保存分析的目标文件名:\n");
       scanf("%s",out_fn);
       fpin=fopen(in_fn,"r");
       fpout=fopen(out_fn,"w");
       fprintf(fpout,"单词符\t\t种类编码\t\t种类\n");
       init();
       analyse(fpin,fpout);
       fclose(fpin);
       fclose(fpout);
       printf(".....程序已分析完成分析并保存至目标文件\n");
       printf("........<谢谢使用>......\n");
       return 0;
    }
    
    

    在这里插入图片描述
    在这里插入图片描述
    我用这个代码本身做的测试!汉字不能够分析,还有一些字符也没有加进去
    如&,|。等有兴趣的小伙伴可以试试!
    给大家看看结果!!!
    在这里插入图片描述
    因为加种类编码,所以可能有些混乱!看懂原理就行了!结构不是很复杂,希望对大家有所帮助。

    展开全文
  • 词法分析器c语言编写

    2008-11-25 15:24:14
    c语言编写词法分析器,挺好用的,我已调试过
  • 词法分析器c语言编写 在编写使用模拟对象的单元测试时,请遵循以下步骤: 配置我们的模拟对象的行为。 调用测试的方法。 验证是否已调用模拟对象的正确方法。 第三步的描述实际上有点误导,因为通常我们...
  • 词法分析器c语言编写 很难为干净的代码找到一个好的定义,因为我们每个人都有自己的“干净”一词定义。 但是,有一个似乎是通用的定义: 简洁的代码易于阅读。 这可能会让您感到有些惊讶,但我认为该定义也适用于...
  • C计算机科学与工程系编译原理课程设计实验报告姓名:__ ******__ 学号_ *******__ 年级专业及班级___08计算机科学与技术成绩实验名称词法分析程序设计与实现完成日期2011/4/12 指导教师*******实验目的:能够采用C...
  • 二、课程设计任务:完成下述文法所描述的单词符号的词法分析程序。 <标识符>--><字母>|<标识符><字母>|<标识符><数字> <无符号整数>--><数字>|<无符号整数><数字> <分界符>-->+|-|*|/|;|(|)|{|}|<||=|!=|>=|>|:=|...
  • 编译原理第一个实验,用C语言编写词法分析器(附源文件)。输入一串字符,可以识别出关键字、运算符,标识符等。
  • 编译原理:c语言词法分析器的实现

    千次阅读 2021-07-08 19:30:31
    编译原理:c语言词法分析器的实现欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容...
  • C语言编写一个C语言源程序的词法分析器(标题收回) 2.要求 [1] 基本要求:识别关键字、运算符、界限符、常量(布尔型、整型)、标识符; [2] 扩展要求:常量(浮点型)、注释、错误处理。 3.对照表 ...
  • c语言实现词法分析器+文法分析器(全代码)
  • 问题描述:用C或C++语言编写一个简单的词法分析程序,扫描C语言小子集的源程序,根据给定的词法规则,识别单词,填写相应的表。如果产生词法错误,则显示错误信息、位置,并试图从错误中恢复。简单的恢复方法是忽略...
  • 河北工业大学编译原理词法分析程序,VCVS都可以直接运行,符合指导书的设计
  • 编译原理——词法分析器C语言实现

    千次阅读 多人点赞 2020-04-26 15:12:55
    根据上面的分析,二元组token设置为: 单词符号与其种别码及内码值 想要编写一个词法分析器,首先需要画出词法的状态转换图,根据状态转换图即可设计相应的程序来分析词法。而状态转化图的设计还需要给定单词符号...
  • 编译原理实验一(C-语言词法分析器编写C语言版本) 1.词法规则 ①语言的关键字: else if int return void while 所有的关键字都是保留字,并且必须是小写。 ②专用符号: + - * / < <= > &...
  • 简易C语言词法分析器

    2021-04-27 16:05:50
    C代码实现的C语言词法分析器,包含源码和可执行文件
  • c语言实现的简易语言词法分析器,学习编译原理。
  • 词法分析器1、实验内容2、前期准备2.1 LEX原理2.2 待分析的C语言子集的词法2.3 C语言子集的单词符号表示表2.4 C语言子集对应的状态转换图3、分析与运行3.1 代码编写3.2 源代码3.3 代码运行4、遇到的问题 1、实验内容...
  • 词法分析器(c语言实现)》由会员分享,可在线阅读,更多相关《词法分析器(c语言实现)(6页珍藏版)》请在人人文库网上搜索。1、词法分析c实现一、 实验目的设计、编制并调试一个词法分析程序,加深对词法分析原理的...

空空如也

空空如也

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

词法分析器c语言编写

友情链接: cornerdetect.rar