Перейти к содержимому

Exeteres

Пользователи
  • Публикации

    26
  • Зарегистрирован

  • Посещение

  • Победитель дней

    8

Сообщения, опубликованные пользователем Exeteres


  1.  

     

     

     

    512px-TypeScript_Logo_(Blue).svg.png

     

     

     

     

    Вместо вступления:

    Я не считаю C-подобный синтаксис лучше синтаксиса lua и не буду заставлять вас переписывать все ваши программы на TypeScript! Я просто хочу поделится с вами альтернативой и рассказать про ее преимущества и недостатки.

     

    # Что такое TypeScript?

    TypeScript — язык программирования, представленный Microsoft в 2012 году и позиционируемый как средство разработки веб-приложений. Он создан для расширения JavaScript и он компилируется в JavaScript, но также существует инструмент для преобразования TypeScript кода в Lua. Вам может показаться, что этот транслятор крайне ограничен, но, поверьте мне, его возможности впечатляют.

     

    # Почему его стоит попробовать?

    Я сначала продемонстрирую некоторые возможности TypeScript графически, а потом подробно расскажу про установку и настройку необходимых инструментов. Я покажу вам далеко не все возможности TypeScript, а только самые основные и интересные.

    Из-за большого размера контент каждого раздела будет скрыт под спойлер.

     

    1. Статический анализ

    Скрытый текст

    Системы типов JavaScript и Lua во многом похожи, и потому они имеют некоторые общие проблемы, которые может решить TypeScript.

     

    1.1 Проверка типов

    Если функция или метод явно требует среди аргументов число, TypeScript не допустит что-то отличное от числа.

    image.png.d5fc0b4b741592cf190d3a77c124c785.png

     

    Lua тоже сообщит об этом, но только после запуска скрипта.

    image.png.21ad2183b440c8b07a9b118bf2b45e19.png

     

    1.2 Проверка существования полей и методов

    Если вы попытаетесь обратится к несуществующему полю или методу, TypeScript сообщит вам об этом и даже поможет исправить, если вы, например, опечатались.

    image.png.e6cf4d71e5b073e5d2595257259c12fa.png

     

    В Lua мы узнаем об этом только после запуска скрипта.

    image.png.610e5cdb29c1d11ea667e1c0587ecb49.png

     

    2. Автодополнение

    Скрытый текст

    Благодаря статической типизации, редактор кода может подсказывать методы и поля объекта.

    image.png.f2bb10226d369c05b792b6e659dfee3f.png

     

    Или показать параметры, которые принимает метод.

    image.png.e05fe44e605ca1ca6b43ba5ae2aa6ea8.png

     

    Или текст документации.

    image.png

     

    3. ООП

    Скрытый текст

    3.1 Классы

    
    class Player {
        username: string;
    
        constructor(username: string) {
            this.username = username;
        }
    
        greet() {
            print("Привет, " + this.username + "!");
        }
    }
    
    let alex = new Player("alex");
    alex.greet(); // Привет, Alex!

     

    Сгенерированный lua код

    Скрытый текст
    
    
    --[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]
    Player = {}
    Player.name = "Player"
    Player.__index = Player
    Player.prototype = {}
    Player.prototype.__index = Player.prototype
    Player.prototype.constructor = Player
    function Player.new(...)
        local self = setmetatable({}, Player.prototype)
        self:____constructor(...)
        return self
    end
    function Player.prototype.____constructor(self, username)
        self.username = username
    end
    function Player.prototype.greet(self)
        print("Привет, " .. tostring(self.username) .. "!")
    end
    local alex = Player.new("alex")
    alex:greet()

     

     

    3.2 Модификаторы доступа

    image.png

     

    3.3 Наследование

    
    class Player {
        username: string;
    
        constructor(username: string) {
            this.username = username;
        }
    }
    
    class Alex extends Player {
        constructor() {
            super("Alex");
        }
    }
    
    let alex = new Alex();
    print(alex.username); // Alex

     

    Сгенерированный lua код

    Скрытый текст
    
    
    --[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]
    Player = {}
    Player.name = "Player"
    Player.__index = Player
    Player.prototype = {}
    Player.prototype.__index = Player.prototype
    Player.prototype.constructor = Player
    function Player.new(...)
        local self = setmetatable({}, Player.prototype)
        self:____constructor(...)
        return self
    end
    function Player.prototype.____constructor(self, username)
        self.username = username
    end
    Alex = {}
    Alex.name = "Alex"
    Alex.__index = Alex
    Alex.prototype = {}
    Alex.prototype.__index = Alex.prototype
    Alex.prototype.constructor = Alex
    Alex.____super = Player
    setmetatable(Alex, Alex.____super)
    setmetatable(Alex.prototype, Alex.____super.prototype)
    function Alex.new(...)
        local self = setmetatable({}, Alex.prototype)
        self:____constructor(...)
        return self
    end
    function Alex.prototype.____constructor(self)
        Player.prototype.____constructor(self, "Alex")
    end
    local alex = Alex.new()
    print(alex.username)

     

     

     

    4. Стандартная библиотека и возможности языка

    Скрытый текст

    Многие методы стандартных типов TypeScript (такие как массивы и строки) также могут быть транслированы в lua.

     

    4.1 Получение всех четных чисел массива, возведение в квадрат и соединение в строку через запятую

    
    const items = [1, 2, 3, 4, 5];
    
    items.push(6);
    
    const result = items
        .filter(x => x % 2 == 0)
        .map(x => x ** 2)
        .join(", ");
    
    print(result); // 4, 16, 36

     

    Сгенерированный lua код

    Скрытый текст
    
    
    --[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]
    -- Lua Library inline imports
    function __TS__ArrayPush(arr, ...)
        local items = ({...})
        for ____, item in ipairs(items) do
            arr[#arr + 1] = item
        end
        return #arr
    end
    
    function __TS__ArrayFilter(arr, callbackfn)
        local result = {}
        do
            local i = 0
            while i < #arr do
                if callbackfn(_G, arr[i + 1], i, arr) then
                    result[#result + 1] = arr[i + 1]
                end
                i = i + 1
            end
        end
        return result
    end
    
    function __TS__ArrayMap(arr, callbackfn)
        local newArray = {}
        do
            local i = 0
            while i < #arr do
                newArray[i + 1] = callbackfn(_G, arr[i + 1], i, arr)
                i = i + 1
            end
        end
        return newArray
    end
    
    local items = {
        1,
        2,
        3,
        4,
        5,
    }
    __TS__ArrayPush(items, 6)
    local result = table.concat(__TS__ArrayMap(__TS__ArrayFilter(items, function(____, x) return x % 2 == 0 end), function(____, x) return x ^ 2 end), ", ")
    print(result)

     

     

    4.2 Модули

    
    // main.ts
    import { myFunction } from "./myLibrary";
    
    myFunction();
    
    // myLibrary.ts
    export function myFunction() {
        print("Hi from myLibrary");
    }

    Сгенерированный lua код

    Скрытый текст
    
    
    -- main.lua
    --[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]
    local ____exports = {}
    local ____myLibrary = require("myLibrary")
    local myFunction = ____myLibrary.myFunction
    myFunction(nil)
    return ____exports
    
    -- myLibrary.lua
    --[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]
    local ____exports = {}
    function ____exports.myFunction(self)
        print("Hi from myLibrary")
    end
    return ____exports

     

     

    4.3 Форматирование строк

    
    const text = `2 + 2 = ${2 + 2}`;
    print(text); // 2 + 2 = 4

    Сгенерированный lua код

    
    --[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]
    local text = "2 + 2 = " .. tostring(2 + 2)
    print(text)

     

    # Как это работает?

    Конечно же все не так просто. Компилятор просто так не узнает типы методов и полей объектов, с которыми мы будем работать. Для того, чтобы описать наше окружение необходимо написать так называемые файлы декларации или тайпинги. Хочу сразу вас обрадовать - это не ваша задача. Существует репозиторий с такими декларациями, в котором, на данный момент, существуют типы для большинства API и компонентов OpenOS и библиотеки GUI.

    От вас требуется только установить все необходимые инструменты и правильно их настроить.

     

    # Установка

    Редактор кода

    Вы можете использовать любой редактор кода с поддержкой TypeScript. Я рекомендую VSCode, который поддерживает его из коробки.

     

    NodeJS

    Он необходим нам для установки необходимых пакетов (он поставляется с пакетным менеджером npm) и для запуска транспилера. Вы можете скачать последнюю стабильную версию с официального сайта.

     

    Использование плагина для VSCode (рекомендуется):

    Скрытый текст

    Вы можете установить плагин OpenComputersTS. В нем есть две команды:

    • OC-TS: Init - Создание нового проекта в пустой папке.
    • OC-TS: Mount - Подключение дисков из сохранений minecraft или эмулятора OCEmu в папку dist.

    Для открытия окна с командами используйте сочетание Ctrl + Shift + P.

     

    ici-RMCx-RWf.gif

     

    Создание проекта вручную:

    Скрытый текст

    После установки NodeJS у вас должны появится команды npm  и node.

    1. Создайте новую папку для своего первого проекта
    2. Переключитесь в нее, используя терминал и все дальнешие действия выполняйте в ней
    3. Создайте npm пакет: npm init. После выполнения этой команды в папке появится файл package.json
    4. Добавьте в объект "scripts" в package.json строку "build": "tstl",
    5. Установите транспилер npm install --dev typescript-to-lua. После установки первого пакета у вас появится папка node_modules
    6. Установите тайпинги npm install --dev @opct/openos
    7. Создайте папку src для исходных файлов
    8. Создайте файл tsconfig.json со следующим содержимым:
    
    {
        "compilerOptions": {
            "target": "esnext",
            "outDir": "dist",
            "module": "commonjs",
            "lib": ["esnext"],
            "strict": true,
            "moduleResolution": "node",
            "rootDir": "src",
            "types": ["lua-types/jit", "@opct/openos"]
        },
        "tstl": {
            "luaTarget": "JIT"
        }
    }

    Теперь вы можете размещать в src исходный код, например, main.ts со следующим содержимым:

    
    import * as component from "component";
    import { front } from "sides";
    
    component.redstone.setOutput(front, 1);

     

    Как запустить сгенерированный код в OpenComputers?

    Самый удобный способ для доставки полученного кода - символическая ссылка. Вы можете заменить каталог dist ссылкой на папку диска.

    
    # Создание ссылки через терминал
    # linux / macos
    ln -s /path/to/disk/home dist
    
    # windows (cmd)
    mklink /j dist C:\path\to\disk\home

    Диски располагаются в папке .minecraft\saves\{сохранение}\opencomputers. Вам также необходимо отключить параметр filesystem.bufferChanges в файле .minecraft\config\opencomputers\settings.conf

    После этого вы сможете запускать сгенерированный код прямо в игре.

     

    Для компиляции используйте команду npm run build. Сгенерированные lua файлы появятся в папке dist.

     

    # Особенности работы транспилера

    В этой секции я подробно раскажу про недостатки этого подхода, возможные проблемы и способы их решения.

     

    1. Параметр self

    Скрытый текст

    Объявление функций

    Одно из первых, что вы скорее всего попробуете сделать - объявить функцию:

    
    function test() {}
    test();

    Однако результат может быть неожиданным:

    
    --[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]
    function test(self)
    end
    test(nil)

    Зачем-то появился параметр self, который тут казалось бы не нужен.

    А теперь попробуйте сделать так:

    
    const obj = {
        a: 5,
        b: 10,
        sum() {
            print(this.a + this.b);
        },
    };
    
    obj.sum();

    Результат:

    
    --[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]
    obj = {
        a = 5,
        b = 10,
        sum = function(self)
            print(self.a + self.b)
        end
    }
    obj:sum()

    У функции все еще есть self, но в этот раз он необходим для доступа к полям obj.

    Если мы теперь попробуем оторвать метод sum от объекта и вызвать отдельно:

    
    const sum = obj.sum;
    sum();

    то увидем, что транспилер все еще передает nil вместо self:

    
    sum = obj.sum
    sum(nil)

    Такое поведение связано с тем, что в JavaScript (для которого изначально был создан TypeScript) this (self) есть у любой функции (кроме стрелочных) и для сохранения совместимости с этой особенностью транспилер автоматически генерирует self параметр для любой функции. Если вы считаете такое поведение по умолчанию недопустимым, то можете отключить его:

    
    // tsconfig.json
    {
        "compilerOptions": { ... },
        "tstl": {
          	...,
            "noImplicitSelf": false,
            ...
        }
    }

    Тип self параметра

    Вы также можете явно задать тип this так же как и тип любого другого параметра, например, назначить ему void:

    
    function test(this: void): void {}
    test();

    И тогда транспилер уберет self параметр:

    
    --[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]
    function test()
    end
    test()

    Особенно это важно при работе с декларациями. Например, мы хотим описать экземпляр некоторого класса:

    
    declare interface Person {
        say(text: string): void;
    }
    
    declare const person: Person;
    
    person.say("hi");

    И ожидаемо получим:

    
    --[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]
    person:say("hi")

    Но если нам понадобится описать какую-либо библиотеку (таблицу с функциями), то такое поведение может быть недопустимо, ведь эти функции не принимают self параметр. Можно вручную задать каждой функции this:void, а можно использовать директиву @noSelf:

    
    /** @noSelf */
    declare interface MyLibrary {
        method1(text: string): void;
        method2(num: number): void;
    }
    
    declare const lib: MyLibrary;
    
    lib.method1("text");
    lib.method2(123);

    И тогда эти функции будут вызваны без двоеточия:

    
    --[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]
    lib.method1("text")
    lib.method2(123)

     

     

    2. Множественные значения

    Скрытый текст

    В TypeScript не поддерживаются множественные значения. Вместо этого используются кортежи.

    Например, объявим функцию, которая возвращает несколько значений:

    
    function getValues(): [string, number, boolean] {
        return ["text", 123, true];
    }
    
    const [str, num, bool] = getValues();

    Этот подход сильно напоминает множественные возвращаемые значения и множественное присваивание в Lua. Тем не менее, сейчас кортежи просто превращаются в таблицы:

    
    --[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]
    function getValues(self)
        return {"text", 123, true}
    end
    str, num, bool = unpack(
        getValues(nil)
    )

    Чтобы заставить транспилер возвращать множественные значения, необходимо использовать директиву @tupleReturn:

    
    /** @tupleReturn */
    function getValues(): [string, number, boolean] {
        return ["text", 123, true];
    }
    
    const [str, num, bool] = getValues();

    И тогда все будет красиво:

    
    --[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]
    function getValues(self)
        return "text", 123, true
    end
    str, num, bool = getValues(nil)

     

     

    3. Индексы

    Скрытый текст

    Числовые индексы в таблицах Lua начинаются с 1, в то время как в TypeScript с 0. Транспилер сам выполняет преобразование индексов TypeScript в Lua, например:

    
    const arr = [1, 2, 3];
    print(arr[1]);

    будет преобразовано в:

    
    --[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]
    arr = {1, 2, 3}
    print(arr[2])

    И мы в обоих случаях получим на экране второй элемент массива.

     

    arr.length является полным аналогом оператора #:

    
    const arr = [1, 2, 3];
    print(arr.length);

    Результат:

    
    --[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]
    arr = {1, 2, 3}
    print(#arr)

     

     

    # Ссылки

     

    • Нравится 6
    • Спасибо 1
×
×
  • Создать...