Skip to content

utility-library/leap3

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

17 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Leap V3

Leap (Lua extension accessibility pre-processor) is a fast pre-processor of a "modernized" and extended version of lua that pre-processes in pure executable lua.
Think of it as an effective "modernity" leap into the future to make lua a feature-rich language.

Leap is inspired by the functionality and syntax found in JS and is primarily intended for FiveM, this however does not deny the possibility of extension to wider horizons.

To assist with debugging and error reporting, Leap retains the original line numbers from the code.

Leap features a caching system that processes only the files that have been modified, ensuring that the resource is only preprocessed when necessary; if no changes are detected, the resource will simply restart. You can find the files created by this system under the cache folder in the root folder of your server (near the resources folder)

After preprocessing, Leap automatically adds the build subfolder (if needed) to all file declarations in the fxmanifest.lua under the client, server, shared, and escrow_ignore sections. Since it will modify the fxmanifest.lua it requires the permission to run the refresh command, you can allow this by adding add_ace resource.leap command.refresh allow in your server.cfg

The vscode syntax highlight extension is available!.

Note To contribute to the grammar and/or preprocessor in general please visit the preprocessor branch.

Usage

To use Leap, simply download it and start it as you would with any normal resource. You'll need to add Leap to your resource's dependencies, after which you can access any of its features. When the resource starts, Leap will handle the preprocessing of the necessary files automatically.

Example: your_resource_that_use_leap > fxmanifest.lua:

fx_version "cerulean"
game "gta5"

server_script "server.lua"

dependency "leap" -- This is necessary to have the resource automatically preprocessed

You can also manually use the leap restart your_resource_that_use_leap command to preprocess the files and restart the resource.

Escrow

To use Leap in the escrow or outside the Leap ecosystem, you can create a standalone version by running the command leap build

Commands

TIP: these commands can also be used in the cfg after the leap startup.

leap restart

leap restart <resource> build and restarts the resource, manual version of the more classic restart <resource>


leap build

leap build <resource> pre-processes the resource by creating a subfolder named build that contains the pre-processed version of the resource (This will ignore the cache)

resource structure tree after build:

│   fxmanifest.lua
│
├───build
│   └───server
│           main.lua
│
└───server
        main.lua

Leap Library

leap.deserialize

Converts serialized class back into an instance of a specific class, restoring the object's structure and data. This is particularly useful for recreating objects when transferring data on the network (client > server, server > client)

Note Object are automatically serialized when sended over the network

Example:

RegisterNetEvent("myEvent", function(myObject)
    local obj = leap.deserialize(myObject)
    print(obj.myVar)
end)

Features

Classes

Classes are a model for creating objects (a particular data structure), providing initial values for state (member variables or attributes), and implementing behavior (member functions or methods).
It is possible as well to extend already existing classes, each method of the class that extends the other class will have a variable named super which is an instantiated object of the original class, calling this variable as a function will call the constructor of the original class, otherwise the data of the original class can be accessed.
Constructor parameters are those passed when a new object is instantiated. Read more here

Note Classes have their own type, so calling type(obj) with an instantiated class will return the class name Note You can edit the class prototype after the definition using the __prototype attribute


Syntax:

Note Methods automatically hold the self variable referring to the instantiated object

class className {
    var1 = 1,
    constructor = function()
        print(self.var1)
    end
}

Example:

class BaseClass {
    someVariable = 100,
    constructor = function()
        print("instantiated base class")
    end
}

class AdvancedClass extends BaseClass {
    constructor = function()
        print("instantiated advanced class")
        self.super()
        
        -- right now they should printout the same value, since they have not been touched
        print(self.super.someVariable)
        print(self.someVariable)
    end
}

new AdvancedClass()
-- output:
--[[
    instantiated advanced class
    instantiated base class
    100
    100
]]

Compact function

An compact function expression is a compact alternative to a traditional function expression. Is simply an alternative to writing anonymous functions, similar to arrow functions

Syntaxes:

(param1, paramN) do
  -- code
end
param do -- code (single expresion)
(param1, paramN) do -- code (single expresion)

Example:

local tab = {3, 10, 50, 20, 5}

-- Inline with multiple params
table.sort(tab, (a, b) do a < b)

 -- Inline with single param
AddEventHandler('onResourceStart', name do print("Resource ${name} started!"))

 -- Multiline with multiple params
AddEventHandler('playerConnecting', (name, setKickReason, deferrals) do
    if name == "XenoS" then
        print("XenoS is connecting! WOW!")
    else
        print("Player ${name} is connecting!")
    end
end)

Continue keyword

Used to continue execution in a loop. It is useful when you want to skip the current iteration of a loop.

Example:

for i = 1, 10 do
    if i == 5 then
        continue
    end
    print(i)
end

-- output:
-- 1
-- 2
-- 3
-- 4

-- 6
-- 7
-- 8
-- 9
-- 10

Cosmetic underscores for integers

Are used to improve readability in large numeric literals by grouping digits without affecting the value. They allow you to visually separate groups, like thousands or millions, making long numbers easier to parse at a glance. The underscores are ignored during preprocessing.

Syntax:

1_000_000

Example:

local myBigNumber = 2_147_483_647

Decorators

By definition, a decorator is a function that takes another function and extends the behavior of the latter function without explicitly modifying it. Its like wrappers You can also pass parameters to the decorators.

Read more here

Syntax:

function decoratorName(func, a, b)
    return function(c, d)
        func(c,d)
    end
end

@decoratorName(a, b)
function fnName(c, d)
    -- code
end

Example:

function stopwatch(func)
    return function(...)
        local time = GetGameTimer()
        local data = func(...)
        print("taken "..(GetGameTimer() - time).."ms to execute")
        return data
    end
end

@stopwatch
function someMathIntensiveFunction(a, b, pow)
    for i=1, 500000 do
        math.pow(a*b, pow)
    end

    return math.pow(a*b, pow)
end

someMathIntensiveFunction(10, 50, 100)
-- output: 
--[[
    someMathIntensiveFunction taken 2ms to execute
]]
function netevent(self, func, name)
    RegisterNetEvent(name)
    AddEventHandler(name, function(...)
        func(self, ...) -- Remember to call the function with the class instance!
    end)

    return func
end

class Events {
    @netevent("Utility:MyEvent") -- Decorators can also be used in classes fields (will pass self instance)
    eventHandler = function()
        print("Event triggered!")
    end
}
function callInit(class)
    class:init()
    return class
end

@callInit -- Decorators can also be used in classes fields like functions
class MyClass {
    init = function()
        print("Class initialized!")
    end
}

Default value

Default function parameters allow named parameters to be initialized with default values if no value or nil is passed.

Syntax:

function fnName(param1 = defaultValue1, ..., paramN = defaultValueN) {
  -- code
}

Example:

function multiply(a, b = 1)
  return a * b
end

print(multiply(5, 2))
-- Expected output: 10

print(multiply(5))
-- Expected output: 5

Filter

A filter is a mechanism for validating specific conditions before a function is executed. It serves as a precondition gatekeeper, ensuring the function runs only if all criteria are satisfied. If any condition fails, the filter stops execution and returns an error message.
Filters simplify code by centralizing validation logic and making it reusable across multiple functions.
They can access local variables from their enclosing scope and are evaluated with the using operator.

Syntax:

filter Name(param1, param2, ...)
    condition1 else "Error message for condition1!",
    condition2
end

filter Name
    condition1 else "Error message for condition1!",
    ...
end

Example:

filter IsAuthorized(action)
    user.role == "admin" else "User is not an admin!",
    user.permissions[action] else "User lacks permission to perform ${action}!",
end

-- Check "using" for a usage example

In operator

Checks whether a specified substring/element exists in an string/table. It returns true if the substring/element is found and false otherwise.

In arrays checks if value is in table. In hash maps checks if the key is in table. For mixed tables, it checks if the value is in the table, or if the key is in the table.

In strings checks if substring is in string.

Syntax:

value in table
key in table
substring in string

Example:

local array = {3, 10, 50, 20, 5}

if 10 in array then
    print("found 10")
end
----
local hashmap = {
    ["key1"] = "value1",
    ["key2"] = "value2",
    ["key3"] = "value3"
}

if "key1" in hashmap then
    print("found key1")
end
----
local mixed = {
    ["key1"] = "value1",
    3,
    10,
    20
}

if 10 in mixed or "key1" in mixed then
    print("found 10 or key1")
end
----
local string = "Hello World"

if "World" in string then
    print("found World")
end

New

The new operator is used to create an instance of an object. During preprocessing it is skipped. Its utility is to make it clear in the code when you are instantiating an object or calling a function

Syntax:

new Class()

Example:

class BaseClass {
    someVariable = 100,
    constructor = function()
        print("instantiated base class")
    end
}

local base = new BaseClass()

Not equal (!=)

An alias of the not equal operator (~=)

Syntax:

if true != false then
    -- code
end

Example:

