3. Описание языка

3. Описание языка

Данный раздел описывает [лексику], [синтаксис] и [семантику] Lua. Другими словами, в этом разделе написано которые из [токенов (tokens)] правильные, как они могут быть скомбинированы и что их сочетания означают.

Примечание: В информатике лексический анализ — процесс аналитического разбора входной последовательности символов (например, такой как исходный код на одном из языков программирования) с целью получения на выходе последовательности символов, называемых «токенами» (подобно группировке букв в слова). Группа символов входной последовательности, идентифицируемая на выходе процесса как токен, называется лексемой. В процессе лексического анализа производится распознавание и выделение лексем из входной последовательности символов.

Языковые конструкции будут разъясняться при помощи обычной расширенной BNF нотации, в которой {a} означает 0 или больше символов a, а выражение [a] означает необязательный символ a. Нетерминалы показаны как non-terminal, ключевые слова (keywords) показаны как kword, другие терминальные символы показаны как ‘=’. Полный синтаксис Lua можно найти в §9 в конце этого руководства.

3.1 – Лексические соглашения

Lua - это свободно-формируемый язык. Он игнорирует пробелы (включая новые строки т.е. межстрочный пробел) и комментарии между лексическими элементами (токенами - лексемами), за исключением разделителей между именами и ключевыми словами.

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

Следующие ключевые слова (keywords) зарезервированы и не могут использоваться в качестве имен:

     and       break     do        else      elseif    end
     false     for       function  goto      if        in
     local     nil       not       or        repeat    return
     then      true      until     while

Lua является языком чувствительным к регистру: and - это зарезервированное слово, но And и AND это два разных, допустимых имени. По соглашению, программы должны избегать создания имен, начинающихся с символа подчеркивания и последующим за ним одной или более букв верхнего регистра (как например, _VERSION).

Следующие строки обозначают другие токены:

     +     -     \*     /     %     ^     #
     &     ~     |     <<    >>    //
     ==    ~=    <=    >=    <     >     =
     (     )     {     }     \[     \]     ::
     ;     :     ,     .     ..    ...

literal strings могут быть заключены соответственно, в одиночные или двойные кавычки, и могут содержать следующие C-подобные escape-последовательности:

'\\a'   (bell - звонок),  
'\\b'   (backspace - возврат на один символ \[забой\]),  
'\\f'   (form feed - перевод страницы),  
'\\n'   (newline - новая строка),  
'\\r'   (carriage return - возврат каретки),  
'\\t'   (horizontal tab - [горизонтальная табуляция],  
'\\v'   (vertical tab - [вертикальная табуляция],  
'\\\\'   (backslash - обратный слеш \[обратная косая черта\]),  
'\\"'   (quotation mark \[double quote\] - кавычка \[двойная кавычка\]), и  
'\\''   (apostrophe \[single quote\] - апостроф \[одиночная кавычка\]).

Обратный слеш (backslash) с последующим реальным символом новой строки в результате приведут к переносу части строки на новую строку. Escape-последовательность ‘\z’ пропускает последующий диапазон разделительных (white-space) символов, включая переносы (break) строки; это особенно полезно для прерывания и отступа длинной буквенной (литеральной) строки в многострочном тексте без добавления символов новой строки и пробела в содержимое строки.

Строки в Lua могут содержать любое 8-битное значение, включая встроенные нули, которые могут указываться как ‘\0’. В общем, можно устанавливать любой байт в литеральной строке его числовым значением. Это можно сделать с escape-последовательностью \x_XX_, где XX - это последовательность ровно из двух шестнадцатиричных цифр, или с escape-последовательностью \ddd, где ddd - последовательность до трех десятичных цифр. (Обратите внимание, что если десятичная управляющая последовательность сопровождается цифрой, то она должна состоять точно из трех цифр.)

UTF-8 кодировка из символов Unicode может быть вставлена в буквенную строку с управляющей последовательностью \u{XXX} (обратите внимание на обязательное заключение в скобки), где XXX - это последовательность из одной или более шестнадцатиричных цифр, представляющих код символа.

Буквенные строки (литералы) также могут быть определены использованием долгого формата заключением в длинные скобки. Мы определяем открывающую длинную скобку уровня вложенности n как открывающую квадратную скобку с последующим n-ным числом знаков равенства, сопровождаемые другой открывающей квадратной скобкой. Так, открывающая длинная скобка уровня 0 записывается как [[, а открывающая длинная скобка уровня 1 записывается как [=[, и так далее. Аналогично определяется и закрывающая длинная скобка; например, закрывающая длинная скобка уровня 4 записывается как ]====]. Длинный литерал начинается с открывающей длинной скобки любого уровня и заканчивается первой закрывающей длинной скобкой того же уровня. Она может содержать любой текст, кроме закрывающей скобки того же уровня. Литералы в этой “скобочной” форме могут продолжаться до нескольких строк, в них не выполняется интерпретация любых управляющих последовательностей и игнорируются длинные скобки любого другого уровня. Любой тип управляющей последовательности окончания строки (возврат каретки, новая строка, возврат каретки, сопровождаемый новой строкой, или новая строка с последующим возвратом каретки) преобразуется в простой символ новой строки.

Любой байт в литеральной строке, явно не затронутые предыдущими правилами, представляет самого себя. Однако, Lua открывает файлы [для анализа (парсинга)] в текстовом режиме и у функций системных файлов могут возникать проблемы с некоторыми управляющими символами. Так что более безопаснее представлять не текстовые данные в виде закавыченных литералов с явно указанными управляющими последовательностями для нетекстовых символов.

Для удобства, когда за открывающей длинной скобкой сразу следует символ новой строки, то он не включается в строку. В качестве примера, в системе использующей ASCII (в которой символ ‘a’ кодируется как 97, символ новой строки кодируется как 10, а символ ‘1’ кодируется как 49), пять литеральных строк ниже обозначают одну и ту же строку:

a = 'alo\n123"'
a = "alo\n123\""
a = '\97lo\10\04923"'
a = [[alo
123"]]
a = [==[
alo
123"]==]

Числовая константа (или число) может быть записана с дополнительной дробной частью и дополнительный показатель степени десятки, маркированный буквой ’e’ или ‘E’. Lua также допускает шестнадцатиричные константы, которые начинаются с 0x или 0X. Шестнадцатиричные константы также допускают дополнительную дробную часть плюс дополнительный показатель степени двойки, маркированный буквой ‘p’ или ‘P’. Числовая константа с десятичной запятой (в англоязычном варианте - десятичной точкой) или показателем степени означает число с плавающей запятой; в противном случае это будет целым числом. Примеры правильных целочисленных констант

3   345   0xff   0xBEBADA

Примеры правильных констант с плавающей запятой

3.0     3.1416     314.16e-2     0.31416E1     34e1
0x0.1E  0xA23p-4   0X1.921FB54442D18P+1

Комментарий начинается с двойного дефиса (--) в любом месте вне строки. Если текст сразу после -- не имеет открывающей длинной квадратной скобки, комментарий является коротким комментарием, который продлится до конца строки. В противном случае, он будет длинным комментарием, который продлится до соответствующей закрывающей длинной квадратной скобки. Длинные комментарии часто используют для временного отключения (т.е. блокировки) кода.

3.2 – Переменные

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

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

var ::= Name

Name обозначает идентификаторы, как было определено в §3.1.

Любая переменная name предполагается глобальной, если явно не объявлена как локальная (смотрите §3.3.7). Локальные переменные ограничены лексически: локальные переменные могут быть свободно доступны функциям, определенным внутри их области видимости (смотрите §3.5).

Перед первым присваиванием переменной, её значением является nil.

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

var ::= prefixexp ‘[’ exp ‘]’

Значение обращений к полям таблицы может быть изменено с помощью метатаблиц. Обращение к индексированной переменной t[i] аналогично вызову gettable_event(t,i) (смотрите §2.4 с полным описанием функции gettable_event. Эта функция не определена и не подлежит вызову в Lua. Здесь она приведена только в разъяснительных целях.)

Синтаксис типа var.Name - это просто синтаксический сахар для var[“Name”]:

var ::= prefixexp ‘.’ Name

Обращение к глобальной переменной x аналогично обращению к _ENV.x. Из-за способа компиляции порций (chunk), _ENV никогда не бывает глобальным именем (смотрите §2.2).

3.3 – Операторы

Lua поддерживает почти стандартный набор операторов, подобный таковым в Pascal или C. Этот набор включает присваивания, управляющие структуры, вызовы функций и объявления переменных.

3.3.1 – Блоки

Блок - это список операторов, выполняемых последовательно:

block ::= {stat}

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

stat ::= ‘;’

Вызовы функций и присваивания могут начинаться с открывающейся круглой скобки.Эта возможность приводит к неоднозначности (двусмысленности) в грамматике Lua. Рассмотрим следующий фрагмент:

a = b + c
(print or io.write)('done')

Грамматически это можно рассматривать двумя способами:

a = b + c(print or io.write)('done')
a = b + c; (print or io.write)('done')

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

;(print or io.write)('done')

Блок может быть явно выделен для создания одиночного оператора:

stat ::= do block end

Явно заданные блоки полезны для управления областью видимости объявлений переменных. Явные блоки также иногда используются для добавления оператора return в середину другого блока (смотрите §3.3.4).

3.3.2 – Порции

Минимальный кусок кода, с которым работает Lua при компиляции, называется порция (chunk). Синтаксически, порция - это просто блок:

chunk ::= block

Lua обрабатывает порцию как тело анонимной функции с переменным числом аргументов (смотрите §3.4.11). В этом качестве, порции могут определять локальные переменные, принимать аргументы и возвращать значения. Кроме того, такая анонимная функция компилируется как область видимости внешней локальной переменной называемой _ENV (смотрите §2.2). Получающаяся функция всегда имеет _ENV как единственную свою upvalue, даже если эту переменную она не использует.

Порция может быть сохранена в файл или строку внутри хост-программы. Для выполнения порции (chunk), Lua сначала загружает её, предварительно компилируя код порции в инструкции для виртуальной машины, и затем Lua выполняет скомпилированный код с интерпретатором виртуальной машины.

Порции также можно предварительно скомпилировать в двоичную форму; для более подробных сведений смотрите программу luac и функцию [string.dump](../ standardlibraries.htm#string.dump). Программы в исходной и компилированной формах взаимозаменяемы; Lua автоматически распознает тип файла и действует соответственно (смотрите [load](../ standardlibraries.htm#pdf-load)).

Примечание: Интерпретатор — программа (разновидность транслятора), выполняющая интерпретацию, … читать далее

т.е. — пооператорный (покомандный, построчный) анализ, обработка и тут же выполнение исходной программы или запроса (в отличие от компиляции, при которой программа транслируется без её выполнения).
Простой интерпретатор анализирует и тут же выполняет (собственно интерпретация) программу покомандно (или построчно), по мере поступления её исходного кода на вход интерпретатора. Достоинством такого подхода является мгновенная реакция. Недостаток — такой интерпретатор обнаруживает ошибки в тексте программы только при попытке выполнения команды (или строки) с ошибкой.
Интерпретатор компилирующего типа — это система из компилятора, переводящего исходный код программы в промежуточное представление, например, в байт-код или p-код, и собственно интерпретатора, который выполняет полученный промежуточный код (так называемая виртуальная машина). Достоинством таких систем является большее быстродействие выполнения программ (за счёт выноса анализа исходного кода в отдельный, разовый проход, и минимизации этого анализа в интерпретаторе). Недостатки — большее требование к ресурсам и требование на корректность исходного кода.

(Из Википедии)

3.3.3 – Присваивание

Lua разрешает множественные присваивания. Поэтому, синтаксис присваивания определяет список переменных с левой стороны, а список выражений с правой стороны. Элементы в обоих списках разделяются запятыми:

stat ::= varlist ‘=’ explist
varlist ::= var {‘,’ var}
explist ::= exp {‘,’ exp}

Выражения обсуждаются в §3.4.

Перед присваиванием список значений согласовывается с длиной списка переменных. Если значений больше чем нужно, избыточные значения отбрасываются. Если значений меньше чем требуется, в список добавляются значения nil до нужного количества. Если список выражений оканчивается вызовом функции, то все значения возвращаемые этим вызовом вводятся в список значений, перед согласованием (кроме случаев, когда вызов заключен в круглые скобки; смотрите §3.4).

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

i = 3
i, a[i] = i+1, 20

устанавливает a[3] равным 20, не затрагивая a[4], так как i в a[i] вычисляется (как 3) перед присвоением ему 4. Аналогично, строка

x, y = y, x

обменивает значения x и y, а

x, y, z = y, z, x

циклически переставляет значения x, y, и z.

Значение присваиваний глобальным переменным и полям таблиц может быть изменено с помощью метатаблиц. Присваивание индексированной переменной t[i] = val эквивалентно settable_event(t,i,val). (смотрите в §2.4 полное описание функции settable_event. Эта функция в Lua не определяется и не вызывается, она используется здесь только в разъяснительных целях.)

Присваивание глобальному имени x = val эквивалентно присваиванию _ENV.x = val (смотрите §2.2).

3.3.4 – Управляющие структуры

Управляющие структуры if, while, и repeat имеют обычное значение и знакомый синтаксис:

stat ::= while exp do block end
stat ::= repeat block until exp
stat ::= if exp then block {elseif exp then block} [else block] end

В Lua также имеется оператор цикла for, в двух вариантах (смотрите §3.3.5).

Условное выражение управляющей структуры может возвращать любое значение. Значения false и nil считаются ложными. Все значения, отличные от nil и false считаются истинными (в частности, число 0 и пустая строка также являются истинными).

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

Оператор goto передает управление программой на метку (label). По синтаксическим причинам, метки в Lua также считаются операторами:

stat ::= goto Name
stat ::= label
label ::= ‘::’ Name ‘::’

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

Метки и пустые операторы называются недействующими операторами (void statements), так как они не выполняют действий.

Оператор break завершает выполнение while, repeat, или цикла for, переходя к следующему оператору после цикла:

stat ::= break

break завершает самый внутренний вложенный цикл.

Оператор return используется для возврата значений из функции или порции (которая является анонимной функцией). Функции могут возвращать более одного значения, так что синтаксис для оператора return таков

stat ::= return [explist] [‘;’]

Оператор return может быть записан только в качестве последнего оператора блока. Если действительно необходимо, чтобы return был в середине блока, то можно использовать явно заданный внутренний блок, в виде идиомы do return end, так как теперь return является последним оператором в своем (внутреннем) блоке.

3.3.5 – Оператор for

Оператор цикла for имеет две формы: числовую и универсальную (типовую).

Числовой цикл for повторяет блок кода до тех пор, пока управляющая переменная цикла проходит через арифметическую прогрессию. Он имеет следующий синтаксис:

stat ::= for Name ‘=’ exp ‘,’ exp [‘,’ exp] do block end

block повторяется для name начиная со значения первого exp, пока он проходит до второго exp с шагом третьего exp. Более точно, оператор цикла for вроде этого

for v = e1, e2, e3 do block end

эквивалентен коду:

do
  local var, limit, step = tonumber(e1), tonumber(e2), tonumber(e3)
  if not (var and limit and step) then error() end
  var = var - step
  while true do
    var = var + step
    if (step >= 0 and var > limit) or (step < 0 and var < limit) then
      break
    end
    local v = var
    block
  end
end

Обратите внимание на следующее:

  • Все три управляющих выражения вычисляются только один раз, перед началом цикла. Все они должны быть приведены в числах.
  • var (переменная), limit (предел), и step (шаг) - невидимые (неявные) переменные. Имена, показанные здесь, приведены только для пояснения.
  • Если третье выражение (step) отсутствует, то используется шаг равный 1.
  • Для выхода из цикла for можно использовать break и goto.
  • Переменная цикла v является локальной для тела цикла. Если потребуется её значение после цикла, назначьте его в другую переменную перед выходом из цикла.

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

stat ::= for namelist in explist do block end
namelist ::= Name {‘,’ Name}

Оператор цикла for подобный

for var_1, ···, var_n in explist do block end

эквивалентен (равнозначен) коду:

do
  local f, s, var = explist
  while true do
    local var_1, ···, var_n = f(s, var)
    if var_1 == nil then break end
    var = var_1
    block
  end
end

Отметьте следующее:

  • explist вычисляется только один раз. Его результатами являются функция iterator, state и начальное значение для первой переменной итератора.
  • f, s, и var являются неявными переменными. Имена, используемые здесь, только для пояснения.
  • Для выхода из цикла for можно использовать break.
  • Переменные цикла var_i являются локальными для цикла, их значения нельзя использовать после завершения цикла for. Если все же эти значения потребовались, то присвойте их другим переменным перед тем как прервать цикл или выйти из него.

3.3.6 – Вызовы функций как операторы

Для разрешения возможных побочных эффектов, вызовы функций могут быть выполнены как операторы:

stat ::= functioncall

В этом случае, все возвращенные значения отбрасываются. Вызовы функций объясняются в §3.4.10.

3.3.7 – Локальные объявления

Локальные переменный могут быть объявлены в любом месте внутри блока. Объявление может включать начальное присваивание:

stat ::= local namelist [‘=’ explist]

Если таковое существует, начальное присваивание имеет ту же семантику как и множественное присваивание (смотрите §3.3.3). В противном случае, все переменные инициализируются со значением nil.

Порция также является блоком (смотрите §3.3.2) и поэтому локальные переменные могут быть объявлены в порции за пределами любого явного блока.

Правила видимости для локальных переменных поясняются в §3.5.

3.4 – Выражения

Основные выражения в Lua следующие:

     exp ::= prefixexp
     exp ::= nil | false | true
     exp ::= Numeral
     exp ::= LiteralString
     exp ::= functiondef
     exp ::= tableconstructor
     exp ::= ‘...’
     exp ::= exp binop exp
     exp ::= unop exp
     prefixexp ::= var | functioncall | ‘(’ exp ‘)’

Числа и символьные строки (литеральные строки) описаны в §3.1; переменные в §3.2; определения функций описаны в §3.4.11; вызовы функций в §3.4.10; конструкторы таблиц описываются в §3.4.9. Выражения с переменным числом аргументов (vararg), обозначаемые тремя точками (’…’), могут использоваться только непосредственно внутри функций с переменным числом аргументов (vararg function); они поясняются в §3.4.11.

В [бинарные операторы] включены арифметические операторы (смотрите §3.4.1), побитовые операторы (смотрите §3.4.2), операторы сравнения (смотрите §3.4.4), логические операторы (смотрите §3.4.5), и оператор конкатенации (смотрите §3.4.6). В унарные операторы включены унарный минус (смотрите §3.4.1), унарный побитовый НЕ (смотрите §3.4.2), унарный логический not (смотрите §3.4.5), и унарный оператор длины (смотрите §3.4.7).

И вызовы функций, и выражения с переменным числом аргументов (vararg expressions) могут приводить к множеству значений. Если вызов функции используется как оператор (смотрите §3.3.6), то его список возврата устанавливается в ноль элементов, таким образом отбрасываются все возвращаемые значения. Если выражение используется в качестве последнего (или единственного) элемента списка выражений, то такая корректировка не делается (если выражение не заключено в круглые скобки). Во всех других ситуациях, Lua сводит список результатов к одному элементу, либо отбрасывая все значения за исключением первого из них, либо добавляя одиночный nil, если значений не существует.

Вот некоторые примеры:

f()             -- возврат корректируется до 0 значений
                -- (т.е. все результаты сбрасываются)
g(f(), x)       -- возврат f() сводится к 1 значению
g(x, f())       -- g получает x плюс все результаты из f()
a,b,c = f(), x  -- возврат f() сводится к 1 значению
                -- (c получает nil)
a,b = ...       -- a получает первый параметр из vararg,
                -- b - второй (и a и b могут получить nil,
                -- если соответствующих vararg параметров
                -- не существует)
a,b,c = x, f()  -- возврат f() сводится к 2 значениям
a,b,c = f()     -- возврат f() сводится к 3 значениям
return f()      -- возвращает все результаты из f()
return ...      -- возвращает все полученные vararg параметры
return x,y,f()  -- возвращает x, y, и все результаты из f()
{f()}           -- создает список со всеми результатами из f()
{...}           -- создает список со всеми vararg параметрами
{f(), nil}      -- возврат f() сводится к 1 значению

Любые выражения заключенные в круглые скобки всегда выдают только одно значение. Таким образом, (f(x,y,z)) это всегда одно значение, даже если f возвращает несколько значений. (Значением из (f(x,y,z)) является первым значением, возвращенным f или значение nil, если f не возвратит никаких значений.)

3.4.1 – Арифметические операторы

Lua поддерживает следующие арифметические операторы:

+: сложение
-: вычитание
*: умножение
/: деление
//: целочисленное деление
%: модуль
^: возведение в степень
-: одноместный минус

За исключением возведения в степень и деления чисел с плавающей запятой, арифметические операторы работают следующим образом:
Если оба операнда являются целыми числами, операция выполняется над целыми числами и в результате получается целое число.
В другом случае, если оба операнда являются числами или строками, которые могут быть конвертированы в числа (смотрите §3.4.3), то они преобразуются в числа с плавающей запятой, операция выполняется следуя обычным правилам для арифметики чисел с плавающей запятой (как правило, стандарт IEEE 754, а вот его описание на русском языке), и результатом является число с плавающей запятой.

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

Деление с округлением вниз (floor division) (//) - это деление, которое округляет частное в направлении к минус бесконечности, то есть, к минимальному уровню деления своих операндов.

Примечание: Для пояснения округления к минус бесконечности посмотрите эту таблицу:

Число                 | -2.9 | -0.5 | 0.3 | 1.5 | 2.9 |
Результат округления  |   -3 |   -1 |   0 |   1 |   2 |

Модуль определяется как остаток от деления, при котором частное округляется в направлении к минус бесконечности. (деление с округлением вниз).

В случае переполнений (когда для представления получившегося числа не хватает разрядности) в целочисленной арифметике, все операции циклически переносятся (wrap around), в соответствии с обычными правилами арифметики дополнительных кодов. (Другими словами, они возвращают уникальное представляемое целое число, равное остатку от деления 264 на результат математического вычисления.)

Некоторые пояснения по арифметике дополнительных кодов … читать далее

Прямой код числа это представление беззнакового двоичного числа. Если речь идет о машинной арифметике, то как правило на представление числа отводится определенное ограниченное число разрядов. Диапазон чисел, который можно представить числом разрядов n равен 2n
Обратный код числа, или дополнение до единицы (one’s complement) это инвертирование прямого кода (поэтому его еще называют инверсный код). То есть все нули заменяются на единицы, а единицы на нули.
Дополнительный код числа, или дополнение до двойки (two’s complement) это обратный код, к младшему значащему разряду которого прибавлена единица.
Это все для удобной работы со знаками. Итак, предположим, что у нас 4 разряда для работы с двоичными числами. Представить таким образом можно 16 чисел - 0,1,… 15

00 - 0000
...
15 - 1111

Но если нет знака, убогая получается арифметика. Нужно вводить знак. Чтобы никого не обидеть, половину диапазона отдадим положительным числам (8 чисел), половину - отрицательным (тоже 8 чисел). Ноль, что отличает машинную арифметику от обычной, мы отнесем в положительные числа (в обычном арифметике у нуля нет знака, если не ошибаюсь). Итого, в положительные числа попадают 0,…,7, а в отрицательные -1,…,-8.
Для различия положительных и отрицательных чисел выделяют старший разряд числа, который называется знаковым (sign bit) 0 в этом разряде говорит нам о том, что это положительное число, а 1 - отрицательное.
С положительными числами все вроде бы понятно, для их представления можно использовать прямой код

0 - 0000
1 - 0001
7 - 0111

А как представить отрицательные числа?
Вот для их представления как раз и используется дополнительный код. То есть, -7 в дополнительном коде получается так

прямой код 7 = 0111
обратный код 7 = 1000
дополнительный код 7 = 1001

Обратим внимание на то, что прямой код 1001 представляет число 9, которое отстоит от числа -7 ровно на 16, или 24. Или, что тоже самое, дополнительный код числа “дополняет” прямой код до 2n, т.е. 7+9=16
Это оказалось очень удобно для машинных вычислений - при таком представлении отрицательного числа операции сложения и вычитания можно реализовать одной схемой сложения, при этом очень легко определять переполнение результата (когда для представления получившегося числа не хватает разрядности).
Пара примеров

7-3=4
0111 прямой код 7
1101 дополнительный код 3
0100 результат сложения 4
-1+7=6
1111 дополнительный код 1
0111 прямой код 7
0110 результат сложения 6

Что касается переполнения - оно определяется по двум последним переносам, включая перенос за старший разряд. При этом если переносы 11 или 00, то переполнения не было, а если 01 или 10, то было. При этом, если переполнения не было, то выход за разряды можно игнорировать.
Примеры где показаны переносы и пятый разряд

7+1=8
00111 прямой код 7
00001 прямой код 1
01110 переносы
01000 результат 8 - переполнение

Два последних переноса 01 - переполнение

-7+7=0
00111 прямой код 7
01001 дополнительный код 7
11110 переносы
10000 результат 16 - но пятый разряд можно игнорировать, реальный результат 0

Два последних переноса 11 - перенос в пятый разряд можно отбросить, оставшийся результат, ноль, арифметически корректен. Проверять на переполнение можно простейшей операцией XOR двух бит переносов.
Благодаря таким удобным свойствам дополнительный код это самый распространенный способ представления отрицательных чисел в машинной арифметике.

Ну а обратный код дополняет число до 2n-1, или до всех 1, потому и называется дополнением до 1. Им тоже можно представлять отрицательные числа, и реализовать вычитание и сложение схемой сложения, только сложение там хитрее - с циклическим переносом, ну и представить можно меньше на одно число, так как все единицы уже заняты - это обратный код нуля, эдакий “минус нуль”, то есть диапазон получается, если брать наш пример от -7 до 7.

3.4.2 – Побитовые операторы

Lua поддерживает следующие побитовые операторы:

&: побитовое И (bitwise and)
|: побитовое ИЛИ (bitwise or)
~: побитовое исключающее ИЛИ (bitwise exclusive or)
>>: сдвиг вправо (right shift)
<<: сдвиг влево (left shift)
~: унарный побитовый НЕ (unary bitwise not)

Все побитовые операции конвертируют свои операнды до целых чисел (смотрите §3.4.3), работают на всех битах этих целых числе и в результате получают целое число.

Оба оператора сдвига, правый и левый, заполняют незанятые биты нулями. При отрицательных значениях смещения сдвиг выполняется в противополжном направлении; перемещения абсолютных значений равных или больших числа бит в целом числе приводят в результате к нулю (поскольку все биты оказываются сдвинутыми).

3.4.3 – Приведения и преобразования

Lua предоставляет некоторые автоматические преобразования между некоторыми типами и представлениями (representations) во время выполнения. Побитовые операторы всегда конвертируют операнды с числами с плавающей запятой в целые числа. Возведение в степень и деление чисел с плавающей запятой всегда конвертируют целые числа в числа с плавающей запятой. Все другие арифметические операции, применяемые к смешанным числам (целые числа и числа с плавающей запятой), преобразуют целочисленный операнд в число с плавающей запятой; это что называется обычное правило. C-ишный API также, по мере необходимости, конвертирует как целые числа в числа с плавающей запятой, так и числа с плавающей запятой в целые числа. Более того, кроме строк, в качестве аргументов, строковая конкатенация принимает числа.

Lua также конвертирует строки в числа всякий раз, когда предполагается число.

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

При преобразовании числа с плавающей запятой в целое число проверяется, не имеет ли число с плавающей запятой точного представления в виде целого числа (то есть проверяется, что число с плавающей запятой имеет целочисленное значение и оно находится в диапазоне целочисленных представлений). Если это так, то это представление и является результатом. В противном случае преобразование завершается неудачей.

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

Преобразование чисел в строки использует не указанный удобочитаемый формат. Для полного управления тем, как числа преобразуются в строки, используйте функцию format из строковой библиотеки (смотрите [string.format](../ standardlibraries.htm#string.format)).

3.4.4 – Операторы сравнения

Lua поддерживает следующие операторы сравнения:

==: равенство (equality)
~=: неравенство (inequality)
<: меньше чем (less than)
>: больше чем (greater than)
<=: меньше или равно (less or equal)
>=: больше или равно (greater or equal)

В качестве результата эти операторы всегда выдают либо false, либо true.

Равенство (==) вначале сравнивает тип своих операндов. Если типы различны, то в результате выдается false. В противном случае сравниваются значения операндов. Строки сравниваются банальным способом. Числа равны, если они означают одно и тоже математическое значение.

Таблицы, userdata и нити (потоки) сравниваются по ссылке: два объекта считаются равными, только если они являются одним и тем же объектом. Каждый раз при создании нового объекта (таблица, userdata или нить), этот новый объект отличается от любого, уже существующего объекта. Замыкания с одной и той же ссылкой всегда равны. Замыкания с любой обнаруженной разницей (разное поведение, разное определение) всегда различны.

Изменить способ, которым Lua сравнивает таблицы и userdata, можно при помощи метаметода “eq” (смотрите §2.4).

При сравнениях на равенство не производится конвертирование строк в числа и наоборот. Таким образом, “0”==0 определяется как false, а t[0] и t[“0”] обозначают разные записи в таблице.

Оператор ~= является точным отрицанием равенства (==).

Операторы порядка работают следующим образом. Если оба аргумента являются числами, то они сравниваются в соответствии с их математическими значениями (не обращая внимания на их подтипы). Иначе, если оба аргумента являются строками, то их значения сравниваются в соответствии с текущей локалью. В противном случае, Lua пытается вызвать метаметод “lt” или “le"смотрите §2.4). Сравнение a > b преобразуется в b < a, а a >= b преобразуется в b <= a.

Согласно стандарту IEEE 754, NaN считается ни меньше чем, ни равный, ни больше чем любое значение (включая самого себя).

3.4.5 – Логические операторы

Логическими операторами в Lua являются and, or, и not. Подобно управляющим структурам (смотрите §3.3.4), все логические операторы рассматривают значения false и nil как ложные, а все остальные - как истинные.

Оператор отрицания not всегда возвращает false или true. Оператор соединения (конъюкции) and возвращает своей первый аргумент, если этим значением является false или nil; в противном случае and возвращает свой второй аргумент. Оператор разъединения (дизъюнкции) or возвращает свой первый аргумент, если это значение отличается от nil и false; в противном случае or возвращает свой второй аргумент. Оба оператора, и and и or используют сокращенное вычисление; то есть, второй операнд вычисляется только при необходимости. Вот некоторые примеры:

10 or 20            --> 10
10 or error()       --> 10
nil or "a"          --> "a"
nil and 10          --> nil
false and error()   --> false
false and nil       --> false
false or nil        --> nil
10 and 20           --> 20

(В данном руководстве, --> указывает на результат предыдущего выражения.)

3.4.6 – Конкатенация

Оператор конкатенации строк в Lua обозначается двумя точками (’..’). Если оба операнда являются строками или числами, то они конвертируются в строки согласно правилам, описанным в §3.4.3. В противном случае вызывается метаметод __concat (смотрите §2.4).

3.4.7 – Оператор длины

Оператор длины обозначается одиночным оператором-приставкой #. Длиной строки является количество байт в ней (то есть, обычное значение длины строки, когда каждый из символов занимает один байт).

Программа может изменять поведение оператора длины для любого значения, а для строк через метаметод __len (смотрите §2.4).

Если не задан метаметод __len, длина таблицы t определяется только если таблица является последовательностью, то есть, набор её положительных цифровых ключей равен {1..n} для некоторого неотрицательного целого числа n. В этом случае, n является её длиной. Обратите внимание, что таблица вроде

{10, 20, nil, 40}

не является последовательностью, так как она имеет ключ 4, но не имеет ключа 3. (Так как не существует такого n, что набор {1..n} равен набору положительных цифровых ключей этой таблицы.) Отметьте впрочем, что нечисловые ключи не мешают таблице быть последовательностью.

3.4.8 – Приоритет

Приоритет операторов в Lua показан в таблице ниже, от низкого приоритета к высокому.

or
and
<     >     <=    >=    ~=    ==
|
~
&
<<    >>
..
+     -
*     /     //    %
unary operators (not   #     -     ~)
^

Как обычно, для изменения приоритета выражения можно использовать круглые скобки. Операторы конкатенации (’..’) и возведения в степень (’^’) имеют правую ассоциативность. Все остальные бинарные операторы являются левоассоциативными.

Примечание: Ассоциативность — очерёдность операций в программировании — установленная синтаксисом последовательность выполнения операций (или направление вычисления), … читать далее

реализуемая когда операции имеют одинаковый приоритет и отсутствует явное (с помощью скобок) указание на очерёдность их выполнения. Ассоциативность (от лат. associatio) — свойство операций, позволяющее восстановить последовательность их выполнения при отсутствии явных указаний на очерёдность при равном приоритете; при этом различается левая ассоциативность, при которой вычисление выражения происходит слева направо, и правая ассоциативность — справа налево. Соответствующие операторы называют левоассоциативными и правоассоциативными.

(Из Википедии)

3.4.9 – Конструкторы таблиц

Конструкторы таблиц - это выражения, которые создают таблицы. Каждый раз при вычислении конструктора, создается новая таблица. Конструктор может создать пустую таблицу или создать таблицу и инициализировать некоторые из её полей. Общий синтаксис для конструкторов следующий

tableconstructor ::= ‘{’ [fieldlist] ‘}’
fieldlist ::= field {fieldsep field} [fieldsep]
field ::= ‘[’ exp ‘]’ ‘=’ exp | Name ‘=’ exp | exp
fieldsep ::= ‘,’ | ‘;’

Каждое поле в виде [exp1] = exp2 добавляет в новую таблицу запись с ключом exp1 и значением exp2. Поле в форме name = exp эквивалентно [“name”] = exp. Наконец, поля в виде exp эквивалентны [i] = exp, где i - это последовательные целые числа, начинающиеся с 1. Поля в других форматах не влияют на этот подсчет. Например,

a = { [f(1)] = g; "x", "y"; x = 1, f(x), [30] = 23; 45 }

эквивалентно

do
  local t = {}
  t[f(1)] = g
  t[1] = "x"         -- первое exp
  t[2] = "y"         -- второе exp
  t.x = 1            -- t["x"] = 1
  t[3] = f(x)        -- третье exp
  t[30] = 23
  t[4] = 45          -- четвертое exp
  a = t
end

Порядок присваивания в конструкторе неопределенный. (Этот порядок будет важен только когда имеются повторяющиеся ключи.)

Если последнее поле в списке имеет форму exp и это выражение является вызовом функции или выражением с переменным числом аргументов (vararg выражением), то все значения, возвращенные этим выражением, последовательно вводятся в список (смотрите §3.4.10).

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

3.4.10 – Вызовы функций

Вызов функции в Lua имеет следующий синтаксис:

functioncall ::= prefixexp args

или на русском языке

вызовфункции ::= префиксныевыражения аргументы

В вызове функции, сначала вычисляются префиксные выражения (prefixexp) и аргументы (args). Если значение prefixexp имеет тип function, то эта функция вызывается с заданными аргументами. В противном случае, prefixexp вызывает метаметод “call”, имеющий в качестве первого параметра значение prefixexp, с последующим вызовом исходных аргументов (смотрите §2.4).

Форма записи

functioncall ::= prefixexp ‘**:**’ Name args

может использоваться для вызова “методов”. Вызов в виде v:name(args) это “синтаксический сахар” для v.name(v,args), за исключением того, что v вычисляется только один раз.

Аргументы имеют следующий синтаксис:

args ::= ‘(’ [explist] ‘)’
args ::= tableconstructor
args ::= LiteralString

Все выражения аргументов вычисляются перед вызовом. Вызов в виде f{fields} является “синтаксическим сахаром” для f({fields}); то есть, список аргументов - это отдельная новая таблица. Вызов в форме f’string’ (или f”string", или f[[string]]) - это “синтаксический сахар” для f(’string’); то есть, список аргументов является отдельной символьной строкой.

Вызов в виде return functioncall называется хвостовым вызовом (tail call). В Lua реализованы правильные хвостовые вызовы (или правильная хвостовая рекурсия): в хвостовом вызове, вызываемая функция повторяет запись стека вызывающей функции. Поэтому не существует ограничения на число вложенных хвостовых вызовов, которые может выполнить программа. Однако, хвостовой вызов стирает любую отладочную информацию о вызывающей функции. Обратите внимание, что хвостовой вызов происходит только при конкретном синтаксисе, где return в качестве аргумента имеет один, единственный вызов функции; такой синтаксис делает возврат вызывающей функции в точности равным возврату вызываемой функции. Так что, ни один из следующих примеров не является хвостовым вызовом:

return (f(x))        -- результаты сводятся к 1 значению
return 2 * f(x)
return x, f(x)       -- дополнительные результаты
f(x); return         -- результаты отбрасываются
return x or f(x)     -- результаты сводятся к 1 значению

3.4.11 – Определения функций

Синтаксис определения функции

functiondef ::= function funcbody
funcbody ::= ‘(’ [parlist] ‘)’ block end

Следующий “синтаксический сахар” упрощает определения функций:

stat ::= function funcname funcbody
stat ::= local function Name funcbody
funcname ::= Name {‘.’ Name} [‘:’ Name]

Заявление

function f () _body_ end

преобразуется в

f = function () _body_ end

Заявление

function t.a.b.c.f () _body_ end

преобразуется в

t.a.b.c.f = function () _body_ end

Заявление

local function f () _body_ end

преобразуется в

local f; f = function () _body_ end

а не в

local f = function () _body_ end

(Различие появляется только когда тело функции содержит ссылки на f.)

Определение функции является выполняемым выражением, чье значение имеет тип function. Когда Lua предварительно компилирует порцию (chunk), все её тела функций также предкомпилируются. Затем, каждый раз, когда Lua выполняет определение функции, функция инстанцируется (или замыкается). Этот экземпляр функции (или замыкание - closure) является конечным значением выражения.

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

parlist ::= namelist [‘,’ ‘...’] | ‘...’

При вызове функции список аргументов корректируется по длине со списком параметров, если функция не является функцией с переменным числом аргументов (vararg функция), которая обозначается тремя точками (’…’) в конце своего списка параметров. Функция с переменным числом аргументов не корректирует список своих аргументов; вместо этого, она собирает все дополнительные аргументы и передает их в функцию через выражение с переменным числом аргументов (vararg выражение), которое также обозначается как три точки. Значением этого выражения является список всех текущих дополнительных аргументов, похожий на функцию с множественным результатом. Если выражение с переменным числом аргументов используется внутри другого выражения или в середине списка выражений, то его список возврата сводится к одному элементу. Если выражение используется как последний элемент в списке выражений, то такая корректировка не производится (если это последнее выражение не заключено в круглые скобки).

В качестве примера рассмотрим следующие определения:

function f(a, b) end
function g(a, b, ...) end
function r() return 1,2,3 end

Далее получаем следующее сопоставление от аргументов к параметрам и vararg выражению:

ВЫЗОВ           ПАРАМЕТРЫ
f(3)             a=3, b=nil
f(3, 4)          a=3, b=4
f(3, 4, 5)       a=3, b=4
f(r(), 10)       a=1, b=10
f(r())           a=1, b=2
g(3)             a=3, b=nil, ... -->  (nothing)
g(3, 4)          a=3, b=4,   ... -->  (nothing)
g(3, 4, 5, 8)    a=3, b=4,   ... -->  5  8
g(5, r())        a=5, b=1,   ... -->  2  3

Результаты возвращены при помощи оператора return (смотрите §3.3.4). Если управление доходит до конца функции не встречая оператор return, то функция ничего не возвращает.

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

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

function t.a.b.c:f (_params_) _body_ end

это “синтаксический сахар” для

t.a.b.c.f = function (self, _params_) _body_ end

3.5 – Правила видимости

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

x = 10            -- глобальная переменная
do                -- начало нового блока
  local x = x     -- новый 'x', со значением 10
  print(x)        --> 10
  x = x+1
  do              -- начало другого блока
    local x = x+1 -- другой 'x'
    print(x)      --> 12
  end
  print(x)        --> 11
end
print(x)          --> 10 глобальная переменная

Обратите внимание, что в объявлении local x = x, новая переменная x будучи объявлена, ещё не находится в области видимости, и поэтому второй x относится к внешней переменной.

Из-за правил лексического разграничения, локальные переменные могут быть широко доступны функциям, определенным в их области видимости. Локальная переменная используемая внутренней функцией называется upvalue, или внешней локальной переменной, внутри вложенной функции.

Заметьте, что каждое выполнение оператора local определяет новую локальную переменную. Рассмотрим следующий пример:

a = {}
local x = 20
for i=1,10 do
  local y = 0
  a[i] = function () y=y+1; return x+y end
end

Цикл создает десять замыканий (closure) (то есть, десять экземпляров анонимной функции). Каждый из этих замыканий использует разную переменную y, хотя все они используют одну и ту же переменную x.

Примечание: Замыкание (англ. closure) в программировании — функция, в теле которой присутствуют ссылки на переменные, объявленные вне тела этой функции … читать далее вернуться

в окружающем коде и не являющиеся её параметрами. Говоря другим языком, замыкание — функция, которая ссылается на свободные переменные в своём контексте.

Замыкание, так же как и экземпляр объекта, есть способ представления функциональности и данных, связанных и упакованных вместе.

Замыкание — это особый вид функции. Она определена в теле другой функции и создаётся каждый раз во время её выполнения. Синтаксически это выглядит как функция, находящаяся целиком в теле другой функции. При этом вложенная внутренняя функция содержит ссылки на локальные переменные внешней функции. Каждый раз при выполнении внешней функции происходит создание нового экземпляра внутренней функции, с новыми ссылками на переменные внешней функции.
В случае замыкания ссылки на переменные внешней функции действительны внутри вложенной функции до тех пор, пока работает вложенная функция, даже если внешняя функция закончила работу, и переменные вышли из области видимости.
Замыкание связывает код функции с её лексическим окружением (местом, в котором она определена в коде). Лексические переменные замыкания отличаются от глобальных переменных тем, что они не занимают глобальное пространство имён. От переменных в объектах они отличаются тем, что привязаны к функциям, а не объектам.

(Из Википедии)