【手搓一个脚本语言】十、用C语言抽象语法树AST实现变量的引用

【手搓一个脚本语言】十、用C语言抽象语法树AST实现变量的引用

1、变量的保存

  • 变量数量是零到多个,解决这种不确定性,最好的办法是链表!!!
  • 变量相关的两个重要元素,变量名和变量值,封装到一个数据结构Var中!
  • 再将数据结构Var做为数据保存到链表的节点中!
  • 最后实现链表的相关功能:创建、追加、释放等!

1.1 定义Var数据类型

/* define variable datatype */
typedef struct _Variable Var;
struct _Variable {
   
   
  char name[32];
  Token value;
};

1.2 定义VarNode数据类型

/* define variable node datatype */
typedef struct _VarNode *VarNode;
struct _VarNode {
   
   
  Var var;
  VarNode next;
};

1.3 定义函数指针,测试遍历链表时使用

/* define function pointer of foreach function */
typedef void (*VarFunc) (VarNode node);

1.4 创建链表节点

/* create a new varnode */
VarNode
varnode_new (Var x)
{
   
   
  VarNode node = (VarNode) malloc (sizeof(struct _VarNode));
  node->var = x; node->next = NULL;
  return node;
}

1.5 释放链表至尾节点

/* free the varnode */
void
varnode_free (VarNode node)
{
   
   
  while (node != NULL)
    {
   
   
      VarNode tmp = node->next;
      free (node);
      node = tmp;
    }
}

1.6 追加节点至链表

/* append vnt to node */
VarNode
varnode_append (VarNode node, VarNode vnt)
{
   
   
  VarNode tmp = node;
  if (tmp == NULL) return vnt;
  while (tmp->next != NULL)
    tmp = tmp->next;
  tmp->next = vnt;
  return node;
}

1.7 取链表尾节点

/* get last varnode */
VarNode
varnode_last (VarNode node)
{
   
   
  VarNode tmp = node;
  if (tmp == NULL) return NULL;
  while (tmp->next != NULL)
    tmp = tmp->next;
  return tmp;
}

1.8 遍历链表,调用函数指针,处理链表节点

/* foreach varnode call vf */
void
varnode_foreach (VarNode node, VarFunc vf)
{
   
   
  while (node != NULL)
    {
   
   
      VarNode tmp = node->next;
      vf (node);
      node = tmp;
    }
}

2、测试变量定义

  • 在test_eval函数中测试表达式:a=9+10,结果是19,符合预期!
  • 如此只要将变量名a和值19保存到链表中即可!
EXPR: a=9+10
--------------------
IDNT: a
ASGN: =
NUMB: 9
  OP: +
NUMB: 10
--------------------
 MID:  a = 9 + 10
--------------------
PREV:  ( = a ( + 9 10 ) )
--------------------
    <[a]
[=]
        <[9]
    >[+]
        >[10]
--------------------
POST:  a 9 10 + =
--------------------
Node count: 5
Node index: 4
PUSH 9, SP: 0
PUSH 10, SP: 1
ADD 9 10 ==> 19
--------------------
RESULT: 19
--------------------

3、运算赋值表达式

  • 定义函数eval_varast,参数一为parse_string函数返回的AstNode,参数二为变量链表首节点
  • 返回值为链表的首节点
/* evalute variable define | assignment */
VarNode
eval_varast (AstNode root, VarNode vlst)
{
   
   
  Token tk;
  Var var;
  VarNode vnode;
  memset (var.name, 0, 32);
  memcpy (var.name, root->left->token.V.sval, strlen (root->left->token.V.sval));
  vnode = varnode_new (var);
  tk = eval_astnode (root);
  vnode->var.value = tk;
  PF("RESULT: %ld\n", tk.V.ival);
  if (vlst == NULL)
    return vnode;
  else
    vlst = varnode_append (vlst, vnode);
  return vlst;
}

4、测试函数test_eval_var

  • 可以定义一个字符串数组来存储表达式字符串,然后循环读取并运行表达式!
/* out variable name and value */
static void
out_var (VarNode node)
{
   
   
  PF("VAR NAME: [%s], VALUE: [%ld]\n", node->var.name, node->var.value.V.ival);
}

/* test eval_varast function */
void
test_eval_var (void)
{
   
   
  VarNode varlist = NULL;
  AstNode root = NULL;
  Token tk;
  //char *st = "a=9";
  char *st[1] = {
   
    "width=9+2*5" };
  //char *st[3] = { "a=10", "b=9+10", "c=(2+8)*9" };

  for (int i = 0; i < 1; i++)
    {
   
   
      PF("expression: %s\n", st[i]);
      PF("--------------------\n");
      root = parse_string (st[i]);
      if (root->token.type == T_OPER && root->token.V.oval[0] == '=') //assignment
	{
   
   
	  PF("--------------------\n");
	  varlist = eval_varast (root, varlist);
	  if (varlist == NULL) PF("VARNODE IS NULL!\n");
	  PF("--------------------\n");
	  varnode_foreach (varlist, out_var);
	  PF("--------------------\n");
	}
      else
	{
   
   
	  PF("Not assignment expression!\n");
	}
      astnode_free (root);
    }

  varnode_free (varlist);
}

5、编译运行

expression: width=9+2*5
--------------------
IDNT: width
ASGN: =
NUMB: 9
  OP: +
NUMB: 2
  OP: *