local a = 10
local b = 20

if a != b then
    print("not equal")
end

Not in operator

Inverse of the in operator.

Syntax:

value not in table
key not in table
substring not in string

String Interpolation

allows to embed expressions within a string, making it easy to insert variable values or expressions directly into a string. Using ${expression} you can dynamically include content without needing to concatenate strings manually.

Syntax:

"... ${expression} ..."
'...${expression}...'

Example:

local example1 = "100+200 = ${100+200}" -- 100+200 = 300
local example2 = "100+200 = ${addNumbers(100, 200)}" -- 100+200 = 300 (using an hypothetical function)

Table comprehension

Is a concise syntax for creating and transforming tables in a single expression, typically by applying conditions or transformations to elements within a specified range. It allows for easy filtering, mapping, or generating values based on criteria, reducing the need for longer loops or conditional structures.

Syntax:

{expression for items in iterable if condition}

Example:

-- Create a new table with every element multiplied by 2
local mult2 = {v*2 for k, v in pairs(tab)}

-- Create a new table with every element multiplied by 2 if it is greater than 2
local mult2IfGreaterThan2 = {v*2 for k, v in pairs(tab) if v > 2}

-- Create a new table with every element multiplied by 2 if it is greater than 2 and less than 50
local mult2IfGreaterThan2AndLessThan50 = {v*2 for k, v in pairs(tab) if v > 2 and v < 50}

-- Create a new table with every element as "element:k"
local keyAsElement = {"element:${k}", v for k, v in pairs(tab)}

Ternary operator

The ternary operator is a shorthand for conditional expressions, allowing for a quick inline if-else statement, return one of two values based on whether the condition is true or false.

Syntax:

condition ? value1 : value2

Example:

local result = 10
local isResultGreaterThan2 = result > 2 ? "Yes" : "No"

Throw

Used to create custom exceptions in code, by using throw, you can specify an error as any value (generally a string or an object) that provides information about the issue, which can then be caught and handled by a try-catch block. the try-catch block can also return a value.

Custom errors need to extend the Error class and can provide a toString to return a fully custom error message, by default the error message will be <type>: <self.message>

Example:

try
    throw "Something went wrong"
catch e then
    print("Error: "..e)
end
class CustomError extends Error {
    constructor = function(importantInfo)
        self.info = importantInfo
    end
}

try 
    throw new CustomError("Some important info here")
catch e then
    if type(e) == "CustomError" then
        print(e.info)
    end
end
class AnotherTypeOfError extends Error {}

throw new AnotherTypeOfError("This is the message")
-- AnotherTypeOfError: This is the message
class CustomError2 extends Error {
    toString = function()
        print("Custom message: "..self.message)
    end
}

throw new CustomError2("Some important info here")
-- Custom message: Some important info here

Try-catch

Used for error handling, The try block contains code that may throw an error, while the catch block handles the error if one occurs, preventing the script from crashing and allowing for graceful error recovery.

Syntax:

try 
    -- code
catch errorVariable then
    -- code
end

Example:

try
    indexingTableThatDoesntExist[100] = true
catch e then
    print("Error: "..e)
end

Type checking

Type checking is the process of verifying data types at runtime to prevent errors and ensure compatibility. It ensures that parameters are used with compatible types. This runtime checking helps catch type errors that could lead to unexpected behavior.

Note Classes can also be used as types

Syntax:

function funcName(param1: type1, ..., paramN: typeN)
    -- code
end

Example:

function DebugText(text: string)
    print("debug: "..text)
end


DebugText("test") -- debug: test
DebugText(100) -- Error loading script *.lua in resource ****: @****/*.lua:7: text: string expected, got number
class Example {
    myVar = true
}

function FunctionThatAcceptOnlyExampleClass(<Example> example)
    print("You passed the right variable!")
end

FunctionThatAcceptOnlyExampleClass("Test") -- Error since string is not of type Example

Using operator

The using operator runs a filter, validating the conditions defined within it. If any of the conditions fail, it throws an error, preventing the execution of the associated code. although they are almost always used in functions nothing prohibits you from being able to use them where you want to

Syntax:

using Filter(param1, param2, ...)

using Filter

Example:

function deletePost(user: User) using IsAuthorized("deletePost") -- Check "filter" to see the filter code
    print("Post deleted successfully!")
end

-- This is also valid!
function deletePost(user: User) 
    using IsAuthorized("deletePost")
    print("Post deleted successfully!")
end
Convar Default Type
leap_verbose false boolean

About

TODO: Move this repository to main (leap)

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published