Таблицы по языкам программирования

перепечатка из программного обеспечения: Практика и опыт 26 #6 (1996) 635-652. Copyright © 1996 John Wiley & Sons. Ltd. [ps · doi]

by Roberto Ierusalimschy. Luiz Henrique de Figueiredo. Waldemar Celes Filho

Абстрактный.

В этой статье описывается Lua. Язык расширения приложений. Lua сочетает процедурные функции с мощными средствами описания данных. Используя простой. Но мощный механизм таблиц. Этот механизм реализует концепции записей. Массивов и рекурсивных типов данных (указателей). А также добавляет некоторые объектно-ориентированные средства. Такие как методы с динамической диспетчеризацией.

Lua представляет собой механизм резервирования это позволяет программистам расширить семантику языка некоторыми нетрадиционными способами. Примечательно. Что резервные варианты позволяют пользователю добавлять в язык различные виды наследования. В настоящее время Lua широко используется в производстве для нескольких задач. Включая настройку пользователя. Ввод данных общего назначения. Описание пользовательских интерфейсов. Хранение структурированных графических метафайлов и универсальную конфигурацию атрибутов для сеток конечных элементов.

Введение

Растет спрос на настраиваемые приложения. По мере усложнения приложений настройка с помощью простых параметров стала невозможной: пользователи теперь хотят принимать решения о конфигурации во время выполнения; пользователи также хотят писать макросы и скрипты для повышения производительности [1,2,3,4]. В ответ на эти потребности в настоящее время существует важная тенденция к разделению сложных систем на две части: ядро и конфигурацию Ядро реализует основные классы и объекты системы и обычно пишется на компилируемом. Статически типизированном языке. Таком как C или Modula-2. Часть конфигурации. Обычно написанная на интерпретируемом гибком языке. Соединяет эти классы и объекты. Чтобы придать конечную форму приложению [

5].

Конфигурация языки бывают нескольких вкусов. Начиная от простых языков для выбора предпочтения. Как правило. Реализуются в качестве параметра перечислены в командной строке или в качестве переменной-значение читается из файлов конфигурации (например. MS-Windows-а .Ини файлы, файлы ресурсов для X11). Для встраиваемых языков, для расширения приложения с пользовательских функций на основе примитивов. Предоставляемых приложений. Встроенные языки могут быть довольно мощными. Иногда являясь упрощенными вариантами основных языков программирования. Таких как Lisp и C. Такие языки конфигурации также называются

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

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

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

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

  • языки расширения нуждаются в хороших средствах описания данных. Поскольку они часто используются в качестве языков конфигурации;

  • языки расширения должны иметь четкий и простой синтаксис. Поскольку их основными пользователями не являются профессиональные программисты;
  • языки расширения должны быть небольшими и иметь небольшую реализацию. В противном случае стоимость добавления библиотеки в приложение может оказаться слишком высокой;
  • языки расширения не предназначены для написания больших программ с сотнями тысяч строк. Поэтому механизмы поддержки программирования в большом. Такие как статическая проверка типов. Сокрытие информации и обработка исключений. Не являются существенными;

  • наконец, языки расширения также должны быть расширяемыми. В отличие от обычных языков. Языки расширений используются на очень высоком уровне абстракции. Достаточном для взаимодействия с пользователями в довольно разнообразных областях.

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

6], другой для визуализации литологических профилей. Полученных с помощью геологических зондов. Когда пользователи стали требовать все больше возможностей в этих языках. Стало ясно. Что необходимы реальные средства программирования. Вместо того чтобы параллельно модернизировать и поддерживать два разных языка. Было принято решение разработать единый язык. Который можно было бы использовать не только для этих двух приложений. Но и для любого другого. Поэтому Lua включает в себя средства. Общие для большинства процедурных языков программирования — структуры управления (

в то времякак s, еслиs и т. Д.), Назначения. Подпрограммы и операторы инфикса — но абстрагирует объекты. Специфичные для любой конкретной области. Таким образом. Lua можно использовать не только как полный язык. Но и как языковую структуру.

Lua удовлетворяет перечисленным выше требованиям достаточно хорошо. Его синтаксис и структуры управления довольно просты. Как паскаль. Lua невелика; вся библиотека составляет около шести тысяч строк ANSI C. Из которых почти две тысячи генерируются yacc. Наконец, Lua является расширяемым. В его дизайне добавление многих различных функций было заменено созданием нескольких мета-механизмов. Которые позволяют программистам реализовать эти функции самостоятельно.

Эти мета-механизмы: динамические ассоциативные массивы, рефлексивные средстваи резервныеварианты .

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

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

Наконец, хотя Lua имеет фиксированный синтаксис. Резервные варианты могут расширить значение многих синтаксических конструкций. Например, резервные варианты могут быть использованы для реализации различных видов наследования, функции. Отсутствующей в Lua.

Обзор Lua

Этот раздел содержит краткое описание основных понятий в Lua. Некоторые примеры фактического кода включены. Чтобы дать вкус языка. Полное определение языка можно найти в его справочном руководстве [7].

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

