1. введение в urbiScript — часть 1
2. введение в urbiScript — часть 2
Продолжим рассмотрение возможностей скриптового языка 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, который в качестве параметров принимает название слота и его значение:
o.setSlot("a", 42); [00000000] 42 o.inspect; [00009837] *** Inspecting Object_0x00000000 [00009837] *** ** Prototypes: [00009837] *** Object [00009838] *** ** Local Slots: [00009838] *** a : Float
выше создан слот 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 параметра — название слота и новое значение:
o.a; [00000000] 42 o.updateSlot("a", 51); [00000000] 51 o.a; [00000000] 51
так же несложно догадаться, что можно поступить намного проще и использовать оператор =
o.b; [00000000] "foo" // эквивалентно: o.updateSlot("b", "bar") o.b = "bar"; [00000000] "bar" o.b; [00000000] "bar"
Но здесь необходимо знать название слота, а updateSlot() позволяет изменить слот, название которого генерируется динамически.
Обратите внимание, что попытка создать слот с одинаковым именем вернёт ошибку:
var o.c = 0; [00000000] 0 // нельзя переопределять слот var o.c = 1; [00000000:error] !!! slot redefinition: c // изменение значения ошибку не вызовет o.c = 1; [00000000] 1
И разумеется слот можно удалить, используя метод removeSlot, который принимает в качестве параметра название слота:
o.localSlotNames; [00000000] ["a", "b", "c"] o.removeSlot("c"); [00000000] Object_0x00000000 o.localSlotNames; [00000000] ["a", "b"]
Остаётся заметить, что попытки чтения, обновления, удаления слотов, которых не существует в объекте вернут ошибку:
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; }; // за пределами области этого объекта уже нет.
Однако, как отмечается в документации, это свойство не является частью языка и автоматическая сборка мусора в будущем может быть изменена. Поэтому не стоит полагаться на текущее поведение сборщика мусора или детерминизм времени удаления объекта.
К тому же, текущий алгоритм уборки мусора не удалит объекты, если они ссылаются друг на друга.
Читать далее: введение в urbiScript - часть 3