Skip to content

Instantly share code, notes, and snippets.

@Cheatoid
Last active December 7, 2023 04:54
Show Gist options
  • Save Cheatoid/ea4573c6bd1992fc4940090543ec9380 to your computer and use it in GitHub Desktop.
Save Cheatoid/ea4573c6bd1992fc4940090543ec9380 to your computer and use it in GitHub Desktop.
TSTL plugins (unsafe_cast, goto/label, continue, next, vararg, methodsof, prototypeof, inline, typedparams) ✨
/**
* Emits a Lua continue statement.
* @internal
* @emits `continue`
*/
declare function __continue(this: void): void;
/**
* Emits a Lua goto statement (requires Lua 5.2+ or JIT).
* @param label - The label name to jump to.
* @internal
* @emits `goto label`
*/
declare function __goto(this: void, label: string): void;
/**
* Inlines the body of the function in-place (useful in hot-path for high-performance code).
* @param body - The function to inline (must be within chunk's scope).
* @internal
* @emits `body`
* @remarks
* This is currently experimental.
*/
declare function __inline(this: void, body: ((this: void) => void)): void;
declare function __inline<T>(this: void, body: ((this: void, arg: T) => void), arg: T): void;
declare function __inline<T1, T2>(this: void, body: ((this: void, arg1: T1, arg2: T2) => void), arg1: T1, arg2: T2): void;
declare function __inline<T1, T2, T3>(this: void, body: ((this: void, arg1: T1, arg2: T2, arg3: T3) => void), arg1: T1, arg2: T2, arg3: T3): void;
declare function __inline<T1, T2, T3, T4>(this: void, body: ((this: void, arg1: T1, arg2: T2, arg3: T3, arg4: T4) => void), arg1: T1, arg2: T2, arg3: T3, arg4: T4): void;
declare function __inline<T1, T2, T3, T4, T5>(this: void, body: ((this: void, arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5) => void), arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5): void;
declare function __inline<T1, T2, T3, T4, T5, T6>(this: void, body: ((this: void, arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6) => void), arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6): void;
declare function __inline<T1, T2, T3, T4, T5, T6, T7>(this: void, body: ((this: void, arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7) => void), arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7): void;
declare function __inline<T1, T2, T3, T4, T5, T6, T7, T8>(this: void, body: ((this: void, arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8) => void), arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8): void;
declare function __inline<T1, T2, T3, T4, T5, T6, T7, T8, T9>(this: void, body: ((this: void, arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9) => void), arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9): void;
declare function __inline<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(this: void, body: ((this: void, arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10) => void), arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10): void;
declare function __inline<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>(this: void, body: ((this: void, arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11) => void), arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11): void;
declare function __inline<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>(this: void, body: ((this: void, arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12) => void), arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12): void;
declare function __inline<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>(this: void, body: ((this: void, arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12, arg13: T13) => void), arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12, arg13: T13): void;
declare function __inline<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14>(this: void, body: ((this: void, arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12, arg13: T13, arg14: T14) => void), arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12, arg13: T13, arg14: T14): void;
declare function __inline<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15>(this: void, body: ((this: void, arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12, arg13: T13, arg14: T14, arg15: T15) => void), arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12, arg13: T13, arg14: T14, arg15: T15): void;
declare function __inline<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16>(this: void, body: ((this: void, arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12, arg13: T13, arg14: T14, arg15: T15, arg16: T16) => void), arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6, arg7: T7, arg8: T8, arg9: T9, arg10: T10, arg11: T11, arg12: T12, arg13: T13, arg14: T14, arg15: T15, arg16: T16): void;
/**
* Emits a Lua label statement (requires Lua 5.2+ or JIT).
* @param label - The label name to define.
* @internal
* @emits `::label::`
*/
declare function __label(this: void, label: string): void;
/**
* Emits a class method iterator. Use in conjunction with for-of loop.
* @typeParam TClass - The class to iterate.
* @internal
* @emits `next, TClass.prototype` (skipping non-functions, metamethods and constructor)
*/
declare function __methodsof<TClass extends AnyNotNil>(this: void): LuaIterable<LuaMultiReturn<[string, ((this: void, ...args: any[]) => any)]>>;
/**
* Emits a Lua next iterator. Use in conjunction with for-of loop.
* @param t - The table to iterate.
* @internal
* @emits `next, t, index`
*/
declare function __next<TKey extends AnyNotNil, TValue>(this: void, t: LuaTable<TKey, TValue>, index?: any): LuaIterable<LuaMultiReturn<[TKey, NonNullable<TValue>]>>;
/**
* Emits a Lua next iterator. Use in conjunction with for-of loop.
* @param t - The object to iterate.
* @internal
* @emits `next, t, index`
*/
declare function __next<T>(this: void, t: T, index?: any): LuaIterable<LuaMultiReturn<[keyof T, NonNullable<T[keyof T]>]>>;
/**
* Emits a class prototype iterator. Use in conjunction with for-of loop.
* @typeParam TClass - The class to iterate.
* @internal
* @emits `next, TClass.prototype`
*/
declare function __prototypeof<TClass extends AnyNotNil>(this: void): LuaIterable<LuaMultiReturn<[string, AnyNotNil]>>;
/**
* Provides an ability to preserve (and validate) parameters' type information of the parent function.
* @param validator - The callback function which will receive parameters' type information.
* @remarks
* This is currently experimental. Must be called directly in the main scope of the function.
*/
declare function __typedparams(this: void, validator: (this: void, ...info: [/*parameter name*/string, /*full type*/string, /*dotDotDotToken*/boolean, /*questionToken*/boolean, /*type*/string, /*initializer*/string?][]) => void): void;
/**
* Emits a Lua vararg operator `...`.
* @internal
* @emits `...`
*/
declare function __vararg(this: void): LuaIterable<LuaMultiReturn<[...any[]]>>;
/**
* Unsafely casts a given value as a value of type T.
* @typeParam T - The type to cast to.
* @param value - The value to be cast.
* @returns Returns the given value.
* @internal
* @emits `value`
* @remarks
* This is useful in-place replacement for "as any" casting, because it allows to "find all references" quickly.
*/
declare function unsafe_cast<T>(this: void, value: unknown): T;
import * as ts from "typescript";
import * as tstl from "typescript-to-lua";
import { createSerialDiagnosticFactory } from "typescript-to-lua/dist/utils";
//#region Courtesy of TSTL codebase :-)
// https://github.com/TypeScriptToLua/TypeScriptToLua/blob/master/src/transformation/utils/diagnostics.ts
type MessageProvider<TArgs extends any[]> = string | ((...args: TArgs) => string);
const createDiagnosticFactory = <TArgs extends any[]>(
category: ts.DiagnosticCategory,
message: MessageProvider<TArgs>
) =>
createSerialDiagnosticFactory((node: ts.Node, ...args: TArgs) => ({
file: ts.getOriginalNode(node).getSourceFile(),
start: ts.getOriginalNode(node).getStart(),
length: ts.getOriginalNode(node).getWidth(),
messageText: typeof message === "string" ? message : message(...args),
category,
}));
const createErrorDiagnosticFactory = <TArgs extends any[]>(message: MessageProvider<TArgs>) =>
createDiagnosticFactory(ts.DiagnosticCategory.Error, message);
const createWarningDiagnosticFactory = <TArgs extends any[]>(message: MessageProvider<TArgs>) =>
createDiagnosticFactory(ts.DiagnosticCategory.Warning, message);
const getLuaTargetName = (version: tstl.LuaTarget) => (version === tstl.LuaTarget.LuaJIT ? "LuaJIT" : `Lua ${version}`);
const unsupportedForTarget = createErrorDiagnosticFactory(
(functionality: string, version: tstl.LuaTarget) =>
`${functionality} is/are not supported for target ${getLuaTargetName(version)}.`
);
//#endregion
const expectedStringLiteralInGoto = createErrorDiagnosticFactory(
"Expected a string literal in '__goto'."
);
const expectedFunctionExpressionInInline = createErrorDiagnosticFactory(
"Expected a function expression in '__inline'."
);
const expectedStringLiteralInLabel = createErrorDiagnosticFactory(
"Expected a string literal in '__label'."
);
const expectedAnArgumentInUnsafeCast = createErrorDiagnosticFactory(
"Expected a value in 'unsafe_cast'."
);
const expectedClassTypeNameInMethodsOf = createErrorDiagnosticFactory(
"Expected a class type name in '__methodsof'."
);
const expectedAnArgumentInNext = createErrorDiagnosticFactory(
"Expected an object in '__next'."
);
const expectedClassTypeNameInPrototypeOf = createErrorDiagnosticFactory(
"Expected a class type name in '__prototypeof'."
);
const typedParamsUsedOutsideOfFunction = createErrorDiagnosticFactory(
"'__typedparams' can not be used outside a function."
);
const plugin: tstl.Plugin = {
visitors: {
[ts.SyntaxKind.CallExpression](node, context) {
const result = context.superTransformExpression(node);
if (tstl.isCallExpression(result) && tstl.isIdentifier(result.expression)) {
switch (result.expression.text) {
case "unsafe_cast": {
if (result.params.length === 1) {
return result.params[0];
}
context.diagnostics.push(expectedAnArgumentInUnsafeCast(node));
break;
}
case "__vararg": {
return tstl.createDotsLiteral(node);
}
}
}
return result;
},
[ts.SyntaxKind.ExpressionStatement](node, context) {
const result = context.superTransformStatements(node);
if (ts.isExpressionStatement(node)) {
const expr = node.expression;
if (ts.isCallExpression(expr) && ts.isIdentifier(expr.expression)) {
switch (expr.expression.text) {
case "__continue": {
return tstl.createExpressionStatement(tstl.createIdentifier("continue", node), node);
}
case "__goto": {
if (context.luaTarget === tstl.LuaTarget.Lua50 || context.luaTarget === tstl.LuaTarget.Lua51) {
context.diagnostics.push(unsupportedForTarget(node, "goto", context.luaTarget));
break;
}
if (expr.arguments.length === 1 && ts.isStringLiteral(expr.arguments[0])) {
return tstl.createGotoStatement(expr.arguments[0].text, node);
}
context.diagnostics.push(expectedStringLiteralInGoto(node));
break;
}
case "__inline": {
if (expr.arguments.length > 0) {
let bodyArg: ts.Expression | ts.Declaration | undefined = expr.arguments[0];
if (ts.isIdentifier(bodyArg)) {
try {
bodyArg = context.checker.getSymbolAtLocation(bodyArg)!.getDeclarations()![0];
} catch (error) {
context.diagnostics.push(expectedFunctionExpressionInInline(node));
break;
}
if (!bodyArg) {
context.diagnostics.push(expectedFunctionExpressionInInline(node));
break;
}
}
const paramNames: string[] = [];
let funcExpr: tstl.FunctionExpression | undefined;
if (ts.isFunctionLike(bodyArg)) {
const bodyNode: tstl.Node | undefined = context.transformNode(bodyArg)[0];
if (!bodyNode) {
context.diagnostics.push(expectedFunctionExpressionInInline(node));
break;
}
if (tstl.isVariableDeclarationStatement(bodyNode) && bodyNode.right) {
if (tstl.isFunctionExpression(bodyNode.right[0])) {
funcExpr = bodyNode.right[0];
}
}
paramNames.push(...bodyArg.parameters.map(p => p.name.getText()));
}
for (const stmt of result) {
if (tstl.isExpressionStatement(stmt)) {
const callExpr = stmt.expression;
if (tstl.isCallExpression(callExpr) && tstl.isIdentifier(callExpr.expression) &&
callExpr.expression.text === expr.expression.text) {
const paramCount = callExpr.params.length;
if (paramCount > 0) {
let body: tstl.Expression | undefined = callExpr.params[0];
if (tstl.isIdentifier(body)) {
body = funcExpr;
}
if (body && tstl.isFunctionExpression(body)) {
const statements: tstl.Statement[] = body.body.statements;
for (let index = 1; index < paramCount; ++index) { // Skip the body parameter.
const param = callExpr.params[index];
statements.unshift(
tstl.createVariableDeclarationStatement([tstl.createIdentifier(paramNames[index - 1])], [param])
);
}
return tstl.createDoStatement(statements, node);
}
}
}
}
}
}
context.diagnostics.push(expectedFunctionExpressionInInline(node));
break;
}
case "__label": {
if (context.luaTarget === tstl.LuaTarget.Lua50 || context.luaTarget === tstl.LuaTarget.Lua51) {
context.diagnostics.push(unsupportedForTarget(node, "label", context.luaTarget));
break;
}
if (expr.arguments.length === 1 && ts.isStringLiteral(expr.arguments[0])) {
return tstl.createLabelStatement(expr.arguments[0].text, node);
}
context.diagnostics.push(expectedStringLiteralInLabel(node));
break;
}
case "__typedparams": {
// TODO: Consider supporting any depth within function
if (ts.isBlock(node.parent) && ts.isFunctionLike(node.parent.parent) && expr.arguments.length === 1) {
const typedParams: tstl.TableExpression[] = [];
for (const param of node.parent.parent.parameters) {
// /*parameter name*/string, /*full type*/string, /*dotDotDotToken*/boolean, /*questionToken*/boolean, /*type*/string, /*initializer*/string?
const paramEntry: tstl.Expression[] = [
tstl.createStringLiteral(param.name.getText()),
tstl.createStringLiteral(`${param.dotDotDotToken?.getText() ?? ""}${param.type?.getText() ?? "any"}${param.questionToken ? "?" : ""}`),
tstl.createBooleanLiteral(param.dotDotDotToken ? true : false),
tstl.createBooleanLiteral(param.questionToken ? true : false),
tstl.createStringLiteral(param.type?.getText() ?? "any")
];
if (param.initializer) {
paramEntry.push(
tstl.createStringLiteral(param.initializer.getText())
);
}
typedParams.push(
tstl.createTableExpression([...paramEntry.map((e) => tstl.createTableFieldExpression(e))])
);
}
for (const stmt of result) {
if (tstl.isExpressionStatement(stmt)) {
const callExpr = stmt.expression;
if (tstl.isCallExpression(callExpr) && tstl.isIdentifier(callExpr.expression) &&
callExpr.expression.text === expr.expression.text && callExpr.params.length === 1) {
return tstl.createExpressionStatement(tstl.createCallExpression(callExpr.params[0], typedParams), node);
}
}
}
}
context.diagnostics.push(typedParamsUsedOutsideOfFunction(node));
break;
}
}
}
}
return result;
},
[ts.SyntaxKind.ForOfStatement](node, context) {
const result = context.superTransformStatements(node);
const expr = node.expression;
if (ts.isCallExpression(expr) && ts.isIdentifier(expr.expression) && ts.isBlock(node.statement)) {
switch (expr.expression.text) {
case "__methodsof": {
if (expr.typeArguments && expr.typeArguments.length === 1) {
const typeArg = expr.typeArguments[0];
if (ts.isTypeReferenceNode(typeArg)) {
const typeInfo = context.checker.getTypeAtLocation(typeArg); // Thanks Perry 😎
if (typeInfo.isClass()) {
const escapedName = typeInfo.symbol.escapedName.toString();
for (const stmt of result) {
if (tstl.isForInStatement(stmt) && stmt.names.length === 2) {
stmt.expressions.splice(0);
stmt.expressions.push(tstl.createIdentifier("next"), tstl.createIdentifier(`${escapedName}.prototype`));
stmt.body.statements.push(tstl.createIfStatement(
tstl.createBinaryExpression(
// Skip non functions
tstl.createBinaryExpression(
tstl.createCallExpression(tstl.createIdentifier("type"), [stmt.names[1]]),
tstl.createStringLiteral("function"),
tstl.SyntaxKind.EqualityOperator
),
// Skip metamethods (and constructor)
tstl.createBinaryExpression(
tstl.createCallExpression(tstl.createIdentifier("string.sub"), [stmt.names[0], tstl.createNumericLiteral(1), tstl.createNumericLiteral(2)]),
tstl.createStringLiteral("__"),
tstl.SyntaxKind.InequalityOperator
),
tstl.SyntaxKind.AndOperator
),
tstl.createBlock(stmt.body.statements.splice(0))
));
}
}
break;
}
}
}
context.diagnostics.push(expectedClassTypeNameInMethodsOf(node));
break;
}
case "__next": {
for (const stmt of result) {
if (tstl.isForInStatement(stmt)) {
const spliced = stmt.expressions.splice(0);
if (spliced.length === 1) {
const callExpr = spliced[0];
if (tstl.isCallExpression(callExpr)) {
stmt.expressions.push(tstl.createIdentifier("next"), ...callExpr.params);
continue;
}
}
context.diagnostics.push(expectedAnArgumentInNext(node));
break;
}
}
break;
}
case "__prototypeof": { // Stripped down version of __methodsof
if (expr.typeArguments && expr.typeArguments.length === 1) {
const typeArg = expr.typeArguments[0];
if (ts.isTypeReferenceNode(typeArg)) {
const typeInfo = context.checker.getTypeAtLocation(typeArg); // Thanks Perry 😎
if (typeInfo.isClass()) {
const escapedName = typeInfo.symbol.escapedName.toString();
for (const stmt of result) {
if (tstl.isForInStatement(stmt)) {
stmt.expressions.splice(0);
stmt.expressions.push(tstl.createIdentifier("next"), tstl.createIdentifier(`${escapedName}.prototype`));
}
}
break;
}
}
}
context.diagnostics.push(expectedClassTypeNameInPrototypeOf(node));
break;
}
}
}
return result;
},
[ts.SyntaxKind.ContinueStatement](node, context) {
// FIXME: Custom tsconfig option check doesn't work (despite being specified in the tsconfig file)
//const luaContinueSupport = (<ts.CompilerOptions & { luaContinueSupport?: boolean; }>context.program.getCompilerOptions()).luaContinueSupport; // context.options.luaContinueSupport
const luaContinueSupport = false; // Change this to true at your own peril (if your target Lua environment supports continue statement as-is).
if (luaContinueSupport) {
return tstl.createExpressionStatement(tstl.createIdentifier("continue", node), node);
}
const result = context.superTransformStatements(node);
return result;
}
}
};
export default plugin;
@Cheatoid
Copy link
Author

