Skip to main content

Quick Start Guide

This guide will walk you through the most common Full Moon operations, from basic parsing to AST traversal and transformation.

Your First Parse

Let’s start with the most basic operation: parsing Lua code into an AST.
1

Create a new Rust project

cargo new my_lua_parser
cd my_lua_parser
2

Add Full Moon dependency

Cargo.toml
[dependencies]
full_moon = "2.1.1"
3

Parse your first Lua code

src/main.rs
use full_moon::parse;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let ast = parse("local x = 1")?;
    println!("Parsed: {}", ast);
    Ok(())
}
4

Run it

cargo run
Output:
Parsed: local x = 1

Understanding the AST

The parse() function returns an Ast struct that represents your entire Lua program. Let’s explore what we can do with it:
use full_moon::parse;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let ast = parse("local x = 1")?;
    
    // Access the root block
    let block = ast.nodes();
    
    // Iterate over statements
    for stmt in block.stmts() {
        println!("Statement: {}", stmt);
    }
    
    // Count tokens
    let token_count = ast.nodes().tokens().count();
    println!("Token count: {}", token_count);
    
    Ok(())
}
Full Moon’s AST is lossless - it preserves all whitespace, comments, and formatting. When you print the AST back to a string, you get exactly the original code!

Handling Parse Errors

Full Moon provides detailed error information when parsing fails:
use full_moon::parse;

fn main() {
    let code = "local x = ";  // Incomplete assignment
    
    match parse(code) {
        Ok(ast) => println!("Success: {}", ast),
        Err(errors) => {
            for error in errors {
                eprintln!("Error: {}", error.error_message());
                let (start, end) = error.range();
                eprintln!("  at line {}, column {}", start.line(), start.character());
            }
        }
    }
}

Traversing the AST with Visitors

The visitor pattern is the most powerful way to analyze Lua code. Here’s how to find all function calls in a program:
use full_moon::{
    ast,
    parse,
    visitors::Visitor,
};

struct FunctionCallVisitor {
    called: Vec<String>,
}

impl Visitor for FunctionCallVisitor {
    fn visit_function_call(&mut self, call: &ast::FunctionCall) {
        if let ast::Prefix::Name(token) = call.prefix() {
            self.called.push(token.to_string());
        }
    }
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let code = parse("foo(bar())")?;
    let mut visitor = FunctionCallVisitor { called: Vec::new() };
    
    visitor.visit_ast(&code);
    
    println!("Functions called: {:?}", visitor.called);
    // Output: Functions called: ["foo", "bar"]
    
    Ok(())
}

Transforming Code with VisitorMut

VisitorMut allows you to modify the AST and generate transformed code:
use full_moon::{
    ast,
    parse,
    tokenizer::{Token, TokenType},
    visitors::VisitorMut,
};

struct SnakeNamer;

impl VisitorMut for SnakeNamer {
    fn visit_local_assignment(
        &mut self,
        assignment: ast::LocalAssignment,
    ) -> ast::LocalAssignment {
        let name_list = assignment
            .names()
            .pairs()
            .map(|name| {
                name.to_owned().map(|value| {
                    value.with_token(Token::new(TokenType::Identifier {
                        identifier: value.token()
                            .to_string()
                            .replace('s', "sss")
                            .into(),
                    }))
                })
            })
            .collect();

        assignment.with_names(name_list)
    }
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let ast = parse("local dogs, snakes = 1")?;
    let mut visitor = SnakeNamer;
    
    let new_ast = visitor.visit_ast(ast);
    
    println!("{}", new_ast);
    // Output: local dogsss, sssnakesss = 1
    
    Ok(())
}

Working with Tokens

Full Moon provides access to individual tokens with position information:
use full_moon::parse;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let source = parse("local abcd = 1")?;
    
    // Iterate through tokens
    for token in source.nodes().tokens() {
        println!("Token: {}", token);
    }
    
    // Count tokens
    let token_count = source.nodes().tokens().count();
    println!("Total tokens: {}", token_count);
    // Output: Total tokens: 4 (local, abcd, =, 1)
    
    Ok(())
}

Accessing Comments and Whitespace

Full Moon preserves trivia (comments and whitespace) which you can access:
use full_moon::{parse, node::Node};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let code = r#"
-- This is a comment
local x = 1
-- Another comment
local y = 2
"#;
    
    let ast = parse(code)?;
    let stmt = ast.nodes().stmts().nth(1);
    
    if let Some(stmt) = stmt {
        let (prev_trivia, _) = stmt.surrounding_trivia();
        
        for trivia in prev_trivia {
            println!("Trivia: {}", trivia);
        }
    }
    
    Ok(())
}

Fallible Parsing for Error Recovery

For tools like IDEs that need to work with incomplete code, use parse_fallible:
use full_moon::{parse_fallible, LuaVersion};

fn main() {
    let code = "local x = if";  // Invalid code
    
    let result = parse_fallible(code, LuaVersion::new());
    
    // Always returns an AST, even with errors
    let ast = result.ast();
    println!("Partial AST: {}", ast);
    
    // Check for errors
    if !result.errors().is_empty() {
        eprintln!("Errors found:");
        for error in result.errors() {
            eprintln!("  - {}", error.error_message());
        }
    }
}
Partial ASTs from parse_fallible may contain phantom tokens with null positions and may not print to valid Lua code. Use ast.update_positions() if you need accurate positions for phantom tokens.

Common Patterns

use full_moon::parse;
use std::fs;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let code = fs::read_to_string("script.lua")?;
    let ast = parse(&code)?;
    println!("Parsed {} tokens", ast.nodes().tokens().count());
    Ok(())
}
use full_moon::{parse, node::Node};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let ast = parse("local x = 1; local x = 1; local x = 2;")?;
    let stmts: Vec<_> = ast.nodes().stmts().collect();
    
    assert!(stmts[0].similar(stmts[1]));  // Same structure
    assert!(!stmts[0].similar(stmts[2])); // Different values
    
    Ok(())
}
use full_moon::{parse_fallible, LuaVersion};

fn main() {
    // Parse as Lua 5.2 (requires lua52 feature)
    #[cfg(feature = "lua52")]
    {
        let code = "goto label; ::label::";
        let result = parse_fallible(code, LuaVersion::lua52());
        println!("Parsed Lua 5.2 code");
    }
    
    // Parse as Luau (requires luau feature)
    #[cfg(feature = "luau")]
    {
        let code = "local x: number = 1";
        let result = parse_fallible(code, LuaVersion::luau());
        println!("Parsed Luau code");
    }
}

Next Steps

API Reference

Explore the complete API documentation

AST Nodes

Learn about all available AST node types

Visitors

Deep dive into the visitor pattern

Examples

See more complete examples and use cases

Build docs developers (and LLMs) love