1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
use std::sync::Arc;

use mlua::prelude::*;
use once_cell::sync::Lazy;

static LUAU_VERSION: Lazy<Arc<String>> = Lazy::new(create_luau_version_string);

/**
    Returns a Lune version string, in the format `Lune x.y.z+luau`.

    The version string passed should be the version of the Lune runtime,
    obtained from `env!("CARGO_PKG_VERSION")` or a similar mechanism.

    # Panics

    Panics if the version string is empty or contains invalid characters.
*/
#[must_use]
pub fn get_version_string(lune_version: impl AsRef<str>) -> String {
    let lune_version = lune_version.as_ref();

    assert!(!lune_version.is_empty(), "Lune version string is empty");
    assert!(
        lune_version.chars().all(is_valid_version_char),
        "Lune version string contains invalid characters"
    );

    format!("Lune {lune_version}+{}", *LUAU_VERSION)
}

fn create_luau_version_string() -> Arc<String> {
    // Extract the current Luau version from a fresh Lua state / VM that can't be accessed externally.
    let luau_version_full = {
        let temp_lua = Lua::new();

        let luau_version_full = temp_lua
            .globals()
            .get::<_, LuaString>("_VERSION")
            .expect("Missing _VERSION global");

        luau_version_full
            .to_str()
            .context("Invalid utf8 found in _VERSION global")
            .expect("Expected _VERSION global to be a string")
            .to_string()
    };

    // Luau version is expected to be in the format "Luau 0.x" and sometimes "Luau 0.x.y"
    assert!(
        luau_version_full.starts_with("Luau 0."),
        "_VERSION global is formatted incorrectly\
        \nFound string '{luau_version_full}'"
    );
    let luau_version_noprefix = luau_version_full.strip_prefix("Luau 0.").unwrap().trim();

    // We make some guarantees about the format of the _VERSION global,
    // so make sure that the luau version also follows those rules.
    if luau_version_noprefix.is_empty() {
        panic!(
            "_VERSION global is missing version number\
            \nFound string '{luau_version_full}'"
        )
    } else if !luau_version_noprefix.chars().all(is_valid_version_char) {
        panic!(
            "_VERSION global contains invalid characters\
            \nFound string '{luau_version_full}'"
        )
    }

    luau_version_noprefix.to_string().into()
}

fn is_valid_version_char(c: char) -> bool {
    matches!(c, '0'..='9' | '.')
}