1 个不稳定版本
0.1.0 | 2021年8月11日 |
---|
#788 在 编程语言
用于 lua-cupid
170KB
4K SLoC
Hematita Da Lua
Hematita Da Lua是一个用于Lua脚本语言的解释器,完全使用100%安全的Rust编写。Hematita是葡萄牙语中赤铁矿(一种氧化铁,或锈)的名称,而lua是葡萄牙语中月亮的名称。"Hematita Da Lua"是对项目本身的一种双关语,以及发现月球上的铁正在生锈的消息(NASA的消息)。
项目的目的是提供一个硬化的Lua解释器,能够抵御安全漏洞。它通过不使用不安全代码,能够在稳定版编译,以及通过依赖最少数量的依赖项来实现这一点。这样,我们可以确信我们不会受到任何尚未发现的C代码安全漏洞的影响。对标准的Lua实现和其他C项目没有不敬。
尽管如此,需要注意的是,Hematita并不稳定,处于早期开发阶段,可能存在错误。我希望能有足够的时间让它成熟,Hematita能够保证这些。
独立运行Hematita
如果您想测试这个解释器,请运行cargo install hematita_cli
,然后运行hematita_cli
。您将进入一个基本的REPL,您可以随时按下Ctrl
+ C
退出。大多数投递给它的Lua代码应该都能正常运行,但并非所有,例如一些for
循环的特性。如果您遇到任何无法正常运行的代码,请提交一个问题,它将在未来的版本中得到修复!
命令行界面有许多选项;使用--help
运行它将显示所有选项。
OPTIONS:
-h, --help Displays this and quits
-V, --version Displays version information
-v, --verbose Runs with verbose output
-i, --interactive Runs in interactive mode, after running SOURCE
-e, --evaluate Treats source as direct source code, rather than a file
-b, --byte-code Shows byte code rather than executing
-s, --ast Shows abstract syntax tree rather than executing
-t, --tokens Shows tokens rather than executing
目前,--verbose
没有任何作用,会被忽略。使用--help
或--version
时将阻止任何代码运行。--interactive
可以与文件一起使用,文件执行完毕后,您将进入一个REPL环境,脚本的所有状态都留给了您去操作。使用--evaluate
将评估命令行上直接传递的代码,而不是从文件加载。
--byte-code
、--ast
和--tokens
选项都会改变解释器的输出。使用--byte-code
将打印程序的编译后的字节码而不是执行它。--ast
将打印解释后的抽象语法树而不是执行,而--tokens
将打印标记流的调试视图而不是执行。每个选项都对应解释器的不同部分,更多详细信息请参阅内部结构部分。
嵌入 Hematita
嵌入 Hematita 比较直接,只需要连接解释器的各个部分。像往常一样,在您的Cargo.toml
中引入该包。然后,您只需六行代码就可以在您的下一个大型项目中运行 Lua 代码。
use hematita::{ast::{lexer, parser}, compiler, vm, lua_lib, lua_tuple};
// Ready our Lua source code.
let source = "print(\"Hello, World!\")";
// Create a lexer (just a token iterator) from the characters of our source code.
let lexer = lexer::Lexer {source: source.chars().peekable()}.peekable();
// Parse from the lexer a block of statements.
let parsed = parser::parse_block(&mut parser::TokenIterator(lexer)).unwrap();
// Compile bytecode from the block of statements.
let compiled = compiler::compile_block(&parsed);
// Prepare the global scope.
let global = lua_lib::standard_globals();
// Create the virtual machine...
let virtual_machine = vm::VirtualMachine::new(global);
// And run the byte code.
virtual_machine.execute(&compiled.into(), lua_tuple![].arc()).unwrap();
VirtualMachine
是Send
+ Sync
,并包含一个用于您创建的本地函数和用户数据的生存期'n
。所以,拿出旧的crossbeam::thread::Scope
,尽情使用。如果您对每行代码的作用感到好奇,请参阅内部结构部分以获取更多信息。
创建您自己的本地函数也很简单。只需修改全局作用域中的任何函数,例如类型,即任何动态分派的可调用类型Fn
。
let number = Mutex::new(0);
// Rust is bad at inferring closure paramaters, so it needs a &_. :(
let counter = move |_, _: &_| {
let mut lock = number.lock().unwrap();
let old = *lock;
*lock += 1;
Ok(lua_tuple![old].arc())
};
let global = {
let globals = standard_globals();
let mut data = globals.data.lock().unwrap();
data.insert(lua_value!("counter"), Value::NativeFunction(&counter));
drop(data);
globals
};
创建您自己的用户数据的方式也相同。创建一个类型,为它实现vm::value::UserData
,然后使用Value::UserData
将其插入全局作用域中。(或者,如果您愿意,可以创建一个返回它的本地函数函数。)与实现您自己的用户数据一样,您的大部分类型将通过元表实现。您可以通过添加一个__metatable
条目来锁定元表。
内部结构
Hematita 由四个主要部分组成。分别是ast::lexer
、ast::parser
、compiler
和vm
,每个部分依赖于上一个部分。每个部分代表一个不同的过程,lexer
是词法分析过程,parser
是解析过程,等等。
每个部分都可以单独使用,但最好一起使用。如果您只想对 Lua 代码进行词法和语法分析,ast
模块完全可以处理。如果您只想运行手工制作的字节码,vm
模块非常适合。但真正的作用来自于将所有这些组合在一起,形成一个完整的解释器。
词法分析器
词法分析器只是将字符流转换为Token
流。它实际上是迭代器上的一个操作。您可以在这里阅读它的文档这里,请注意它们是不完整的。
解析器
解析器接受一个令牌流,将其转换为 Block
。一个 Block
只是一个 Vec
的 Statement
。 Statement
是 Lua 语句的内部表示。你可以在这里阅读它的文档 这里,注意它们是不完整的。
编译器
编译器接受一个 Block
,并生成一个 Chunk
。一个 Chunk
只是一个包含一些元数据的 Vec
的 OpCode
。它实际上是一个一对一的转换,因此不需要错误处理。你可以在这里阅读它的文档 这里,注意它们是不完整的。
虚拟机
虚拟机接受一个 Function
并执行它。一个 Function
只是一个实例化的 Chunk
,带有相关的向上值。它可以通过对 Chunk
调用 into
来创建。虚拟机实际上是对每个 OpCode
和实现它的代码的匹配语句。你可以在这里阅读它的文档 这里,注意它们是不完整的。
依赖
~3MB
~59K SLoC