LuaJIT源码分析(五)词法分析

LuaJIT源码分析(五)词法分析

lua虽然是脚本语言,但在执行时,还是先将脚本编译成字节码,然后再由虚拟机解释执行。在编译脚本时,首先需要对源代码进行词法分析,把源代码分解为token流。lua的token可以分为若干不同的类型,比如关键字,标识符,字面量,运算符,分隔符等等。

标识符

可以是由字母、数字和下划线组成的任意字符串,但不能以数字开头。

关键字

具有特殊含义的保留字,不可以用作标识符,共有22个。

     and       break     do        else      elseifend       false     for       function  ifin        local     nil       not       orrepeat    return    then      true      until     while

字符串字面常量

lua的字面字符串定义相当灵活,以下几种写法都是合法的,而且表示同一个字符串:

     a = 'alo\n123"'a = "alo\n123\""a = '\97lo\10\04923"'a = [[alo123"]]a = [==[alo123"]==]

数字字面常量

一个数值常量可以用可选的小数部分和可选的小数指数来表示。lua还接受十六进制整数常量,通过在前面加上0x来表示。以下几种表示形式都是合法的:

3   3.0   3.1416   314.16e-2   0.31416E1   0xff   0x56

运算符和分隔符

主要有以下若干种。

     +     -     *     /     %     ^     #==    ~=    <=    >=    <     >     =(     )     {     }     [     ];     :     ,     .     ..    ...

LuaJIT的词法分析代码集中在lex_scan这个函数上。在深入之前,我们先了解一下LuaJIT用于词法分析的数据结构。

// lj_lex.h
/* Lua lexer state. */
typedef struct LexState {struct FuncState *fs;	/* Current FuncState. Defined in lj_parse.c. */struct lua_State *L;	/* Lua state. */TValue tokval;	/* Current token value. */TValue lookaheadval;	/* Lookahead token value. */const char *p;	/* Current position in input buffer. */const char *pe;	/* End of input buffer. */LexChar c;		/* Current character. */LexToken tok;		/* Current token. */LexToken lookahead;	/* Lookahead token. */SBuf sb;		/* String buffer for tokens. */lua_Reader rfunc;	/* Reader callback. */void *rdata;		/* Reader callback data. */BCLine linenumber;	/* Input line counter. */BCLine lastline;	/* Line of last token. */GCstr *chunkname;	/* Current chunk name (interned string). */const char *chunkarg;	/* Chunk name argument. */const char *mode;	/* Allow loading bytecode (b) and/or source text (t). */VarInfo *vstack;	/* Stack for names and extents of local variables. */MSize sizevstack;	/* Size of variable stack. */MSize vtop;		/* Top of variable stack. */BCInsLine *bcstack;	/* Stack for bytecode instructions/line numbers. */MSize sizebcstack;	/* Size of bytecode stack. */uint32_t level;	/* Syntactical nesting level. */int endmark;		/* Trust bytecode end marker, even if not at EOF. */int fr2;		/* Generate bytecode for LJ_FR2 mode. */
} LexState;

数据结构看上去很复杂,不过好在每个成员变量都有相应的注释,而且在目前讨论的词法分析阶段中,只有少数几个成员变量需要考虑:

// lj_lex.h
/* Lua lexer state. */
typedef struct LexState {TValue tokval;	/* Current token value. */TValue lookaheadval;	/* Lookahead token value. */const char *p;	/* Current position in input buffer. */const char *pe;	/* End of input buffer. */LexChar c;		/* Current character. */LexToken tok;		/* Current token. */LexToken lookahead;	/* Lookahead token. */SBuf sb;		/* String buffer for tokens. */
} LexState;

tokval和lookaheadval分别表示当前扫描到的token和下一个即将被扫描的token;p和pe表示扫描的源代码buffer当前位置和重点位置;c表示当前扫描到的字符;tok和lookahead分别表示当前和下一个扫描的token类型;最后sb表示处理当前token所缓存的buffer。

LuaJIT的词法分析实现基本上也是个有限状态机,根据当前读到的字符,切换到不同的读取状态:

