1 个不稳定版本

0.1.0 2021年8月11日

#788编程语言


用于 lua-cupid

GPL-3.0 许可协议

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();

VirtualMachineSend + 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::lexerast::parsercompilervm,每个部分依赖于上一个部分。每个部分代表一个不同的过程,lexer是词法分析过程,parser是解析过程,等等。

每个部分都可以单独使用,但最好一起使用。如果您只想对 Lua 代码进行词法和语法分析,ast模块完全可以处理。如果您只想运行手工制作的字节码,vm模块非常适合。但真正的作用来自于将所有这些组合在一起,形成一个完整的解释器。

词法分析器

词法分析器只是将字符流转换为Token流。它实际上是迭代器上的一个操作。您可以在这里阅读它的文档这里,请注意它们是不完整的。

解析器

解析器接受一个令牌流,将其转换为 Block。一个 Block 只是一个 VecStatementStatement 是 Lua 语句的内部表示。你可以在这里阅读它的文档 这里,注意它们是不完整的。

编译器

编译器接受一个 Block,并生成一个 Chunk。一个 Chunk 只是一个包含一些元数据的 VecOpCode。它实际上是一个一对一的转换,因此不需要错误处理。你可以在这里阅读它的文档 这里,注意它们是不完整的。

虚拟机

虚拟机接受一个 Function 并执行它。一个 Function 只是一个实例化的 Chunk,带有相关的向上值。它可以通过对 Chunk 调用 into 来创建。虚拟机实际上是对每个 OpCode 和实现它的代码的匹配语句。你可以在这里阅读它的文档 这里,注意它们是不完整的。

依赖

~3MB
~59K SLoC