Skip to content

mzujev/lattelua

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ru en

Справочник по языку Lattelua

Lattelua — это динамический скриптовый язык, компилируемый в Lua. Он основан на семантике(Парсер/Лексер) MoonScript, но использует JS-подобный синтаксис, что обеспечивает свободное форматирование кода и легкость минификации.

Основные отличия от MoonScript:

  • Блочная структура: Использование { и } вместо отступов.
  • Свободное форматирование: Игнорирование переносов строк и пробелов.
  • Разделители: Использование ; для разделения инструкций (препроцессор заменяет их на перевод строки).
  • Синтаксис методов: Оператор :: для вызова методов экземпляра.
  • Switch: Ключевое слово case вместо when.
  • Обработка ошибок: Встроенная конструкция try/catch/finally.
  • Автономный интерпретатор: llua с возможностью листинга компиляции.

Оглавление

  1. Базовый синтаксис
  2. Переменные и присваивание
  3. Типы данных и таблицы
  4. Управляющие конструкции
  5. Циклы
  6. Линейные декораторы
  7. Функции
  8. Объектно-ориентированное программирование
  9. With оператор
  10. Do оператор
  11. Обработка ошибок (Try-Catch)
  12. Автономный интерпретатор

Базовый синтаксис

Блоки кода и разделители

В Lattelua отступы не имеют значения. Группировка выражений происходит с помощью { и }. Символ ; используется как разделитель инструкций, что позволяет писать код в одну строку:

-- Обычная запись
if true {
	print("Hello")
}

-- Однострочная запись
if true { print("Hello") }

Комментарии

Комментарии игнорируются компилятором. Символ ; внутри комментариев и строк не обрабатывается препроцессором:

-- Это однострочный комментарий

--[[
		Это многострочный комментарий.
		Он работает точно так же, как в Lua.
		В MoonScript такой тип комментариев не поддерживается!
--]]

Переменные и присваивание

По умолчанию все переменные являются локальными (local):

a = 1              -- local a = 1
str = "hello"      -- local str = "hello"
x, y = 10, 20      -- local x, y = 10, 20

Обновление значений

Доступны операторы быстрого обновления значений: +=, -=, *=, /=, %=, ..=:

count = 0
count += 1          -- count = count + 1
name = "Lattelua"
name ..= " Lang"    -- Конкатенация

Глобальные переменные

Чтобы создать глобальную переменную или экспортировать её из модуля, используется ключевое слово export:

export VERSION = "1.0"

Это особенно полезно при объявлении того, что будет видно извне в модуле:

-- some module.llua
export some_print

add = (x, y) -> { x + y }

some_print = (x, y) -> {print "Addition is: ", add x, y}



-- some script.llua
require "some_module"

some_module.some_print 5, 10      -- 15
print some_module.add 5, 10       -- errors, `add` not visible

Экспорт не будет иметь эффекта, если в области видимости уже есть локальная переменная с таким же именем.

В контексте переменных, часто требуется перенести некоторые значения из таблицы/модуля в текущую область как локальные переменные по их имени.

Для этого используется ключевое слово import:

import insert from table          -- local insert = table.insert

Можно указать несколько имен, каждое через запятую:

import C, Ct, Cmt from lpeg       -- local C, Ct, Cmt = lpeg.C, lpeg.Ct, lpeg.Cmt

Иногда требуется, чтобы таблица была передана в качестве self-аргумента. Для сокращения можно добавить префикс :: к имени, чтобы связать функцию с этой таблицей:

t = {
	val: 100
	add: (value) => {
		self.val + value
	}
}

import ::add from t

print add 22                      -- equivalent to add(t, 22) or t::add(22) 

Типы данных и таблицы

Литералы

num = 123  
float = 1.5  
str_double = "Text"  
str_single = 'Text'
str_multi = [[
	multi line
	text
]]
bool = true  
nothing = nil

Строковая интерполяция

Допускается смешивать выражения со строковыми литералами, используя #{} синтаксис:

print "This is #{math.random() * 100}% work, I'm sure"      -- print("This is " .. tostring(math.random() * 100) .. "% work, I'm sure")

Интерполяция строк доступна только в строках, заключенных в двойные кавычки.

Таблицы

Как и в Lua, таблицы заключаются в фигурные скобки:

array = { 1, 2, 3, 4 }

В отличие от Lua, присвоение значения ключу в таблице выполняется с помощью : (вместо =):

config = {  
	port: 8080,  
	host: "localhost", 
	list: { 1, 2, 3 },  
	["key with spaces"]: "some value"
}

Перевод строки можно использовать для разделения значений вместо запятой (или и то, и другое):

config = {  
	port: 8080  
	host: "localhost" 
	list: { 1, 2, 3 }  
	["key with spaces"]: "some value"
}

Ключи таблицы могут быть ключевыми словами языка без экранирования:

t = {
	do: "do"
	end: "end"
}

Если создается таблица из переменных и требуется, чтобы ключи совпадали с именами переменных, можно использовать префиксный оператор ::

gender = "male"
age = 25

person = {
	:gender                  -- gender: gender
	:age                     -- age: age
	key: "value"             -- key: "value"
}

print :gender, :age	         -- {gender: gender, age: age}

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

t = {
	[1 + 2]: "three",
	["some value"]: true,
	"another some value": false
}

Деструктуризация

Деструктуризация - это способ быстрого извлечения значений из таблицы по их имени или положению в таблицах на основе массива.

vec = { x: 10, y: 20, z: 30 }

{ :x, :y } = vec

print(x, y)             -- 10 20

arr = {1, 2, 3}

{f,_,t} = arr

print f, t              -- 1 3

Это также работает с вложенными структурами данных:

obj = {
	array: {1, 2, 3, 4}
	properties: {
		align: "center"
		vec: { x: 10, y: 20, z: 30 }
	}
}

{
	array: { first, second }
	properties: {
		:align
		vec: { :x, :y }
	}
} = obj

-- first, second, align, x, y = obj.array[1], obj.array[2], obj.properties.align, obj.properties.vec.x, obj.properties.vec.y

Обычно значения из таблицы извлекаются и присваиваются локальным переменным, имеющим то же имя, что и ключ. Чтобы избежать повторения, возможно использовать префиксный оператор ::

{:concat, :insert} = table      -- local concat, insert = table.concat, table.insert

По сути, это то же самое, что и import, но мы можем переименовать поля, которые хотим извлечь:

{:mix, :max, random: rand } = math      -- local mix, max, rand = math.mix, math.max, math.random

Деструктуризация также может проявляться в тех местах, где неявно выполняется присваивание:

array = {
	{1, 2, 3, 4}
	{5, 6, 7, 8}
}

for {first, second} in *array {
	print first, second                   -- 1 2   &   5 6
}

Генераторы коллекций

Генераторы предоставляют удобный синтаксис для создания новой таблицы путем итерации по некоторому существующему объекту и применения выражения к его значениям. Существует два типа генераторов: генератор списка и генератор таблицы. Они оба создают таблицы Lua. Генераторы списков накапливают значения в таблицу, подобную массиву, а генераторы таблиц позволяют устанавливать как ключ, так и значение на каждой итерации.

Генераторы списков

Следующий пример создаёт копию таблицы элементов, с удвоенными значениями:

array = { 1, 2, 3, 4 }
doubled = [item * 2 for i, item in ipairs array]                   -- doubled = { 2, 4, 6, 8 }

Элементы, включенные в новую таблицу, можно ограничить с помощью when выражения:

iter = ipairs array
slice = [item for i, item in iter when i > 1 and i < 3]            -- slice = { 2 }

Операторы for и when возможно объединять в цепочки сколько угодно. Единственное требование, чтобы в выражении был хотя бы один оператор for.

Использование нескольких операторов for аналогично использованию вложенных циклов:

x = {4, 5, 6, 7}
y = {9, 2, 3}

points = [{x,y} for x in ipairs x for y in ipairs y]

Генераторы таблиц

Синтаксис генератора таблиц очень похож, отличается только использованием {} и получением двух значений на каждой итерации:

t ={
	gender: "male",
	age: 25
}
copy = {k,v for k,v in pairs t}

Генераторы таблиц, как и генераторы списков, также поддерживают несколько операторов for и when:

copy = {k,v for k,v in pairs t when k != "gender"}

Управляющие конструкции

If Else Unless

if x > 10 {  
	print("Big")  
} elseif x == 10 {  
	print("Equal")  
} else {  
	print("Small")  
}

-- Unless (если НЕ)  
unless ready {  
	init()  
}

-- Тернарный оператор / Однострочный if  
val = if check { true; } else { false; }

Условные выражения также можно использовать в операторах возврата и присваиваниях:

test = (c)->{
	if c {
		true
	} else {
		false
	}
}

out = if test true {
	"is true"
} else {
	"is false"
}

print out                    -- "is true"

Оператор Switch

Использует ключевое слово case для веток и else для значения по умолчанию:

value = 2

switch value {  
	case 1
		print("One")
	case 2
		print("Two")
	case 1,2,3
		print "One..Three"
	else
		print("Other")
}

switch также можно использовать в качестве выражения, тем самым присвоить результат switch переменной:

out = switch value {  
	case 1
		"One"
	case 2
		"Two"
	case 1,2,3
		"One..Three"
	else
		"Other"
}

print out                    -- "Two"

Циклы

For (Числовой)

-- Без шага
for i = 1, 10 {  
	print(i)                   -- 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
}

-- С шагом  
for i = 0, 10, 2 {  
	print(i)                   -- 0, 2, 4, 6, 8, 10
}

For In (Итератор)

t = { a: 1, b: 2 }  
for k, v in pairs(t) {  
	print(k, v)
}

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

Удвоение каждого четного числа:

doubled = for i=1,20 {
	if i % 2 == 0 {
		i * 2
	} else {
		i
	}
}

print i for _,i in ipairs doubled             -- 4, 8, 12, 16, 20, 24, 28, 32, 36, 40

Также возможно фильтровать значения, комбинируя выражения цикла for с оператором continue.

Циклы for в конце тела функции не накапливаются в таблице для возвращаемого значения (вместо этого функция вернет nil). Возможно использовать явный оператор возврата, либо цикл можно преобразовать в генератор списка.

funca = -> {for i=1,10 {i}}
funcb = -> {return [i for i=1,10] }

print funca() -- prints nil
print funcb() -- prints table object

Это сделано для того, чтобы избежать ненужного создания таблиц для функций, которым не нужно возвращать результаты цикла.

While

Цикл while также существует в двух вариантах:

i = 10
while i > 0 {
	print i
	i -= 3
}

while running == true {some_func()}

Как и в случае с for, в цикле while также можно использовать выражение. Кроме того, чтобы функция возвращала накопленное значение цикла while, оператор должен быть возвращен явно.

Управление циклом

Break

Оператор break прерывает цикл (while или for), в теле которого встречается. В результате выполнения оператора break управление передаётся первой инструкции, следующей непосредственно за оператором цикла.

i = 0
while i < 10 {
	break if i > 5
	print i
	i += 1
}

Continue

Оператор continue можно использовать для пропуска текущей итерации в цикле.

i = 0
while i < 10 {
	continue if i % 2 == 0
	print i
	i += 1
}

Также continue можно использовать с выражениями цикла, чтобы предотвратить накопление этой итерации в результате.

В этом примере массив фильтруется только по четным числам:

array = {1,2,3,4,5,6}
odds = for x in ipairs array {
	continue if x % 2 == 1
	x
}

Функции

Все функции создаются с использованием функционального выражения. Простая функция обозначается стрелкой: ->

some_func = ->
some_func()        -- call that empty function

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

funca = -> {print "hello world"}

funcb = -> {
	message = "world"
	print "hello #{message}"
}

Если функция не имеет аргументов, ее можно вызвать с помощью оператора !, вместо пустых круглых скобок. ! вызов - предпочтительный способ вызова функций без аргументов.

funca!
funcb()

Функции с аргументами можно создать, указав перед стрелкой список имен аргументов в круглых скобках:

sum = (a, b) -> {  
	return a + b  
}

Для аргументов функции можно указать значения по умолчанию. Аргумент считается пустым, если его значение равно нулю. Любые нулевые аргументы, имеющие значение по умолчанию, будут заменены перед запуском тела функции.

greet = (name = "World") -> {  
	print("Hello " .. name)  
}

Значения аргументов по умолчанию вычисляется в теле функции в порядке объявления аргументов. Именно по этой причине значения по умолчанию имеют доступ к ранее объявленным аргументам.

(x=100, y=x+1000) -> {
	print x + y
}

Функции можно вызывать, перечисляя аргументы после имени выражения, результатом которого является функция. При объединении вызовов функций аргументы применяются к ближайшей функции слева.

sum 10, 20            -- sum(10, 20)
print sum 10, 20      -- print(sum(10, 20))

a b c "a", "b", "c"   -- a(b(c("a", "b", "c")))

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

print "x:", sum(10, 20), "y:", sum(30, 40)     -- print("x:", sum(10, 20), "y:", sum(30, 40))

Между открывающей скобкой и функцией(sum) не должно быть пробела.

Как и в Lua, функции могут возвращать несколько значений. Последний оператор должен представлять собой список значений, разделенных запятыми:

some_func = (x, y) -> {x + y, x - y}
a, b = some_func 10, 20

Self-контекст

Для создания функций предусмотрен специальный синтаксис =>, который автоматически включает аргумент self.

obj = {  
	val: 10  
	update: (num) => {  
		self.val = num           -- self передается автоматически  
	}  
}

obj::update(13)

print obj.val                -- val = 13

Линейные декораторы

Для удобства операторы цикла for и if можно применять к отдельным операторам в конце строки:

print "hello world" if 1 == 1

И с базовыми циклами:

print "value: #{v}" for _, v in ipairs {1,2,3,4,5,6}

Объектно-ориентированное программирование

Классы

Класс объявляется с помощью оператора class, за которым следует табличное объявление, в котором перечислены все методы и свойства.

class Animal {
	new: (name) => {
		self.name = name
	}
	speak: => {
		print(self.name)
	}
}

Объявление класса также можно использовать как выражение, которое можно присвоить переменной или вернуть явно.

Метод new, если определён, становится конструктором.

Создание экземпляра класса осуществляется путем вызова имени класса в качестве функции.

dog = Animal "woof woof"

Все свойства класса являются общими для всех экземпляров. Это нормально для методов, но для других типов объектов могут возникнуть нежелательные результаты:

class Animal {
	speech: {}
	new: (speech) => {
		table.insert self.speech, speech
	}
	speak: (who) => {
		print "#{who} say: #{speech}" for _, speech in ipairs self.speech
	}
}

dog = Animal "woof"
cat = Animal "meow"

dog::speak("dog")      -- will print both `woof` and `meow`
cat::speak("dog")      -- will print both `woof` and `meow`

свойство speech является общим для всех экземпляров, поэтому изменения, внесенные в него в одном экземпляре, будут отображаться в другом. Правильный способ избежать такого поведения - создать изменяемое состояние объекта в конструкторе:

class Animal {
	new: (speech) => {
		self.speech = {}                         -- private property for instance
		table.insert self.speech, speech
	}
	speak: (who) => {
		print "#{who} say: #{speech}" for _, speech in ipairs self.speech
	}
}

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

Ключевое слово extends можно использовать в объявлении класса для наследования свойств и методов другого класса.

class Dog extends Animal {
	new: (speech) => {
		super(speech)
	}

	speak: (who)=> {
		print("#{who} say: WOOF")
	}
}

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

super - это специальное ключевое слово, которое можно использовать двумя способами: как объект или как функцию. super обладает функциональностью только внутри класса. При вызове в качестве функции super вызовет функцию с тем же именем в родительском классе. В качестве первого аргумента автоматически будет передан текущий объект self. При использовании super в качестве обычного значения, это ссылка на объект родительского класса. К super можно обращаться как к любому объекту для получения значений в родительском классе.

При наследовании классом наследника, он отправляет сообщение родительскому классу, вызывая метод __inherited родительского класса, если он существует. Метод принимает два аргумента: наследуемый класс и дочерний класс:

class Animal {
	__inherited: (child) => {
		print "#{self.__name} was inherited by #{child.__name}"
	}
}

class Dog extends Animal{}

Концепция ООП в MoonScript не допускает множественное наследование, в LatteLua этот механизм поддерживается через «встраивание». Каждый класс имеет private-метод implement, который наследует поля и методы встраиваемого класса и использует их так, как если бы они были его собственными.

class Animal {
	setname: (n)=>{
		@name = n
	}
	getname: =>{
		@name
	}
	speak: =>
		print "#{name} makes sound"
	}
}

class Alias {
	setalias: (alias) => {
		@aliases = alias
	}
	getalias: =>{
		@aliases
	}
}

class Dog {
	speak: =>{
		print "#{@getname!}, niknamed '#{@getalias!}', says: Woof!"
	}
	implement self, Animal, Alias
}

class Cat {
	speak: =>{
		print "#{@getname!}, niknamed '#{@getalias!}', says: Meow!"
	}
	implement self, Animal, Alias
}

c, d = Cat!, Dog!

c::setname "Misty"
c::setalias "Shadow"

d::setname "Rex"
d::setalias "Good Boy"

c::speak!                    --Misty, niknamed 'Shadow', says: Meow!
d::speak!                    --Rex, niknamed 'Good Boy', says: Woof!

Важно: «встраивание» не является наследованием — это форма композиции, при встраивании не работают механизмы extends и не создаётся новая область видимости, но замыкания всё равно общие.

With оператор

Блок with позволяет сократить код при множественных обращениях к одному объекту. Внутри блока, свойства начинающиеся с . или методы с ::, относятся к целевому объекту.

user = { name: "John", age: 30 }
user.show = => { print self.name }

with user {
	.name ..= " Doe"      -- user.name = "John Doe"
	::show()              -- user:show()
	print(.age)           -- print(user.age)
}

Оператор with также можно использовать как выражение, возвращающее значение, к которому он предоставил доступ:

name = with user {
	.name = 'Jane Smith'
}

name::show()            -- Jane Smith

Выражение в операторе with также может быть присвоением, если требуется дать выражению имя:

name = with n = setmetatable{name: user.name},{__index: user} {
	.name = 'John Doe'
}

name::show()            -- John Doe
user::show()            -- Jane Smith

Do оператор

Использование оператора do работает так же, как и в Lua.

do {
	msg = "world"
	print "hello #{msg}"
}

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

print do {
	msg = "world"
	"hello #{msg}"
}

Оператор do возможно использовать для расширения синтаксических конструкций:

do {
  print "#{k} => #{v}" if k != "lattelua" else do {
    print "\t#{kk} => #{vv}" for kk,vv in pairs lattelua
  }
} for k,v in pairs _G


-- for k, v in pairs(_G) do
--   do
--     if k ~= "lattelua" then
--       print(tostring(k) .. " => " .. tostring(v))
--     else
--       do
--         for kk, vv in pairs(lattelua) do
--           print("\t" .. tostring(kk) .. " => " .. tostring(vv))
--         end
--       end
--     end
--   end
-- end

Обработка ошибок (Try-Catch)

Блок try используется для обработки исключений. Это позволяет тестировать блок кода на наличие ошибок и корректно обрабатывать их, предотвращая неожиданный сбой программы.

try {
	-- Код, который вызывает ошибку
	error("Boom\!")
} catch {
	-- Обработка ошибки (self содержит текст ошибки)
	print("Error caught: " .. self)
} finally {
	-- Выполняется всегда, если присутствует
	print("Cleanup")
}

Оператор try также может использоваться как выражение. Результатом выражения try является последнее выражение в блоках try/catch соответственно.

Автономный интерпретатор

Вместе с LatteLua устанавливается автономный интерпретатор llua. Интерпретатор предназначен как для непосредственного запуска кода LatteLua, так и для компиляции кода в Lua.

llua [options] [script [args]]

Опции запуска:

  • -e code - выполняет строку code
  • -l name - подгружает библиотеку name
  • -i - интерактивный режим после выполнения script
  • -c - скомпилировать и напечатать результат код lua
  • -- - прекратить обработку опций
  • - - выполнить stdin и прекратить обработку опций

About

A language that compiles to Lua

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors