Lua源码分析 - 虚拟机篇 - 语义解析之Token分割器(15)

Gretchen ·
更新时间:2024-11-14
· 840 次阅读

目录

虚拟机篇 - 语义分割单位Token结构

虚拟机篇 - 语义分割主流程

虚拟机篇 - 保留字类型的实现

虚拟机篇 - 复杂语义信息存储

上一篇,我们讲到了Lua脚本文件加载和读取的方式。其中luaX_next函数就是用来将Lua脚本字符串逐个切割出Token。

虚拟机篇 - 语义分割单位Token结构

Token定义:Lua会对脚本语言逐个切分出最小单位Token。例如lua保留字“if”的Token是TK_IF,字符串Token为TK_STRING

Lua通过luaX_next逐个读取字符流字符,直到切割出一个完整的Token。(每次切1个) Token包含计算机语言的基础保留符号(;{}等)、Lua保留字(nil、if等)和其它标记Token关键值 Token包含各种保留字和基础符号等,同时针对字符串/数字等类型,Token结构提供了SemInfo来保存语法信息

看一下Token的数据结构:

Token中的token代表类型 SemInfo用于存储不同token类型下的语义辅助信息(例如:TK_STRING,seminfo->ts上用于存储字符串) //语义辅助信息 typedef union { lua_Number r; lua_Integer i; TString *ts; } SemInfo; /* 语义信息 semantics information */ //语义分割最小单位Token typedef struct Token { int token; //Token类型 SemInfo seminfo; //语义信息,例如token为字符串/数字等都需要存储具体的值 } Token;

然后看一下Token的类型

Token的类型是用int来存储的。枚举RESERVED中包含了两部分类型:Lua系统关键字和其它标记Token关键值 FIRST_RESERVED是从257开始的,相当于将系统基础符号的类型给留空了。 基础保留符号类型,则直接返回单个字符(每个符号对应是一个int类型编号) enum RESERVED { /* 系统默认关键字 terminal symbols denoted by reserved words */ TK_AND = FIRST_RESERVED, TK_BREAK, TK_DO, TK_ELSE, TK_ELSEIF, TK_END, TK_FALSE, TK_FOR, TK_FUNCTION, TK_GOTO, TK_IF, TK_IN, TK_LOCAL, TK_NIL, TK_NOT, TK_OR, TK_REPEAT, TK_RETURN, TK_THEN, TK_TRUE, TK_UNTIL, TK_WHILE, /* 其它关键字 other terminal symbols */ TK_IDIV, TK_CONCAT, TK_DOTS, TK_EQ, TK_GE, TK_LE, TK_NE, TK_SHL, TK_SHR, TK_DBCOLON, TK_EOS, TK_FLT, TK_INT, TK_NAME, TK_STRING }; static const char *const luaX_tokens [] = { "and", "break", "do", "else", "elseif", "end", "false", "for", "function", "goto", "if", "in", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while", "//", "..", "...", "==", ">=", "<=", "~=", "<>", "::", "", "", "", "", "" }; 虚拟机篇 - 语义分割主流程

我们先看一个非常简单的Lua语言代码例子:

age=5; name='zhuli';

这个例子中,通过luaX_next分割后,会分割成N个部分。age分割成TK_NAME类型,=分割成=符号Token,5分割成TK_INT类型等。

luaX_next方法底层主要调用的是llex函数。llex是一个for循环状态机,通过循环读取文件流中的字符,进行Token的切割操作,当切割到一个Token后,就会返回Token的类型和语义信息。针对换行,空格等符号,则会跳过。

/** * Token解析函数,逐个读取字符流 * 其中next函数:从ZIO文件流上读取下一个字符 * 完成一个Token的切割,则返回Token结果 */ static int llex (LexState *ls, SemInfo *seminfo) { luaZ_resetbuffer(ls->buff); for (;;) { switch (ls->current) { /* 换行符号 ,跳过 */ case '\n': case '\r': { /* line breaks */ inclinenumber(ls); break; } /* 长字符串处理 */ case '[': { /* long string or simply '[' */ int sep = skip_sep(ls); if (sep >= 0) { read_long_string(ls, seminfo, sep); return TK_STRING; } else if (sep != -1) /* '[=...' missing second bracket */ lexerror(ls, "invalid long string delimiter", TK_STRING); return '['; } /* == 处理 */ case '=': { next(ls); if (check_next1(ls, '=')) return TK_EQ; else return '='; } //......................... /* 变量名称等处理/关键字 */ default: { if (lislalpha(ls->current)) { /* identifier or reserved word? */ TString *ts; do { save_and_next(ls); } while (lislalnum(ls->current)); ts = luaX_newstring(ls, luaZ_buffer(ls->buff), luaZ_bufflen(ls->buff)); seminfo->ts = ts; if (isreserved(ts)) /* 保留关键字? reserved word? */ return ts->extra - 1 + FIRST_RESERVED; else { return TK_NAME; } } else { /* single-char tokens (+ - / ...) */ int c = ls->current; next(ls); return c; } } } } } 虚拟机篇 - 保留字类型的实现

上面的枚举RESERVED中,我们看到了Lua的Token切割器会将语法的保留字切割出来。

保留字是一个luaX_tokens类型的数组,对应了RESERVED里面的Token类型 保留字模块初始化的时候,会将luaX_tokens上的字符串缓存到字符串池上 被缓存的保留字,通过ts->extra保存对应的token类型,通过函数isreserved进行判断是否是保留字 保留字模块的初始化,在f_luaopen中调用。luaX_init主要将保留字数组循环设置到字符串缓存池上。 void luaX_init (lua_State *L) { int i; TString *e = luaS_newliteral(L, LUA_ENV); /* 创建环境变量名称 create env name */ luaC_fix(L, obj2gco(e)); /* never collect this name */ for (i=0; iextra = cast_byte(i+1); /* reserved word */ } } //llex函数 TString *ts; do { save_and_next(ls); } while (lislalnum(ls->current)); ts = luaX_newstring(ls, luaZ_buffer(ls->buff), luaZ_bufflen(ls->buff)); seminfo->ts = ts; if (isreserved(ts)) /* 保留关键字? reserved word? */ return ts->extra - 1 + FIRST_RESERVED; else { return TK_NAME; } 虚拟机篇 - 复杂语义信息存储

上面我们知道,Token主要用来存储类型值(int),而针对字符串/数字等复杂的类型,需要通过SemInfo语义辅助结构来存储辅助的语义信息(字符串/数字等)。

我们可以看一个数字的例子:

/* ** this function is quite liberal in what it accepts, as 'luaO_str2num' ** will reject ill-formed numerals. ** 读取数字类型,具体的数字放置在seminfo->i/seminfo->r上 */ static int read_numeral (LexState *ls, SemInfo *seminfo) { .......... if (luaO_str2num(luaZ_buffer(ls->buff), &obj) == 0) /* format error? */ lexerror(ls, "malformed number", TK_FLT); if (ttisinteger(&obj)) { seminfo->i = ivalue(&obj); //存储数字 return TK_INT; } else { lua_assert(ttisfloat(&obj)); seminfo->r = fltvalue(&obj); return TK_FLT; } }
作者:initphp



Lua token 虚拟机

需要 登录 后方可回复, 如果你还没有账号请 注册新账号