| Предыдущая глава |
↓ Содержание ↓
↑ Свернуть ↑
| Следующая глава |
шаблон и генерирующий. Если первый сопоставился с неким фрагментом строки1, то
он заменяется на фрагмент сгенерированный вторым по образчику. Например так
можно найти (и выделить) в строке S слово Вася (но только одно): S/"Вася" или
даже любое слово на В состоящее из русских буковок: S/"В*%р" — найдётся и
Вася и Ваня и Вагонетка (а вот от слова ВАННА — только первая буква В). Или
можно (из хулиганских побуждений) заменить все буквы Е на Э (и заглавные и
строчные), чтобы получилось как у Великого Комбинатора на пишущей машинке с
турецким акцентом: S*"ЕЭеэ" ("Значит, по Вашему я — начальник отдЭлЭния?
Свинья Вы, Шура после этого!"(Ц))
И вот тут — на общем случае генерирующих шаблонов, мы и приплыли! Нет, с
простыми частными случаями (например замена всех заглавных букв на строчные:
S*"%Р%р%Л%л" и русских и латинских) более-менее всё понятно. Но нам же хочется
самый общий! А в общем случае может быть что угодно. В частности оные шаблоны
(они же регулярные выражения) с помощью двух видов скобочек: () обозначающих
последовательность и [] — выбор, могут вкладываться друг в дружку на любую
глубину, в точности так же как выражения арифметические. (Конструкция (...)
срабатывает если последовательно сопоставились все находящиеся внутри шаблоны,
а [...] — если хотя бы один.) И всё это еще и с учетом префиксов повторения.)
Ну и что с этим делать? В смысле — как реализовать? Чтобы прояснить этот
вопрос, книжку пришлось писать. ("Фокал снаружи и внутри".) По принципу: не
знаешь сам — объясни другому! Глядишь, и у тебя самого в голове малость
прояснится. И таки да — помогло: с такого разбега, хотя и не с первой попытки,
но проблему генерирующих шаблонов я таки забодал...
* * *
А про ёлку-то ты не забыл, боярин дубовый?! (Ц)
У нас же типа пересказ, что там было в первой серии. А даже "канон" еще
не весь изложил!
А что осталось? Ну буквально же один аспект — про функцию, определяемую
пользователем. И, да — еще хотя бы перечислить операторы и функции:
Comment — коментарий: ничего не делает, игнорируется всё до конца строки
собственно вычисления
Set переменная = выражение; — присваивает значение выражения переменной
Xecut выражение; — только вычисляет, результат — теряется
(Надо бы eXecut — "выполнить", да буква Е занята под более важный Eraze.)
ввод/вывод
Аск список ввода; — ввод
Type список вывода; — вывод
Operate устройство; — переключение каналов ввода/вывода
обслуживание программы
Write номер_строки_или_группы; — вывод текста программы
Eraze номер_строки_или_группы; — удаление
Modify номер_строки_или_группы; — исправление (чтобы заново не вводить)
управление
Go номер_строки_или_группы; — безусловная передача управления
If (условие) N1, N2, N3; — условная передача управления
Do номер_строки_или_группы; — передача управления подпрограмме
Ret — возврат из подпрограммы
Quit — прекращение выполнения
For переменная = нач, кон, шаг; тело цикла до конца строки — цикл
дополнительно
? и ?? — включение/выключение трассировки и пошагового режима
Break — установить реакцию на внешнее событие
Kill — устроить такое событие, или хотя бы системный сброс
Vizual — нечто графическое
! командная строка операционной системы — обращение к ОС
Help ключевое_слово — справка
а так же
Load — подгрузить дополнительные функции (до сих пор не реализовано)
Provide — выделить место под очень большой массив (тоже не реализовано)
Job номер_строки_или_группы; — запустить впараллель (только планируется)
Да, это смесь того что сейчас есть, раньше было и возможно еще будет...
А функции...
математические: FSQrt FExp FLOg — корень, Е в степени Х, логарифм
FSIn FCOS FTAn — тригонометрические
FASin FACos FATan — обратные к ним
преобразования чисел: FABs FSGn — модуль, знак
FItr FMOd — целая и дробная часть числа
специальные: FRnd — генератор случайных чисел
FCHr — посимвольный ввод/вывод
FCLk — отсчет временных интервалов
FX — обращение к управляющим регистрам внешних устройств
FSBr — обращение к подпрограмме как к функции
Все подпрограммы что в Фокале что в Бейскике — "процедуры". Параметры, если
они им нужны, получают через глобальные переменные. (А они — все глобальные.)
Через них же, если что, могут вернуть результат. Но есть ситуации, когда
необходима именно функция, которую можно было бы вызвать из середины выражения.
Функция как раз и отличается от процедуры тем, что возвращает результат так,
что его можно сразу использовать для дальнейшего вычисления этого выражения.
Да и параметры (они же аргументы) в этом случае должны быть подвыражениями
того же выражения. И в Фокале и в Бейсике для этого есть "функция, определяемая
пользователем".
В Бейсике она объявляется заранее с помощью оператора DEF и вызывается
с помощью встроенной функции FNх где "х" первая буква имени, указанного при
объявлении. (Напоминаю: в те поры и переменные в Бейсике тоже распознавались
только по одной первой букве их имени.)
В Фокале для тех же целей — функция FSUbroutine (что значит "подпрограмма"),
но заранее ничего не объявляется: первый аргумент FSUb указывает куда передать
управление, а второй передаётся в качестве параметра — присваивается
спецпеременной с именем & которая может быть только одна, увы.
Но я в своей реализации разрешил передавать не один, а любое, произвольное
количество параметров. Которые становятся начальными значениями по-настоящему
локальных переменных с именами &0 (она же просто &), &1, &2, &3... К которым
к тому же можно обращаться как к одномерному массиву &[i]... И к тем же самым
переменным, равняясь на UNIX`овский sh, разрешил обращаться: $0, $1, $2... $[i]
(а на самом деле далеко не сразу вспомнил, как же в Фокале называется эта
чертова переменная — параметр подпрограммы).
Самое интересное здесь — как возвращается результат? Самое логичное, если
бы в качестве результата FSBr возвращалось то значение, которое вычислено в
операторе возврата из подпрограммы Ret. (Все и всегда так делают!) Ан,
фокаловскому оператору Ret выражение не положено. (Хотя я у себя разрешил.)
Более того — этого самого Ret может и не быть: "естественная подпрограмма"
вернёт управление и так — по достижении её конца... Оказалось, что
результатом FSBr становится значение последнего выражения, вычисленного перед
возвратом из вызванной ею подпрограммы. Вычисленное в любом операторе, в том
числе Xecut, который результат вроде как теряет...
Получается, что где-то в недрах Фокала есть скрытая переменная (назовём её
"аккумулятором" или А1), куда попадает результат любого выражения. Вот что там
оказалось на момент возврата, то и возвращается!
Намотаем это на ус: оказывается, Фокал страдает аккумуляторностью!
А1 — первый из обнаруженных аккумуляторов, но не единственный: потом были еще
найдены А2 и А3.
Если усов пока нет — можно приклеить или даже нарисовать.
А что касается глобальности/локальности переменной &, то она значима только
если из подпрограммы, вызванной с помощью FSBr мы вызовем еще такую же — тоже с
передачей ей параметра. Например пишем в командной строке:
1.1 t &; x fsbr(1.2, 222222); t & !
1.2 t &
x fsbr(1.1,111111);
Что будет напечатано? Если (как у меня): 111111 222222 111111 то
переменная & по-настоящему локальная, а если: 111111 222222 222222 то нет.
И тогда фигу нам с маком, а не рекурсия...
Рекурсия, это когда подпрограмма вызывает самоё себя, прямо или косвенно.
Полезная штука, совершенно необходимая для обхода дерева. Не путешествия
вокруг берёзы или например сосны, а что-то типа разбора арифметического
выражения: дерево здесь это граф без циклов, каковым это выражение удобно
представляется: операции — вершины (узлы), связи их с операндами — веточки, а
сам такой операнд — либо листик, если это константа или переменная, либо тоже
узел, если это подвыражение, заключенные в скобочки. Или обращение к функции
с параметрами, для вычисления которых тоже задействованы подвыражения. Самая
первая вершина, из которой и растёт дерево, принято называть "корнем". Вот
например для: 1+2+3 или 1+(2+3) или (a+1)*(b-2)/c^3 или -sin(x)+cos(2*x)
+ + / +
дерево: ┌┴┐ ┌┴┐ ┌──┴──┐ ┌──┴──┐
+ 3 1 + * ^ — cos
┌┴┐ ┌┴┐ ┌─┴─┐ ┌┴┐ │ │
1 2 2 3 + — c 3 sin *
┌┴┐ ┌┴┐ │ ┌┴┐
Ха, все деревья — корнями вверх! a 1 b 2 x 2 x
Соорудить нечто подобное можно и здесь, в Фокале. Да, ни структур ни
ссылок нету... (Вершины обычно изображаются структурами, а веточки — ссылками
из них друг на друга...) Ну и что? Массивы-то у нас здесь — динамические! Ну
для разбора выражения надо со строками и/или отдельными символами работать...
(Тоже можно, но пока сложно и громоздко получится.) А вот пусть нам вводят
числа, а мы должны их запоминать (это запросто: массивы то — динамические),
а вот как все ввели — мы должны выдать их в отсортированном порядке!
Вот такая задачка. Основная проблема здесь — узнать, что числа кончились.
А вот пусть среди них не может быть числа ноль. Вернее ноль — обязательно
последний. Да, можно тупо запоминать числа в элементы массива, а потом, перед
выдачей, просто отсортировать его. Ну хотя бы методом "пузырька"...
1.1 T "вводите числа, 0 — последнее"!; S N=-1;
1.2 S N=N+1; A x[N]; If(x[N]) 1.2, 1.3, 1.2
1.3 If(N) 1.4,1.4; D 2; D 3; T !"Всё."!; Q
1.4 t "Чего же сразу-то ноль? Сортировать нечего!"!; Q
2.1 S j=0; C сортировка методом пузырька — самая тривиальная...
2.2 S j=j+1; S k=0; F i=0,N-j,1; S y=x[i]; If(x[i+1]-y) 2.7;
2.3 If(-k) 2.2; R; C k — признак, что хоть одну пару переставили местами
2.7 S x[i]=x[i+1]; S x[i+1]=y; S k=k+1; C вот как раз это обмен такой пары
3.1 F i=0,N; T x[i]; C тупо выдаём содержимое массива как есть
...Но мы будем сортировать их сразу в процессе получения: строить
бинарное дерево. Корень — всегда нулевой элемент массива (то есть самое первое
полученное число); m[] и b[] указывают на поддеревья, где все числа меньше и
больше вот этого, а -1 там означает что таковых пока нет. (Ибо номера
элементов массива — с нуля.)
1.1 T "вводите числа, 0 — последнее"!; S N=-1;
1.2 A y; D 2; If(y) 1.2, 1.3, 1.2; C помещение в массив вынесли в группу 2
1.3 If(N) 1.4,1.4; X FSBr(3,0); T !"Всё."!; Q
1.4 t "Чего же сразу-то ноль? Вот даже выдавать нечего..."!; Q
C не только x[]=y но и ссылки на поддеревья устанавливаем...
2.1 S N=N+1; S x[N]=y; S m[N]=-1; S b[N]=-1; S i=0; If(-N)2.2; R;
2.2 S z=x[i]-y; S j=b[i]; I(z) 2.3; S j=m[i]
C то же самое на Си: j = (y>=x[i]) ? b[i] : m[i]; по-моему куда понятней!
2.3 If(j) 2.4; S i=j; G 2.2; C спуск по дереву линейный — можно без рекурсии
2.4 If(z) 2.5; S m[i]=N; R; C очередное число повесим как "листик"
2.5 S b[i]=N; R;
C Вот он — обход дерева с помощью рекурсии. Почти в одну строчку...
3.1 If(&) 3.2; X FSBr(3,m[&]); T x[&]; X FSBr(3,b[&]);
3.2 R
Надо ли тут чего разжевывать, или и так всё понятно? Ну разве что — что
квадратные скобки при обращении к массиву — выпендрёж чистой воды: это не Си,
можно любые... Впрочем, и построение бинарного дерева вместо сортировки вот для
такой задачки — тоже выпендрёж. А вот его обход (уж коли у нас дерево) — нет:
он возможен только рекурсией. (Но штука таки дорогая — стэк жрет неумеренно...)
Но обычно в качестве примера на рекурсию все и всегда приводят вычисление
факториала: произведения всех чисел от 1 до N (что обычно обозначается как N!)
потому что видите-ли: (N+1)! = (N+1)*N! Они что — опухли?! Здесь дерево
вырождается в линейную последовательность, для обхода которой достаточно
обычного цикла. Однако вместо того, чтобы написать:
s X=1; f i=2,N,1; s X=X*i; C и всё: X==N! — осталось только напечатать
t N,"! = ",X
и поискать другой, более содержательный пример, сооружают нечто типа:
4.1 if(1-&)4.2; x 1; r
4.2 x &*fsbr(4,&-1); r
t N,"! = ",fsbr(4,N)
Как ни странно, это будет работать, даже если переменная & не локальная.
А вот если бы написали не &*fsbr(...) а fsbr(...)*& — вот тогда нет! Понятно,
почему?
Разумеется, у меня локальные переменные тоже создаются автоматически в
момент первого присваивания. Но уничтожаются при выходе из подпрограммы. При
этом становится доступным предыдущий комплект, если он был. Кроме того, если
данная локальная переменная не существует — это не ошибка, как для глобальной:
она ищется в предыдущих комплектах, а если так и не найдена — то будет ноль.
* * *
И вот теперь, когда с "каноном" — "базовым", исходным Фокалом, надеюсь,
всё более-менее понятно, краткое содержание первой части сего опуса.
(Напоминаю: опус это сам Фокал-1А.)
Всё вышеописанное я либо просто воспроизвёл (сохранив и то, что обычно
считают недостатками: всего два значащих символа в имени переменной и "вето"
на букву Ф), либо немножко модернизировал. Прежде всего уточнил и дополнил
концепцию ввода/вывода, в том числе в части взаимодействия с файловой
системой ОС. Ну и так, кое-что по-мелочи — разные удобства и красивости:
— оператор Help для получения справок; просмотрщик к нему (аналог
UNIX`овского more) чтобы их с удобством просматривать; "экранный" редактор
командной строки со всяческими прибамбасами, чтобы с удобствами её набирать.
Или править с помощью оператора Modify. Работающего поэтому только и
исключительно с терминалом, независимо от того куда направлены каналы ввода и
вывода.
— вот эти самые по-настоящему локальные переменные — как позиционные
параметры при вызове подпрограммы с помощью FSUbr, так и сами по себе. И плюс
к ним — произвольное количество параметров не только у FSUbr но и у других
встроенных функций (де лишние будут просто игнорироваться). А так же
возможность пропуска параметра или параметр в виде текстовой константы — для
расширения функциональности без увеличения количества этих самых встроенных
функций. Например FSQrt(X) — просто квадратный корень, а FSQrt(X,N) — корень
N-ой степени; FLOg(X) — логарифм натуральный, а FLOg(X,N) — по основанию N.
Или суровая и опасная функция FX( действие, адрес, параметр ) производящая
действия с управляющими регистрами внешних устройств (каковых действий всего
| Предыдущая глава |
↓ Содержание ↓
↑ Свернуть ↑
| Следующая глава |