A memory safe Lua interpreter

Overview

Hematita Da Lua


Hematita Da Lua is an interpreter for the scripting language Lua, written entirely in 100% safe Rust. Hematita is the portugese word for hematite, a type of iron oxide, or rust, and lua is the portugese word for moon. 'Hematita Da Lua' is a pun on what the project is, and the discovery that iron on the moon is rusting.

The purpose of the project is to provide a hardened Lua interpreter resiliant to security vulnerabilities. It accomplishes this by using no unsafe code, being compileable on stable, and by relying upon a minimal number of dependencies. With this, we can be confident we are safe from any yet-to-be-found security vulnerabilities in C code. No disrespect to the standard Lua implementation and other C projects.

That said, it is important to note that Hematita is not stable, is very early in it's development and may be buggy. It is my hope that, with enough time to mature, Hematita will be able to garuntee these things.

Running Hematita Standalone

If you'd like to give the interpreter a test drive, run cargo install hematita_cli, and then run hematita_cli. You'll be placed in a basic REPL, and you can press Ctrl + C at any time to exit. A large proportion of the Lua code you throw at it should work fine, but not everything, such as some features of for loops. Please file an issue if you encounter anything that doesn't work, and it'll be fixed soon in a future version!

The command line interface has quite a few options; running it with --help will show them all.

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

Currently, --verbose does nothing, and is ignored. Running with either --help or --version will prevent any code from being ran. --interactive can be passed with a file, and after execution of the file ends, you'll be dropped into a REPL with all the state of the script left for you to mess with. Using --evaluate will evaluate code passed directly on the command line, rather than loading it from a file.

The --byte-code, --ast, and --tokens options all change the output of the interpreter. Using --byte-code will print out the compiled byte code of the program rather than executing it. --ast will print out the interpreted abstract syntax tree rather than executing, and --tokens will print out debug views of the token stream rather than executing. Each of these options correspond to a different segment of the interpreter, see the internals section for more information.

Embedding Hematita

Embedding Hematitia is fairly straight forward and only requires stringing together each segment of the interpreter. As always, require the crate in your Cargo.toml. Then, you're just six lines of code away from running Lua code in your next big project.

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 is Send + Sync, and includes a lifetime 'n for the native functions and user data you make for your convenience. So, bust out the old crossbeam::thread::Scope, and go wild. If you're curious about what each of the lines do, see the internals section for more information.

Creating your own native function is easy too. All it takes is to modify the global scope with any old function like type, i.e. any dynamically dispatchable 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
};

The same goes for creating your own user data. Make a type, implement vm::value::UserData for it, and insert it into the global scope with Value::UserData. (Or, if you prefer, make a native function function that returns it.) As is typical with implementing your own user data, the vast majority of your type will be implemented via the metatable. You can lock your metatable by adding a __metatable entry into it.

The Internals

Hematita is composed of four main segments. Those being, ast::lexer, ast::parser, compiler, and vm, each segment depending on the last. Each segment represents a different process, the lexer is the lexing process, the parser is the parsing process, so on and so forth.

Each segment can be used on it's own, but they're best used all together. If you'd like to just lex and parse lua code, the ast module can totally handle that. If you'd like to just run hand crafted bytecode, the vm module is well suited for it. But the real effect comes from stringing everything together, to form a complete interpreter.

The Lexer

The lexer just turns a stream of characters into a stream of Tokens. It's effectively just an operation over an iterator. You can read it's docs here, note that they are incomplete.

The Parser

The parser takes a stream of tokens, and turns it into a Block. A Block is just a Vec of Statements. Statements are an internal representation of a Lua statement. You can read it's docs here, note that they are incomplete.

The Compiler

The compiler takes a Block, and produces a Chunk. A Chunk is just a Vec of OpCodes, with some metadata. It is effectively a one to one transformation, so no error handling is needed. You can read it's docs here, note that they are incomplete.

The Virtual Machine

The virtual machine takes a Function, and executes it. A Function is just an instantiated form of a Chunk, with associated up-values. It can be made just by calling into on a Chunk. The virtual machine is effectively a match statement over every OpCode, and the code that implements it. You can read it's docs here, note that they are incomplete.

Comments
  • Errors with `hashbrown`

    Errors with `hashbrown`

    The example in the README will fail due to the lua_tuple! macro needing the hasbrown crate. What's a bit unfortunate is that hasbrown 0.12 has restructured things a bit.

    Without hasbrown at all, you'll receive

    use of undeclared crate or module `hashbrown`
    

    And with 0.12 you'll receive

    expected struct `hashbrown::map::HashMap`, found struct `hashbrown::HashMap`
    

    After setting the version in my projects Cargo.toml to 0.11.2 things started working.

    Type - Bug Domain - Virtual Machine Bug - Reproducible 
    opened by RandomInsano 1
  • [Suggestion] It may be interesting to include benchmarks in the README

    [Suggestion] It may be interesting to include benchmarks in the README

    Running the same benchmarks as in LuaJIT against both the canonical and LuaJIT interpreters would be interesting to have a ballpark estimation about whether this is also a proper replacement where speed matters. It may also help guide optimizations if needed.

    opened by Oppen 0
  • Remove several dependencies

    Remove several dependencies

    It seems that the visibility crate was to just make certain functions public to test. I figured the easier thing to do would be to change the function visibility to just be for the crate and this should make sure they can't be used by other crates (the CLI included) effectively meaning they should by dropped for being unused.

    The only real reason I did this is that I want to use Hematita on Windows 7 (yes, it's over 13 years old) and I had thought that some of my compiler errors being thrown by some dependencies could be fixed by removing them. Sadly no! But hopefully things compile a lot faster. That's a win too.

    opened by RandomInsano 0
  • Remove an 'allow' that was generating warnings

    Remove an 'allow' that was generating warnings

    Rust 1.67 nightly was complaining about the allow here:

    1101 |                 #[allow(unused_mut, unused_variable, unused_assignments)]
         |                                     ^^^^^^^^^^^^^^^ help: did you mean: `unused_variables`
    

    Seems to be fine now with both 1.67 and 1.65.

    opened by RandomInsano 0
  • No support for double/float numeric values?

    No support for double/float numeric values?

    In Lua, all numbers are assumed to be doubles, not integers.

    Using double numeric values causes the parser to crash.

    local PI = 3.14
    

    panics on let parsed = parser::parse_block(&mut parser::TokenIterator(lexer)).unwrap(); while

    local meaning_of_life=42
    

    works perfectly fine.

    opened by AviiNL 0
  • CLI doesn't install

    CLI doesn't install

    Hmm. Tried installing the CLI based on the README and it failed:

    $ cargo install hematita_cli
        Updating crates.io index
    error: could not find `hematita_cli` in registry `crates-io` with version `*`
    

    I've tried on Ubuntu 21.10 and Windows 7 (yes, it's old on purpose :D)

    opened by RandomInsano 3
