C解释器Picoc阅读 (2011-12-21 18:06:11)转载▼
标签: c 解释器 picoc 分类: 编程
Picoc是google开源代码项目中看到的一个项目,其初衷貌似是要做一个在小的嵌入设备上的C解释器。它的核心代码只有3500行左右,可读性不错,虽然没有实现完整的ISO C标准,基本的C运行库还是具备了。
我们改造的目标是想构建一个类C的语法,在隐藏指针之类的数据结构,增加string数据类型,struct带默认构造,struct 默认传引用调用(有点像JAVA了...)。
一、基本模块
picoc代码上看,基本有如下几块:lex词法解析,table一个基本数据结构(用于存放变量),是个字符串hash表,heap管理内存分配(包括了stack frame的实现), type做类型管理(基本型别和程序自定义的struct,typedef等),expression做表达式解析,variable变量管理分配释放栈帧。
picoc的定位是一个解释器,它的解析和代码运行是在同一块代码块里做。这个估计也得要花时间改进。
table里有一张表StringTable,这个表只放名称,具体的定义要在全局表或者局部表中去找(如NULL的实现),LexInit时词法关键字(预编译指令\语法相关字)会存放到这里来,type里类型解析的时候解释器要为一些匿名的struct\enum生成临时类型名,也存在StringTable里,另外,全局变量以及静态变量都会存到StringTable中。
variable维护了两个表GlobalTable和StringLiteralTable,以及一个栈TopStackFrame。GlobalTable存放全局变量的定义,StringLiteralTable里存放字符串常量(包括#include中尖括号内的头文件等)。
LexAnalyse()里会把变量存到StringTable表里去,返回一个存放整个以token为单位的串(一块动态分配的内存)。
LexInitParser()里简单初始化ParseState结构,ParseState是里面一个涉及解析和运行的关键结构。
二、表达式的实现
操作符的顺序有四种:OrderNone, OderPrefix, OrderInfix, OrderPostfix, 所有的操作符都会定义后三种顺序对应的优先级。OrderNone的话,是作为判断是操作数的标识。
ExpressionStack是实现表达式栈的节点结构,不管是操作符还是操作数都会被封装成一个ExpressionStack节点,压栈。ExpressionStack会记录上一个节点、当前值(如果是操作符就是操作符和优先级)。优先级的设计也是实现表达式的关键之一。 每一个leftbracket 或者 LeftSquareBracket都会将当前Precedence+=BRACKET_PRECEDENCE。ExpressionStackCollapse会把当前表达式栈上,优先级高于Precedence的进行合并计算。
int ExpressionParse(struct ParseState *Parser, struct Value **Result)
这个函数是对表达式进行解释运行。它在parse的过程中,对读到的token主要区分两种类型分别进行压栈处理:一种是操作符,一种是TokenIdentifier(TokenIdentifier可能是变量也可能是函数),其它的还有左右括号、常量字符串和类型名。
整个表达式压栈之后,调用ExpressionStackCollapse(),对整个表达式调用栈,根据优先级进行计算压栈。
举一个取数组里单个元素的例子:ExpressionStackPushLValue将数组变量压栈,再通过ExpressionStackPushOperator将'['压栈,数组下标是常数的直接调用ExpressionStackPushValue压栈,碰到']'之后,则对表达式栈上进行ExpressionStackCollapse压栈,栈上最后形成{...-[-下标}这样子下标在栈顶的布局。
最后用当前precedence=0来调用ExpressionStackCollapse():获得栈顶的第一个操作符'[',其操作符是OrderInfix,前面是数组名,后面是下标,调用ExpressionInfixOperator,下标支持浮点转换到整型,最后通过VariableAllocValueFromExistingData函数将数组中的数据取出。
三、函数调用
函数调用的栈帧结构如下:
struct StackFrame
{
struct ParseState ReturnParser;
const char *FuncName;
struct Value *ReturnValue;
struct Value **Parameter;
int NumParams;
struct Table LocalTable;
struct TableEntry *LocalHashTable[LOCAL_TABLE_SIZE];
struct StackFrame *PreviousStackFrame;
};
估计每次函数调用会对应一个新的StackFrame,其中FuncName是当前的调用函数名,LocalTable存储局部变量和参数。
ParseFunctionDefinition,我们需要执行脚本里写好的函数,这个是Parse函数的。函数也是AnyValue(Union)中的一个成员FuncDef,里面存储函数的信息(函数名,返回值类型,是否变长参数,参数类型表,参数名称表,直接的调用地址,函数体对应的ParseState)。调用ParseStatementMaybeRun,用RunModeSkip模式来Parse函数体(->ParseBlock),但不运行,函数对应的值,会被存到GlobalTable中。
表达式解析里,对于TokenIdentifier是函数的,则会调用ExpressionParseFunctionCall来实现函数调用过程的解释运行。函数定义如下:
void ExpressionParseFunctionCall(struct ParseState *Parser, struct ExpressionStack **StackTop, const char *FuncName, int RunIt)
RunIt表示是运行还是仅解析,运行的话,找到函数的定义FuncValue,为函数参数分配空间,把函数参数定义到局部变量里,从FuncValue->Val->FuncDef.Body中提取函数体FuncParser,通过ParseStatement(&FuncParser, TRUE)来实现函数体的调用(->ParseBlock->ParseStatement)。
ExpressionParseFunctionCall执行的时候,如果是运行状态(RunIt),返回值压栈,StackFrame压栈,参数列表压栈取出FuncDef.Body,作为当前ParseState,参数个数压栈,调用VariableStackFrameAdd做栈帧构造工作:
StackFrame压栈,初始化栈帧的ReturnParse(记录函数调用前的ParseState),FuncName,Parameter,把当前栈帧TopStackFrame保存并重置,还需要初始化局部变量表LocalTable。
标签: c 解释器 picoc 分类: 编程
Picoc是google开源代码项目中看到的一个项目,其初衷貌似是要做一个在小的嵌入设备上的C解释器。它的核心代码只有3500行左右,可读性不错,虽然没有实现完整的ISO C标准,基本的C运行库还是具备了。
我们改造的目标是想构建一个类C的语法,在隐藏指针之类的数据结构,增加string数据类型,struct带默认构造,struct 默认传引用调用(有点像JAVA了...)。
一、基本模块
picoc代码上看,基本有如下几块:lex词法解析,table一个基本数据结构(用于存放变量),是个字符串hash表,heap管理内存分配(包括了stack frame的实现), type做类型管理(基本型别和程序自定义的struct,typedef等),expression做表达式解析,variable变量管理分配释放栈帧。
picoc的定位是一个解释器,它的解析和代码运行是在同一块代码块里做。这个估计也得要花时间改进。
table里有一张表StringTable,这个表只放名称,具体的定义要在全局表或者局部表中去找(如NULL的实现),LexInit时词法关键字(预编译指令\语法相关字)会存放到这里来,type里类型解析的时候解释器要为一些匿名的struct\enum生成临时类型名,也存在StringTable里,另外,全局变量以及静态变量都会存到StringTable中。
variable维护了两个表GlobalTable和StringLiteralTable,以及一个栈TopStackFrame。GlobalTable存放全局变量的定义,StringLiteralTable里存放字符串常量(包括#include中尖括号内的头文件等)。
LexAnalyse()里会把变量存到StringTable表里去,返回一个存放整个以token为单位的串(一块动态分配的内存)。
LexInitParser()里简单初始化ParseState结构,ParseState是里面一个涉及解析和运行的关键结构。
二、表达式的实现
操作符的顺序有四种:OrderNone, OderPrefix, OrderInfix, OrderPostfix, 所有的操作符都会定义后三种顺序对应的优先级。OrderNone的话,是作为判断是操作数的标识。
ExpressionStack是实现表达式栈的节点结构,不管是操作符还是操作数都会被封装成一个ExpressionStack节点,压栈。ExpressionStack会记录上一个节点、当前值(如果是操作符就是操作符和优先级)。优先级的设计也是实现表达式的关键之一。 每一个leftbracket 或者 LeftSquareBracket都会将当前Precedence+=BRACKET_PRECEDENCE。ExpressionStackCollapse会把当前表达式栈上,优先级高于Precedence的进行合并计算。
int ExpressionParse(struct ParseState *Parser, struct Value **Result)
这个函数是对表达式进行解释运行。它在parse的过程中,对读到的token主要区分两种类型分别进行压栈处理:一种是操作符,一种是TokenIdentifier(TokenIdentifier可能是变量也可能是函数),其它的还有左右括号、常量字符串和类型名。
整个表达式压栈之后,调用ExpressionStackCollapse(),对整个表达式调用栈,根据优先级进行计算压栈。
举一个取数组里单个元素的例子:ExpressionStackPushLValue将数组变量压栈,再通过ExpressionStackPushOperator将'['压栈,数组下标是常数的直接调用ExpressionStackPushValue压栈,碰到']'之后,则对表达式栈上进行ExpressionStackCollapse压栈,栈上最后形成{...-[-下标}这样子下标在栈顶的布局。
最后用当前precedence=0来调用ExpressionStackCollapse():获得栈顶的第一个操作符'[',其操作符是OrderInfix,前面是数组名,后面是下标,调用ExpressionInfixOperator,下标支持浮点转换到整型,最后通过VariableAllocValueFromExistingData函数将数组中的数据取出。
三、函数调用
函数调用的栈帧结构如下:
struct StackFrame
{
struct ParseState ReturnParser;
const char *FuncName;
struct Value *ReturnValue;
struct Value **Parameter;
int NumParams;
struct Table LocalTable;
struct TableEntry *LocalHashTable[LOCAL_TABLE_SIZE];
struct StackFrame *PreviousStackFrame;
};
估计每次函数调用会对应一个新的StackFrame,其中FuncName是当前的调用函数名,LocalTable存储局部变量和参数。
ParseFunctionDefinition,我们需要执行脚本里写好的函数,这个是Parse函数的。函数也是AnyValue(Union)中的一个成员FuncDef,里面存储函数的信息(函数名,返回值类型,是否变长参数,参数类型表,参数名称表,直接的调用地址,函数体对应的ParseState)。调用ParseStatementMaybeRun,用RunModeSkip模式来Parse函数体(->ParseBlock),但不运行,函数对应的值,会被存到GlobalTable中。
表达式解析里,对于TokenIdentifier是函数的,则会调用ExpressionParseFunctionCall来实现函数调用过程的解释运行。函数定义如下:
void ExpressionParseFunctionCall(struct ParseState *Parser, struct ExpressionStack **StackTop, const char *FuncName, int RunIt)
RunIt表示是运行还是仅解析,运行的话,找到函数的定义FuncValue,为函数参数分配空间,把函数参数定义到局部变量里,从FuncValue->Val->FuncDef.Body中提取函数体FuncParser,通过ParseStatement(&FuncParser, TRUE)来实现函数体的调用(->ParseBlock->ParseStatement)。
ExpressionParseFunctionCall执行的时候,如果是运行状态(RunIt),返回值压栈,StackFrame压栈,参数列表压栈取出FuncDef.Body,作为当前ParseState,参数个数压栈,调用VariableStackFrameAdd做栈帧构造工作:
StackFrame压栈,初始化栈帧的ReturnParse(记录函数调用前的ParseState),FuncName,Parameter,把当前栈帧TopStackFrame保存并重置,还需要初始化局部变量表LocalTable。
http://blog.sina.com.cn/s/blog_811c1b7e0100vv4o.html
http://blog.sina.com.cn/s/blog_811c1b7e0100vv4o.html