Сколько же существует всяких языков программирования, еще один? Ну можно и так сказать, а можно сказать и по другому: я программист и пишу программы на разных языках программирования для разных задач. В одних языках есть одни плюсы, в других - другие. Вот я и решил предложить свой универсальный язык программирования для множества задач.
ObjectScript - новый объектно-ориентированный язык программирования с открытым исходным кодом. Сами исходники занимают
459 Кб (парсер, компилятор и виртуальная машина) и находятся в двух файлах
source\objectscript.h и
source\objectscript.cpp. Скачать их можно
по прямой ссылке тут. ObjectScript - очень легкий, предназначен для вставки в приложение на C++.
ObjectScript сочетает в себе возможности таких языков, как JavaScript, Lua и PHP. Например, синтаксис в основном взят из JavaScript, множественное присваивание - из Lua, работа со свойствами через перегружаемые методы - из PHP.
Кроме унификации нескольких существующих языков программирования, ObjectScript добавляет также и свои уникальные и полезные фишки.
Далее по тексту я буду показывать код языка ObjectScript в теге для JavaScript, т.к. тега ObjectScript пока несуществует.
Синтаксис
Javascript |
1
2
| x = 12;
y = "Hello World!"; |
|
А что если убрать точки с запятыми?
Javascript |
1
2
| x = 12
y = "Hello World!" |
|
ObjectScript автоматически разпознает отдельные выражения (новая строка тут не причем, все можно писать и в одну строчку), поэтому точку с запятой (
;) можно не использовать без явной на то необходимости.
Вызовы функций
Привычный синтаксис, который используется в большинстве языках программирования:
Javascript |
1
| print(5, " differences") |
|
А зачем там собственно запятая?
Javascript |
1
| print(5 " differences") |
|
Запятые в ObjectScript при перечислении параметров не обязательны. Например, есть в языке такая функции
concat, которая соединяет все аргументы в одну строку, тогда игнорируя запятые можно записать вот так:
Javascript |
1
| var s = concat("name: "name", count: "count", time: "time) |
|
Красиво и понятно!
name,
count и
time - некоторые переменные. Соединение строк конечно же не обязательно делать через эту функция, есть специальный оператор
.. (две точки) для конкатенации, но иногда функция
concat может быть удобнее, да и быстрее при обработке нескольких параметров.
Иногда в функцию передается только один параметр, например:
Javascript |
1
| print({firstname:"Ivan", lastname:"Petrov"}) |
|
В фигурных скобках задан объект в привычном для JavaScript синтаксисе. Такой синтаксис полностью поддерживается в ObjectScript, но подобный вызов выглядит
НЕ очень красиво. А что если убрать круглые скобки?
Javascript |
1
| print {firstname:"Ivan", lastname:"Petrov"} |
|
Уже симпатичнее?! Эта возможность взята из Lua. Так можно вызывать любые функции и не только с объектом в качестве параметра, например:
Довольно таки просто и читабильно!
Объекты
Но вернемся к предыдущему примеру. А зачем там собственно запятая в описании объекта? А если без нее?
Javascript |
1
| print {firstname:"Ivan" lastname:"Petrov"} |
|
Довольно неплохо, ничего лишнего, а еще можно так:
Javascript |
1
| print {firstname="Ivan" lastname="Petrov"} |
|
Т.е. при формировании пар в объекте (индекс и значение) можно использовать как двоеточие, так и знак равно. Кроме этого, допускается отделение пар запятыми, точкой с запяток (
;) или не использовать разделитель вовсе. Следует также отметить, что использование разделяющих символов после конечного значения допускается, например, следующее выращение полностью допустимо в ObjectScript:
Javascript |
1
| a = {x=1, y=3; "zero" "one", "two" last:7,} |
|
Что полностью эквивалентно привычной в JavaScript-е записи:
Javascript |
1
| a = {x:1, y:3, 0:"zero", 1:"one", 2:"two", last:7} |
|
В данном примере используются не только ассоциативные значения, но и порядковые с автоматическим индексом, как в масиве. Индекс начинается с нуля. Например:
выведет
one. А что если необходимо в качестве индекса значения использовать выражение, а не константу, легко:
Т.е. выражение в квадратных скобках будет вычислено на этапе выполнения программы и результат будет использован в качестве индекса соответствующего значения в объекте. Иначе говоря:
Выведет
five
Порядок значений в объекте сохраняется таким, в каком порядке значения были добавлены в объект (это бывает важно в итерационных процессах, о которых мы поговорим позже).
Еще одной важной особенностью является то, что в качестве индекса значения может выступать значение любого типа, например:
Javascript |
1
2
3
| a = {x=1 y=2}
b = {[a]="powerful" 7="greate"}
print b[a] |
|
Выведет
powerful, причем это никак не уменьшает скорость доступа к данным объекта и не увеличивает потребление памяти. Иначе говоря, если есть потребность, можно использовать смело.
Масивы
Масивы - это индексные списки. Как и в JavaScript масив можно записать следующим образом:
Ну в целом все понятно и нормально, единственное, что можно тут упростить - убрать запятые:
Выглядит даже интересно и полностью валидно для ObjectScript.
Множественное присваивание
ObjectScript полностью поддерживает множественное присваивание и выглядит это следующим образом:
Переменной
i присвоится значение 0,
j присвоится 1,
k - 3. Интересным следствием множественного присваивания является возможность смены значений в переменых одной строкой:
Довольно просто и красиво. С помощью множественного присваивания можно одной строкой инициализировать сразу несколько переменных, менять в переменых значения, а также получать множественные результаты вызываемых функций, например:
Javascript |
1
2
| var test = function(){ return 1, 2 }
var a, b = test() |
|
Функция
test возвращает два значения, в переменную
a сохранится 1, а в
b - 2. Если затребовать из функции больше значений, чем она возвращает, то количество результатов дополнится пустыми значениями -
null
Javascript |
1
2
| var a, b, c = test()
print(a, b, c) |
|
Выведет:
1 2 null
Итераторы
Итераторы позволяют обработать элементы некоторого списка друг за другом по очереди и выполнить какую-то работу с этими элементами. Например, пусть нам нужно обработать элементы объекта и показать их индексы и значения:
Javascript |
1
2
3
4
| obj = { null awesome=true 12 "excellent" }
for(k, v in obj){
print( k " --> " v )
} |
|
Данная программа выведет:
Javascript |
1
2
3
4
| 0 --> null
awesome --> true
1 --> 12
2 --> excellent |
|
Когда компилятор ObjectScript видит
for in, он генерит на выходе специальный код, для приведенного выше примера следующий:
Javascript |
1
2
3
4
5
6
7
8
9
| 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, который должен вернуть итерационную функции (для всех стандартных типов эта функция уже есть, но никто не запрещает ее переписать по своему усмотрению):
Javascript |
1
| var iter_func = obj.__iter() |
|
Затем эта функция вызывается перед каждым шагом итерации:
Javascript |
1
| iter_valid, k, v = iter_func() |
|
Она должна вернуть первым результатом логическое значение, иначе говоря
true, если текущая итерация валидна, а затем любое количество значений, которые ожидает программист от данного процесса итерации. Первый результат функции обрабатывается языком самостоятельно:
Если процесс итерации завершен, то
break прерывает цикл.
Если процесс итерации валидный, то программист может обработать полученные значения. В данном случае, при итерации объекта, функция итерации возвращает два значения, которые доступны программисту - индекс и само значение. Причем не обязательно все их принимать, можно, например, обработать только индексы следующим образом:
Javascript |
1
2
3
4
| obj = { null awesome=true 12 "excellent" }
for(k in obj){
print k
} |
|
Это мы говорили об объектах, теперь перейдем к масивам. Итератор масива на ObjectScript выглядит следующим образом:
Javascript |
1
2
3
4
5
6
7
8
| Array.__iter = function(){
var i, self = 0, this
return function(){
if(i < #self){
return true, i, self[i++]
}
}
} |
|
Тут может быть немного сложно, давайте по-порядку.
Array — это глобальная переменная, в которой содержится описание функционала для масивов. В С++ это был бы
class Array. Когда создается масив, он получает ссылку на объект
Array к качестве прототипа. Ее даже можно прочитать из программы следующим образом:
Javascript |
1
| print [1 2 3].prototype === Array |
|
Выведет
true (оператор === это строгое сравнение без преобразования типов аргументов). Но вернемся к нашему примеру. В прототипе
Array перегружается функция
__iter (как мы помним она вызывается при запуске процесса итерации). А далее начинается хитрая штука под названием
замыкание (анг.
closure). Да, ObjectScript поддерживает
замыкания в полном объеме - это когда любая вложенная функция имеет доступ к локальным переменным всех своих функций-родителей (даже если родители завершили выполнения). В данном случае, нас интересуют переменные
i, self.
В
i сохраняется 0, а в
self сохраняется
this (это ссылка на текущий объект, в данном случае масив, с которым мы работаем).
Javascript |
1
2
3
4
5
| return function(){
if(i < #self){
return true, i, self[i++]
}
} |
|
Далее возвращается функция, которая будет вызвана при каждом шаге итерации. Эта функция проверяет, не достигли ли мы конца масива (
# - это оператор, который возвращает количество элементов), и если все ок, то возвращает
true (показывая, что мы в процессе), далее индекс и само значение. При этом сам индекс каждый раз инкрементируется. В противном случае ничего не возвращается, т.е. функция завершается своим естественным путем. Это приводит к тому, что затребованные значения, в том числе
iter_valid, принимают
null и цикл прекращается по условию:
Имя переменной iter_valid приведено тут только в качестве удобства, реально создается временная переменная, доступ к которой программист не имеет.
В качестве примера давайте сами напишем итератор.
Javascript |
1
2
3
4
5
6
7
8
9
10
| var range = function(a, b){
return function(){
if(a <= b){
return true, a++
}
}
}
for(var i in range(10, 13)){
print( "i = ", i )
} |
|
Выведется:
Javascript |
1
2
3
4
| i = 10
i = 11
i = 12
i = 13 |
|
Внимательный читатель может подметить "а как же это работает, там же должен вызваться метод
__iter?" и будет прав. Дело в том, что итератор для функций возвращает самого себя и описан следющим образом:
Javascript |
1
| Function.__iter = function(){ return this } |
|
Function - это прототип для всех функций, например, там находятся методы
call и
apply, которые полностью эквивалентны аналогичным в JavaScript.
Объектно-ориентированное программирование (ООП) в ObjectScript
Как можно было бы понять из названия языка, он просто обязан быть объектно ориентированным и поддерживает ООП во всей своей красе.
Опишем
класс следующим образом:
Javascript |
1
2
3
4
5
6
7
8
9
10
11
12
| 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!"
}
} |
|
Теперь создадим экземпляр данного класса:
Javascript |
1
| var p = Person("James", "Bond") |
|
Фактически
Person - это обычный объект, когда объект вызывается, как функция, ObjectScript автоматически создает новый экземпляр данного объекта и инициализирует его методом
__construct. Выше приведенный код будет реально выполнен следующим образом:
Javascript |
1
2
3
| var p = {}
p.prototype = Person
p.__construct("James", "Bond") |
|
Если затем выполнить:
то выведется:
Javascript |
1
2
| James Bond is walking!
{"firstname":"James","lastname":"Bond"} |
|
Из новых фишек следует выделить метод
__get@fullname, который неявно вызывается из метода
walk:
Javascript |
1
2
3
4
5
6
| __get@fullname = function(){
return this.firstname .. " " .. this.lastname
}
walk = function(){
print this.fullname .. " is walking!"
} |
|
Метод
__get@fullname возвращает значение свойства
fullname. Могут быть также специальные методы для установки свойств, но об этом позже в разделе
Свойства, getter-ы и setter-ы.
Из интересного тут нужно отметить, что метод
__get@fullname содержит символ
@, который в ObjectScript является полностью валидным для любых имен методов и переменных, на ряду с символом
$ ну и остальными уже более стандартными символами.
Наследование
Теперь самое время унаследоваться от Person.
Javascript |
1
2
3
4
5
6
7
8
9
| // опишем класс IvanPerson
var IvanPerson = extends Person {
__construct = function(){
super("Ivan", "Petrov")
}
}
var p = IvanPerson() // создадим экземпляр класса IvanPerson
p.walk()
print p |
|
Выведется:
Javascript |
1
2
| Ivan Petrov is walking!
{"firstname":"Ivan","lastname":"Petrov"} |
|
Наследование делается оператором
extends, который принимает два выражения
exp1 и
exp2, любых, в том числе расчитанных на этапе выполнение и эквивалентен следующему коду:
Javascript |
1
2
3
4
| (function(exp1, exp2){
exp2.prototype = exp1
return exp2
})() |
|
Тут важно отметить:
Javascript |
1
| super("Ivan", "Petrov") |
|
super вызывает метод родительского класса (прототипа) с именем метода, из которого он был вызван, в данном случае - это
__construct, который и инициализирует экземпляр объекта.
ООП на закуску
Давайте создадим совершено новый тип данных, который будет работать как трехмерный вектор:
Javascript |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| 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 автоматически понимает, считывается свойство или устанавливается, и вызывает соответствующие методы.
Javascript |
1
2
3
4
5
6
7
8
9
| 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 |
|
Выведется:
Как же это реально работает? При чтении свойства
color ObjectScript ищет значение в объекте с именем
color. Если таковое найдено, то оно просто возвращается. Если нет, то ищется метод
__get@color, нашелся - отлично, значит ObjectScript вызывает и возвращает его результат. Если не нашелся, не беда, ObjectScript ищет метод
__get. Если таковой присутствует, то ObjectScript вызывает этот метод с именем запрошенного свойства. Если ничего не нашлось, то возвращается
null и точка.
При установке свойства, все происходит аналогично, но вместо
__get используется
__set. Еще один пример:
Javascript |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| 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 |
|
Выведется:
Тут показан новый оператор
delete, который удаляет значение в объекте и использование метода
__del.
Многомерные свойства
ObjectScript поддерживает следующий (обычный для некоторых языков) синтаксис, который является полностью эквивалентным:
Javascript |
1
2
| print a.color
print a["color"] |
|
А что, если в квадратных скобках передать несколько значений? В ObjectScript это вполне реально!
Javascript |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| 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)) |
|
Выведется:
Остается только обратить внимание на метод
__setdim, который первым параметром принимает новое значение, а в остальных параметрах - атрибуты свойства (их количество может быть любым начиная от двух).
Пустые свойства
А что на счет следующего кода?
Javascript |
1
2
3
| b = a[]
a[] = 2
delete a[] |
|
Вполне! ObjectScript в этом случае вызывает следующие методы соответственно:
__getempty,
__setempty,
__delempty. Программист может решить по своему усмотрению, как использовать этот функционал.
Заключение
На закуску несколько не отмеченных выше моментов.
При описании функции в блоке перечисления параметров, запятые ставить также не обязательно:
Javascript |
1
| print function(a b c){ return a + b * c }(1 2 3) |
|
Выведет
7
В функциях можно использовать
arguments - возвращает масив всех параметров, с которыми функция была запущена,
... (три точки) - масив дополнительных параметров, которые не описаны в объявлении функции.
Оператор
# вызывает для объекта применения метод
__len. Для строк он возвращает количество символов в строке, а для объектов и масивов - количество элементов. Также в классе Object заведено свойство:
Javascript |
1
| Object.__get@length = function(){ return #this } |
|
А т.к. все объекты, в том числе строка и масивы, унаследованы от
Object, то можно использовать свойство
length, например, в масивах, на манер JavaScript.
Из необычных математических операторов можно отметить
** - возведение в степень.
Из структурных конструкций на данный момент реализованы:
Javascript |
1
2
3
4
5
6
| 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 хранит в единственном экземпляре, это делается автоматически. Причем не важно, была строка получена на этапе выполнения или компиляции программы.
Локальные переменные имеют область видимости, например.
Javascript |
1
2
3
4
5
6
7
| var i = 1;
{
var i = i
i++
print i
}
print i |
|
Выведется
ObjectScript имеет два зарезервированных слова для целей отладки, первое -
debugger (как в JavaScript). При срабатывании
debugger программа остановится в дебагере, как при точке останова. Второе -
debuglocals, которое возвращает ассоциативный объект с названиями видимых из точки использования
debuglocals локальных переменых и их значений. Например:
Javascript |
1
2
3
4
5
6
7
| function(a){
var c = a * 2;
{
var c = a - 1
print debuglocals
}
}(10) |
|
Выведется:
ObjectScript разпознает tail call. Это когда внутри функции возвращается результат выполнения др. функции. В этом случае вызываемая функция может заместить call stack текущей функции, а не увеливать call stack, добавляя себя в него.
Преобразование выражений в тип
boolean происходит следующим образом: значения
null,
false и
NaN возвращают
false, все др. значения -
true, в том числе пустая строка и число 0.
Операторы
&& и
|| возвращают то значение, которые было им передано, например:
Javascript |
1
2
| print 7 && 9
print 7 || 9 |
|
Выведется:
При присваивании объектов, их копии не создаются, присваивается ссылка на сам объект. Чтобы создать копию, необходимо воспользоваться оператором
clone, который клонирует аргумент. Для объектов и масивов, этот оператор копирует все значения в новый объект, др. типы не клонируются по умолчанию, а возвращаются как есть. Но на финальной стадии процесса вызывается метод
__clone, в котором можно сделать что-то свое или дополнительное. Вопрос о спецификации данного метода пока не решен окончательно. Возможно есть смысл возвращать результатом клонирования то, что вернет метод
__clone. В этом случае программист сможет клонировать и свои внутренние типы данных, созданные с помощью
userdata, если пожелает.
Также есть
typeof,
valueof,
numberof,
stringof,
arrayof,
objectof,
functionof,
userdataof. Спецификация этих операторов прорабатывается.
Ну вот как-то так. Если вам ObjectScript кажется интересным, давайте разрабатывать и развивать язык вместе. Текущие задачи, над которыми я работаю - сборщик мусора, багфикс, оптимизация, документация, environment для функции, расширение синтаксиса, поддержка многострочных констант, компиляция в байт-код, компиляция в JavaScript (чтобы писать клиенты для веба). Предлагайте и комментируйте.
Стандарт языка в процессе формирования, поэтому любые идеи будут интересны.
Об интеграции с C++ расскажу в одной из следующих статей. Вкратце:
C++ |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| 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 скомпилировал исходник, это может потребоваться для лучшего понимания процессов, которые происходят внутри языка, например:
Javascript |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| [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 и записывать видео, вы можеет посмотреть некоторые из них по следующим ссылкам:
http://www.youtube.com/watch?v=OCWIfQYW9rc
http://www.youtube.com/watch?v=P5KPJOVSs3E
http://www.youtube.com/watch?v=htDqDNqHX-I
http://www.youtube.com/watch?v=wqiDeuf7yu8
http://www.youtube.com/watch?v=uep2SvXdCNU
в описании к видео указаны ссылки, от куда можно скачать полные исходники примеров.