Ruby и special/predefined variables
15 Jan 2014Не так давно я обнаружил интересный пример в одной замечательной книге. С этого примера, я бы и хотел начать наш разговор.
Выглядел примерно так:
Так как до этого я не часто встречался с подобными “глобальными” переменными, пример заинтересовал меня и захотелось выяснить, что же это за переменные. Первым делом, я решил узнать, как они называются и где их можно найти. Спустя несколько минут, стало ясно, что это так называемые “special variables”. Не долго думая и открыв google, просмотрев пару тройку результатов, стало ясно, что кроме списка этих переменных ничего особо нет. Это не сильно меня обрадовало и стало понятно, что пришло время открыть репозитарий ruby и начать искать в нем. Так же мне очень помогла одна небезизвестная книга. Как оказалось, ruby создает несколько специальных переменных, в зависимости от откружения, в котором запускаются программы, или в зависимости от действий, которые были выполены ранее. Кстати, это не совсем глобальные перменные, в чем легко можно убедиться, рассмотрев простой пример:
Как видно из примера, в каждом scope (main и метода), “глобальная” переменная отличается. Любой адекватный человек спросит: как такое, тысяча чертей, возможно? На самом деле все довольно просто, но, для полного понимания, начать придется с основ. Как многие знают, начиная с верисии 1.9 в ruby была добавлена виртуальная машина или YARV или же yet another ruby virtual machine, называйте как хотите, суть одна и та же. Смысл в том, что каждый раз, при запуске, YARV так же создает особый стек, для локальных переменных. В этом стеке указываются абсолютно все локальные переменные, свои для каждого scope. Разделение scope-ов происходит с помощью специальной точки или указателя - environment point (далее EP). Так же, в стеке, перед каждой EP, создается специальная переменная svar, которая как раз и указывает на таблицу специальных символов. Именно из-за этого для каждого scope могут быть свои значения специальных символов, что мы видели в примере выше. Но самое интересное, что у обычного блока и у места, где он будет вызван, scope одинаковый, в чем можно легко убедиться благодаря такому примеру:
На самом деле это логичное поведение, ибо замыкания никто не отменял. Как я уже говорил, таких переменных много, но расскажу я о самых интересных(естественно для себя):
$&
Переменная, с которой начался наш рассказ. Хранит, как вы уже догадались, результат последнего совпадения регулярного выражения.
$1 $2 $3 …
Думаю, многим знакомая похожая переменная из регулярных выражений. Хотя, кого я обманываю? Это та же самая перменная, которая хранит совпадения из скобок:
$~
Содержит объект класса MatchData, соответствующий последнему совпадению.
$+
Содержит значение последней круглой скобки из последнего совпадения:
$`
Содержит все то, что не совпало в последнем регулярном выражении:
$!
Содержит последнее вызванное исключение:
$@
Ну а эта переменная содержит массив со всеми trace stack-ами из последнего исключения:
$*
Эта переменная равносильна переменной ARGV, думаю этим все сказанно.
$$
Переменная возвращает номер процесса, под которым выполняется скрипт.
.
Так где же определены эти переменные в исходном коде ruby? Как оказалось, все не так сложно, как кажется. Определенны эти переменные в файле parse.y примерно на 7950-той строке (да да, файл не очень большой, всего 11.5к строк кода). Для тех, кто не в курсе, parse.y - грамматический файл интерпритатора, благодаря которому происходит разбиение написанного вами кода на токены (лексемы/указатели), которые в последующем преобразуются в AST структуру, а затем в YARV структуру, ну а дальше в машинный код, который в последующем и будет выполняется. Как не трудно заметить, case функция ищет совпадение символа “$” и специальных символов (блок case), после чего передает их функции set_yylval_name:
И в завершение, следует упомянуть особый файл - English.rb, в котором прописаны алиасы для специальных переменных, благодаря чему можно использовать данные переменные намного понятнее, нежели чем использование $$, $& и так далее: