Опубликовано:
·
Изменено пользователем Exeteres Раздел "Особенности работы транспилера"
Вместо вступления:
Я не считаю C-подобный синтаксис лучше синтаксиса lua и не буду заставлять вас переписывать все ваши программы на TypeScript! Я просто хочу поделится с вами альтернативой и рассказать про ее преимущества и недостатки.
# Что такое TypeScript?
TypeScript — язык программирования, представленный Microsoft в 2012 году и позиционируемый как средство разработки веб-приложений. Он создан для расширения JavaScript и он компилируется в JavaScript, но также существует инструмент для преобразования TypeScript кода в Lua. Вам может показаться, что этот транслятор крайне ограничен, но, поверьте мне, его возможности впечатляют.
# Почему его стоит попробовать?
Я сначала продемонстрирую некоторые возможности TypeScript графически, а потом подробно расскажу про установку и настройку необходимых инструментов. Я покажу вам далеко не все возможности TypeScript, а только самые основные и интересные.
Из-за большого размера контент каждого раздела будет скрыт под спойлер.
1. Статический анализ
Скрытый текст
Системы типов JavaScript и Lua во многом похожи, и потому они имеют некоторые общие проблемы, которые может решить TypeScript.
1.1 Проверка типов
Если функция или метод явно требует среди аргументов число, TypeScript не допустит что-то отличное от числа.
Lua тоже сообщит об этом, но только после запуска скрипта.
1.2 Проверка существования полей и методов
Если вы попытаетесь обратится к несуществующему полю или методу, TypeScript сообщит вам об этом и даже поможет исправить, если вы, например, опечатались.
В Lua мы узнаем об этом только после запуска скрипта.
2. Автодополнение
Скрытый текст
Благодаря статической типизации, редактор кода может подсказывать методы и поля объекта.
--[[ 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
endfunction Player.prototype.____constructor(self, username)
self.username = username
endfunction Player.prototype.greet(self)
print("Привет, ".. tostring(self.username).."!")endlocal alex = Player.new("alex")
alex:greet()
3.2 Модификаторы доступа
3.3 Наследование
classPlayer{
username: string;constructor(username: string){this.username = username;}}classAlex extends Player{constructor(){
super("Alex");}}let alex =newAlex();
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
endfunction 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
endfunction Alex.prototype.____constructor(self)
Player.prototype.____constructor(self,"Alex")endlocal 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 importsfunction __TS__ArrayPush(arr,...)local items =({...})for ____, item in ipairs(items)do
arr[#arr +1]= item
endreturn#arr
endfunction __TS__ArrayFilter(arr, callbackfn)local result ={}dolocal i =0while i <#arr doif callbackfn(_G, arr[i +1], i, arr)then
result[#result +1]= arr[i +1]end
i = i +1endendreturn result
endfunction __TS__ArrayMap(arr, callbackfn)local newArray ={}dolocal i =0while i <#arr do
newArray[i +1]= callbackfn(_G, arr[i +1], i, arr)
i = i +1endendreturn newArray
endlocal items ={1,2,3,4,5,}
__TS__ArrayPush(items,6)local result = table.concat(__TS__ArrayMap(__TS__ArrayFilter(items,function(____, x)return x %2==0end),function(____, x)return x ^2end),", ")
print(result)
4.2 Модули
// main.tsimport{ myFunction } from "./myLibrary";
myFunction();// myLibrary.tsexportfunction 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")endreturn ____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.
Создание проекта вручную:
Скрытый текст
После установки NodeJS у вас должны появится команды npm и node.
Создайте новую папку для своего первого проекта
Переключитесь в нее, используя терминал и все дальнешие действия выполняйте в ней
Создайте npm пакет: npm init. После выполнения этой команды в папке появится файл package.json
Добавьте в объект "scripts" в package.json строку "build": "tstl",
Установите транспилер npm install --dev typescript-to-lua. После установки первого пакета у вас появится папка node_modules
Теперь вы можете размещать в 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, который тут казалось бы не нужен.
--[[ 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 параметр для любой функции. Если вы считаете такое поведение по умолчанию недопустимым, то можете отключить его:
--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]
person:say("hi")
Но если нам понадобится описать какую-либо библиотеку (таблицу с функциями), то такое поведение может быть недопустимо, ведь эти функции не принимают self параметр. Можно вручную задать каждой функции this:void, а можно использовать директиву @noSelf:
--[[ 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. Тем не менее, сейчас кортежи просто превращаются в таблицы:
Числовые индексы в таблицах 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)
TypeScript to Lua
в Гайды
Опубликовано: · Изменено пользователем Exeteres
Раздел "Особенности работы транспилера"
Вместо вступления:
Я не считаю C-подобный синтаксис лучше синтаксиса lua и не буду заставлять вас переписывать все ваши программы на TypeScript! Я просто хочу поделится с вами альтернативой и рассказать про ее преимущества и недостатки.
# Что такое TypeScript?
TypeScript — язык программирования, представленный Microsoft в 2012 году и позиционируемый как средство разработки веб-приложений. Он создан для расширения JavaScript и он компилируется в JavaScript, но также существует инструмент для преобразования TypeScript кода в Lua. Вам может показаться, что этот транслятор крайне ограничен, но, поверьте мне, его возможности впечатляют.
# Почему его стоит попробовать?
Я сначала продемонстрирую некоторые возможности TypeScript графически, а потом подробно расскажу про установку и настройку необходимых инструментов. Я покажу вам далеко не все возможности TypeScript, а только самые основные и интересные.
Из-за большого размера контент каждого раздела будет скрыт под спойлер.
1. Статический анализ
Системы типов JavaScript и Lua во многом похожи, и потому они имеют некоторые общие проблемы, которые может решить TypeScript.
1.1 Проверка типов
Если функция или метод явно требует среди аргументов число, TypeScript не допустит что-то отличное от числа.
Lua тоже сообщит об этом, но только после запуска скрипта.
1.2 Проверка существования полей и методов
Если вы попытаетесь обратится к несуществующему полю или методу, TypeScript сообщит вам об этом и даже поможет исправить, если вы, например, опечатались.
В Lua мы узнаем об этом только после запуска скрипта.
2. Автодополнение
Благодаря статической типизации, редактор кода может подсказывать методы и поля объекта.
Или показать параметры, которые принимает метод.
Или текст документации.
3. ООП
3.1 Классы
Сгенерированный lua код
3.2 Модификаторы доступа
3.3 Наследование
Сгенерированный lua код
4. Стандартная библиотека и возможности языка
Многие методы стандартных типов TypeScript (такие как массивы и строки) также могут быть транслированы в lua.
4.1 Получение всех четных чисел массива, возведение в квадрат и соединение в строку через запятую
Сгенерированный lua код
4.2 Модули
Сгенерированный lua код
4.3 Форматирование строк
Сгенерированный lua код
# Как это работает?
Конечно же все не так просто. Компилятор просто так не узнает типы методов и полей объектов, с которыми мы будем работать. Для того, чтобы описать наше окружение необходимо написать так называемые файлы декларации или тайпинги. Хочу сразу вас обрадовать - это не ваша задача. Существует репозиторий с такими декларациями, в котором, на данный момент, существуют типы для большинства API и компонентов OpenOS и библиотеки GUI.
От вас требуется только установить все необходимые инструменты и правильно их настроить.
# Установка
Редактор кода
Вы можете использовать любой редактор кода с поддержкой TypeScript. Я рекомендую VSCode, который поддерживает его из коробки.
NodeJS
Он необходим нам для установки необходимых пакетов (он поставляется с пакетным менеджером npm) и для запуска транспилера. Вы можете скачать последнюю стабильную версию с официального сайта.
Использование плагина для VSCode (рекомендуется):
Вы можете установить плагин OpenComputersTS. В нем есть две команды:
Для открытия окна с командами используйте сочетание Ctrl + Shift + P.
Создание проекта вручную:
После установки NodeJS у вас должны появится команды npm и node.
Теперь вы можете размещать в src исходный код, например, main.ts со следующим содержимым:
Как запустить сгенерированный код в OpenComputers?
Самый удобный способ для доставки полученного кода - символическая ссылка. Вы можете заменить каталог dist ссылкой на папку диска.
Диски располагаются в папке .minecraft\saves\{сохранение}\opencomputers. Вам также необходимо отключить параметр filesystem.bufferChanges в файле .minecraft\config\opencomputers\settings.conf
После этого вы сможете запускать сгенерированный код прямо в игре.
Для компиляции используйте команду npm run build. Сгенерированные lua файлы появятся в папке dist.
# Особенности работы транспилера
В этой секции я подробно раскажу про недостатки этого подхода, возможные проблемы и способы их решения.
1. Параметр self
Объявление функций
Одно из первых, что вы скорее всего попробуете сделать - объявить функцию:
Однако результат может быть неожиданным:
Зачем-то появился параметр self, который тут казалось бы не нужен.
А теперь попробуйте сделать так:
Результат:
У функции все еще есть self, но в этот раз он необходим для доступа к полям obj.
Если мы теперь попробуем оторвать метод sum от объекта и вызвать отдельно:
то увидем, что транспилер все еще передает nil вместо self:
Такое поведение связано с тем, что в JavaScript (для которого изначально был создан TypeScript) this (self) есть у любой функции (кроме стрелочных) и для сохранения совместимости с этой особенностью транспилер автоматически генерирует self параметр для любой функции. Если вы считаете такое поведение по умолчанию недопустимым, то можете отключить его:
Тип self параметра
Вы также можете явно задать тип this так же как и тип любого другого параметра, например, назначить ему void:
И тогда транспилер уберет self параметр:
Особенно это важно при работе с декларациями. Например, мы хотим описать экземпляр некоторого класса:
И ожидаемо получим:
Но если нам понадобится описать какую-либо библиотеку (таблицу с функциями), то такое поведение может быть недопустимо, ведь эти функции не принимают self параметр. Можно вручную задать каждой функции this:void, а можно использовать директиву @noSelf:
И тогда эти функции будут вызваны без двоеточия:
2. Множественные значения
В TypeScript не поддерживаются множественные значения. Вместо этого используются кортежи.
Например, объявим функцию, которая возвращает несколько значений:
Этот подход сильно напоминает множественные возвращаемые значения и множественное присваивание в Lua. Тем не менее, сейчас кортежи просто превращаются в таблицы:
Чтобы заставить транспилер возвращать множественные значения, необходимо использовать директиву @tupleReturn:
И тогда все будет красиво:
3. Индексы
Числовые индексы в таблицах Lua начинаются с 1, в то время как в TypeScript с 0. Транспилер сам выполняет преобразование индексов TypeScript в Lua, например:
будет преобразовано в:
И мы в обоих случаях получим на экране второй элемент массива.
arr.length является полным аналогом оператора #:
Результат:
# Ссылки