| Crates.io | pipeline-script |
| lib.rs | pipeline-script |
| version | 0.3.12 |
| created_at | 2024-05-19 13:00:45.922031+00 |
| updated_at | 2025-06-29 11:40:03.721472+00 |
| description | Script engine designed for the project construction tool pipeline(crate name pipeline-cli) |
| homepage | |
| repository | |
| max_upload_size | |
| id | 1244912 |
| size | 600,020 |
脚本引擎运行流程: 分词(产生Token)->解析生成AST->宏函数展开->编译为LLVM IR ->调用LLVM JIT执行器执行
Lexer(分词器)->产生Token,into_iter()返回一个TokenStream Parser(解析器)->生成AST(Module,Function,Struct,Module,CLass,Stmt,Expr),parse_file()返回一个Module Compiler(编译器)->生成LLVM IR,compile_module返回一个LLVMModule
Engine(引擎)->run_function调用LLVM JIT执行器执行LLVMModule,run_file()依次调用分词器,解析器,编译器,按照流程执行
宏函数展开 动态参数实现,Any类型,Trait类型
变量分为值,引用和指针,const声明的都是值,let会声明一个变量,指针常常作为参数和返回值,目的是兼容ffi的第三方接口
compile_member和compile_index 分为值和引用,目标是值使用extract_value,返回的是字段的值,引用返回的是字段的指针,member为左值的时候,因为需要得到指针,需要保证目标是引用,如果目标是指针,和引用一同处理,所以只需要考虑Type::ref和Type::pointer这两者使用getelementpointer,其余都是提取值就行,对泛型进行特殊考虑,一般是泛型实例,需要转成非泛型类型,几乎所有泛型的地方都需要获得它的实例类型,可以抽象一个新方法get_instance_type,对于其他类型,返回他们自身,对于泛型实例,返回它的实例,因为ref和pointer处理逻辑是一样的,可以考虑一个通用方法进行判断,get_underlying_type将ref转成pointer,将泛型实例转成实例在进行递归转换,有ref变成pointer.
考虑特殊情况self.data[index] = 1,self是引用,data是指针,
对于引用对象,有左值应用和右值引用之分,左值取指针,右值取引用值
以上例子中,self.data[index]是左值,尽量取指针,self(获取变量指针),self.data获取data元素的指针,因为data原本就是一个指针,此处获取的是指针的指针,因为self[index]本质上是对self指针进行偏移index计算,self是指针的时候,该语法成立,但是self实际上是指向一个变量的指针,因此,index只能允许原本值是指针才能进行,在左值语境中,index操作前需要进行一次额外的load。那我们接着考虑index在右值的情况,吧b = self.data[index],self是结构体,self.data取结构体变量获取指针data,进行index操作,获取便宜后的指针,最后进行load.
总结:对于index操作,左值,先load再index;右值,先index再load.
对于member操作,左值使用getelementptr,右值使用提取,target在左值会保证是指针,在右值会保证是结构体值
关于类型系统,原则上,compile_xxx函数的返回值是Value,不包含Type,但是有些时候我们系统进行一些类型约束,比如函数调用,函数的参数可能是分为值类型和引用类型,FunctionCall结构体包含了调用的函数,我们编译的时候可以获取到函数的类型,检查函数参数,检查函数的形参和实参类型是否匹配,此时实参还是expr,当形参是Type::Ref时,调用左值求职,当不是ref的时候调用右值求职,这里需要考虑一下指针的特殊参数类型,是否可以应用右值求职,以下是一个例子:
const p = malloc()
memread(p)
p是一个指针,使用const声明,注册的符号是(p,pointer),如果使用let声明,注册的符号是p,ref(p)指向指针的指针,可以改变指针指向的内容空气,const无法改变指向的内存空间。
当执行到FunctionCall时,p是指针类型,直接执行右值,获取p符号的类型,发现不是ref,不会进行额外求值load,返回指针,OK。
所以对于函数参数来说,也只需要考虑值(左值)类型和引用(右值)类型。
从以上例子也可以看出符号表应该存储Type,因为要知道是否是引用类型,引用类型在右值环境会额外求值,但是能否移除符号表的类型呢,似乎可以,那就是给Value引入RefValue,这似乎局不需要再符号表存储额外的类型信息,符号表就是简单的(String,Value)映射,那函数类型的形参类型声明是否有必要呢,考虑以上例子中的函数,memread,符号是memread,符号表是FunctionValue声明,我们需要获取其函数形参声明,需要根据参数名(默认值系统)和根据下标获取形参类型,以下是对FunctionValue结构体的构思:
struct FunctionValue{
reference:LLVMValueRef
index_map:Hashmap<String,usize>
param_types:Vec<Type> // 这里的type似乎无法避免,不然如何获取形参类型呢,这里还需要记录形参默认值,能够直接通过Value声明类型呢,用一种新的类型的空值来表示没有默认值,并且这种空值无法被用户声明,避免默认值就是空值的情况,非空值就是有默认值,这里似乎可以参考js的undefined,因此引入UndefValue,但是如何兼容诸多的值类型,比如Int64Value,FloatValue呢,他们也有空值吧,哦,对了,为每个Value定义一个undef方法,llvm-sys似乎可以定义类型空值,因此我们可以约束下Valeu的trait
}
trait IValue {
// get_type(&self)->Type 该方法似乎没有必要
fn get_llvm_type(&self,ctx:&Context)->LLVMType
fn as_ref(&self)->LLVMValueRef
fn get_undef(&self)-> Self
// 现在问题是该函数怎么实现,undef声明后的返回值也是LLVMValueRef,如何判断它是不是空类型呢,取看看LLVM-sys是否有支持判断LLVMValueRef是否是空类型。
// 查阅后发现确实存在对应的函数,LLVMGetUndef和LLVMIsUndef,所以函数值也可以不包括Type了,这样在编译过程中Type已经被消除了。
fn is_undef(&self)->bool
}
其次我们还注意到 一个问题,那就是LLVMType,类型在一个上下文中实际上是单例,通过LLVMXxxType()声明一次后可以无限次使用,而考虑到LLVMType返回的也是一个LLVMTypeRef,因此可能即时无限次调用LLVMType创建类型,都是返回的同一个类型引用。结果确实是对的,因此我对于基本类型直接调用LLVMXxxType返回即可。
对于复杂类型,比如结构体,如何获得他们的LLVMType呢,一个结构体有名字,有字段。这个时候一般是创建命名结构体和非命名结构体两种,如果StructValue有名字则,采用命名结构体,没有,则无命名结构体,考虑到命名结构体创建确实比较复杂,而且创建命名结构体值时需要检查结构体的类型约束,因此需要存储他们的类型,在第一次编译模块时编译所有结构体,存在ctx中, 以后的结构体值有名字时,直接获取结构体类型,进行检查,因此get_llvm_type()应该有一个入参ctx,对于Type的get_llvm_type也应该有一个ctx入参,类型都通通ctx创建,比如ctx.llvm_float_type()
另外,对于更复杂一点的类型和值系统,枚举,llvm本身并没有原生支持枚举,需要我们通过struct进行枚举,我们初步设定枚举的值是tag+字节数组,并通过bitcast进行转换。因此,它的value定义可以是以下的样子:
struct EnumValue{
name:(String,String) // 枚举名和变体名
tag:Int64Value
data:ArrayValue
}
//其llvm_type应该是一种结构固定的结构体,
LLVMType::Enum(EnumLLVMType)
struct EnumLLVMType{
reference:LLVMTypeRef
// 标签和变体映射
variant_tag:Hashmap<string,Int64Value>
}
对value系统进行重设计,目前的value包括type和llvmValue,但是实际上llvmValue就包扩了类型信息,type实际上可以只存在于AST阶段,然后现在构建都是通过builder实现的,但是对于一个IntValue应该可以是啊a.add()另外一个IntValue
enum Value{
Bool(BoolValue)
Int8(Int8Value)
Int16(Int16Value)
Int32(Int32Value)
Int64(Int64Value)
Float(FloatValue)
Double(DoubleValue)
String(StringValue)
Struct(StructValue)
Pointer(PointerValue)
Function(FunctionPointer)
Array(ArrayValue)
}
struct PointerValue{
element: Box<Value>
value:Option<Value>
}
impl PointerValue{
fn get_value()->Value {
match self.value
}
fn get_element_pointer()
}
struct Int64Value {
value:LLVMValueRef
}
impl Int64Value {
fn add(&self,ctx:&Context,value:Int64Value)->Int64Value
fn add_i8(&self,ctx:&Context,value:Int8Value)
}
四、编译前端设计与实现
4.1 词法分析器设计
词法分析器作为编译器的第一道处理环节,其设计直接影响到整个编译过程的效率和可靠性。本系统采用基于Logos库的词法分析器实现,主要考虑以下几个方面:
4.1.1 Token定义
系统定义了完整的Token集合,采用Rust枚举类型实现:
#[derive(Logos, Debug, PartialEq, Clone)]
pub enum Token {
// 字面量类型
#[regex(r#""([^"\\]|\\.)*""#, |lex| lex.slice().to_string())]
String(String),
#[regex(r#"f"([^"\\]|\\.)*""#, |lex| lex.slice().to_string())]
FormatString(String),
#[regex(r"[0-9]+", |lex| lex.slice().parse())]
Int(i64),
#[regex(r"[0-9]+\.[0-9]+", |lex| lex.slice().parse())]
Float(f32),
// 标识符和关键字
#[regex(r"[a-zA-Z_][a-zA-Z0-9_]*", |lex| {
let slice = lex.slice();
match slice {
"true" => Token::Boolean(true),
"false" => Token::Boolean(false),
_ if KEYWORDS.contains(&slice) => Token::Keyword(slice.to_string()),
_ => Token::Identifier(slice.to_string()),
}
})]
Identifier(String),
// 运算符和分隔符
#[token("(")] BraceLeft,
#[token(")")] BraceRight,
#[token("[")] BracketLeft,
#[token("]")] BracketRight,
#[token("{")] ParenLeft,
#[token("}")] ParenRight,
#[token(".")] Dot,
#[token(":")] Colon,
#[token("::")] ScopeSymbol,
#[token("=")] Assign,
#[token(",")] Comma,
#[token("+")] Plus,
#[token("-")] Minus,
#[token("*")] Star,
#[token("/")] Slash,
#[token("%")] Mod,
#[token(">")] Greater,
#[token("<")] Less,
#[token("==")] Equal,
#[token("!=")] NotEqual,
#[token("->")] Arrow,
#[token("!")] Negate,
#[token("&&")] And,
#[token("|")] Vertical,
#[token("@")] Annotation,
#[token("&")] BitAnd,
// 错误和空白处理
#[error]
#[regex(r"[ \t\n\f]+", logos::skip)]
Error,
}
4.1.2 实现策略
4.1.2.1 基于查找表的快速分词算法
本系统采用Logos库提供的自动生成词法分析器功能,其核心是基于查找表的快速分词算法。该算法通过以下方式实现高效分词:
lazy_static! {
static ref KEYWORDS: HashSet<&'static str> = {
let mut set = HashSet::new();
set.insert("fn");
set.insert("let");
set.insert("const");
set.insert("if");
set.insert("else");
set.insert("while");
set.insert("for");
set.insert("return");
set.insert("break");
set.insert("continue");
set
};
}
pub struct Lexer<'a> {
source: &'a str,
position: usize,
line: usize,
column: usize,
tokens: Vec<Token>,
errors: Vec<LexError>,
}
impl<'a> Lexer<'a> {
pub fn new(source: &'a str) -> Self {
Lexer {
source,
position: 0,
line: 1,
column: 1,
tokens: Vec::new(),
errors: Vec::new(),
}
}
pub fn tokenize(&mut self) -> Result<Vec<Token>, Vec<LexError>> {
let mut lexer = Token::lexer(self.source);
while let Some(token) = lexer.next() {
match token {
Ok(tok) => self.tokens.push(tok),
Err(_) => self.errors.push(LexError::new(
self.line,
self.column,
"Invalid token"
)),
}
self.update_position(&lexer);
}
if self.errors.is_empty() {
Ok(self.tokens.clone())
} else {
Err(self.errors.clone())
}
}
}
4.1.2.2 批量读取机制
系统实现了高效的批量读取机制,通过缓冲区技术优化I/O性能:
pub struct SourceReader {
buffer: Vec<u8>,
buffer_size: usize,
position: usize,
file: File,
}
impl SourceReader {
pub fn new(path: &Path, buffer_size: usize) -> io::Result<Self> {
let file = File::open(path)?;
Ok(SourceReader {
buffer: Vec::with_capacity(buffer_size),
buffer_size,
position: 0,
file,
})
}
pub fn read_chunk(&mut self) -> io::Result<&[u8]> {
self.buffer.clear();
self.file.read_to_end(&mut self.buffer)?;
Ok(&self.buffer)
}
}
4.1.2.3 错误恢复机制
系统实现了完善的错误恢复机制,确保在遇到错误时能够继续解析:
#[derive(Debug, Clone)]
pub struct LexError {
line: usize,
column: usize,
message: String,
}
impl LexError {
pub fn new(line: usize, column: usize, message: &str) -> Self {
LexError {
line,
column,
message: message.to_string(),
}
}
pub fn to_string(&self) -> String {
format!("Error at line {}, column {}: {}",
self.line, self.column, self.message)
}
}
pub struct ErrorRecovery {
errors: Vec<LexError>,
recovery_points: Vec<usize>,
}
impl ErrorRecovery {
pub fn new() -> Self {
ErrorRecovery {
errors: Vec::new(),
recovery_points: Vec::new(),
}
}
pub fn add_error(&mut self, error: LexError) {
self.errors.push(error);
}
pub fn add_recovery_point(&mut self, position: usize) {
self.recovery_points.push(position);
}
}
4.1.2.4 Unicode支持
系统通过UTF-8编码处理实现了完整的Unicode支持:
pub struct UnicodeProcessor {
decoder: Decoder,
}
impl UnicodeProcessor {
pub fn new() -> Self {
UnicodeProcessor {
decoder: Decoder::new(),
}
}
pub fn process_char(&mut self, c: char) -> Result<(), UnicodeError> {
if c.is_alphabetic() || c == '_' {
Ok(())
} else {
Err(UnicodeError::InvalidIdentifier)
}
}
}
4.1.2.5 位置信息追踪
系统实现了精确的位置信息追踪,用于错误报告和调试:
#[derive(Debug, Clone, Copy)]
pub struct Position {
pub line: usize,
pub column: usize,
pub offset: usize,
}
impl Position {
pub fn new() -> Self {
Position {
line: 1,
column: 1,
offset: 0,
}
}
pub fn advance(&mut self, c: char) {
self.offset += 1;
if c == '\n' {
self.line += 1;
self.column = 1;
} else {
self.column += 1;
}
}
}
4.1.2.6 性能优化
系统通过多种技术实现性能优化:
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::*;
pub unsafe fn simd_process_chunk(chunk: &[u8]) -> Vec<u8> {
let mut result = Vec::with_capacity(chunk.len());
let mut i = 0;
while i + 16 <= chunk.len() {
let data = _mm_loadu_si128(chunk.as_ptr().add(i) as *const __m128i);
// SIMD处理逻辑
i += 16;
}
result
}
pub struct TokenCache {
cache: LruCache<String, Vec<Token>>,
max_size: usize,
}
impl TokenCache {
pub fn new(max_size: usize) -> Self {
TokenCache {
cache: LruCache::new(max_size),
max_size,
}
}
pub fn get(&mut self, key: &str) -> Option<&Vec<Token>> {
self.cache.get(key)
}
pub fn put(&mut self, key: String, tokens: Vec<Token>) {
self.cache.put(key, tokens);
}
}
4.1.2.7 LLVM集成
系统通过LLVM JIT编译器实现高效的代码生成和执行。词法分析器与LLVM的集成主要体现在以下几个方面:
上下文管理的主要职责包括:
类型映射:将Token类型映射到对应的LLVM类型
值转换:将Token的值转换为LLVM常量
符号表管理:维护变量和函数的符号表
函数级优化:
模块级优化:
初始化阶段:
编译阶段:
执行阶段:
资源生命周期管理:
安全的内存访问:
错误处理:
编译时优化:
运行时优化:
并行处理:
编译错误:
运行时错误:
错误恢复:
4.2 语法分析器设计
语法分析器负责将Token流转换为抽象语法树(AST),本系统采用递归下降法实现。
4.2.1 AST结构设计 系统定义了完整的AST节点类型:
enum Expr {
Literal(Literal), // 字面量表达式
Binary(BinaryExpr), // 二元运算表达式
Unary(UnaryExpr), // 一元运算表达式
Call(CallExpr), // 函数调用表达式
Member(MemberExpr), // 成员访问表达式
Index(IndexExpr), // 数组索引表达式
Identifier(Identifier), // 标识符表达式
Block(BlockExpr), // 代码块表达式
If(IfExpr), // 条件表达式
Loop(LoopExpr), // 循环表达式
Break, // 中断语句
Continue, // 继续语句
Return(Option<Box<Expr>>), // 返回语句
}
enum Stmt {
Let(LetStmt), // 变量声明语句
Const(ConstStmt), // 常量声明语句
Expr(Expr), // 表达式语句
Return(Option<Expr>), // 返回语句
Break, // 中断语句
Continue, // 继续语句
}
struct Module {
items: Vec<Item>, // 模块项列表
}
enum Item {
Function(Function), // 函数定义
Struct(Struct), // 结构体定义
Enum(Enum), // 枚举定义
TypeAlias(TypeAlias), // 类型别名
Import(Import), // 导入语句
}
4.2.2 语法分析策略
4.3 语义分析器设计
语义分析器负责类型推导、类型检查和符号表管理,是确保程序语义正确性的关键环节。
4.3.1 类型系统设计 系统定义了完整的类型系统:
enum Type {
Bool, // 布尔类型
Int8, Int16, Int32, Int64, // 整数类型
Float, Double, // 浮点类型
String, // 字符串类型
Array(Box<Type>, usize), // 数组类型
Struct(StructType), // 结构体类型
Enum(EnumType), // 枚举类型
Function(FunctionType), // 函数类型
Pointer(Box<Type>), // 指针类型
Reference(Box<Type>), // 引用类型
Generic(String), // 泛型类型
Any, // 任意类型
}
struct FunctionType {
params: Vec<Type>, // 参数类型列表
return_type: Box<Type>, // 返回类型
is_variadic: bool, // 是否可变参数
}
struct StructType {
name: String, // 结构体名称
fields: Vec<(String, Type)>, // 字段列表
}
struct EnumType {
name: String, // 枚举名称
variants: Vec<(String, Option<Type>)>, // 变体列表
}
4.3.2 语义分析策略
4.4 LLVM IR生成器设计
LLVM IR生成器负责将AST转换为LLVM中间表示,是连接前端和后端的关键组件。
4.4.1 代码生成流程
4.4.2 优化策略
4.4.3 实现要点
AST到LLVM IR的转换
函数调用和函数声明处理
内存管理机制
异常处理机制
优化策略实现