/* Get next lexical token. */
static LexToken lex_scan(LexState *ls, TValue *tv)
{lj_buf_reset(&ls->sb);for (;;) {if (lj_char_isident(ls->c)) {GCstr *s;if (lj_char_isdigit(ls->c)) {  /* Numeric literal. */lex_number(ls, tv);return TK_number;}/* Identifier or reserved word. */do {lex_savenext(ls);} while (lj_char_isident(ls->c));s = lj_parse_keepstr(ls, ls->sb.b, sbuflen(&ls->sb));setstrV(ls->L, tv, s);if (s->reserved > 0)  /* Reserved word? */return TK_OFS + s->reserved;return TK_name;}switch (ls->c) {case '\n':case '\r':lex_newline(ls);continue;case ' ':case '\t':case '\v':case '\f':lex_next(ls);continue;case '-':lex_next(ls);if (ls->c != '-') return '-';lex_next(ls);if (ls->c == '[') {  /* Long comment "--[=*[...]=*]". */int sep = lex_skipeq(ls);lj_buf_reset(&ls->sb);  /* `lex_skipeq' may dirty the buffer */if (sep >= 0) {lex_longstring(ls, NULL, sep);lj_buf_reset(&ls->sb);continue;}}/* Short comment "--.*\n". */while (!lex_iseol(ls) && ls->c != LEX_EOF)lex_next(ls);continue;case '[': {int sep = lex_skipeq(ls);if (sep >= 0) {lex_longstring(ls, tv, sep);return TK_string;} else if (sep == -1) {return '[';} else {lj_lex_error(ls, TK_string, LJ_ERR_XLDELIM);continue;}}case '=':lex_next(ls);if (ls->c != '=') return '='; else { lex_next(ls); return TK_eq; }case '<':lex_next(ls);if (ls->c != '=') return '<'; else { lex_next(ls); return TK_le; }case '>':lex_next(ls);if (ls->c != '=') return '>'; else { lex_next(ls); return TK_ge; }case '~':lex_next(ls);if (ls->c != '=') return '~'; else { lex_next(ls); return TK_ne; }case ':':lex_next(ls);if (ls->c != ':') return ':'; else { lex_next(ls); return TK_label; }case '"':case '\'':lex_string(ls, tv);return TK_string;case '.':if (lex_savenext(ls) == '.') {lex_next(ls);if (ls->c == '.') {lex_next(ls);return TK_dots;   /* ... */}return TK_concat;   /* .. */} else if (!lj_char_isdigit(ls->c)) {return '.';} else {lex_number(ls, tv);return TK_number;}case LEX_EOF:return TK_eof;default: {LexChar c = ls->c;lex_next(ls);return c;  /* Single-char tokens (+ - / ...). */}}}
}

lex_scan会返回当前扫描的token类型,LuaJIT只对那些不能用单字符表示的token,进行了定义,如果token本身就是单字符的,比如( + - / )之类,就直接用该字符作为它的token类型。由于char的取值范围为0-255,那么特殊定义的token类型,需要从256开始了。

// lj_lex.h
/* Lua lexer tokens. */
#define TKDEF(_, __) \_(and) _(break) _(do) _(else) _(elseif) _(end) _(false) \_(for) _(function) _(goto) _(if) _(in) _(local) _(nil) _(not) _(or) \_(repeat) _(return) _(then) _(true) _(until) _(while) \__(concat, ..) __(dots, ...) __(eq, ==) __(ge, >=) __(le, <=) __(ne, ~=) \__(label, ::) __(number, <number>) __(name, <name>) __(string, <string>) \__(eof, <eof>)enum {TK_OFS = 256,
#define TKENUM1(name)		TK_##name,
#define TKENUM2(name, sym)	TK_##name,
TKDEF(TKENUM1, TKENUM2)
#undef TKENUM1
#undef TKENUM2TK_RESERVED = TK_while - TK_OFS
};

可能会有疑问的一点是,为什么这里要引入TKENUM1和TKENUM2两种不同的宏,明明作用完全相同。答案是作者为了简洁,少写一些代码,把LuaJIT的关键字定义,也套用到了TKDEF这个宏上:

// lj_lex.c
/* Lua lexer token names. */
static const char *const tokennames[] = {
#define TKSTR1(name)		#name,
#define TKSTR2(name, sym)	#sym,
TKDEF(TKSTR1, TKSTR2)
#undef TKSTR1
#undef TKSTR2NULL
};

接下来我们回到lex_scan函数上,首先函数会调用lj_buf_reset清理缓存的token buffer,这个buffer只在单次scan中有效。然后,LuaJIT开始判断当前字符是一个什么样的字符。这里LuaJIT使用了查表的方式,预先将ASCII表中的所有字符进行属性标记。

// lj_char.h
#define LJ_CHAR_CNTRL	0x01
#define LJ_CHAR_SPACE	0x02
#define LJ_CHAR_PUNCT	0x04
#define LJ_CHAR_DIGIT	0x08
#define LJ_CHAR_XDIGIT	0x10
#define LJ_CHAR_UPPER	0x20
#define LJ_CHAR_LOWER	0x40
#define LJ_CHAR_IDENT	0x80
#define LJ_CHAR_ALPHA	(LJ_CHAR_LOWER|LJ_CHAR_UPPER)
#define LJ_CHAR_ALNUM	(LJ_CHAR_ALPHA|LJ_CHAR_DIGIT)
#define LJ_CHAR_GRAPH	(LJ_CHAR_ALNUM|LJ_CHAR_PUNCT)LJ_DATA const uint8_t lj_char_bits[257];

剩下的逻辑其实就比较简单了,如果当前字符是数字,那么就走假设token是数字字面常量的逻辑;如果是字母下划线,那就走关键字或是标识符的逻辑;否则就走其他处理逻辑。这些处理逻辑都比较简单,如果只通过当前字符无法判断token类型,就会去读取下一个字符甚至更多字符来进行判断。例如遇到字符.时,会尝试再读取一个字符,如果依旧是.,那么还需要再读一个字符来确定当前token是TK_dots ...还是TK_concat ..;如果不是,那么根据字符是否为数字,就能得出token是TK_number还是.类型了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/58562.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Django+Vue全栈开发旅游网项目景点详情

一、VueRouter实现多个页面 VueRouter引入步骤&#xff1a; 1、了解Vue.js中的路由管理 2、掌握VueRouter的安装和配置 3、掌握在Vue.js中使用VueRouter 什么是路由 路由&#xff08;Routing&#xff09;是指在网络中&#xff0c;将数据包&#xff08;packet&#xff09;从…

零跑汽车嵌入式面试题汇总及参考答案

C++ 的三大特性是什么? C++ 的三大特性分别是封装、继承和多态。 封装 概念:封装是把数据和操作数据的函数绑定在一起,对数据的访问进行限制。通过将数据成员声明为私有或保护,只允许通过公共的成员函数来访问和修改数据,从而隐藏了类的内部实现细节。这有助于提高代码的安…

WPF+MVVM案例实战(七)- 系统初始化界面字体描边效果实现

文章目录 1、案例效果展示2、项目准备3、功能实现1、资源获取2、界面代码3、后台代码4 源代码获取1、案例效果展示 2、项目准备 打开项目 Wpf_Examples,新建系统初始化界面 WelcomeWindow.xmal,如下所示: 3、功能实现 1、资源获取 案例中使用的CSDN文字为路径文字,从字体…

crc16 with word byte--查表法

#1, BYTE CRC 参考&#xff1a; https://blog.csdn.net/m0_37697335/article/details/113267780?ops_request_misc%257B%2522request%255Fid%2522%253A%2522F1451286-1B97-44AA-A5FC-386045B4939B%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&reques…

开源Verilog EDA工具--iverilog+GTKWave

开源Verilog EDA工具--iverilogGTKWave 1 Linux下环境安装及编译2 bash脚本 iverilogvvpgtkwave的开源EDA工具&#xff0c;其中vpp已经包含在iverilog中&#xff1b;并且在Windows或者Linux下都有对应的版本&#xff1b; iverilog:用于编译verilog和vhdl文件&#xff0c;进行语…

MFC工控项目实例二十七添加产品参数

