news 2026/11/4 16:46:28

从 lex/yacc 到 SQL 执行:编译原理前端与 AST 落地

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从 lex/yacc 到 SQL 执行:编译原理前端与 AST 落地

简介:面向编译原理课程设计与上机实践场景的《DBMS 的设计与实现》报告,适合计算机科学与技术等专业学生参考编译器构建与数据库管理系统入门实现的完整思路。报告围绕项目概况、编译部分、DBMS 部分和总结展开,涵盖开发环境与工具、词法分析、语法分析、物理结构及主要功能等内容,并结合 lex/yacc 的使用说明语句解析流程。压缩包内共 1 个 doc 文件,约 82KB,以课程报告文档形式呈现,便于直接查阅和整理报告结构。已有 1332 人学习,内容预览可见其包含人员分工、完成情况、正规式与词法分析代码片段、上机心得等,能帮助读者了解从词法/语法分析到简单 DBMS 功能设计的组织方式,也可作为撰写上机报告、梳理实验步骤和复盘实现难点的参考。

1. 手写一个 SQL 子集的编译器,比读十页龙书更管用

有个朋友接了个课设辅导的活,学生交上来的东西是“能识别 CREATE TABLE、SELECT、INSERT 这些语句,但一句也不会执行”。听起来像半成品,其实这恰恰是编译原理上机最有价值的那一段:词法分析和语法分析占到了整个工作量的大头,也是最容易出错、最能看出功底的地方。这份报告做的事情就是拿一个 DBMS 的 SQL 子集当靶子,用 lex/yacc 把词法、语法前端搭起来,再往 DBMS 的物理结构和执行流程上对接。

它适合两类人看:一类是要交编译原理课设、需要一份能跑通的参考骨架的人;另一类是已经工作、想回头补一遍“正则式怎么落到 token、产生式怎么落到语法树”的人。工具链很老,思路不老——报告里对 Token 结构、关键字匹配顺序、Name 与 ID 抢字符这些细节的记录,放到今天手写递归下降或者补 flex/bison 都还能直接用上。

2. 工程骨架与工具链:Generator + VC++6.0 里那份报告的三层结构

2.1 报告拆开是三块:词法前端、语法前端、DBMS 执行

把目录翻一遍能看出来,整份东西不是一篇“讲编译原理”的文章,而是一个三段式的工程:第一段是词法分析,用 lex 风格的规则文件把 SQL 文本切成 token;第二段是语法分析,用 yacc 风格的产生式把 token 流归约成语句结构;第三段是 DBMS,负责建库建表、字段定义、记录的物理存放和语句的执行。

这三段在代码组织上是解耦的。词法部分只对外暴露一个“给我下一个 token”的接口,语法部分只依赖 token 类型和值,不关心字符是怎么被吃掉的;DBMS 部分只关心“来了一条什么样的语句”,不关心这条语句是手敲的还是解析出来的。这种切法在 2011 年的上机作业里算是比较规矩的做法,常见做法是把三块全塞进一个 main 函数,改一个关键字要全文搜索,那才是真正的血泪经验。

报告里给出的完成度也很诚实:词法和语法部分完成,DBMS 部分只写到物理结构设计和功能描述,尚未落地。所以读这份材料时要有个心理预期——前面两段可以直接抄作业,后面那段得自己补。

2.2 lex/yacc 的代码生成链路:.l 和 .y 怎么变成 .cpp

报告里写的环境是 Generator 加 VC++6.0。这里的 Generator 指的是那类 Parser Generator 工具,它的特点是直接产出 C++ 类——词法器叫mylexer、语法器叫myparser,各自带构造函数、析构函数和yycreate/yyparse成员函数,和 Unix 下裸 flex/bison 产出的 C 数组风格不一样。文件头那句注释已经说明了来源:

%{ /**************************************************************************** mylexer.l ParserWizard generated Lex file. Date: 2011年4月12日 ****************************************************************************/ #include "myparser.h" #include "define.h" %} %name mylexer %%

这段是 lex 文件的第一段(声明段),%{ %}之间的内容会被原样抄进生成的.cpp文件顶部,所以头文件包含、宏定义、全局变量都写在这里。%name mylexer指定生成类的名字,后面{ }包起来的部分是类成员、构造函数、析构函数,这份报告里全是空的,说明作者没用到额外状态。

第二段%%之后是规则段,第三段%%之后是用户代码段。这种三段式是 lex 的固定骨架,换成 flex 也一样,只是%name没有对应物,得靠%option prefix="my"之类的选项去改符号前缀。

真正要理解的是生成链路:.l文件不是 C++ 源码,得先过一遍 Generator,产出mylexer.cpp和mylexer.h;.y文件同样过一遍,产出myparser.cpp和myparser.h。然后才是 VC++6.0 把它们和define.h、main.cpp一起编译链接。顺序错了就会看到一堆“找不到 myparser.h”的报错。

2.3 生成产物清单与编译顺序

把工程文件摊开,大致是这么几类:

文件来源作用是否手写
mylexer.l手写词法规则、关键字表是
myparser.y手写产生式、token 声明是
define.h手写token 编号、公共结构是
mylexer.cpp/.hGenerator 生成词法器实现否
myparser.cpp/.hGenerator 生成语法器实现否
main.cpp手写创建两个对象并启动解析是

main.cpp在报告里也贴出来了,短得有点反直觉:

int main(void) { int n = 1; mylexer lexer; myparser parser; if (parser.yycreate(&lexer)) { if (lexer.yycreate(&parser)) { n = parser.yyparse(); } } return n; }

这里的关键是yycreate的双向绑定:parser.yycreate(&lexer)把词法器指针挂到语法器上,lexer.yycreate(&parser)反过来把语法器挂到词法器上。为什么要互相挂?因为语法器需要“取下一个 token”,而词法器在动作里需要拿到yylval的引用(规则段开头那行YYSTYPE YYFAR& yylval = *(YYSTYPE YYFAR*)yyparserptr->yylvalptr;就是在干这事)。单向绑定也能跑,但语义值传不回去。

参数说明:yycreate返回非零表示初始化成功,两个都成功才调yyparse(),返回值n就是解析结果,通常 0 表示语法分析正常结束。这种“层层嵌套 if”的写法在 C 里不好看,但胜在不用处理异常。

2.4 搬到现代工具链:flex/bison 的等价命令

如果不想折腾 VC++6.0,把这套东西搬到 Linux 上其实很快。工具换名字,流程不变:

# 1. 生成词法器与语法器 C 源码 flex -o mylexer.c mylexer.l bison -d -o myparser.c myparser.y # 2. 编译链接,注意 -d 生成的 myparser.h 要先被包含 gcc -c mylexer.c myparser.c main.c gcc -o sqldemo mylexer.o myparser.o main.o

-d的作用是让 bison 额外生成头文件,把 token 编号用enum yytokentype导出来,替代原来手写的define.h。-o指定输出文件名,不指定的话 flex 默认产出lex.yy.c、bison 产出y.tab.c,混在一起很难管。这套命令我一般放在 Makefile 里,改.l就make一下,比在 IDE 里点半天菜单省事。

需要注意的差异:bison 生成的解析器是yyparse()全局函数,不是类;语义值叫yylval,类型用%union定义;词法器里返回 token 的写法return CREATE;可以直接沿用。改造成本主要在把yycreate那套对象化的初始化换成全局变量,半小时的活。

3. 词法分析:mylexer.l 里的 Token 定义与关键字规则

3.1 Token 结构与 define.h 的约定

词法器的产出物是一个个 token,token 至少要有两样东西:它是什么类型、它的值是什么。报告里定义的struct Token有三字段:

struct Token { int type; /* token 类型,对应 define.h 里的宏 */ char *yych; /* 词素原文,比如表名、字段名 */ int value; /* 数值型 token 的值 */ };

type是语法器唯一认的东西,语法产生式里写的CREATE、Name、NUMBER全部是type的取值。yych存原始字符串,建表语句里的表名、字段名都靠它往下传,因为语法器不知道“users”这个词长什么样,它只收到一个Name和一段字符串。value只在数字场景用得上,CHAR(20)里的 20 就得从这里取。

这种“类型 + 原文 + 数值”的三件套几乎是所有教学型词法器的标配。实际工程里更常见的是一个union加一个kind字段,省内存;但教学代码用结构体更直观,调试时printf一下就能看到全部信息,属于可接受的浪费。

3.2 mylexer.l 的三段式结构

规则段是整份词法文件的核心,把关键字全列了一遍:

%% "\n" { /* 换行,直接丢弃 */ } ''' { /* 单引号,原报告这里写法有问题,见第 5 章 */ } [ \t] { /* 空白字符,跳过 */ } "CREATE" { return CREATE; } "DATABASE" { return DATABASE; } "DATABASES" { return DATABASES; } "USE" { return USE; } "SHOW" { return SHOW; } "TABLE" { return TABLE; } "TABLES" { return TABLES; } "SELECT" { return SELECT; } "DELETE" { return DELETE; } "INSERT" { return INSERT; } "VALUES" { return VALUES; } "UPDATE" { return UPDATE; } "DROP" { return DROP; } "EXIT" { return EXIT; } "INT" { return INT; } "CHAR" { return CHAR; } "AND" { return AND; } "OR" { return OR; } "WHERE" { return WHERE; } "INTO" { return INTO; } "SET" { return SET; } "FROM" { return FROM; } "(" { return '('; } ")" { return ')'; } ";" { return ';'; } "," { return ','; } "*" { return '*'; } ">" { return '>'; } "<" { return '<'; } "=" { return '='; } "!=" { return NE; } [0-9]+ { return NUMBER; } [a-zA-Z][_0-9a-zA-Z()\.\*]* { printf("%s",yytext); return Name; } [a-zA-Z][_a-zA-Z0-9]+ { return ID; } %%

规则的读法是“左边模式,右边动作”。带双引号的模式是字面量匹配,匹配上就把对应的 token 编号返回给语法器;[0-9]+是正则,匹配一串数字。单字符运算符直接返回字符本身,因为 yacc 允许产生式里写'('这样的字符字面量,省去一堆 token 定义。

yytext是 flex/lex 内置的指针,指向本次匹配到的原始文本。printf("%s", yytext)那行其实是调试残留,正式版应该删掉或者改成写yylval.yych,否则每识别到一个名字就往屏幕上吐一次,输出会乱成一锅粥。

3.3 关键字表与最长匹配:为什么 Name 规则放在 ID 前面

看最后两条规则的顺序很有讲究:

[0-9]+ { return NUMBER; } [a-zA-Z][_0-9a-zA-Z()\.\*]* { printf("%s",yytext); return Name; } [a-zA-Z][_a-zA-Z0-9]+ { return ID; }

lex 的匹配原则是“最长匹配优先,长度相同则靠前的规则优先”。Name那条允许字符里出现括号、点号和星号,是为了兼容db.tbl这种带库名前缀的写法,以及t.*这种通配;ID那条只允许下划线和字母数字,是标准的标识符形态。两条都能匹配users这个串,长度一样,谁在前面谁赢,所以Name被放在了前面。

这个顺序直接决定了语法器收到的 token 种类:所有裸标识符都变成Name,ID实际上很难被触发。这在当时的测试用例里没出问题,但埋了个坑——第 5 章会专门说NUMBER被Name抢走的那次翻车。

顺带说一句,关键字用"CREATE"这种字面量规则、标识符用正则规则,两者不会打架:CREATE匹配长度 6,但Name规则也能匹配CREATE且长度相同,靠前的字面量规则赢,所以关键字优先级天然高于标识符。这是 lex 最省心的一点,不需要像手写词法器那样维护一张哈希表再判断“这个词是不是关键字”。

3.4 单引号与空白处理的那点事

空白处理用的是[ \t] { },动作体是空的,意思是匹配到空格或制表符就什么也不返回,词法器继续找下一个能返回 token 的模式。换行同理。这种“吞掉不返回”的规则必须写在前面,否则空格会被当成未识别字符触发默认动作(一般是回显到标准输出),看着像程序出了玄学问题。

单引号那条规则是整份文件里唯一明显写错的:'''在 lex 里不是一个合法的单引号匹配。要在规则里匹配一个单引号字符,得写成\'或者用双引号包起来"'"。报告作者在“存在问题及解决方法”里也承认了这一点,说“在识别单引号时由于没有使用斜线标示,导致无法识别”,后来翻了课本才改对。这条记录挺真实的,因为单引号在 SQL 里是字符串常量的边界,一旦识别不了,INSERT INTO t VALUES ('abc')这种语句从词法层就断了。

修法很简单,把规则改成:

"'" { return '\''; } /* 字符串常量起始/结束 */ [ \t\n] { /* 统一吞掉所有空白 */ }

'\''是 C 里的转义单引号字符常量,作为 token 编号返回给语法器,语法器那边用'\''接住,配对逻辑就通了。这一步看着小,但不修的话后面所有带字符串的 INSERT 都过不了,属于“卡在最前面”的那类问题。

4. 语法分析:myparser.y 产生式与语法树落地

4.1 声明段:%token 与优先级声明

.y文件和.l的结构对称,也是三段。声明段先解决“有哪些 token”:

%include { #ifndef YYSTYPE #define YYSTYPE int #endif } %token ID NUMBER Name %token CREATE USE SHOW DROP %token SELECT INSERT VALUES DELETE UPDATE %token TABLE DATABASE TABLES DATABASES %token CHAR INT NULLX %token EXIT %token WHERE INTO SET FROM %token '(' ')' '*' '>' '<' '=' %token NE %left AND OR %token ',' ';' %%

%token是告诉语法器“这个词法层会送过来”,名字和.l里return的一致。单字符的'('、')'也声明了一遍,其实 yacc 允许直接用字符字面量而不声明,写上去纯粹是为了读着清楚。

%left AND OR是唯一一条优先级声明,含义是AND和OR左结合、优先级相同。这条声明直接影响WHERE a = 1 AND b = 2 OR c = 3这种条件串的解析树形状。严格说AND的优先级应该高于OR,写成两行%left OR在前、%left AND在后才对;报告里合并成一行,等于把两者当同级别处理,条件表达式会出现结合性歧义。这是教学代码常见的简化,功能上不影响跑通,但语义上是错的。

YYSTYPE在这里被定义成int,说明语义值只传整数。这对教学够用,但一旦要在归约动作里传字符串(表名、字段名),就得改成union或者结构体指针。报告里没做这一步,语法树是“打出来看的”,不是“存下来用的”,这也是为什么它只能验证语法正确、不能真正执行。

4.2 sts 与 st:语句列表的闭包写法

规则段开头两条产生式决定了整个程序的骨架:

sts: sts st { printf("Operation is ok\n"); } | /* empty */ ; st: create_st ';' | show_st ';' | drop_st ';' | use_st ';' | select_st ';' | delete_st ';' | update_st ';' | insert_st ';' ;

sts是开始符,第一个产生式sts st是标准的左递归写法,作用等价于正则里的st*——允许一个程序里连续写多条语句,每条以分号结尾。第二条空产生式让“空输入”也能合法归约,否则空文件会直接报语法错误。左递归在这里不是随便写的,yacc 是 LALR(1) 解析器,左递归产生式消耗栈空间恒定,右递归写多了反而容易栈溢出,而且左递归配%left声明消除冲突更自然。

st是语句分派点,八条产生式对应八种顶层语句,每条都强制以';'结尾。这个设计把“语句之间怎么分隔”这件事收敛到了一处,后面加新语句类型只要在st上加一条分支,sts完全不用动。我更倾向于把';'放进各个子产生式里(比如create_st: CREATE DATABASE Name ';'),好处是报错位置更准——现在如果漏了分号,错误会报在st这个非终结符上,不告诉你到底哪条语句缺了。

4.3 各语句产生式的拆解

建表这条最能看出“用产生式描述结构”的写法:

create_st : CREATE DATABASE Name | CREATE TABLE Name '(' create_field ')' ; create_field: create_field ',' field_type | field_type ; field_type : field type ; type : CHAR '(' NUMBER ')' | INT ;

读法是自上而下递归。CREATE TABLE后面跟表名,然后一对括号,括号里是字段列表;字段列表用左递归展开成字段, 字段, 字段,最少一个字段;每个字段是“名字 + 类型”,类型要么是INT,要么是CHAR(n),其中 n 是数字 token。

这里的嵌套层次是create_st → create_field → field_type → type,四层。层数多不是坏事,每一层对应一个语义单位:字段列表、字段定义、类型,后面写语义动作的时候正好一层挂一个动作。挤成一条产生式也能解析,但动作就没地方挂了。

查询和更新这两条稍微复杂些:

select_st : SELECT select_name FROM tables | SELECT select_name FROM tables WHERE conditions ; select_name : table_fields | '*' ; update_st : UPDATE table SET set_fields WHERE conditions ; set_fields : set_field | set_field ',' set_field ; set_field : comp_left comp_op comp_right ; conditions : condition | '(' conditions ')' | conditions AND conditions | conditions OR conditions ; condition : comp_left comp_op comp_right ; comp_left : table_field ; comp_right : table_field | NUMBER ; comp_op : '<' | '>' | '=' | NE ;

conditions是标准的二元表达式文法,带括号、带AND/OR递归。conditions AND conditions和%left AND OR配合,能正确消掉二义性——没有优先级声明的话,这种产生式会产生移进-归约冲突,yacc 会警告“conflicts: 1 shift/reduce”,然后按默认策略(移进)处理,结果可能和你想的不一样。

comp_right只允许table_field或NUMBER,也就是说WHERE a = b和WHERE a = 1都能解析,但WHERE a = 'abc'不行,因为词法层的字符串 token 没接进来。这个限制在报告当时的完成度下是合理的,补的话在comp_right上加一条| literal、literal里加Name | NUMBER | STRING即可。

4.4 语法树的数据结构与从归约到执行的衔接

报告里用图形描述了典型语句的语法树,比如CREATE TABLE那棵树的形状是根节点CREATE TABLE、两个子节点分别是表名和字段定义,SELECT * FROM t WHERE a = 1的树根是SELECT,下面挂字段列表、表名、条件子树。图形是给人看的,代码要落地得有一个结构体:

/* 语法树节点:教学版够用,工程版要加链表挂兄弟节点 */ typedef enum { N_STMT_LIST, N_CREATE_DB, N_CREATE_TABLE, N_SELECT, N_INSERT, N_UPDATE, N_DELETE, N_DROP } NodeKind; typedef struct AstNode { NodeKind kind; /* 节点类型,决定怎么解释后续字段 */ struct AstNode *child; /* 第一个子节点 */ struct AstNode *next; /* 兄弟节点,构成列表 */ char *name; /* 表名/字段名/库名 */ int ival; /* 类型长度、数字常量 */ char op; /* 比较运算符 < > = 或 NE */ } AstNode;

字段说明一下:child和next这套“左孩子右兄弟”的表示法,是为了让create_field这种列表型产生式能挂成链,不用给每种节点写不同的结构体;name存yych里那份原文,用完要strdup,不能直接指yytext,因为yytext指向的缓冲区下一轮就被覆盖了,这是新手最容易翻车的地方;ival同时承担CHAR(20)的长度和WHERE a = 1里的常量,靠kind区分场景。

有了这个结构,归约动作就从printf变成建节点:

create_st : CREATE TABLE Name '(' create_field ')' { $$ = mk_node(N_CREATE_TABLE); $$->name = strdup($3); /* 表名 */ $$->child = $5; /* 字段链表 */ } ;

$$是归约结果的语义值,$3、$5是产生式右部第 3、5 个符号的语义值。mk_node是自己写的分配函数,负责把child/next置空。这样一条CREATE TABLE语句解析完,根节点就握在sts的语义值里,交给 DBMS 层去执行——执行层只需要按kind做 switch,取name建表、遍历child建字段,完全不用回头碰词法和语法。

这条“语法树 → 执行”的边界就是编译器前端的终点。报告里 DBMS 部分描述的物理结构(数据文件、表定义、记录布局)之所以没写代码,卡的就是这一步:没有 AST,执行层拿不到结构化的语句,只能靠全局变量传参,写出来必然一团乱。先把 AST 定下来,DBMS 部分才有得谈。

5. 避坑与排查:这几个问题我当年也踩过

5.1 数字被 Name 规则截获,NUMBER 永远不出现

现象:写CHAR(20)或WHERE a = 1时语法器一直报 syntax error,打日志发现词法器返回的全是Name,NUMBER一次都没出现过。

原因:Name那条规则是[a-zA-Z][_0-9a-zA-Z()\.\*]*,首字符要求是字母,理论上不该匹配纯数字。但如果规则顺序被调整过、或者Name的正则里首字符被放宽成[a-zA-Z0-9],数字就会先被Name吃掉。报告作者在“存在问题及解决方法”里写的正是这件事:“开始时没有考虑 Name 会将 NUMBER 截获,导致数字对应的位置无法识别”。

解决:把[0-9]+这条规则放在所有标识符规则之前,并且严格保证标识符正则的首字符集合不含数字。lex 的最长匹配会优先选长度更长的,同长度才看顺序,所以顺序和首字符约束两个都要对。

5.2 改了 .l 文件,跑起来还是旧行为

现象:把单引号规则改对了,重新编译,程序行为一点没变,甚至报错信息都和改之前一模一样。

原因:只编译了mylexer.cpp,没重新跑 Generator 生成它。.l是源,.cpp是产物,改了源不重新生成,编译器看到的还是旧产物。VC++6.0 的项目文件里如果没有把.l配成自定义生成步骤,就得手动跑一遍工具再编译。

解决:把生成命令挂到构建流程里。VC++6.0 时代是在 Project Settings 的 Custom Build 里加命令行,现代工具链写进 Makefile:

mylexer.c: mylexer.l flex -o mylexer.c mylexer.l myparser.c myparser.h: myparser.y bison -d -o myparser.c myparser.y

这样make会根据时间戳判断要不要重新生成,改.l一定触发重生成,后悔药就省了。

5.3 语法冲突只警告不报错,跑起来结果诡异

现象:bison 编译时打出一行conflicts: 2 shift/reduce,没当回事,结果WHERE a = 1 AND b = 2 OR c = 3解析出来的树形状和预期不一致。

原因:shift/reduce 冲突说明文法有二义性,bison 的默认策略是“移进优先”,这个默认值在某些场景下会把AND和OR绑成一样优先级,或者在conditions AND conditions和conditions OR conditions之间选错分支。警告不阻止生成,所以很容易被忽略。

解决:先用bison -v生成.output文件,里面会列出每个冲突状态的具体项,看清楚是哪两条产生式在抢;然后要么补优先级声明(%left OR在前、%left AND在后),要么重写文法消除二义性。零冲突不是目标,但每条冲突都得知道它为什么存在。

5.4 语义值用 yytext 指针,日志全是乱码

现象:printf打表名的时候,有时候正常,有时候打出来是后面几个 token 拼起来的乱码。

原因:规则里printf("%s", yytext)能用,但把yytext直接赋给yylval.yych再往下传就不行。yytext指向的是词法器内部的扫描缓冲区,下一轮匹配会往里写新内容,之前存下的指针就悬空了。

解决:存进去之前先拷贝一份,yylval.yych = strdup(yytext);,用完记得在 AST 销毁时free。教学代码里为了省事经常不释放,跑一次就退出无所谓,但如果你要在一个进程里连续解析多条语句,内存会一直涨。

5.5 报 syntax error 却不知道错在哪

现象:输入SELECT * FROM,程序只回一句syntax error,没有行号、没有出错 token、没有上下文。

原因:yyerror用的是默认实现,只打固定字符串。yacc 允许你自己实现yyerror(char *msg),但报告里没写,等于把诊断信息全丢了。

解决:自己实现一个,把当前行号和出错 token 打出来:

void yyerror(const char *msg) { extern int yylineno; /* flex 里加 %option yylineno */ extern char *yytext; fprintf(stderr, "第 %d 行附近: %s,出错 token = %s\n", yylineno, msg, yytext ? yytext : "(空)"); }

yylineno需要词法器开%option yylineno才会自动维护,否则一直是 0。yytext在语法错误发生时指向的是刚被读进来、还没归约的那个 token,用它基本能定位到出错位置。这一条改完,调试效率的提升比前面几条加起来都明显。

6. 二次开发:从“能识别”到“能执行”的改造路线

这份材料的价值在词法和语法这两段,缺的是执行。真要往上加,我建议按这个顺序推,别一上来就写存储引擎。

第一步,把printf换成 AST。所有归约动作用mk_node建节点,sts的语义值最终是一棵语句链表的头指针。这一步做完,程序行为不变,但你手里有了结构化数据,后面所有事都好办。验证方法很简单:写个dump_ast(node, 0)递归打缩进树,输入三条语句,看输出和报告里画的语法树图形是否一致。

第二步,给每条语句挂一个执行函数。按kind分发,N_CREATE_TABLE走建表、N_INSERT走插记录、N_SELECT走全表扫。表结构可以先做成内存里的哈希表加vector,字段定义存name/type/len三元组,记录存vector<string>,先把语义跑通再考虑落盘。这一步是改造量最大的,也是最容易失控的,建议先只做CREATE TABLE+INSERT+SELECT *三条,跑通了再补WHERE。

第三步,补错误处理和事务边界。语法层通过不代表语义层合法,CREATE TABLE t (id INT, id CHAR(10))重复字段名、SELECT * FROM 不存在的表都得在执行层拦下来,报错信息要带上表名。这条不做,二次开发出来的东西只能自己玩,交给别人一用就崩。

有个地方值得单独说:条件表达式WHERE a = 1 AND b = 2的执行。AST 里它是conditions节点,左孩子一个condition、右孩子另一个condition、中间挂着AND。求值的时候写一个递归的eval_cond(node, record),遇到AND就两边同时为真,遇到比较节点就按op取字段值和常量比。这套逻辑和编译原理的关系就不大了,属于解释器写法,但它是把前端成果用起来的唯一路径。

从那以后我每次改这类前端工程,都强制先跑一遍 AST dump 再往下接执行层——语法树对了,执行错了是逻辑问题;语法树都不对,往下写的每一行都是浪费。希望这份材料能被你改成真正跑得起来的东西,也希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/4 16:46:00

STAR-CCM+动态资源调度优化实践

1. 项目背景与核心价值在工程仿真领域&#xff0c;STAR-CCM作为主流CFD软件&#xff0c;其计算资源分配常面临核心利用率波动的痛点。我们团队通过构建智能回收系统&#xff0c;将传统固定分配模式升级为动态资源调度&#xff0c;实测使集群平均核心利用率从58%提升至89%。这个…

作者头像 李华
网站建设 2026/11/4 16:44:49

智能床垫核心技术解析与选购指南

1. 智能床垫行业现状与市场格局 2026年的智能睡眠市场已经形成了明显的技术分层和品牌梯队。根据最新行业报告显示&#xff0c;全球智能床垫市场规模较2021年增长了近3倍&#xff0c;其中亚太地区贡献了45%的增量。在这个快速扩张的市场中&#xff0c;头部品牌通过持续的技术迭…

作者头像 李华
网站建设 2026/11/4 16:43:02

WonderTrader Ubuntu 22.04依赖编译与CMake避坑

简介&#xff1a;WonderTrader 依赖库面向在 Ubuntu 环境下编译 WonderTrader 的 C 开发者与量化交易系统搭建者&#xff0c;针对 GCC 11.4.0 工具链下常见的第三方依赖缺失、头文件路径分散及 CMake 配置易出错等问题&#xff0c;提供可直接复用的依赖集合。压缩包为 tgz 格式…

作者头像 李华
网站建设 2026/11/4 16:41:42

多Agent A/B评测系统:大模型策略评估实战

1. 项目背景与核心价值在大模型技术快速迭代的当下&#xff0c;如何科学评估不同Agent或策略组合的实际效果&#xff0c;成为行业亟需解决的痛点问题。传统单点测试方法往往存在三个致命缺陷&#xff1a;测试环境与生产环境差异导致的"实验室效应"、人工评估的主观偏…

作者头像 李华
网站建设 2026/11/4 16:40:53

绿色HTML5简历模板:语义化结构与CSS变量配色实战

简介&#xff1a;这是一款面向求职者与前端初学者的响应式在线简历网站模板&#xff0c;以绿色为主色调&#xff0c;营造清新自然的视觉观感&#xff0c;适合需要在线展示教育背景、工作经验与专业技能的人群使用。压缩包共50个文件&#xff0c;约1.38MB&#xff0c;包含1个HTM…

作者头像 李华
网站建设 2026/11/4 16:39:13

Desigo CC项目冷启动实战指南:绕开SMC与HDB高频故障

简介&#xff1a;本资源是西门子Desigo CC楼宇自动化系统官方中文技术手册的专项章节&#xff0c;聚焦工程配置核心实践&#xff0c;面向楼宇自控工程师、系统集成商及BA项目实施人员&#xff0c;解决Desigo CC项目从零部署到稳定运行的关键配置难题。内容覆盖历史数据库&#…

作者头像 李华