Releases(0.1.0)
Owner
Daniel
Someone trying to make something interesting for the world to enjoy. Back end developer and tinkerer.
Daniel
Safe Rust bindings to Lua 5.1

rust-lua Copyright 2014 Lily Ballard Description This is a set of Rust bindings to Lua 5.1. The goal is to provide a (relatively) safe interface to Lu

Lily Ballard 124 Jan 5, 2023
High-level memory-safe binding generator for Flutter/Dart <-> Rust

flutter_rust_bridge: High-level memory-safe binding generator for Flutter/Dart <-> Rust Want to combine the best between Flutter, a cross-platform hot

fzyzcjy 2.1k Dec 31, 2022
Lua 5.3 bindings for Rust

rust-lua53 Aims to be complete Rust bindings for Lua 5.3 and beyond. Currently, master is tracking Lua 5.3.3. Requires a Unix-like environment. On Win

J.C. Moyer 150 Dec 14, 2022
Zero-cost high-level lua 5.3 wrapper for Rust

td_rlua This library is a high-level binding for Lua 5.3. You don't have access to the Lua stack, all you can do is read/write variables (including ca

null 47 May 4, 2022
Rust library to interface with Lua

hlua This library is a high-level binding for Lua 5.2. You don't have access to the Lua stack, all you can do is read/write variables (including callb

Pierre Krieger 488 Dec 26, 2022
Pure Rust Lua implementation

purua Pure Rust Lua implementation Usage $ bat lua_examples/defun.lua ───────┬────────────────────────────────────────── │ File: lua_examples/d

Kondo Uchio 35 Dec 28, 2021
📦 Pack hundreds of Garry's Mod Lua files into just a handful

?? gluapack gluapack is a program that can pack hundreds of Garry's Mod Lua files into just a handful. Features Quick, easy and portable - perfect for

null 11 Aug 10, 2021
A script language like Python or Lua written in Rust, with exactly the same syntax as Go's.

A script language like Python or Lua written in Rust, with exactly the same syntax as Go's.

null 1.4k Jan 1, 2023
A super-lightweight Lua microservice (toy) framework.

Hive A super-lightweight microservice (toy) framework written in Rust. It uses Lua as interface to provide simple, fun developing experience and fast

Eric Long 39 Aug 14, 2022
🐱‍👤 Cross-language static library for accessing the Lua state in Garry's Mod server plugins

gmserverplugin This is a utility library for making Server Plugins that access the Lua state in Garry's Mod. Currently, accessing the Lua state from a

William 5 Feb 7, 2022
A parser, compiler, and virtual machine evaluator for a minimal subset of Lua; written from scratch in Rust.

lust: Lua in Rust This project implements a parser, compiler, and virtual machine evaluator for a minimal subset of Lua. It is written from scratch in

Phil Eaton 146 Dec 16, 2022
Rust scaffold system with Lua embedded applets.

brickpack-2022 Demo Powered by Github Actions CI/CD (Heroku) https://demo-1642622230.herokuapp.com/#/users Frontent Runner Rendered sample code (Lua 5

null 0 Nov 24, 2022
This tool converts Lua code to TS automatically, including the conversion of common standards to their TS equivalents.

lua-to-ts This tool converts Lua code to TS automatically, including the conversion of common standards to their TS equivalents. Code that fails to be

Dion 11 Nov 21, 2022
Another cursed Garry's Mod module. This time, it adds the C preprocessor to Lua scripts

gm_cpreprocessor Another cursed Garry's Mod module. This time, it adds the C preprocessor to Lua scripts. It works by detouring RunStringEx and overri

William 6 Aug 14, 2022
Node.js bindings to Lua

Node.js bindings to Lua

Connor Brewster 6 Dec 19, 2022
⚡ Fast Web Security Scanner written in Rust based on Lua Scripts 🌖 🦀

⚡ Fast Web Security Scanner written in Rust based on Lua Scripts ?? ??

Rusty Sec 14 Nov 28, 2022
Rust bindings for the Python interpreter

PyO3 Rust bindings for Python. This includes running and interacting with Python code from a Rust binary, as well as writing native Python modules. Us

PyO3 7.2k Jan 4, 2023
A simple Pascal interpreter written in rust.

rascal A simple Pascal interpreter written in rust. Usage Download the latest rascal executable from the release page. Run the executable. rascal.exe

null 47 Dec 7, 2022
An interpreter for the esoteric programming language, Brainf*ck.

Brainf*ck Interpreter This is just a normal Brainf*ck interpreter written in Rust. If you don't know what Brainf*ck is, you can check out the wikipedi

Callum Irving 0 Dec 23, 2021