Продолжим рассмотрение возможностей скриптового языка urbiScript от компании Gostai, предназначенного для управления роботами.
Напомню, что urbiScript выполняется на Urbi-сервере, который запускается либо на роботе, либо на компьютере и к которому можно подключиться через сеть простым telnet-ом.
Взаимодействие с аппаратной частью робота осуществляется через объекты UObject, которые выполняют роль драйверов (прослойка между Urbi и аппаратной частью робота).
Данные драйвера реализуются в виде библиотек (.dll), подключаются к серверу Urbi и далее могут прозрачным образом использоваться в urbiScript (вызывать функции, изменять переменные).
9. Объекты
Все используемые в urbiScript сущности являются объектами (и переменные определённые через var и строки).
Объект в urbiScript строится на простой концепции — список слотов.
Слот, в данном случае, — это значение связанное с именем.
Т.о. объект — это список названий слотов, каждое из которых индексирует значение (как словарь).
// Создаём объект с двумя слотами
class Foo
{
var a = 42;
var b = "foo";
};
[00000000] Foo
Чтобы получить список названий слотов объекта можно воспользоваться методом localSlotNames:
// посмотрим список слотов
Foo.localSlotNames;
[00000000] ["a", "asFoo", "b", "type"]
Значение пременной объекта можно получить, обратившись к ней через точку (.)
// зная имя слота, можно посмотреть его содержимое
Foo.a;
[00000000] 42
Foo.b;
[00000000] "foo"
Так же есть метод inspect, который распечатывает более подробную информацию о структуре объекта:
Foo.inspect;
[00000009] *** Inspecting Foo
[00000010] *** ** Prototypes:
[00000011] *** Object
[00000012] *** ** Local Slots:
[00000014] *** a : Float
[00000015] *** asFoo : Code
[00000016] *** b : String
[00000013] *** type : String
Все объекты urbiScript имеют своим родителем Object.
// содане переменной o как нового объекта
var o = Object.clone;
[00000000] Object_0x00000000
// посмотрим его содержимое
o.inspect;
[00006725] *** Inspecting Object_0x00000000
[00006725] *** ** Prototypes:
[00006726] *** Object
[00006726] *** ** Local Slots:
Получили пустой объект.
Обратите внимание, что объект выводится, как Object_ и далее следует шестнадцатеричное число — это уникальный идентификатор (есть у каждого urbiScript-объекта).
Приведённому выше пустому объекту можно динамически создать слоты, используя метод setSlot, который в качестве параметров принимает название слота и его значение:
выше создан слот a в котором хранится 42. Создать слот можно и другим способом — используя ключевое слово var:
// эквивалентно: o.setSlot("b", "foo").
var o.b = "foo";
[00000000] "foo"
o.inspect;
[00072678] *** Inspecting Object_0x00000000
[00072678] *** ** Prototypes:
[00072679] *** Object
[00072679] *** ** Local Slots:
[00072679] *** a : Float
[00072680] *** b : String
получить слот можно используя метод getSlot, который в качестве параметра принимает название слота.
Используя setSlot() и getSlot() можно динамически работать со слотами объекта.
function set(object, name, value)
{
// используем setSlot с динамическим названием слота
return object.setSlot("x_" + name, value);
} |;
function get(object, name)
{
// используем getSlot с динамическим названием слота
return object.getSlot("x_" + name);
} |;
var x = Object.clone;
[00000000] Object_0x00000000
set(x, "foo", 0);
[00000000] 0
set(x, "bar", 1);
[00000000] 1
x.localSlotNames;
[00000000] ["x_bar", "x_foo"]
get(x, "foo");
[00000000] 0
get(x, "bar");
[00000000] 1
Как не трудно догадаться чтобы изменить содержимое слота существует метод updateSlot, который принимает 2 параметра — название слота и новое значение:
Но здесь необходимо знать название слота, а updateSlot() позволяет изменить слот, название которого генерируется динамически.
Обратите внимание, что попытка создать слот с одинаковым именем вернёт ошибку:
var o.c = 0;
[00000000] 0
// нельзя переопределять слот
var o.c = 1;
[00000000:error] !!! slot redefinition: c
// изменение значения ошибку не вызовет
o.c = 1;
[00000000] 1
И разумеется слот можно удалить, используя метод removeSlot, который принимает в качестве параметра название слота:
Остаётся заметить, что попытки чтения, обновления, удаления слотов, которых не существует в объекте вернут ошибку:
o.d;
[00000000:error] !!! lookup failed: d
o.d = 0;
[00000000:error] !!! lookup failed: d
10. Методы
Метод в urbiScript — это слот объекта, содержащий функцию.
Дотуп к методу осуществляется так же через точку "."
Внутри метода можно использовать ключевое слово this, которое как и в С++ даст доступ к текущему объекту.
var o = Object.clone;
[00000000] Object_0x0
// сохраняем функцию 'f', как слот объекта 'o'.
function o.f ()
{
echo("This is f with target " + this);
return 42;
} |;
// получим содержимое слота f
o.getSlot("f");
[00000001] function () {
echo("This is f with target ".'+'(this));
return 42;
}
// вызовем метод
o.f;
[00000000] *** This is f with target Object_0x0
[00000000] 42
// можно и с круглыми скобками
o.f();
[00000000] *** This is f with target Object_0x0
[00000000] 42
Получается, что т.к. метод — это всего-лишь слот объекта, а вызов метода не требует обязательного наличия круглых скобок, то методом можно замещать аттрибуты объекта без изменения интерфейса объекта.
Чтобы узнать что же содержит слот — можно использовать метод getSlot:
// метод 'empty' возвращает true если строка пустая
"foo".empty;
[00000000] false
"".empty;
[00000000] true
"".getSlot("empty");
[00000000] Primitive_0xf20488
// посмотрим другой метод
"".getSlot("asList");
[00000000] function () {
split("")
}
"foo".size;
[00000000] 3
"foo".getSlot("size");
[00000000] Primitive_0x0
как видим метод asList — это метод-обёртка, который вызывает функцию split()
А методы empty и size — это методы-примитивы — функции реализованные на С++.
11. Модель переменных в urbiScript
У каждого объекта в urbiScript есть свой уникальный идентификатор. А так как все сущности urbiScript — это объекты, то и идентификатор есть у всех сущностей. Посмотреть его можно через метод uid:
var o = Object.clone;
[00000000] Object_0x100000
o.uid;
[00000000] "0x100000"
42.uid;
[00000000] "0x200000"
42.uid;
[00000000] "0x300000"
Как видно, введение целочисленного значения 42 порождает два отдельных объекта.
Чтобы продолжить разбираться далее нужно вспомнить полезные операторы сравнения: == возвращает симантическое равенство (сравнивается величины (содержимое объектов)) === возвращает физическое равенство (т.е. если два объекта имеют одинаковый uid):
var a = 42;
[00000000] 42
var b = 42;
[00000000] 42
a == b;
[00000000] true
a === b;
[00000000] false
Используя эти операторы можно легко убедиться, что в urbiScript при определении переменной или слота не происходит копирование значения (как в С или С++), а происходит связывание с уже существующим значением (как в Ruby или Java).
var a = 42;
[00000000] 42
var b = 42;
[00000000] 42
var c = a; // c связывается(указывает) с тем же объектом, что и a.
[00000000] 42
// a, b и c - равны: они имеют одинаково значение
a == b && a == c;
[00000000] true
// Но только a и c - фактически один и тот же объект
a === b;
[00000000] false
a === c;
[00000000] true
Получается, что a и c указывают на один и тот же объект(число 42) и модификации a затронут и c:
a.localSlotNames;
[00000000] []
b.localSlotNames;
[00000000] []
c.localSlotNames;
[00000000] []
var a.flag; // создание слота в a.
a.localSlotNames;
[00000000] ["flag"]
b.localSlotNames;
[00000000] []
c.localSlotNames;
[00000000] ["flag"]
Однако, обновление слота или локальной переменной не вызовет обновления связанного значения.
var a = 42;
[00000000] 42
var b = a;
[00000000] 42
// b и a указывают на число 42
a === b;
[00000000] true
// Обновление b не скажется на связанном значении 42,
// а просто b теперь будет ссылаться на число 51.
b = 51;
[00000000] 51
// Поэтому a остаётся неизменным:
a;
[00000000] 42
Понимание двух последних примеров очень важно!
На аргументы функций и методов так же можно ссылаться и модифицировать:
function test(arg)
{
var arg.flag; // добавляется слот аргументу arg
echo(arg.uid); // печатаем идентификатор uid
} |;
var x = Object.clone;
[00000000] Object_0x1
x.uid;
[00000000] "0x1"
test(x);
[00000000] *** 0x1
x.localSlotNames;
[00000000] ["flag"]
Однако, нужно очень внимательно подходить к такому изменению аргументов, т.к. поведение может быть не таким, как вы ожидаете:
function test(arg)
{
// обновляем локальную переменную arg,
// но это никак не сказывается на связанном аргументе
arg = 1;
} |;
var x = 0;
[00000000] 0
test(x);
[00000000] 1
// x не модифицируется
x;
[00000000] 0
12. Операторы if/while/for/switch
Операторы if/while/for аналогичны одноимённым операторам в С/С++.
12.1 if
if (true)
echo("ok");
[00000000] *** ok
if (false)
echo("ko")
else
echo("ok");
[00000000] *** ok
можно использовать внутри выражений:
echo({ if (false) "a" else "b" });
12.2 while
var x = 2;
[00000000] 2
while (x < 40)
{
x += 10;
echo(x);
};
[00000000] *** 12
[00000000] *** 22
[00000000] *** 32
[00000000] *** 42
12.3 for
for (var x = 2; x < 40; x += 10)
echo(x);
[00000000] *** 2
[00000000] *** 12
[00000000] *** 22
[00000000] *** 32
другой вариант использования for — передавать ему коллекцию переменных в виде в виде списка.
Т.е. сначала идёт for, затем var, идентификатор, двоеточие или in и выражение:
for (var e : [1, 2, 3]) { echo(e) }; //for (var e in [1, 2, 3]) { echo(e) };
[00000000] *** 1
[00000000] *** 2
[00000000] *** 3
12.4 break и continue
как и в С++ внутри циклов for и while можно использовать ключевые слова break и continue:
break — выполняет остановку цикла (переход за границу цикла)
continue — запускает следующую итерацию цикла.
var i = 5|
for (; true; echo(i))
{
if (i > 8)
break;
i++;
};
[00000000] *** 6
[00000000] *** 7
[00000000] *** 8
[00000000] *** 9
for (var i = 0; i < 8; i++)
{
if (i % 2 != 0) // если число нечётное
continue;
echo(i);
};
[00000000] *** 0
[00000000] *** 2
[00000000] *** 4
[00000000] *** 6
12.5 switch
Синтаксис оператора switch аналогичен С/С++, но в urbiScript он работает с любыми типами объектов, а не только с целочисленными. Сравнение аналогично оператору ==.
Так же не требуется использовать break;
switch ("bar")
{
case "foo": 0;
case "bar": 1;
case "baz": 2;
case "qux": 3;
};
[00000000] 1
12.6 do
оператор do позволяет выполнить сразу несколько действий над объектом.
Например, можно написать так:
var o1 = Object.clone;
[00000000] Object_0x100000
var o1.one = 1;
[00000000] 1
var o1.two = 2;
[00000000] 2
echo(o1.uid);
[00000000] *** 0x100000
, а можно то же самое реализовать через оператор do:
var o2 = Object.clone;
[00000000] Object_0x100000
// все сообщения в данной области предназначены объекту o2
do (o2)
{
var one = 1; // var - простой вызов setSlot
var two = 2;
echo(uid);
};
[00000000] *** 0x100000
[00000000] Object_0x100000
12.7 loop
ключевое слово loop по смыслу эквивалентно бесконечному циклу while(true)
{
var n = 10|;
var res = []|;
loop;
{
n--;
res << n;
if (n == 0)
break
};
res
}
==
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0];
13. Области и выражения
Области в urbiScript — это выражения и поэтому они могут быть использованы там, где ожидается выражение:
// области выполняются до последнего выражения внутри:
{ 1; 2; 3};
[00000000] 3
// область - это выражение
echo({1; 2; 3});
[00000000] *** 3
Внутри области можно использовать внутренние переменные:
var x = 0; // Define the outer x.
[00000000] 0
{
var x = 1; // определяется внутренняя переменная x.
x = 2; // изменение значения
echo(x); // внутренней переменной x
};
[00000000] *** 2
x; // внешний x не затрагивался
[00000000] 0
{
x = 3; // а вот это уже внешний x.
echo(x);
};
[00000000] *** 3
x;
[00000000] 3
14. Локальные функции
Функции могут быть определены в любом месте программы. Если они определяются внутри области, то и доступ к ним может быть только в данной области:
function max3(a, b, c) // максимальная из 3 переменных
{
function max2(a, b)
{
if (a > b)
return a
else
return b
};
max2(a, max2(b, c));
} | {};
max3(2,4,1); // проверим
[00000000] 4
15. Замыкания Замыкание — это захват функцией переменной внешней функции. urbiScript поддерживает замыкание, т.о. функция может ссылаться на внешнюю переменную пока та видна (в области).
function printSalaries(rate)
{
var charges = 100;
function computeSalary(hours)
{
// rate и charges захватываются
// из внешней функции
rate * hours - charges
};
echo("Alice's salary is " + computeSalary(35));
echo("Bob's salary is " + computeSalary(30));
} | {};
printSalaries(15);
[00000000] *** Alice's salary is 425
[00000000] *** Bob's salary is 350
Замыкание так же позволяет изменять внешние переменные:
var a = 0;
[00000000] 0
var b = 0;
[00000000] 0
function add(n)
{
// x и y обновляются замыканием
a += n;
b += n;
void
} | {};
add(25);
add(25);
add(1);
a;
[00000000] 51
b;
[00000000] 51
NB!
Стоит обратить внимание, что urbiScript испольует автоматический сборщик мусора(automatic garbage collection). Т.е. при создании нового объекта можно не заботиться о последующей очистке памяти. urbiScript подсчитывает количество ссылок на объект и в случае когда этот счётчик достигает нуля — объект удаляется.
{
var x = List.new; // новый объект списка
x << 42;
};
// за пределами области этого объекта уже нет.
Однако, как отмечается в документации, это свойство не является частью языка и автоматическая сборка мусора в будущем может быть изменена. Поэтому не стоит полагаться на текущее поведение сборщика мусора или детерминизм времени удаления объекта.
К тому же, текущий алгоритм уборки мусора не удалит объекты, если они ссылаются друг на друга.
Комментарии (0)
RSS свернуть / развернутьТолько зарегистрированные и авторизованные пользователи могут оставлять комментарии.