Будучи встроенным языком. Lua не имеет понятия о “главной” программе; он работает только встраиваемый в хост-клиент. Lua предоставляется в виде библиотеки функций C. Связанных с хост-приложениями. Хост может вызывать функции в библиотеке для выполнения части кода в Lua. Записи и чтения переменных Lua. А также регистрации функций C. Вызываемых кодом Lua. Более того, откаты может быть указано. Что он вызывается всякий раз. Когда Lua не знает. Как действовать дальше. Таким образом. Lua может быть расширен. Чтобы справиться с довольно различными областями. Создавая таким образом индивидуальные языки программирования. Разделяющие единую синтаксическую структуру [

8]. Именно в этом смысле Lua — это языковая структура. С другой стороны. Очень легко написать интерактивный автономный интерпретатор для Lua (рис. 1).

#включить заголовочный файл #include int main (int argc. Char *argv[]) { char line[BUFSIZ]; iolib_open(); /* открывает библиотеку ввода-вывода (необязательно) */ strlib_open(); /* открывает строку lib (необязательно) */ mathlib_open(); /* открывает math lib (необязательно) */ 

while (gets(line) != 0)

Рисунок 1: Интерактивный интерпретатор для Lua.

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

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

На рис. 2 показан пример использования Lua в качестве очень простого языка конфигурации. Этот код определяет три глобальные переменные и присваивает им значения. Lua-это динамически типизированный язык: переменные не имеют типов. А имеют только значения. Все ценности имеют свой собственный тип. Поэтому в Lua нет определений типов.

 ширина = 420 высота = ширина*3/2 -- обеспечивает соотношение сторон 3/2 цвет = 

Рисунок 2: Очень простой конфигурационный файл.

Более мощные конфигурации могут быть написаны с использованием управления потоком и определений функций. Lua использует традиционный синтаксис. Подобный Pascal. С зарезервированными словами и явно завершенными блоками; точки с запятой необязательны. Такой синтаксис знаком. Надежен и легко поддается синтаксическому анализу. Небольшой пример представлен на рис. 3.Обратите внимание. Что функции могут возвращать несколько значений. И для сбора этих значений можно использовать несколько назначений. Таким образом. Передача параметров по ссылке. Всегда являющаяся источником небольших семантических трудностей. Может быть исключена из языка.

ширина, высота = Граница(420, 500) если монохромный. То color = 

Рис. 3: Конфигурационный файл с использованием функций.

Функции в Lua являются значениями первого класса. Определение функции создает значение типа functionи присваивает это значение глобальной переменной (Boundна рис. 3). Как и любое другое значение. Значения функций могут храниться в переменных. Передаваться в качестве аргументов другим функциям и возвращаться в виде результатов. Эта функция значительно упрощает реализацию объектно-ориентированных объектов. Как описано далее в этом разделе.

Помимо основных типов number(floats) и string, а также типа function, Lua предоставляет три других типа данных: nil, userdata, и table. Всякий раз. Когда требуется явная проверка типа, typeможет использоваться примитивная функция; она возвращает строку. Описывающую тип ее аргумента.

Тип nilимеет одно значение. Также называемое nil, основное свойство которого должно отличаться от любого другого значения. Перед первым назначением значение переменной равно nil. Поэтому неинициализированные переменные. Основной источник ошибок программирования. Не существуют в Lua. Использование nilв контексте. Где требуется фактическое значение (например. В арифметическом выражении). Приводит к ошибке выполнения. Предупреждая программиста о том. Что переменная не была правильно инициализирована.

Этот тип userdataпредоставляется для того. Чтобы произвольные данные хоста. Представленные в void*виде указателей C. Хранились в переменных Lua. Единственными допустимыми операциями со значениями этого типа являются присваивание и тест на равенство.

Наконец, тип tableреализует ассоциативные массивы. То есть массивы. Которые могут индексироваться не только целыми числами. Но и строками, реалами. Таблицами и значениями функций.

Ассоциативные массивы

Ассоциативные массивы-мощная языковая конструкция; многие алгоритмы упрощены до тривиальности. Поскольку требуемые структуры данных и алгоритмы их поиска неявно предоставляются языком [9]. Большинство типичных контейнеров данных. Таких как обычные массивы, наборы. Пакеты и таблицы символов. Могут быть непосредственно реализованы таблицами. Таблицы также могут имитировать записи. Просто используя имена полей в качестве индексов. Lua поддерживает это представление. Предоставляя a.nameв качестве синтаксического сахара for a["name"].

В отличие от других языков. Реализующих ассоциативные массивы. Таких как AWK [10], Tcl [11] и Perl [12однако таблицы в Lua не привязаны к имени переменной; вместо этого они представляют собой динамически создаваемые объекты. Которыми можно манипулировать так же. Как указателями в обычных языках. Недостатком этого выбора является то. Что таблица должна быть явно создана перед использованием. Преимущество состоит в том. Что таблицы могут свободно ссылаться на другие таблицы и, следовательно, обладают выразительной способностью моделировать рекурсивные типы данных и создавать общие графовые структуры, возможно, с циклами. В качестве примера на рис.4 показано. Как создавать циклические связанные списки в Lua.

i = i+1 конечный ток.значение = i ток.далее = список 

Рисунок 4: Круговой связанный список в Lua.

Lua предоставляет ряд интересных способов создания таблицы. Простейшей формой является выражение {}, которое возвращает новую пустую таблицу. Более описательный способ. Который создает таблицу и инициализирует некоторые поля. Показан ниже; синтаксис несколько вдохновлен форматом базы данных BibTeX [13] :


Эта команда создает таблицу. Инициализирует ее поля xyи foregroundприсваивает ее переменной window1. Обратите внимание. Что таблицы не обязательно должны быть однородными; они могут одновременно хранить значения всех типов.

Подобный синтаксис можно использовать для создания списков:


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

цвета[4] = 

Иногда нужны более мощные строительные сооружения. Вместо того. Чтобы пытаться предоставить все. Lua предоставляет простой механизм конструктора. Конструкторы пишутся name{...}, что является просто синтаксическим сахаром для name({...}) Таким образом. С помощью конструктора создается таблица. Инициализируется и передается в качестве параметра функции. Эта функция может выполнять любую необходимую инициализацию. Такую как (динамическая) проверка типов. Инициализация отсутствующих полей и обновление вспомогательных структур данных. Даже в основной программе. Как правило. Функция конструктора заранее определена в C или Lua. И часто пользователи конфигурации не знают. Что конструктор является функцией; они просто пишут что-то вроде:


и подумайте об “окнах” и других абстракциях высокого уровня. Таким образом. Хотя Lua динамически типизируется. Он предоставляет управляемые пользователем конструкторы типов.

Поскольку конструкторы являются выражениями. Они могут быть вложены для описания более сложных структур в декларативном стиле. Как в приведенном ниже коде:

 d = диалог{ hbox{ 

Рефлексивные средства

Еще одним мощным механизмом Lua является его способность пересекать таблицы. Используя встроенную функцию next. Эта функция принимает два аргумента: таблицу для обхода и индекс этой таблицы. Когда индекс есть nil, функция возвращает первый индекс данной таблицы и значение . Связанное с этим индексом; когда индекс нетnil, функция возвращает следующий индекс и его значение. Индексы извлекаются в произвольном порядке, а nil индекс возвращается. Чтобы сигнализировать об окончании обхода. В качестве примера использования средств обхода Lua на рис. 5 показана процедура клонирования объектов. Локальная переменная iработает над индексами объекта o, одновременно vполучая их значения. Эти значения. Связанные с соответствующими индексами. Хранятся в локальной таблице new_o.


Рисунок 5: Функция клонирования универсального объекта.

Таким же образом nextпересекает таблицу. Связанную функциюnextvar, пересекает глобальные переменные Lua. На рис. 6 представлена функция. Которая сохраняет глобальную среду Lua в виде таблицы. Как и в функции clone, локальная переменная nработает над именами всех глобальных переменных. В то время vкак получает их значения. Которые хранятся в локальной таблице env. При выходе функция saveвозвращает эту таблицу. Которая впоследствии может быть передана функции restoreдля восстановления среды (рис. 7). Эта функция имеет две фазы. Во-первых, стирается вся текущая среда. Включая предопределенные функции. Затем локальные переменные nи v проведите по индексам и значениям данной таблицы. Сохранив эти значения в соответствующих глобальных переменных. Хитрость заключается в том. Что вызываемые функции restoreдолжны храниться в локальных переменных. Поскольку все глобальные имена стираются.

 функция сохранения () 

Рисунок 6: Функция сохранения среды Lua.

 восстановление функции (env) -- сохраните некоторые встроенные функции перед стиранием глобальной среды local nextvar, next. Setglobal = nextvar, next. Setglobal -- стереть все глобальные переменные local n. V = nextvar(nil) а н у setglobal(п. Отсутствует) н в = nextvar(Н) конец -- восстановить старые значения н в = следующий(ОКР. Нил) - получить первый индекс; в = ОКР[Н] а н у setglobal(н. В) - установить глобальную переменную с именем N н в = следующий(ЕНВ. П) конец конец 

Рис. 7: Функция восстановления среды Lua.

Хотя это интересный пример. Манипуляция глобальной средой в Lua вряд ли нужна. Поскольку таблицы. Используемые в качестве объектов. Обеспечивают лучший способ поддерживать несколько сред.

Поддержка объектно-ориентированного программирования

Поскольку функции являются значениями первого класса. Поля таблицы могут ссылаться на функции. Это свойство позволяет реализовать некоторые интересные объектно-ориентированные объекты. Которые упрощаются синтаксическим сахаром для определения и вызова методов.

Во-первых, определения методов могут быть записаны в виде

 объект функции:метод (параметры) ... конец 

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

 функция dummy_name (self. Params) ... end object.method = dummy_name 

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

Во-вторых, вызов метода может быть записан в виде

 приемник:метод(параметры) 

что в переводе означает

 receiver.method(приемник,параметры) 

Другими словами. Получатель метода передается в качестве его первого аргумента. Придавая параметру ожидаемое значение self.

Стоит отметить некоторые особенности приведенной конструкции. Во-первых, он не обеспечивает сокрытие информации. Таким образом. Пуристы могут (правильно) утверждать. Что важная часть объектной ориентации отсутствует. Во-вторых, он не предоставляет классов; каждый объект выполняет свои операции. Тем не менее. Эта конструкция чрезвычайно легка (только синтаксический сахар). И классы могут быть смоделированы с использованием наследования. Как это обычно происходит в других языках. Основанных на прототипах. Таких как Self [14]. Однако, прежде чем обсуждать наследование. Необходимо обсудить резервные варианты.

Резервные варианты

Будучи нетипизированным языком. Lua имеет семантику со многими ненормальными условиями выполнения. Примерами могут служить арифметические операции. Применяемые к нечисловым операндам. Попытка индексировать значение. Не относящееся к таблице. Или попытка вызвать значение. Не относящееся к функции. Поскольку остановка в таких ситуациях была бы неподходящей для встроенного языка. Lua позволяет программистам устанавливать свои собственные функции для обработки условий ошибок; такие функции называются резервными функциями. Резервные копии также используются для предоставления крючков для обработки других ситуаций. Которые не являются строго условиями ошибки. Таких как доступ к отсутствующему полю в таблице и сигнализация сборки мусора.

Чтобы задать резервную функцию. Программист вызывает setfallbackее с двумя аргументами: строкой. Идентифицирующей резервную функцию. И новой функцией. Вызываемой всякий раз. Когда возникает соответствующее условие. Функция setfallbackвозвращает старую резервную функцию. Поэтому программы могут связывать резервные копии для различных типов объектов.

Lua поддерживает следующие резервные варианты. Идентифицируемые заданными строками:

Эти резервные варианты вызываются. Когда операция применяется к недопустимым операндам. Они получают три аргумента: два операнда и строку. Описывающую обиженный оператор ("add", "sub", …). Их возвращаемое значение является конечным результатом операции. Функции по умолчанию для этих резервных вариантов выдают ошибку.
Этот резерв вызывается. Когда Lua пытается получить значение индекса. Отсутствующего в таблице. Он получает в качестве аргументов таблицу и индекс. Его возвращаемое значение является конечным результатом операции индексации. Возвращается функция по умолчанию nil.
Вызывается. Когда Lua пытается прочитать или записать значение индекса в не табличное значение. Функции по умолчанию выдают ошибку.
Вызывается. Когда Lua пытается вызвать значение не функции. Он получает в качестве аргументов значение не функции и аргументы. Приведенные в исходном вызове. Его возвращаемые значения являются конечными результатами операции вызова. Функция по умолчанию выдает ошибку.
Вызывается во время сбора мусора. Он получает в качестве аргумента собираемую таблицу и nilсигнализирует о завершении сборки мусора. Функция по умолчанию ничего не делает.

Прежде чем продолжить. Важно заметить. Что резервные варианты обычно не устанавливаются обычными программистами Lua. Резервные варианты используются в основном опытными программистами при привязке Lua к конкретному приложению. После этого объект используется как неотъемлемая часть языка. Как типичный пример. Большинство реальных приложений используют резервные варианты реализации наследования. Как описано ниже. Но большинство программистов Lua используют наследование. Даже не зная (или не заботясь) о том. Как оно реализовано.

Использование резервных возможностей

На рис. 8 показан пример. В котором используются резервные варианты для более объектно-ориентированного стиля интерпретации двоичных операторов. Когда этот резерв установлен. Выражения типа a+b, где aнаходится таблица. Выполняются как a:add(b). Обратите внимание на использование глобальной переменной oldFallbackдля цепочки резервных функций.

 функция отправки (приемник, параметр. Оператор) если тип(приемник) == else return oldFallback(приемник, параметр. Оператор) конец конец oldFallback = setfallback(

Рисунок 8: Пример резервов.

Еще одним необычным средством. Предоставляемым резервными вариантами. Является повторное использование парсера Lua. Многие приложения выиграли бы от парсера арифметических выражений. Но не включают его. Потому что не все имеют необходимый опыт или склонность писать парсер с нуля или использовать генератор парсеров. Такой как yacc. На рис. 9 показана полная реализация синтаксического анализатора выражений с использованием резервных версий. Эта программа считывает арифметическое выражение на переменных a, …, z, и выводит ряд примитивных операций, необходимых для оценки выражения, используя переменныеt1, t2, … как временные переменные. Например, код. Сгенерированный для выражения

 (a*a+b*b)*(a*a-b*b)/(a*a+b*b+c)+(a*(b*b)*c) 

является

 t1=mul(a,a) t2=mul(b,b) t3=add(t1,t2) t4=sub(t1,t2) t5=mul(t3,t4) t6=add(t3,c) t7=div(t5,t6) t8=mul(a,t2) t9=mul(t8,c) t10=добавить(t7,t9) 

Основной частью этой программы является функция arithfb, которая задается как резерв для арифметических операций. Функция createиспользуется для инициализации переменных a, …, zс помощью таблиц. Каждая из которых содержит полеname, содержащее имя переменной. После этой инициализации цикл считывает строки. Содержащие арифметические выражения. Строит присваивание переменной Eи передает его интерпретатору Lua. Вызывая dostring. Каждый раз . Когда интерпретатор пытается выполнить подобный кодa*a, он вызывает "arith"резервный вариант. Так как значение a это таблица. А не число. Резервный вариант создает временную переменную для хранения символического представления результата каждой примитивной арифметической операции.

Несмотря на небольшой размер. Этот код фактически выполняет глобальную идентификацию общих подвыражений и генерирует оптимизированный код. Обратите внимание в приведенном выше примере. Как a*a+b*bи a*a-b*bоба оцениваются на основе одной оценки a*aи b*b. Заметьте также. Что a*a+b*bэто вычисляется только один раз. Оптимизация кода выполняется просто путем кэширования ранее вычисленных величин в таблице T, индексированной текстовым представлением примитивных операций. Значения которых являются временными переменными. Содержащими результаты. Например, значение T["mul(a,a)"]is t1.

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

В реальном приложении переменныеa,…, zбудут представлять прикладные объекты. Такие как комплексные числа. Матрицы или даже изображения, а "arith"резервный вызов вызовет прикладные функции для выполнения фактических вычислений над этими объектами. Таким образом. Основное назначение синтаксического анализатора Lua состоит в том. Чтобы позволить программистам использовать знакомые арифметические выражения для представления сложных вычислений над объектами приложения.

 n=0 -- счетчик временных переменных функция arithfb(a,b,op) local i=op .. T[i]=create(setfallback(функция create(v) -- создать символьную переменную setglobal(v,t) возврат t конец create(в то время как 1 do -- read expressions local s=чтение() if (s==nil) then exit() end dostring(конец 

Рисунок 9: Оптимизирующий компилятор арифметических выражений в Lua.

Наследование через резервные копии

Конечно, одно из самых интересных применений резервных возможностей заключается в реализации наследования в Lua. Простое наследование позволяет объекту искать значение отсутствующего поля в другом объекте. Называемом его родительским; в частности. Это поле может быть методом. Этот механизм является своего рода наследованием объектов. В отличие от более традиционного наследования классов. Принятого в Smalltalk и C++. Один из способов реализации простого наследования в Lua состоит в том. Чтобы сохранить родительский объект в выделенном поле. Вызываемомparent, например. И установить резервную функцию индекса. Как показано на рис. 10. Этот код определяет функцию Inherit и устанавливает его как "index"запасной вариант. Всякий раз. Когда Lua пытается получить доступ к полю. Которое отсутствует в объекте. Резервный механизм вызывает функцию Inherit. Эта функция сначала проверяет. Имеет ли объект полеparent, содержащее табличное значение. Если это так. Он пытается получить доступ к нужному полю в этом родительском объекте. Если это поле отсутствует в родителе. Резервный вариант автоматически вызывается снова; этот процесс повторяется “вверх” до тех пор. Пока не будет найдено значение для поля или не закончится родительская цепочка.

 функция наследования (объект. Поле) если field == иначе возврат nil end end setfallback(

Рисунок 10: Реализация простого наследования в Lua.

Приведенная выше схема допускает бесконечные вариации. Например, могут быть унаследованы только методы или только поля. Начинающиеся с символа подчеркивания. Многие формы множественного наследования также могут быть реализованы. Среди них часто используется форма двойного наследования. В этой модели всякий раз. Когда поле не найдено в родительской иерархии. Поиск продолжается через альтернативного родителя. Обычно вызываемого "godparent". В большинстве случаев достаточно одного дополнительного родителя. Более того, двойное наследование может моделировать универсальное множественное наследование. В приведенном ниже коде, например, aнаследуется от a1, a2, и a3, в таком порядке:


Использование Lua в реальных приложениях

TeCGraf-это научно-исследовательская лаборатория Папского католического университета в Рио-де-Жанейро (PUC-Rio) со многими промышленными партнерами. За последние два года около сорока программистов из TeCGraf использовали Lua для разработки нескольких важных продуктов. В этом разделе описаны некоторые из этих применений.

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

Как упоминалось во введении. Lua первоначально возникла для поддержки двух различных приложений. Которые имели свои собственные. Но ограниченные. Языки расширения. Одним из таких приложений является инструмент для визуализации профилей литологии. Полученных с помощью геологических зондов. Его основная характеристика заключается в том. Чтобы позволить пользователю настроить макет профиля. Объединяя экземпляры объектов и указывая данные для отображения. Программа поддерживает несколько видов объектов. Таких как непрерывные кривые. Гистограммы. Представление литологии. Масштабы и т. Д.

Чтобы построить макет. Пользователи могут написать код Lua. Описывающий эти объекты (рис. 11). Само приложение также имеет Lua-код. Который позволяет создавать такие описания с помощью графического интерфейса пользователя. Этот объект был построен на основе системы EDG. Описанной ниже.


Рисунок 11: Описание объекта литологического профиля в Lua.

Хранение структурированных графических метафайлов

Еще одно важное применение Lua-хранение структурированных графических метафайлов. Универсальный редактор чертежей TeCDraw. Разработанный компанией TeCGraf. Сохраняет метафайлы. Содержащие высокоуровневые описания графических объектов. Составляющих чертеж. На языке Lua. Рисунок 12 иллюстрирует эти описания.

 линия{ текст{ x = 0,8, круг{ 

Рис. 12: Выдержка из структурированного графического метафайла.

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

  • Как прямое следствие. Интерпретатор Lua может использоваться для загрузки и анализа метафайла; редактор предоставляет только функции для удержания объектов Lua и преобразования их в соответствующие объекты приложения.
  • Приложения могут совместно использовать графические объекты. Используя один и тот же формат метафайла. Кроме того, графические объекты. Генерируемые в таких приложениях. Могут быть отредактированы с помощью TeCDraw.
  • Структурированное описание с синтаксисом Lua делает метафайл доступным для редактирования человеком: объект легко идентифицировать и модифицировать с помощью обычных текстовых редакторов.
  • Поскольку каждый объект легко идентифицируется. Им можно управлять индивидуально. Эта функция используется в системе EDG для реализации поддержки активных графических объектов.
  • Графический метафайл в Lua позволяет создавать экземпляры процедурных объектов. Например, можно описать кривые с помощью математических выражений.

Общий графический ввод данных высокого уровня

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

Система EDG использует резервную функцию Lua для реализации двойного наследования. Как описано выше. Таким образом. Новый интерфейс и графические объекты могут быть построены. Наследуя исходное поведение объекта. Еще одно интересное использование наследования. Присутствующее в EDG,-это кросс-языковое наследование. EDG построен на базе портативного инструментария пользовательского интерфейса IUP [15]. Чтобы избежать дублирования в данных Lua IUP. Находящихся на хосте. EDG использует резервные"gettable" "settable" для доступа к полям инструментария непосредственно из Lua. Таким образом. К данным хоста можно обращаться напрямую. Используя интуитивно понятный синтаксис записи. Без создания функции доступа для каждого экспортированного элемента данных в хосте.

Система EDG была использована при разработке нескольких программ ввода данных. Во многих инженерных системах полный анализ делится на три этапа: ввод данных. Называемый предварительной обработкой; сам анализ. Называемый обработкой или моделированием; и отчет о результатах и проверка. Называемые постобработкой. Задачу ввода данных можно упростить. Нарисовав графическое представление данных. Которые должны быть указаны в качестве входных данных для анализа. Для таких приложений система EDG чрезвычайно полезна и обеспечивает быстрый инструмент разработки для индивидуального ввода данных. Эти графические средства ввода данных дали новую жизнь устаревшему коду программ пакетного моделирования.

Общая конфигурация атрибутов для сеток конечных элементов

Еще одна инженерная область. В которой используется Lua. — это генерация сеток конечных элементов. Сетка конечных элементов состоит из узлов и элементов. Которые разлагают область анализа. Для завершения модели физические свойства (атрибуты) должны быть связаны с узлами и элементами. Такими как тип материала. Условия поддержки и случаи загрузки. Набор атрибутов. Которые должны быть указаны. Широко варьируется в зависимости от выполняемого анализа. Таким образом. Для реализации универсальных генераторов сетки конечных элементов рекомендуется. Чтобы атрибуты оставались настраиваемыми пользователем. И не жестко закодировано в программе.

ESAM [16] является универсальной системой. Которая использует Lua для обеспечения поддержки конфигурации атрибутов. Как и EDG, ESAM использует объектно-ориентированный подход: пользователи создают определенные свойства. Производные от предопределенных основных классов. На рис. 13 показан пример создания нового вида материала. Называемого “Изотропным”.

функция ISO_MAT:CrtDlg () ... -- создает диалоговое окно для указания конца этого материала. 

Рис. 13: Создание нового материала в ESAM.

Сопутствующая работа

В этом разделе рассматриваются некоторые другие языки расширений и сравниваются они с Lua. Нет намерения быть всеобъемлющим; вместо этого были выбраны некоторые представители современных тенденций в языках расширений: Scheme, Tcl и Python. Полный список встроенных языков доступен в Интернете [17]. В этом разделе также сравнивается резервный механизм с некоторыми другими языковыми механизмами.

Диалекты Lisp. Особенно Scheme. Всегда были популярным выбором для языков расширения. Благодаря их простому. Легко анализируемому синтаксису и встроенной расширяемости [8,18,19]. Например. Большая часть текстового редактора Emacs фактически написана на собственном варианте Lisp; несколько других текстовых редакторов пошли по тому же пути. В настоящее время существует множество реализаций схемы в виде библиотек. Специально предназначенных для использования в качестве встроенного языка (например. Libscheme [18], OScheme [20] и Elk [3Однако Lisp нельзя назвать удобным для пользователя. Когда дело доходит до настройки. Его синтаксис довольно груб для непрограммистов. Более того, немногие реализации Lisp или Scheme действительно переносимы.

Еще одним очень популярным языком расширения в настоящее время является Tcl [11]. Несомненно, одной из причин его успеха является наличие Tk. Мощного инструментария Tcl для построения графических пользовательских интерфейсов. Tcl имеет очень примитивный синтаксис. Что значительно упрощает его интерпретатор. Но и усложняет написание даже слегка сложных конструкций. Например, код Tcl для удвоения значения переменной Aтаков set A [expr $A*2]. Tcl поддерживает один примитивный тип string. Этот факт, добавленный к отсутствию предварительной компиляции. Делает Tcl довольно неэффективным даже для языка расширения. Исправление этих проблем может повысить эффективность Tcl в 5-10 раз. Как показано в TC [21Lua, с более адекватными типами данных и предварительной компиляцией. Работает в 10-20 раз быстрее, чем Tcl. Простой тест показывает, что вызов процедуры без аргументов в Tcl 7.3, работающей в Sparcstation 1, стоит около 44 мкс. В то время как приращение глобальной переменной занимает 76 мкс. В Lua v. 2.1 те же операции стоят 6 мкс и 4 мкс соответственно. С другой стороны. Lua примерно в 20 раз медленнее C. Это. По-видимому. Типичное значение для интерпретируемых языков [22].

Tcl не имеет встроенных управляющих структур. Таких как whiles и ifs. Вместо этого управляющие структуры программируются с помощью отложенной оценки. Как в Smalltalk. Несмотря на свою мощь и элегантность. Программируемые структуры управления могут привести к очень загадочным программам и редко используются на практике. Более того, они часто приводят к высокому штрафу за производительность.

Python [23] — интересный новый язык. Который также был предложен в качестве языка расширения. Однако, по словам его собственного автора. По-прежнему существует необходимость в “улучшенной поддержке встраивания Python в другие приложения, например. Путем переименования большинства глобальных символов. Чтобы иметь префикс `Py’” [24]. Python не является крошечным языком и имеет много функций. Не необходимых в языках расширений. Таких как модули и обработка исключений. Эти функции добавляют дополнительную стоимость приложениям. Использующим язык.

Lua был разработан. Чтобы объединить лучшее из существующих языков. Чтобы выполнить свою цель в качестве расширяемого языка расширения. Как и Tcl, Lua-это небольшая библиотека с простым интерфейсом к C; этот интерфейс представляет собой один заголовочный файл со 100 строками. Однако, в отличие от Tcl. Lua предварительно компилируется в стандартную промежуточную форму байт-кода. Как и Python. Lua имеет чистый. Но знакомый синтаксис и встроенное понятие объектов. Как и Lisp, Lua имеет единый механизм структуры данных (таблицы). Достаточно мощный. Чтобы эффективно реализуйте большинство структур данных. Таблицы реализуются с помощью хэширования. Коллизии обрабатываются линейным зондированием с автоматическим перераспределением и повторным разбором. Когда таблица становится более чем на 70% полной. Хэш-значения кэшируются для повышения производительности доступа.

Резервный механизм. Представленный в Lua. Можно рассматривать как своего рода механизм обработки исключений с возобновлением [25]. Однако динамическая природа Lua позволяет использовать его во многих случаях. Когда статически типизированный язык выдаст ошибку во время компиляции; оба примера. Представленные выше. Такого рода. Три конкретных резервных "arith"варианта, "order"и "concat", в основном используются для реализации перегрузки. В частности. Пример на рис. 9 может быть легко переведен на другие языки с перегрузкой. Такие как Ada или C++. Однако из-за своей динамической природы резервные варианты являются более гибкими. Чем механизмы обработки исключений или перегрузки. С другой стороны. Некоторые авторы [26] утверждают. Что программы. Использующие эти механизмы. Как правило. Трудно проверить. Понять и отладить; эти трудности усугубляются при использовании резервных вариантов. Резервные копии должны быть написаны с осторожностью и умеренностью. И только опытными программистами.

Вывод

Растущий спрос на конфигурацию приложений приводит к изменению структуры программ. В настоящее время многие программы написаны на двух разных языках: один для написания мощной “виртуальной машины”. А другой для написания отдельных программ для этой машины. Lua-это язык. Разработанный специально для выполнения последней задачи. Она невелика: как уже отмечалось. Вся библиотека-это около шести тысяч строк ANSI C. Он портативен: Lua используется на самых разных платформах-от PC-DOS до CRAY. У него простой синтаксис и простая семантика. И она гибкая.

Такая гибкость была достигнута благодаря некоторым необычным механизмам. Которые делают язык очень расширяемым. Среди этих механизмов мы выделяем следующие::

Ассоциативные массивы-это сильный объединяющий конструктор данных. Кроме того, он позволяет более эффективные алгоритмы. Чем другие объединяющие конструкторы. Такие как строки или списки. В отличие от других языков . Реализующих ассоциативные массивы [10,11,12], таблицы в Lua являются динамически создаваемыми объектами с идентификатором. Это значительно упрощает использование таблиц в качестве объектов и добавление объектно-ориентированных объектов.

Резервные варианты позволяют программистам расширить смысл большинства встроенных операций. В частности. С помощью резервных вариантов для операций индексирования в язык могут быть добавлены различные виды наследования. В то время как резервные варианты для "arith"и другие операторы могут реализовывать динамическую перегрузку.

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

В дополнение к использованию Lua в различных областях промышленности. В настоящее время мы экспериментируем с lua в ряде исследовательских проектов. Начиная от вычисления распределенные объекты. Которые посылают друг другу сообщения. Содержащий код на языке Lua [27] (идея. Предложенная ранее в TCL [4]), в прозрачном режиме расширение ВСП браузере на стороне клиента код на Lua. Поскольку все функции. Которые взаимодействуют Lua с операционной системой. Предоставляются во внешних библиотеках. Легко ограничить мощность интерпретатора. Чтобы обеспечить адекватную безопасность.

Мы также планируем улучшить средства отладки Lua; в настоящее время доступна только простая трассировка стека. Следуя философии предоставления мощных метамеханизмов. Позволяющих программистам создавать собственные расширения. Мы планируем добавить простые крючки в систему времени выполнения. Чтобы позволить пользовательским программам получать информацию о важных событиях. Таких как вход или выход из функции. Выполнение строки пользовательского кода и т. Д. Поверх этих базовых крючков могут быть построены различные отладочные интерфейсы. Кроме того, крючки также полезны для создания других инструментов, например. Профилировщики для анализа производительности.

Реализация Lua. Описанная в этой статье. Доступна в Интернете по адресу:

 http://www.lua.org/ftp/lua-2.1.tar.gz 

Признание

Мы хотели бы поблагодарить сотрудников ICAD и TeCGraf за использование и тестирование Lua. А также Джона Ролла за ценные предложения по почте относительно резервных версий предыдущей версии Lua. Промышленные приложения. Упомянутые в тексте. Разрабатываются в партнерстве с исследовательскими центрами PETROBRAS (Бразильская нефтяная компания) и ELETROBRAS (Бразильская электроэнергетическая компания). Авторы частично поддерживаются грантами на исследования и разработки бразильского правительства (CNPq и CAPES). Луа по-португальски означает

Рекомендации

[1] Б. Райан, Байт, 15(8), 235-240 (1990).

[2] N. Frankks, Dr. Dobb’s Journal, 16(9), 34-43 (1991).

[3] О. Лауман и К. Борман. Elk: The extension language kit. ftp://ftp.cs.indiana.edu:/pub/scheme-repository/imp/elk-2.2.tar.gz, Technische Universität Berlin, Germany.

[4] J. Ousterhout, Proc. of the Winter 1990 USENIX Conference. Ассоциация USENIX, 1990.

[5] D. Cowan, R. Ierusalimschy и T. Stepien, 12th World Computer Congress. IFIP, Sep 1992, pp. 54-60 Vol. A-14.

[6] L. H. Figueiredo, C. S. Souza, M. Gattass, and L. C. Coelho, V SIBGRAPI, 1992, pp. 169–175.

[7] R. Ierusalimschy, L. H. Figueiredo и W. Celes, Monografias em Ciência da Computação 08/95, PUC-Rio, Рио-де-Жанейро, Бразилия, 1995. (доступно по ftp по адресу ftp.inf.puc-rio.br/pub/docs/techreports).

[8] B. Beckman, Software, Practice & Experience, 21, 187-207 (1991).

[9] J. Bentley, More programming pearls, Addison-Wesley, 1988.

[10] A. V. Aho, B. W. Kerninghan и P. J. Weinberger, The AWK programming language, Addison-Wesley, 1988.

[11] J. K. Ousterhout, Tcl and the Tk Toolkit, Addison-Wesley, 1994.

[12] L. Wall and R. L. Schwartz, Programming perl, O’Reilly & Associates. Inc., 1991.

[13] L. Lamport, LaTeX: A Document Preparation System, Addison-Wesley, 1986.

[14] D. Ungar et al., Sigplan Notices, 22(12), 227-242 (1987) (ОПСЛА’87).

[15] C. H. Levy, L. H. de Figueiredo. C. J. Lucena и D. D. Cowan. Software: Practice & Experience 26 #7 (1996) 737-762.

[16] M. T. de Carvalho and L. F. Martha, PANEL95 — XXI Conferência Latino Americana de Informática, 1995, pp. 123–134.

[17] С. Нахабу. Каталог встроенных языков. ftp://koala.inria.fr:/pub/EmbeddedInterpretersCatalog.txt.

[18] B. W. Benson Jr., Proceedings of the 1994 USENIX Symposium on Very High Level Languages. USENIX, октябрь 1994, стр.

[19] A. Sah and J. Blow, Proc. USENIX Symposium on Very High Level Languages, 1994.

[20] А. Бэрд-Смит. http://www.inria.fr/koala/abaird/oscheme/manual.html, 1995.

[21] A. Sah, магистерская диссертация, Калифорнийский университет в Беркли. Департамент компьютерных наук, Беркли. Калифорния, 1994.

[22] Sun Microsystems, Java, The Language, 1995. http://java.sun.com/people/avh/talk.ps.

[23] G. van Rossum, Proc. of the UUG najaarsconferentie. Голландская группа пользователей UNIX, 1993. (ftp://ftp.cwi.nl/pub/python/nluug-paper.ps).

[24] Г. ван Россум. Python часто задаваемые вопросы. Версия 1.20++. ftp://ftp.cwi.nl/pub/python/python-FAQмарт 1995 года.

[25] S. Yemini and D. Berry, ACM Transactions on Programming Languages and Systems, 7(2) (1985).

[26] A. Black, Ph. D. Диссертация, Оксфордский университет, 1982.

[27] R. Cerqueira, N. Rodriguez, and R. Ierusalimschy, PANEL95 — XXI Conferência Latino Americana de Informática, 1995, pp. 225–236.