NUMB: 5
--------------------
PUSH 9, SP: 0
PUSH 2, SP: 1
PUSH 5, SP: 2
MUL 2 5 ==> 10
ADD 9 10 ==> 19
RESULT: 19
--------------------
VAR NAME: [width], VALUE: [19]
--------------------

6、检查内存分配情况

  • 提示出错,将eval_astnode函数中的malloc函数分配的tks数组初始化一下,值都置为0!
  for (int i = 0; i < count; i++)
    {
   
    tks[i].type = 0; tks[i].V.nval = 0; }
  • 再编译运行,测试检查内存分配情况,输出结果如下:
==3718== Memcheck, a memory error detector
==3718== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==3718== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==3718== Command: ./gt
==3718== 
expression: a=10
--------------------
IDNT: a
ASGN: =
NUMB: 10
--------------------
PUSH 10, SP: 0
RESULT: 10
--------------------
VAR NAME: [a], VALUE: [10]
--------------------
expression: b=9+10
--------------------
IDNT: b
ASGN: =
NUMB: 9
  OP: +
NUMB: 10
--------------------
PUSH 9, SP: 0
PUSH 10, SP: 1
ADD 9 10 ==> 19
RESULT: 19
--------------------
VAR NAME: [a], VALUE: [10]
VAR NAME: [b], VALUE: [19]
--------------------
expression: c=(2+8)*9
--------------------
IDNT: c
ASGN: =
LPAR: (
NUMB: 2
  OP: +
NUMB: 8
RPAR: )
  OP: *
NUMB: 9
--------------------
PUSH 2, SP: 0
PUSH 8, SP: 1
ADD 2 8 ==> 10
PUSH 9, SP: 1
MUL 10 9 ==> 90
RESULT: 90
--------------------
VAR NAME: [a], VALUE: [10]
VAR NAME: [b], VALUE: [19]
VAR NAME: [c], VALUE: [90]
--------------------
==3718== 
==3718== HEAP SUMMARY:
==3718==     in use at exit: 0 bytes in 0 blocks
==3718==   total heap usage: 25 allocs, 25 frees, 926 bytes allocated
==3718== 
==3718== All heap blocks were freed -- no leaks are possible
==3718== 
==3718== For counts of detected and suppressed errors, rerun with: -v
==3718== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

7、完整代码

/* filename gt.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* compile: gcc gt.c -o gt */
/*     run: ./gt           */
/* memcheck: valgrind --leak-check=yes ./gt */

/* debug on|off */
#define GT_DEBUG 1

#if GT_DEBUG
#define PFI(...) fprintf (stderr, __VA_ARGS__)
#else
#define PFI(...)
#endif
#define PF
### C语言中的抽象语法树AST)构建与解析 #### 定义与概念 抽象语法树(Abstract Syntax Tree, AST)是一种用于表示程序源代码的中间形式。它由词法分析和语法分析阶段生成,通常作为编译器或解释器的一部分存在[^2]。在C语言中,AST 是一种重要的中间表示形式,能够帮助优化器理解代码逻辑并生成高效的机器码。 具体来说,在 GCC 编译器中,AST 被视为源代码的一种中间表示形式,它是通过前端的词法和语法分析过程构造出来的[^3]。这种树形结构保留了源代码的主要语义信息,同时去除了无关紧要的部分(如括号、分隔符等),从而便于进一步处理。 --- #### 构建过程 构建 C 语言AST 需要经历以下几个核心环节: 1. **词法分析** 使用词法分析器将源代码转换成一系列标记(Token)。这些 Token 表示基本的语言单元,例如关键字、变量名、运算符等。此阶段的目标是识别输入字符串的基本组成部分。 2. **语法分析** 利用上下文无关文法规则,将 Token 序列转化为一棵语法树。这棵树反映了源代码的句法结构,并遵循特定编程语言的语法规则。在此过程中,可能会应用递归下降算法或其他解析技术来验证代码的有效性。 3. **抽象化** 将原始的语法树简化为更加紧凑的形式——即抽象语法树AST)。这一阶段涉及删除冗余节点以及重新组织某些部分以便于后续操作。例如,表达式的二叉树可能被压缩以减少不必要的嵌套层次[^1]。 --- #### 实现工具 为了方便开发者研究或扩展 C 的 AST 功能,社区提供了多种实用工具和技术栈: - **pycparser**: 这是一个 Python 实现的 C99 语言解析器,可以从头开始创建完整的 AST 结构[^5]。借助 pycparser 可以轻松探索标准库文件或者自定义头文件的内容。 - **Clang/LLVM**: Clang 提供了一个强大的 API 来访问其内部使用的 AST 数据结构。相比动编写解析器,利用现有框架往往能节省大量时间和精力。 以下是使用 `pycparser` 创建简单 C 文件 AST 的例子: ```python from pycparser import parse_file def generate_ast(filename): ast = parse_file(filename, use_cpp=True) ast.show() # 显示整个 AST 层次关系 if __name__ == "__main__": filename = "example.c" generate_ast(filename) ``` 上述脚本读取名为 `example.c` 的 C 源文件,并打印对应的 AST 输出。 --- #### 复杂场景下的挑战 当面对复杂的继承体系或多态行为时,传统的 Visitor 模式可能出现局限性。例如,在早期版本的 Python 中引入多方法机制时发现,如果依赖声明顺序决定匹配策略,则可能导致维护成本增加甚至错误频发[^4]。类似问题也可能存在于大型 C 工程项目里;因此设计良好的 AST 和遍历方式至关重要。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值