zoukankan      html  css  js  c++  java
  • 计算器:递归下降法构造,支持标识符 yongmou

    文法,用BNF表示
    <Expr>    -> <Term> { (+|-) <Term> }
    <Term>    -> <Factor> { (*|/) <Factor> }
    <Factor>  -> (<Expr>)
    | num
    | indent
    | indent = <Expr>
    num 表示十进制实数
    indent 表示标识符。标识符是由字母和数字构成的序列,第一个字符必须是字母
    另外要求,Expr以';'结尾。
    根据语法分析树的特点,位于低层的先被求值,也就是说优先级高。
    由文法可以看出: *、/ 优先级最高; +、-次之;赋值操作符=最低,另外赋值操作符具有右结合性,例如a=b=c;等价于a=(b=c);。(这些都是包含在文法中的,不是约定)
    1. 词法分析
      Token种类:实数、标识符、运算符、分隔符。
    enum token_type{
    NUMBER, IDENT, OPERATOR, SEPARATOR, UNKOWN
    };
       
      Token结构
    struct Token{
    int type;
    union{
    double val; // 数值
    char *name; // 标识符名字
    int op; // 运算符、分隔符,以及其他单个字符
    }u;
    };
      使用hash存储标识符
      HashNode结构
    // hash, 标识符名字,和其对应的数值
    struct HashNode{
    char *name;
    double value;
    struct HashNode *next;
    };
    typedef
    struct HashNode HashNode;
        字符串hash 函数
    int hash(char *s)
    {
    int hashval;
    for (hashval = 0; *s != '\0'; ++s)
    hashval
    = *s + 31 * hashval;
    if(hashval < 0)
    hashval
    = -hashval;
    return hashval % HASHSIZE;
    }
      lookup(char *name)查询存储存储name的HashNode
    HashNode *lookup(char *name)
    {
    int hash_value = hash(name);
    HashNode
    *ptr;
    ptr
    = hash_table[hash_value];
    for (; ptr != NULL; ptr = ptr->next)
    if (strcmp(ptr->name, name) == 0)
    return ptr;

    // 没有找到
    ptr = (HashNode *)malloc(sizeof(HashNode));
    ptr
    ->name = (char *)malloc(strlen(name) * sizeof(char) + 1);
    strcpy(ptr
    ->name, name);
    ptr
    ->value = 0;

    ptr
    ->next = hash_table[hash_value];
    hash_table[hash_value]
    = ptr;

    return ptr;
    }
      词法分析程序:
    struct Token next_token;
    // 词法分析程序
    void lex(){
    int c;
    while((c = getchar()) != EOF && isspace(c))
    ;
    if(isdigit(c)){ // 识别实数
    next_token.type = NUMBER;
    next_token.u.val
    = get_num(c);
    return;
    }
    else if(isalpha(c)){ // 识别标识符
    char *name = get_ident(c);
    name
    = lookup(name)->name;
    next_token.type
    = IDENT;
    next_token.u.name
    = name;
    return;
    }
    // 其他
    next_token.u.op = c;
    switch(c){
    case '+':
    case '-':
    case '*':
    case '/':
    case '=':
    next_token.type
    = OPERATOR;
    break;
    case '(':
    case ')':
    case ';':
    next_token.type
    = SEPARATOR;
    break;
    default:
    next_token.type
    = UNKOWN;
    }
    }

    double get_num(char c){
    int val;
    val
    = c - '0';
    while (isdigit(c = getchar()))
    val
    = val * 10 + c - '0';
    int n = 0;
    if(c == '.'){
    while (isdigit(c = getchar())){
    n
    ++;
    val
    = val * 10 + c - '0';
    }
    }
    ungetc(c, stdin);
    return (val / pow(10, n));
    }

    #define BUFSIZE 100
    char *get_ident(char c){
    static char buf[BUFSIZE];
    int bufp = 0;

    buf[bufp
    ++] = c;
    while (isalnum(c = getchar()))
    buf[bufp
    ++] = c;
    ungetc(c, stdin);
    buf[bufp]
    = '\0';
    return buf;
    }

      
    2. 语法分析
    编写递归下降子程序有一个协定:
    每一个子程序都在全局量nextToken中放入下一个输入的token。
    因而,当开始一个语法分析函数时,假定nextToken具有还没有被语法分析过程使用的输入标记中最左面的那个标记。
    还有另一种解决方案,每个递归子程序开始都自己取下一个标记,若不能处理则放回。我们采用前一种solution。
    三个非终结符对应三个递归子程序:
    表达式:
    // <expr> -> <term> { (+ | -) <term> }
    double expr()
    {
    double val;

    // 分析第一个term
    val = term();

    // 只要下一个token是 + 或 - ,
    // 调用lex() 取得下一个token,
    // 然后分析下一个term
    while(next_token.type == OPERATOR &&
    (next_token.u.op
    == '+' || next_token.u.op == '-')){
    int op = next_token.u.op;
    lex();

    if(op == '+')
    val
    += term();
    else if(op == '-')
    val
    -= term();
    }
    return val;
    }

    项:
    // <term> -> <factor> { (* | /) <factor> }
    double term()
    {
    double val;

    // 分析第一个factor
    val = factor();

    // 只要下一个token是 * 或 / ,
    // 调用lex() 取得下一个token,
    // 然后分析下一个factor
    while(next_token.type == OPERATOR &&
    (next_token.u.op
    == '*' || next_token.u.op == '/')){
    int op = next_token.u.op;
    lex();

    if(op == '*'){
    val
    *= factor();
    }
    else if(op == '/'){
    int tmp;
    tmp
    = factor();
    if(fabs(tmp) < 1e-6){
    fprintf(stderr,
    "除0");
    exit(
    1);
    }
    else{
    val
    /= tmp;
    }
    }
    }
    return val;
    }

    因子:
    // <factor> -> (<expr>) | num | ident | ident = <expr>
    double factor(){
    double val;
    if(next_token.type == SEPARATOR && next_token.u.op == '('){
    lex();
    val
    = expr();
    if(next_token.type == SEPARATOR && next_token.u.op == ')'){
    lex();
    // 读取下一个token
    }else{
    fprintf(stderr,
    "括号不匹配\n");
    exit(
    1);
    }
    }
    else if(next_token.type == NUMBER){
    val
    = next_token.u.val;
    lex();
    }
    else if(next_token.type == IDENT){
    HashNode
    *ptr;
    ptr
    = lookup(next_token.u.name);
    lex();
    if(next_token.type == OPERATOR && next_token.u.op == '='){
    // 赋值操作
    lex();
    val
    = expr();
    ptr
    ->value = val;
    }
    else{
    val
    = ptr->value;
    }
    }
    else{ // 既不是数字也不是'(', 也不是标识符
    fprintf(stderr, "unkown charactor: %c\n", next_token.u.op);
    exit(
    1);
    }
    return val;
    }

    3、在语法分析的同时进行求值
    递归下降法的优点之一,就语义加工来说,这种方法十分灵活,可以在子程序的任何地方插入语义处理程序。
    4、错误处理
    括号是否匹配,除0。
    目录树
    ./
    |-- hash.h
    |-- hash.c
    |-- main.c
    |-- makefile
    |-- front.h         词法分析程序和语法分析程序的头文件
    |-- scanner.c     词法分析
    |-- parser.c       语法分析
    |-- specification.txt   说明
    `-- test.in       测试数据
       最后给出完整的程序包和运行演示:
    [lym@localhost calculator]$ make
    gcc -g -Wall -c hash.c
    gcc -g -Wall -c parser.c
    gcc -g -Wall -c scanner.c
    gcc -g -Wall -c main.c
    gcc -g -Wall hash.o parser.o scanner.o main.o -o cal -lm
    [lym@localhost calculator]$ ./cal
    a = 10;
    10.000000
    b = 20;
    20.000000
    a + b * 5;
    110.000000
    a = 20;
    20.000000
    (a+b)*5;
    200.000000
    (a+b)/5;
    8.000000
    a = 3.14;
    3.140000
    a * 2 * 2;
    12.560000
    

  • 相关阅读:
    [MSDN] How to Debug a Release Build
    抽象成员 虚方法
    强制类型转换符 和 as 运算符
    一份超长的MySQL学习笔记
    Java反射基础
    c3p0config.xml
    一个JDBC封装工具类
    Spring5——IOC操作Bean管理(基于xml文件)
    Android游戏开发大全
    移除项目里的所有.svn命令
  • 原文地址:https://www.cnblogs.com/liyongmou/p/2014641.html
Copyright © 2011-2022 走看看