Cheatoid commented Sep 25, 2022

Here is an example TS code to show usages of the above TSTL plugin:

class Person {
    constructor(private readonly firstName: string, private readonly lastName: string) { }

    public get FullName(): string {
        return `${this.firstName} ${this.lastName}`;
    }

    public toString(): string {
        return this.FullName;
    }

    public printFullName(): void {
        print(this.FullName);
    }
}

const JohnLua = new Person("John", "Lua");

for (const [methodName, func] of __methodsof<Person>()) {
    print(`methodName: ${methodName} | func: ${func}`);
    func(JohnLua);
}

function foreach(t: LuaTable | any[], callback: (this: void, k: AnyNotNil, v: AnyNotNil) => boolean | void): void {
    let k: any, v: any;
    __label("loop");
    [k, v] = next(t, k);
    if (k !== undefined) {
        if (callback(k, v)) {
            return;
        }
        __goto("loop");
    }
}
foreach(["first", 2, "last"], (k, v) => { print(k, v); });

function foreachWith(...args: any[]): void {
    for (const [k, v] of __vararg()) {
        print(k, v);
    }
}
foreachWith(next, _G);

function foreachNext(t: LuaTable | any[]): void {
    for (const [k, v] of __next(t)) {
        print(k, v);
    }
}
foreachNext(unsafe_cast<LuaTable>(_G));

function foreachOffset(t: LuaTable | any[], index: any): void {
    for (const [k, v] of __next(t, index)) {
        print(k, v);
    }
}
foreachOffset(["first", "second", "third", "last"], 2);

function InlineExample(name: string) {
    print("The code to be inlined goes here");
    for (const _ of $range(1, 5)) {
        print(`Hello ${name}`);
    }
}
const john = "John";
__inline(InlineExample, john);
__inline(InlineExample, "Moon");

function TypedTest(s: string, b?: boolean, n = 123) {
    __typedparams((...staticTypeInfo) => {
        for (const param of staticTypeInfo) {
            print(...param);
        }
    });
    print("TSTL is awesome!");
}

The following is an output Lua code generated after transpilation:

local Person = __TS__Class()
Person.name = "Person"
function Person.prototype.____constructor(self, firstName, lastName)
    self.firstName = firstName
    self.lastName = lastName
end
__TS__SetDescriptor(
    Person.prototype,
    "FullName",
    {get = function(self)
        return (self.firstName .. " ") .. self.lastName
    end},
    true
)
function Person.prototype.__tostring(self)
    return self.FullName
end
function Person.prototype.printFullName(self)
    print(self.FullName)
end
local JohnLua = __TS__New(Person, "John", "Lua")
for methodName, func in next, Person.prototype do
    if type(func) == "function" and string.sub(methodName, 1, 2) ~= "__" then
        print((("methodName: " .. methodName) .. " | func: ") .. tostring(func))
        func(JohnLua)
    end
end
local function foreach(t, callback)
    local k
    local v
    ::loop::
    k, v = next(t, k)
    if k ~= nil then
        if callback(k, v) then
            return
        end
        goto loop
    end
end
foreach(
    {"first", 2, "last"},
    function(k, v)
        print(k, v)
    end
)
local function foreachWith(...)
    for k, v in ... do
        print(k, v)
    end
end
foreachWith(next, _G)
local function foreachNext(t)
    for k, v in next, t do
        print(k, v)
    end
end
foreachNext(_G)
local function foreachOffset(t, index)
    for k, v in next, t, index do
        print(k, v)
    end
end
foreachOffset({"first", "second", "third", "last"}, 2)
local function InlineExample(name)
    print("The code to be inlined goes here")
    for _ = 1, 5 do
        print("Hello " .. name)
    end
end
local john = "John"
do
    local name = john
    print("The code to be inlined goes here")
    for _ = 1, 5 do
        print("Hello " .. name)
    end
end
do
    local name = "Moon"
    print("The code to be inlined goes here")
    for _ = 1, 5 do
        print("Hello " .. name)
    end
end
local function TypedTest(s, b, n)
    if n == nil then
        n = 123
    end
    (function(...)
        local staticTypeInfo = {...}
        for ____, param in ipairs(staticTypeInfo) do
            print(unpack(param))
        end
    end)({
        "s",
        "string",
        false,
        false,
        "string"
    }, {
        "b",
        "boolean?",
        false,
        true,
        "boolean"
    }, {
        "n",
        "any",
        false,
        false,
        "any",
        "123"
    })
    print("TSTL is awesome!")
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment