Сколько же существует всяких языков программирования, еще один? Ну можно и так сказать, а можно сказать и по другому: я программист и пишу программы на разных языках программирования для разных задач. В одних языках есть одни плюсы, в других — другие. Вот я и решил предложить свой универсальный язык программирования для множества задач.
ObjectScript — новый объектно-ориентированный язык программирования с открытым исходным кодом. Сами исходники занимают 459 Кб (парсер, компилятор и виртуальная машина) и находятся в двух файлах
ObjectScript сочетает в себе возможности таких языков, как JavaScript, Lua и PHP. Например, синтаксис в основном взят из JavaScript, множественное присваивание — из Lua, работа со свойствами через перегружаемые методы — из PHP.
Кроме унификации нескольких существующих языков программирования, ObjectScript добавляет также и свои уникальные и полезные фишки.
А что если убрать точки с запятыми?
ObjectScript автоматически разпознает отдельные выражения (новая строка тут не причем, все можно писать и в одну строчку), поэтому точку с запятой (;) можно не использовать без явной на то необходимости.
Привычный синтаксис, который используется в большинстве языках программирования:
А зачем там собственно запятая?
Запятые в ObjectScript при перечислении параметров не обязательны. Например, есть в языке такая функции concat, которая соединяет все аргументы в одну строку, тогда игнорируя запятые можно записать вот так:
Красиво и понятно! name, count и time — некоторые переменные. Соединение строк конечно же не обязательно делать через эту функция, есть специальный оператор .. (две точки) для конкатенации, но иногда функция concat может быть удобнее, да и быстрее при обработке нескольких параметров.
Иногда в функцию передается только один параметр, например:
В фигурных скобках задан объект в привычном для JavaScript синтаксисе. Такой синтаксис полностью поддерживается в ObjectScript, но подобный вызов выглядит НЕ очень красиво. А что если убрать круглые скобки?
Уже симпатичнее?! Эта возможность взята из Lua. Так можно вызывать любые функции и не только с объектом в качестве параметра, например:
Довольно таки просто и читабильно!
Но вернемся к предыдущему примеру. А зачем там собственно запятая в описании объекта? А если без нее?
Довольно неплохо, ничего лишнего, а еще можно так:
Т.е. при формировании пар в объекте (индекс и значение) можно использовать как двоеточие, так и знак равно. Кроме этого, допускается отделение пар запятыми, точкой с запяток (;) или не использовать разделитель вовсе. Следует также отметить, что использование разделяющих символов после конечного значения допускается, например, следующее выражение полностью допустимо в ObjectScript:
В данном примере используются не только ассоциативные значения, но и порядковые с автоматическим индексом, как в масиве. Индекс начинается с нуля. Например:
выведет one. А что если необходимо в качестве индекса значения использовать выражение, а не константу, легко:
Т.е. выражение в квадратных скобках будет вычислено на этапе выполнения программы и результат будет использован в качестве индекса соответствующего значения в объекте. Иначе говоря:
Выведет five
Порядок значений в объекте сохраняется таким, в каком порядке значения были добавлены в объект (это бывает важно в итерационных процессах, о которых мы поговорим позже).
Еще одной важной особенностью является то, что в качестве индекса значения может выступать значение любого типа, например:
Выведет powerful, причем это никак не уменьшает скорость доступа к данным объекта и не увеличивает потребление памяти. Иначе говоря, если есть потребность, можно использовать смело.
Масивы — это индексные списки. Как и в JavaScript масив можно записать следующим образом:
Ну в целом все понятно и нормально, единственное, что можно тут упростить — убрать запятые:
Выглядит даже интересно и полностью валидно для ObjectScript.
ObjectScript полностью поддерживает множественное присваивание и выглядит это следующим образом:
Переменной i присвоится значение 0, j присвоится 1, k — 3. Интересным следствием множественного присваивания является возможность смены значений в переменых одной строкой:
Довольно просто и красиво. С помощью множественного присваивания можно одной строкой инициализировать сразу несколько переменных, менять в переменых значения, а также получать множественные результаты вызываемых функций, например:
Функция test возвращает два значения, в переменную a сохранится 1, а в b — 2. Если затребовать из функции больше значений, чем она возвращает, то количество результатов дополнится пустыми значениями — null
Выведет: 1 2 null
Итераторы позволяют обработать элементы некоторого списка друг за другом по очереди и выполнить какую-то работу с этими элементами. Например, пусть нам нужно обработать элементы объекта и показать их индексы и значения:
Данная программа выведет:
Когда компилятор ObjectScript видит for in, он генерит на выходе специальный код, для приведенного выше примера следующий:
По-русски говоря для obj вызывается метод __iter, который должен вернуть итерационную функции:
Затем эта функция вызывается перед каждым шагом итерации:
Она должна вернуть первым результатом логическое значение, иначе говоря true, если текущая итерация валидна, а затем любое количество значений, которые ожидает программист от данного процесса итерации. Первый результат функции обрабатывается языком самостоятельно:
Если процесс итерации завершен, то break прерывает цикл.
Если процесс итерации валидный, то программист может обработать полученные значения. В данном случае, при итерации объекта, функция итерации возвращает два значения, которые доступны программисту — индекс и само значение. Причем не обязательно все их принимать, можно, например, обработать только индексы следующим образом:
Это мы говорили об объектах, теперь перейдем к масивам. Итератор масива на ObjectScript выглядит следующим образом:
Тут может быть немного сложно, давайте по-порядку. Array — это глобальная переменная, в которой содержится описание функционала для масивов. В С++ это был бы class Array. Когда создается масив, он получает ссылку на объект Array к качестве прототипа. Ее даже можно прочитать из программы следующим образом:
Выведет true (оператор === это строгое сравнение без преобразования типов аргументов). Но вернемся к нашему примеру. В прототипе Array перегружается функция __iter (как мы помним она вызывается при запуске процесса итерации). А далее начинается хитрая штука под названием замыкание (анг. closure). Да, ObjectScript поддерживает замыкания в полном объеме — это когда любая вложенная функция имеет доступ к локальным переменным всех своих функций-родителей (даже если родители завершили выполнения). В данном случае, нас интересуют переменные i, self.
В i сохраняется 0, а в self сохраняется this (это ссылка на текущий объект, в данном случае масив, с которым мы работаем).
Далее возвращается функция, которая будет вызвана при каждом шаге итерации. Эта функция проверяет, не достигли ли мы конца масива (# — это оператор, который возвращает количество элементов), и если все ок, то возвращает true (показывая, что мы в процессе), далее индекс и само значение. При этом сам индекс каждый раз инкрементируется. В противном случае ничего не возвращается, т.е. функция завершается своим естественным путем. Это приводит к тому, что затребованные значения, в том числе iter_valid, принимают null и цикл прекращается по условию:
Имя переменной iter_valid приведено тут только в качестве удобства, реально создается временная переменная, доступ к которой программист не имеет.
В качестве примера давайте сами напишем итератор.
Выведется:
Внимательный читатель может подметить «а как же это работает, там же должен вызваться метод __iter?» и будет прав. Дело в том, что итератор для функций возвращает самого себя и описан следющим образом:
Function — это прототип для всех функций, например, там находятся методы call и apply, которые полностью эквивалентны аналогичным в JavaScript.
Как можно было бы понять из названия языка, он просто обязан быть объектно ориентированным и поддерживает ООП во всей своей красе.
Опишем класс следующим образом:
Теперь создадим экземпляр данного класса:
Фактически Person — это обычный объект, когда объект вызывается, как функция, ObjectScript автоматически создает новый экземпляр данного объекта и инициализирует его методом __construct. Выше приведенный код будет реально выполнен следующим образом:
Если затем выполнить:
то выведется:
Из новых фишек следует выделить метод __get@fullname, который неявно вызывается из метода walk:
Метод __get@fullname возвращает значение свойства fullname. Могут быть также специальные методы для установки свойств, но об этом позже в разделе Свойства, getter-ы и setter-ы.
Из интересного тут нужно отметить, что метод __get@fullname содержит символ @, который в ObjectScript является полностью валидным для любых имен методов и переменных, на ряду с символом $ ну и остальными уже более стандартными символами.
Теперь самое время унаследоваться от Person.
Выведется:
Наследование делается оператором extends, который принимает два выражения exp1 и exp2, любых, в том числе расчитанных на этапе выполнение и эквивалентен следующему коду:
Из интересного нужно отметить:
super вызывает метод родительского класса (прототипа) с именем метода, из которого он был вызван, в данном случае — это __construct, который и инициализирует экземпляр объекта.
Давайте создадим совершено новый тип данных, который будет работать как трехмерный вектор:
Выведется: {«x»:11,«y»:24,«z»:39}
Свойство — это некоторая абстрактная сущность (нет, не в виде гномика, хотя...), которая со стороны выглядит как обычное значение в объекте, но при чтении и записи может выполнять некоторую работу в соответствующих методах.
Геттер (getter) — возвращает значение свойства, сеттер (setter) — устанавливает значение. ObjectScript автоматически понимает, считывается свойство или устанавливается, и вызывает соответствующие методы.
Выведется:
Как же это реально работает? При чтении свойства color ObjectScript ищет значение в объекте с именем color. Если таковое найдено, то оно просто возвращается. Если нет, то ищется метод __get@color, нашелся — отлично, значит ObjectScript вызывает и возвращает его результат. Если не нашелся, не беда, ObjectScript ищет метод __get. Если таковой присутствует, то ObjectScript вызывает этот метод с именем запрошенного свойства. Если ничего не нашлось, то возвращается null и точка.
При установке свойства, все происходит аналогично, но вместо __get используется __set. Еще один пример:
Выведется:
Тут показан новый оператор delete, который удаляет значение в объекте и использование метода __del.
ObjectScript поддерживает следующий (обычный для некоторых языков) синтаксис, который является полностью эквивалентным:
А что, если в квадратных скобках передать несколько значений? В ObjectScript это вполне реально!
Выведется:
Остается только обратить внимание на метод __setdim, который первым параметром принимает новое значение, а в остальных параметрах — атрибуты свойства (их количество может быть любым начиная от двух).
А что на счет следующего кода?
Вполне! ObjectScript в этом случае вызывает следующие методы соответственно: __getempty, __setempty, __delempty. Программист может решить по своему усмотрению, как использовать этот функционал.
На закуску несколько не отмеченных выше моментов.
При описании функции в блоке перечисления параметров, запятые ставить также не обязательно:
Выведет 7
В функциях можно использовать arguments — возвращает масив всех параметров, с которыми функция была запущена, ... (три точки) — масив дополнительных параметров, которые не описаны в объявлении функции.
Оператор # вызывает для объекта применения метод __len. Для строк он возвращает количество символов в строке, а для объектов и масивов — количество элементов. Также в классе Object заведено свойство:
А т.к. все объекты, в том числе строка и масивы, унаследованы от Object, то можно использовать свойство length, например, в масивах, на манер JavaScript.
Из необычных математических операторов можно отметить ** — возведение в степень.
Из структурных конструкций на данный момент реализованы:
Одинаковые строки ObjectScript хранит в единственном экземпляре, это делается автоматически. Причем не важно, была строка получена на этапе выполнения или компиляции программы.
Локальные переменные имеют область видимости, например.
Выведется
ObjectScript имеет два зарезервированных слова для целей отладки, первое — debugger (как в JavaScript). При срабатывании debugger программа остановится в дебагере, как при точке останова. Второе — debuglocals, которое возвращает ассоциативный объект с названиями видимых из точки использования debuglocals локальных переменых и их значений. Например:
Выведется:
ObjectScript разпознает tail call. Это когда внутри функции возвращается результат выполнения др. функции. В этом случае вызываемая функция может заместить call stack текущей функции, а не увеливать call stack, добавляя себя в него.
Преобразование выражений в тип boolean происходит следующим образом: значения null, false и NaN возвращают false, все др. значения — true, в том числе пустая строка и число 0.
Операторы && и || возвращают то значение, которые было им передано, например:
Выведется:
При присваивании объектов, их копии не создаются, присваивается ссылка на сам объект. Чтобы создать копию, необходимо воспользоваться оператором clone, который клонирует аргумент. Для объектов и масивов, этот оператор копирует все значения в новый объект, др. типы не клонируются по умолчанию, а возвращаются как есть. Но на финальной стадии процесса вызывается метод __clone, в котором можно сделать что-то свое или дополнительное. Вопрос о спецификации данного метода пока не решен окончательно. Возможно есть смысл возвращать результатом клонирования то, что вернет метод __clone. В этом случае программист сможет клонировать и свои внутренние типы данных, созданные с помощью userdata, если пожелает.
Также есть typeof, valueof, numberof, stringof, arrayof, objectof, functionof, userdataof. Спецификация этих операторов прорабатывается.
Ну вот как-то так. Если вам ObjectScript кажется интересным, давайте разрабатывать и развивать язык вместе. Текущие задачи, над которыми я работаю — сборщик мусора, багфикс, оптимизация, документация, environment для функции, расширение синтаксиса, поддержка многострочных констант, компиляция в байт-код, компиляция в JavaScript (чтобы писать клиенты для веба). Предлагайте и комментируйте.
Стандарт языка в процессе формирования, поэтому любые идеи будут интересны.
Об интеграции с C++ расскажу в одной из следующих статей. Вкратце:
Как запустить пример того, что описано в этой статье? Скачать исходники, откомпилированный файл с примером с репозитория на github, прямая ссылка для загрузки. Перейти в папку
В файле test3.txt генерируется отладочная информация того, как ObjectScript скомпилировал исходник, это может потребоваться для лучшего понимания процессов, которые происходят внутри языка, например:
ObjectScript полностью совместим с JSON, т.к. понимает этот формат, как свой родной, но добавляет в описание объектов и масивов свой расширенный и простой синтаксис. ObjectScript реализует все плюсы таких языков, как JavaScript, Lua и PHP, при этом добавляет свои уникальные возможности программирования. ObjectScript — объектно-ориентированный язык программирования, реализует все его парадигмы. Оператор new при этом не используется, минимизируя код и делая его более читабильным. Синтаксис ObjectScript позволет реализовать все необходимые конструкции, но направлен на простоту и читабильность. ObjectScript предназначен для вставки в приложение на C++, позволяет интегрироваться с С++ на уровне функций и пользовательских данных (в том числе объектно-ориентированных). ObjectScript — очень легкий, текущие исходники занимают 459 Кб. Язык пока не имеет стабильной версии и находится в стадии формирования спецификации и балансировки.
На данный момент я начал делать некоторые примеры по использованию языка ObjectScript и записывать видео, вы можеет посмотреть некоторые из них по следующим ссылкам:
www.youtube.com/watch?v=OCWIfQYW9rc
www.youtube.com/watch?v=P5KPJOVSs3E
www.youtube.com/watch?v=htDqDNqHX-I
www.youtube.com/watch?v=wqiDeuf7yu8
www.youtube.com/watch?v=uep2SvXdCNU
в описании к видео указаны ссылки, от куда можно скачать полные исходники примеров.
ObjectScript — новый объектно-ориентированный язык программирования с открытым исходным кодом. Сами исходники занимают 459 Кб (парсер, компилятор и виртуальная машина) и находятся в двух файлах
source\objectscript.h
и source\objectscript.cpp
. Скачать их можно по прямой ссылке тут. ObjectScript — очень легкий, предназначен для вставки в приложение на C++.ObjectScript сочетает в себе возможности таких языков, как JavaScript, Lua и PHP. Например, синтаксис в основном взят из JavaScript, множественное присваивание — из Lua, работа со свойствами через перегружаемые методы — из PHP.
Кроме унификации нескольких существующих языков программирования, ObjectScript добавляет также и свои уникальные и полезные фишки.
Синтаксис
x = 12;
y = "Hello World!";
А что если убрать точки с запятыми?
x = 12
y = "Hello World!"
ObjectScript автоматически разпознает отдельные выражения (новая строка тут не причем, все можно писать и в одну строчку), поэтому точку с запятой (;) можно не использовать без явной на то необходимости.
Вызовы функций
Привычный синтаксис, который используется в большинстве языках программирования:
print(5, " differences")
А зачем там собственно запятая?
print(5 " differences")
Запятые в ObjectScript при перечислении параметров не обязательны. Например, есть в языке такая функции concat, которая соединяет все аргументы в одну строку, тогда игнорируя запятые можно записать вот так:
var s = concat("name: " name ", count: " count ", time: " time)
Красиво и понятно! name, count и time — некоторые переменные. Соединение строк конечно же не обязательно делать через эту функция, есть специальный оператор .. (две точки) для конкатенации, но иногда функция concat может быть удобнее, да и быстрее при обработке нескольких параметров.
Иногда в функцию передается только один параметр, например:
print({firstname:"Ivan", lastname:"Petrov"})
В фигурных скобках задан объект в привычном для JavaScript синтаксисе. Такой синтаксис полностью поддерживается в ObjectScript, но подобный вызов выглядит НЕ очень красиво. А что если убрать круглые скобки?
print {firstname:"Ivan", lastname:"Petrov"}
Уже симпатичнее?! Эта возможность взята из Lua. Так можно вызывать любые функции и не только с объектом в качестве параметра, например:
print "Hello World!"
Довольно таки просто и читабильно!
Объекты
Но вернемся к предыдущему примеру. А зачем там собственно запятая в описании объекта? А если без нее?
print {firstname:"Ivan" lastname:"Petrov"}
Довольно неплохо, ничего лишнего, а еще можно так:
print {firstname="Ivan" lastname="Petrov"}
Т.е. при формировании пар в объекте (индекс и значение) можно использовать как двоеточие, так и знак равно. Кроме этого, допускается отделение пар запятыми, точкой с запяток (;) или не использовать разделитель вовсе. Следует также отметить, что использование разделяющих символов после конечного значения допускается, например, следующее выражение полностью допустимо в ObjectScript:
a = {x=1, y=3; "zero" "one", "two" last:7,}
В данном примере используются не только ассоциативные значения, но и порядковые с автоматическим индексом, как в масиве. Индекс начинается с нуля. Например:
print a[1]
выведет one. А что если необходимо в качестве индекса значения использовать выражение, а не константу, легко:
a = {[2+3]="five" y=3}
Т.е. выражение в квадратных скобках будет вычислено на этапе выполнения программы и результат будет использован в качестве индекса соответствующего значения в объекте. Иначе говоря:
print a[5]
Выведет five
Порядок значений в объекте сохраняется таким, в каком порядке значения были добавлены в объект (это бывает важно в итерационных процессах, о которых мы поговорим позже).
Еще одной важной особенностью является то, что в качестве индекса значения может выступать значение любого типа, например:
a = {x=1 y=2}
b = {[a]="powerful" 7="greate"}
print b[a]
Выведет powerful, причем это никак не уменьшает скорость доступа к данным объекта и не увеличивает потребление памяти. Иначе говоря, если есть потребность, можно использовать смело.
Масивы
Масивы — это индексные списки. Как и в JavaScript масив можно записать следующим образом:
a = [10, 20, 30, 40]
Ну в целом все понятно и нормально, единственное, что можно тут упростить — убрать запятые:
a = [10 20 30 40]
Выглядит даже интересно и полностью валидно для ObjectScript.
Множественное присваивание
ObjectScript полностью поддерживает множественное присваивание и выглядит это следующим образом:
i, j, k = 0, 1, 3
Переменной i присвоится значение 0, j присвоится 1, k — 3. Интересным следствием множественного присваивания является возможность смены значений в переменых одной строкой:
i, j = j, i
Довольно просто и красиво. С помощью множественного присваивания можно одной строкой инициализировать сразу несколько переменных, менять в переменых значения, а также получать множественные результаты вызываемых функций, например:
var test = function(){ return 1, 2 }
var a, b = test()
Функция test возвращает два значения, в переменную a сохранится 1, а в b — 2. Если затребовать из функции больше значений, чем она возвращает, то количество результатов дополнится пустыми значениями — null
var a, b, c = test()
print(a, b, c)
Выведет: 1 2 null
Итераторы
Итераторы позволяют обработать элементы некоторого списка друг за другом по очереди и выполнить какую-то работу с этими элементами. Например, пусть нам нужно обработать элементы объекта и показать их индексы и значения:
obj = { null awesome=true 12 "excellent" }
for(k, v in obj){
print( k " --> " v )
}
Данная программа выведет:
0 --> null
awesome --> true
1 --> 12
2 --> excellent
Когда компилятор ObjectScript видит for in, он генерит на выходе специальный код, для приведенного выше примера следующий:
obj = { null awesome=true 12 "excellent" };
{
var iter_func = obj.__iter()
for(var iter_valid;;){
iter_valid, k, v = iter_func()
if(!iter_valid) break
print( k " --> " v )
}
}
По-русски говоря для obj вызывается метод __iter, который должен вернуть итерационную функции:
var iter_func = obj.__iter()
Затем эта функция вызывается перед каждым шагом итерации:
iter_valid, k, v = iter_func()
Она должна вернуть первым результатом логическое значение, иначе говоря true, если текущая итерация валидна, а затем любое количество значений, которые ожидает программист от данного процесса итерации. Первый результат функции обрабатывается языком самостоятельно:
if(!iter_valid) break
Если процесс итерации завершен, то break прерывает цикл.
Если процесс итерации валидный, то программист может обработать полученные значения. В данном случае, при итерации объекта, функция итерации возвращает два значения, которые доступны программисту — индекс и само значение. Причем не обязательно все их принимать, можно, например, обработать только индексы следующим образом:
obj = { null awesome=true 12 "excellent" }
for(k in obj){
print k
}
Это мы говорили об объектах, теперь перейдем к масивам. Итератор масива на ObjectScript выглядит следующим образом:
Array.__iter = function(){
var i, self = 0, this
return function(){
if(i < #self){
return true, i, self[i++]
}
}
}
Тут может быть немного сложно, давайте по-порядку. Array — это глобальная переменная, в которой содержится описание функционала для масивов. В С++ это был бы class Array. Когда создается масив, он получает ссылку на объект Array к качестве прототипа. Ее даже можно прочитать из программы следующим образом:
print [1 2 3].prototype === Array
Выведет true (оператор === это строгое сравнение без преобразования типов аргументов). Но вернемся к нашему примеру. В прототипе Array перегружается функция __iter (как мы помним она вызывается при запуске процесса итерации). А далее начинается хитрая штука под названием замыкание (анг. closure). Да, ObjectScript поддерживает замыкания в полном объеме — это когда любая вложенная функция имеет доступ к локальным переменным всех своих функций-родителей (даже если родители завершили выполнения). В данном случае, нас интересуют переменные i, self.
var i, self = 0, this
В i сохраняется 0, а в self сохраняется this (это ссылка на текущий объект, в данном случае масив, с которым мы работаем).
return function(){
if(i < #self){
return true, i, self[i++]
}
}
Далее возвращается функция, которая будет вызвана при каждом шаге итерации. Эта функция проверяет, не достигли ли мы конца масива (# — это оператор, который возвращает количество элементов), и если все ок, то возвращает true (показывая, что мы в процессе), далее индекс и само значение. При этом сам индекс каждый раз инкрементируется. В противном случае ничего не возвращается, т.е. функция завершается своим естественным путем. Это приводит к тому, что затребованные значения, в том числе iter_valid, принимают null и цикл прекращается по условию:
if(!iter_valid) break
Имя переменной iter_valid приведено тут только в качестве удобства, реально создается временная переменная, доступ к которой программист не имеет.
В качестве примера давайте сами напишем итератор.
var range = function(a, b){
return function(){
if(a <= b){
return true, a++
}
}
}
for(var i in range(10, 13)){
print( "i = ", i )
}
Выведется:
i = 10
i = 11
i = 12
i = 13
Внимательный читатель может подметить «а как же это работает, там же должен вызваться метод __iter?» и будет прав. Дело в том, что итератор для функций возвращает самого себя и описан следющим образом:
Function.__iter = function(){ return this }
Function — это прототип для всех функций, например, там находятся методы call и apply, которые полностью эквивалентны аналогичным в JavaScript.
Объектно-ориентированное программирование (ООП) в ObjectScript
Как можно было бы понять из названия языка, он просто обязан быть объектно ориентированным и поддерживает ООП во всей своей красе.
Опишем класс следующим образом:
Person = {
__construct = function(firstname, lastname){
this.firstname = firstname
this.lastname = lastname
}
__get@fullname = function(){
return this.firstname .. " " .. this.lastname
}
walk = function(){
print this.fullname .. " is walking!"
}
}
Теперь создадим экземпляр данного класса:
var p = Person("James", "Bond")
Фактически Person — это обычный объект, когда объект вызывается, как функция, ObjectScript автоматически создает новый экземпляр данного объекта и инициализирует его методом __construct. Выше приведенный код будет реально выполнен следующим образом:
var p = {}
p.prototype = Person
p.__construct("James", "Bond")
Если затем выполнить:
p.walk()
print p
то выведется:
James Bond is walking!
{"firstname":"James","lastname":"Bond"}
Из новых фишек следует выделить метод __get@fullname, который неявно вызывается из метода walk:
__get@fullname = function(){
return this.firstname .. " " .. this.lastname
}
walk = function(){
print this.fullname .. " is walking!"
}
Метод __get@fullname возвращает значение свойства fullname. Могут быть также специальные методы для установки свойств, но об этом позже в разделе Свойства, getter-ы и setter-ы.
Из интересного тут нужно отметить, что метод __get@fullname содержит символ @, который в ObjectScript является полностью валидным для любых имен методов и переменных, на ряду с символом $ ну и остальными уже более стандартными символами.
Наследование
Теперь самое время унаследоваться от Person.
// опишем класс IvanPerson
var IvanPerson = extends Person {
__construct = function(){
super("Ivan", "Petrov")
}
}
var p = IvanPerson() // создадим экземпляр класса IvanPerson
p.walk()
print p
Выведется:
Ivan Petrov is walking!
{"firstname":"Ivan","lastname":"Petrov"}
Наследование делается оператором extends, который принимает два выражения exp1 и exp2, любых, в том числе расчитанных на этапе выполнение и эквивалентен следующему коду:
(function(exp1, exp2){
exp2.prototype = exp1
return exp2
})()
Из интересного нужно отметить:
super("Ivan", "Petrov")
super вызывает метод родительского класса (прототипа) с именем метода, из которого он был вызван, в данном случае — это __construct, который и инициализирует экземпляр объекта.
ООП на закуску
Давайте создадим совершено новый тип данных, который будет работать как трехмерный вектор:
var vec3 = {
__construct = function(x, y, z){
this.x = x
this.y = y
this.z = z
}
__add = function(a, b){
return vec3(a.x + b.x, a.y + b.y, a.z + b.z)
}
__mul = function(a, b){
return vec3(a.x * b.x, a.y * b.y, a.z * b.z)
}
}
var v1 = vec3(10 20 30)
var v2 = vec3(1 2 3)
var v3 = v1 + v2 * v2
print v3
Выведется: {«x»:11,«y»:24,«z»:39}
Свойства, getter-ы и setter-ы
Свойство — это некоторая абстрактная сущность (нет, не в виде гномика, хотя...), которая со стороны выглядит как обычное значение в объекте, но при чтении и записи может выполнять некоторую работу в соответствующих методах.
Геттер (getter) — возвращает значение свойства, сеттер (setter) — устанавливает значение. ObjectScript автоматически понимает, считывается свойство или устанавливается, и вызывает соответствующие методы.
a = {
_color = "red"
__get@color = function(){ return this._color }
__set@color = function(v){ this._color = v }
}
print a["color"]
a.color = "blue"
print a.color
Выведется:
red
blue
Как же это реально работает? При чтении свойства color ObjectScript ищет значение в объекте с именем color. Если таковое найдено, то оно просто возвращается. Если нет, то ищется метод __get@color, нашелся — отлично, значит ObjectScript вызывает и возвращает его результат. Если не нашелся, не беда, ObjectScript ищет метод __get. Если таковой присутствует, то ObjectScript вызывает этот метод с именем запрошенного свойства. Если ничего не нашлось, то возвращается null и точка.
При установке свойства, все происходит аналогично, но вместо __get используется __set. Еще один пример:
a = {
_color = "white"
__get = function(name){
if(name == "color")
return this._color
}
__set = function(name, v){
if(name == "color")
this._color = v
}
__del = function(name){
if(name == "color")
delete this._color
}
}
print a.color
a.color = "green"
print a.color
delete a.color
print a.color
Выведется:
white
green
null
Тут показан новый оператор delete, который удаляет значение в объекте и использование метода __del.
Многомерные свойства
ObjectScript поддерживает следующий (обычный для некоторых языков) синтаксис, который является полностью эквивалентным:
print a.color
print a["color"]
А что, если в квадратных скобках передать несколько значений? В ObjectScript это вполне реально!
a = {
_matrix = {}
__getdim = function(x, y){
return this._matrix[y*4 + x]
}
__setdim = function(value, x, y){
this._matrix[y*4 + x] = value
}
__deldim = function(x, y){
delete this._matrix[y*4 + x]
}
}
a[1, 2] = 5 // компилятор преобразует это в a.__setdim(5, 1, 2)
print a[1, 2] // print(a.__getdim(1, 2))
delete a[1, 2] // a.__deldim(1, 2)
print a[1, 2] // print(a.__getdim(1, 2))
Выведется:
5
null
Остается только обратить внимание на метод __setdim, который первым параметром принимает новое значение, а в остальных параметрах — атрибуты свойства (их количество может быть любым начиная от двух).
Пустые свойства
А что на счет следующего кода?
b = a[]
a[] = 2
delete a[]
Вполне! ObjectScript в этом случае вызывает следующие методы соответственно: __getempty, __setempty, __delempty. Программист может решить по своему усмотрению, как использовать этот функционал.
Заключение
На закуску несколько не отмеченных выше моментов.
При описании функции в блоке перечисления параметров, запятые ставить также не обязательно:
print function(a b c){ return a + b * c }(1 2 3)
Выведет 7
В функциях можно использовать arguments — возвращает масив всех параметров, с которыми функция была запущена, ... (три точки) — масив дополнительных параметров, которые не описаны в объявлении функции.
Оператор # вызывает для объекта применения метод __len. Для строк он возвращает количество символов в строке, а для объектов и масивов — количество элементов. Также в классе Object заведено свойство:
Object.__get@length = function(){ return #this }
А т.к. все объекты, в том числе строка и масивы, унаследованы от Object, то можно использовать свойство length, например, в масивах, на манер JavaScript.
Из необычных математических операторов можно отметить ** — возведение в степень.
Из структурных конструкций на данный момент реализованы:
if(exp) block [elseif(exp) block ][else block ]
for(pre_block; exp; post_block) block
for(assign_list in exp) block
break
continue
function(var_list){ block }
Одинаковые строки ObjectScript хранит в единственном экземпляре, это делается автоматически. Причем не важно, была строка получена на этапе выполнения или компиляции программы.
Локальные переменные имеют область видимости, например.
var i = 1;
{
var i = i
i++
print i
}
print i
Выведется
2
1
ObjectScript имеет два зарезервированных слова для целей отладки, первое — debugger (как в JavaScript). При срабатывании debugger программа остановится в дебагере, как при точке останова. Второе — debuglocals, которое возвращает ассоциативный объект с названиями видимых из точки использования debuglocals локальных переменых и их значений. Например:
function(a){
var c = a * 2;
{
var c = a - 1
print debuglocals
}
}(10)
Выведется:
{a:10,c:9}
ObjectScript разпознает tail call. Это когда внутри функции возвращается результат выполнения др. функции. В этом случае вызываемая функция может заместить call stack текущей функции, а не увеливать call stack, добавляя себя в него.
Преобразование выражений в тип boolean происходит следующим образом: значения null, false и NaN возвращают false, все др. значения — true, в том числе пустая строка и число 0.
Операторы && и || возвращают то значение, которые было им передано, например:
print 7 && 9
print 7 || 9
Выведется:
9
7
При присваивании объектов, их копии не создаются, присваивается ссылка на сам объект. Чтобы создать копию, необходимо воспользоваться оператором clone, который клонирует аргумент. Для объектов и масивов, этот оператор копирует все значения в новый объект, др. типы не клонируются по умолчанию, а возвращаются как есть. Но на финальной стадии процесса вызывается метод __clone, в котором можно сделать что-то свое или дополнительное. Вопрос о спецификации данного метода пока не решен окончательно. Возможно есть смысл возвращать результатом клонирования то, что вернет метод __clone. В этом случае программист сможет клонировать и свои внутренние типы данных, созданные с помощью userdata, если пожелает.
Также есть typeof, valueof, numberof, stringof, arrayof, objectof, functionof, userdataof. Спецификация этих операторов прорабатывается.
Ну вот как-то так. Если вам ObjectScript кажется интересным, давайте разрабатывать и развивать язык вместе. Текущие задачи, над которыми я работаю — сборщик мусора, багфикс, оптимизация, документация, environment для функции, расширение синтаксиса, поддержка многострочных констант, компиляция в байт-код, компиляция в JavaScript (чтобы писать клиенты для веба). Предлагайте и комментируйте.
Стандарт языка в процессе формирования, поэтому любые идеи будут интересны.
Об интеграции с C++ расскажу в одной из следующих статей. Вкратце:
int test(OS * os, int, int, int, void*)
{
os->pushNumber(123);
return 1;
}
int main(int argc, char* argv[])
{
OS * os = OS::create();
os->pushCFunction(test);
os->setGlobal("test");
os->eval("print(test())"); // выведет 123
os->release();
return 0;
}
Как запустить пример того, что описано в этой статье? Скачать исходники, откомпилированный файл с примером с репозитория на github, прямая ссылка для загрузки. Перейти в папку
OS\examples
и запустить файл test3.cmd. В файле test3.txt генерируется отладочная информация того, как ObjectScript скомпилировал исходник, это может потребоваться для лучшего понимания процессов, которые происходят внутри языка, например:
[14] print( a[v1] a.v2 )
begin call
get env var print
begin params 2
begin get property
get local var a (1 0 param)
get local var v1 (0 0 param)
end get property ret values 1
,
begin get property
get local var a (1 0 param)
push const string "v2"
end get property ret values 1
end params ret values 2
end call ret values 0
В сухом остатке
ObjectScript полностью совместим с JSON, т.к. понимает этот формат, как свой родной, но добавляет в описание объектов и масивов свой расширенный и простой синтаксис. ObjectScript реализует все плюсы таких языков, как JavaScript, Lua и PHP, при этом добавляет свои уникальные возможности программирования. ObjectScript — объектно-ориентированный язык программирования, реализует все его парадигмы. Оператор new при этом не используется, минимизируя код и делая его более читабильным. Синтаксис ObjectScript позволет реализовать все необходимые конструкции, но направлен на простоту и читабильность. ObjectScript предназначен для вставки в приложение на C++, позволяет интегрироваться с С++ на уровне функций и пользовательских данных (в том числе объектно-ориентированных). ObjectScript — очень легкий, текущие исходники занимают 459 Кб. Язык пока не имеет стабильной версии и находится в стадии формирования спецификации и балансировки.
На данный момент я начал делать некоторые примеры по использованию языка ObjectScript и записывать видео, вы можеет посмотреть некоторые из них по следующим ссылкам:
www.youtube.com/watch?v=OCWIfQYW9rc
www.youtube.com/watch?v=P5KPJOVSs3E
www.youtube.com/watch?v=htDqDNqHX-I
www.youtube.com/watch?v=wqiDeuf7yu8
www.youtube.com/watch?v=uep2SvXdCNU
в описании к видео указаны ссылки, от куда можно скачать полные исходники примеров.