承接专栏《MFC工控项目实例二十六创建数据库》 在型号参数界面添加三个参数试验时间、最小值、最大值。变量为double m_edit_time; double m_edit_min; double m_edit_max; 1、在SEAL_PRESSURE.h中添加代码 class CProductPara { public:union{struct{...double m_edit_min;…

【02】ZooKeeper经典应用场景实战一

1、ZooKeeper Java客户端实战 ZooKeeper应用的开发主要通过Java客户端API去连接和操作ZooKeeper集群。可供选择的Java客户端API有&#xff1a; ZooKeeper官方的Java客户端API。第三方的Java客户端API&#xff0c;比如&#xff1a;Curator ZooKeeper官方的客户端API提供了基本的…

信息安全工程师(73)网络安全风险评估过程

一、确定评估目标 此阶段需要明确评估的范围、目标和要求。评估目标通常包括特定的网络系统、信息系统或网络基础设施&#xff0c;评估范围可能涉及整个组织或仅特定部门。明确评估要求有助于确保评估过程的针对性和有效性。 二、收集信息 在评估开始之前&#xff0c;需要对目标…

2024年10月23日Github流行趋势

项目名称&#xff1a;hiteshchoudhary / apihub 项目维护者&#xff1a;wajeshubham, atulbhatt-system32, jwala-anirudh, arnb-smnta, shrey-dadhaniya 项目介绍&#xff1a;您自己的API Hub&#xff0c;用于学习和掌握API交互。非常适合前端、移动开发人员和后端开发人员。 …

Vmos pro-虚拟机 解锁永久vip

[应用名称] 应用名称&#xff1a;Vmos pro [应用版本] 应用版本&#xff1a;2.99 [软件大小] 软件大小&#xff1a;32.2mb [应用简介] 应用简介&#xff1a;Vmos Pro这款安卓虚拟机平台&#xff0c;提供了多样化的ROM版本选择。用户可根据自身需求更换ROM&#xff0c;调…

华为OD机试 - 最多购买宝石数目 - 滑动窗口(Python/JS/C/C++ 2024 C卷 100分)

华为OD机试 2024E卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试真题&#xff08;Python/JS/C/C&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;私信哪吒&#xff0c;备注华为OD&#xff0c;加入华为OD刷题交流群&#xff0c;…

1:基本电路专题:R(电阻)的介绍

说实话这个其实我不想写的&#xff0c;因为这个是初中的知识&#xff0c;并没有很难&#xff0c;但是为了保持整齐性&#xff0c;我还是写了一下关于这个的知识点。是电子学中三大基本无源元件之一。&#xff08;R&#xff08;电阻&#xff09;,L&#xff08;电感&#xff09;,…

【每日一题】2020年考研数据结构 - 求最短三元组

本题要求我们在三个非空整数集合中各取一个元素构成三元组&#xff0c;使其具有最小的距离。 题目描述 定义三元组 ( ( a , b , c ) ) ((a, b, c)) ((a,b,c)) 的距离 ( D ∣ a − b ∣ ∣ b − c ∣ ∣ c − a ∣ ) (D |a - b| |b - c| |c - a|) (D∣a−b∣∣b−c∣∣…

基于SpringBoot的“CSGO赛事管理系统”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“CSGO赛事管理系统”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统首页界面图 赛事信息界面图 赛事通知界面…

阿里云VPC机器如何访问公网

1.创建专有网络和交换机。 2.在弹性公网IP页面&#xff0c;创建弹性公网IP&#xff0c;详情请参见申请EIP。 3.在公网NAT网关页面&#xff0c;创建NAT网关&#xff0c;并绑定弹性公网IP&#xff0c; 参考&#xff1a; https://help.aliyun.com/zh/nat-gateway/getting-starte…

【QT】windows 平台 QT6.8 安装

qt-online-installer-windows-x64-4.8.1.exe Index of /qt/archive/online_installers/4.8/mirror 这里介绍了如何使用mir

CesiumJS 案例 P13:删除标记、移动标记、标记点击事件

CesiumJS CesiumJS API&#xff1a;https://cesium.com/learn/cesiumjs/ref-doc/index.html CesiumJS 是一个开源的 JavaScript 库&#xff0c;它用于在网页中创建和控制 3D 地球仪&#xff08;地图&#xff09; 一、删除标记 <!DOCTYPE html> <html lang"en&…

一七二、Vue3性能优化方式

Vue 3 的性能优化相较于 Vue 2 有了显著提升&#xff0c;利用新特性和改进方法可以更高效地构建和优化应用。以下是 Vue 3 的常见性能优化方法及示例。 1. 使用组合式 API (Composition API) Vue 3 引入的组合式 API&#xff0c;通过逻辑拆分和复用来实现更高效的代码组织和性…

漏洞挖掘 | 通过域混淆绕过实现账户接管

由于这是一个私有项目&#xff0c;我将使用 example.com 来代替。 很长一段时间以来&#xff0c;我一直想在漏洞赏金项目中找到一个账户接管&#xff08;ATO&#xff09;漏洞。于是&#xff0c;我开始探索项目范围内的 account.example.com。 我做的第一件事就是注册一个新账…

Rust 基础语法与常用特性

Rust 跨界&#xff1a;全面掌握跨平台应用开发 第一章&#xff1a;快速上手 Rust 1.2 基础语法与常用特性 1.2.1 数据类型与控制流 数据类型 Rust 提供了丰富的内置数据类型&#xff0c;主要分为标量类型和复合类型。 标量类型 标量类型表示单一的值&#xff0c;Rust 中…