moonsharp bytecode types, assembler, disassembler, and static analyzer
  • Rust 98.5%
  • Nix 1.5%
Find a file
2026-07-09 10:49:09 -07:00
.vscode init 2026-02-06 22:09:19 -08:00
src implement analyzer, start optimizer/compiler/bundler/decompiler 2026-07-09 10:49:09 -07:00
.editorconfig init 2026-02-06 22:09:19 -08:00
.envrc rename verify.rs to analyzer.rs, fix slight bug with symbolrefs, explode devenv 2026-02-18 11:44:17 -08:00
.gitignore rename verify.rs to analyzer.rs, fix slight bug with symbolrefs, explode devenv 2026-02-18 11:44:17 -08:00
Cargo.lock implement analyzer, start optimizer/compiler/bundler/decompiler 2026-07-09 10:49:09 -07:00
Cargo.toml implement analyzer, start optimizer/compiler/bundler/decompiler 2026-07-09 10:49:09 -07:00
flake.lock implement analyzer, start optimizer/compiler/bundler/decompiler 2026-07-09 10:49:09 -07:00
flake.nix rename verify.rs to analyzer.rs, fix slight bug with symbolrefs, explode devenv 2026-02-18 11:44:17 -08:00
grammar.pest implement analyzer, start optimizer/compiler/bundler/decompiler 2026-07-09 10:49:09 -07:00
README.md implement analyzer, start optimizer/compiler/bundler/decompiler 2026-07-09 10:49:09 -07:00
rustfmt.toml init 2026-02-06 22:09:19 -08:00

moongen provides both

  • a command line program for assembling/disassembling/analyzing moonsharp bytecode
  • a library for interacting with it

installing

as a CLI

any of the following:

  • Nix:
    • nix run git+https://code.dolls.today/voidstella/moongen -- asm test.txt to run directly
    • nix shell git+https://code.dolls.today/voidstella/moongen to get in a shell
  • NixOS:
    • add moongen.url = "git+https://code.dolls.today/voidstella/moongen" to flake inputs
    • then get the package from inputs.moongen.packages.${system}.default
  • cargo:
    • cargo install moongen

as a library

cargo add moongen

CLI usage

the CLI has the following global args:

  • [--color,-c = auto,always,never] choose whether output will be colored

and the following subcommands:

  • asm <src> <dst> assembles the textual format into a bytecode dump
  • disasm <src> <dst> [--asm,-a] disassembles a bytecode dump into the textual format
  • analyze <src> <dst> [--asm,-a] analyzes a bytecode dump and prints any diagnostics if it violated any rules, along with the full path taken to the diagnostics
  • optimize <src> <dst> [--asm,-a] [--disasm,-d] attempt to optimize bytecode by analyzing it and applying changes the analyzer found
  • vanity <src> <dst> <vanity> modify an existing bytecode dump and add a somewhat-readable string near the start, useful if you want to add a note for someone reading the bytecode
  • compile <src> <dst> unfinished, will compile lua source code into bytecode
  • bundle <src> <dst> unfinished, will compile and bundle multiple files into one, walking any requires it can find
  • decompile <src> <dst> unfinished, will attempt to decompile bytecode into somewhat readable source

notes:

  • all src/dst args accept - as their path, indicating they should read data from stdin (src) or write it to stdout (dst)
  • --asm indicates that src should be treated as the textual form rather than the binary form
  • --disasm indicates that dst should be written with the textual form rather than the binary form

assembly format

for an instruction reference, review the Inst documentation

syntax is defined by grammar.pest, and follows the following format

  • each line may start with a label definition: @ident:
  • each line may have one instruction
    • an instruction name (ident)
    • if the instruction takes addr, one of the following:
      • an integer specifying the instruction address relative to the start of the chunk
      • ~, followed by an integer specifying the instruction address relative to the current instruction
      • @, followed by an ident referring to a label
    • if the instruction takes arg1, an integer
    • if the instruction takes arg2, an integer
    • if the instruction takes name, a string
    • if the instruction takes value, an =, followed by one of the following:
      • null
      • nil
      • void
      • true
      • false
      • a float
      • a string
      • {} (creates an empty table)
    • if the instruction takes symbol, a symbol
    • if the instruction takes symbol_list, [, comma-separated symbols, ]

terminology

  • idents follow the regex /[a-zA-Z_][a-zA-Z0-9_]*/
  • integers follow the regex /-?(?:0|[1-9][0-9]*)/
  • floats follow the regex /-?(?:0|[1-9][0-9]*)(?:\.[0-9]*)/
  • strings are either
    • JSON-escaped content wrapped in quotes ("this is a string with \"embedded\" quotes")
    • base64-encoded content wrapped in quotes and prefixed with b (b"dGhpcyBpcyBhIHN0cmluZyB3aXRoICJlbWJlZGRlZCIgcXVvdGVz", useful for binary data)
  • symbols are one of the following:
    • &, symbol name (local name), :, integer (local index)
    • ^, symbol name (upvalue name), :, integer (upvalue index)
    • %, symbol name (global name), :, symbol (global _ENV)
    • env (_ENV symbol)
    • nullref (null symbol)
  • symbol names are one of the following:
    • an ident (name)
    • an ident, @, integer (name + disambiguation)
    • ... (vararg)

full demonstration

#![has_env]
// useful for debugging purposes
meta 25 1 "greeter" =null
// does nothing but is in the function header anyways
fn 0 -1 []

fn.new @greet []
upv.ld ^_ENV:0
// %greet:^_ENV:0 isnt necessary, but moonsharp emits it anyways
// you can use nullref for index.set
index.set 0 0 ="greet" %greet:^_ENV:0

// moonsharp likes to generate closures by emitting their instructions and jumping over them
// you dont have to do it this way though (it also saves an instruction to Not Do That)
// but this example will do it moonsharp's way
jmp @over_greet
	@greet:
	meta 9 1 "greet" =null
	fn 1 0 [&who:0]
	fn.args [&who:0]

	lit ="hello "
	loc.ld &who:0
	lit ="!"
	op.concat
	op.concat

	ret 1
	// moonsharp also generates unreachable `ret 0`s even when the last instruction in a function is a `ret 1`...
	ret 0
@over_greet:

// indentation isn't forced either way! lay it out in a way that makes more sense if you'd like
			upv.ld ^_ENV:0
		index ="print"
				upv.ld ^_ENV:0
			index ="greet"
			lit ="dolly"
		call 1 "calling greet"
	call 1 "calling print"
pop 1

ret 0