| Предыдущая глава |
↓ Содержание ↓
↑ Свернуть ↑
| Следующая глава |
целей) решил сперва немножко развлечься — сделать слегка "доведенную до ума"
реализацию традиционного Фокала (назвав это Фокал-1А). Вот до сих пор и
развлекаюсь...
Идея там была вот какая: строка + строка это их сцепление ("конкатенация");
строка * число — это размножение строки, дублирование указанное числом
количество раз. В обратном порядке — то же самое. Потому что от перестановки
сомножителей... (Разница появляется если это самое "число раз" — не целое.)
Если же число отрицательное, то строка инвертируется. Унарный минус — чисто
инверсия строки (т.е. буковки в обратном порядке), а унарный плюс — получение
длины (т.е. результат — число). (А для комплексного числа унарный плюс это
получение комплексно-сопряженного.) Соответстенно деление строки на число это
её разрезание. Вернее — взять часть строки с указанного символа, которые
нумеруются с первого, чтобы на ноль тоже было делить нельзя. В обратном
порядке (т.е. число / строка) можно — результат будет ноль или указанное
числом количество символов. Если число отрицательное, то с конца.
Вычитание строк это их сравнение: поиск позиции, с которой одна строка
входит в другую: числа в операторе If сравниваются именно так! Если вторая
строка "больше" — результат будет отрицательный, а если ни одна из них не
часть другой — то ноль.
Вычитание значения из самого себя это еще и возможность определить его тип:
для числа получится ноль, для строки — единица (она же сама в себя входит с
первой позиции!), а вот для "пустого" значения результат будет -1.
Это самое пустое значение при любых других операциях даёт ошибку, а содержится
в неинициализированной переменной, в том числе локальной, если был пропущен
параметр соответствующий её позиции.
Сложение и вычитание строки и числа я было хотел запретить — сделать чтобы
они давали ошибку, но если трактовать число как код символа и соответственно
числовое значение как односимвольную строку... Встроенная в Фокал функция FCHr
делает именно так, значит можно и тут.
Операция ^ и для чисел самая трудозатратная, так пусть такой же будет и
для строк: "полукорреляция" — сопоставление двух строк на предмет поиска общей
подстроки. Вернее самой длинной из них, буде совпадений окажется несколько.
А "полу" потому, что неполная. А то полная корреляция строки с собою найдёт её
всю целиком. (А то мы не знаем, что совпадает сама с собою!) Нам вообще-то надо
повторяющийся фрагмент: (S/2)^S (здесь мы в первом операнде отрезали от S
первый символ — теперь не совпадёт). А коли так уж нужна полная — извольте:
fmax(S1^S2,S2^S1).
Ну что можно сказать? Всё вроде бы логично, разве что, с одной стороны,
слишком много внимания не больно-то реально нужным реверсу строки и её
размножению (но это диктуют имеющиеся у нас операции), а с другой — всего этого
явно недостаточно: возможных действий со строками куда больше...
Например буковки бывают заглавные и строчные, а так же русские и латинские.
При сравнении двух строк (в том числе на полное равенство: (S1-S2)*(S2-S1)) в
одних случаях все их надо считать разными, а в других — совпадающими. Ну и как?
(Ввести некие внутренние признаки эквивалентности, устанавливать и сбрасывать
их некоторой спецфункцией? Некрасиво получается...) И это далеко не
единственное...
Вон в Бейсике ввели для работы со строками кучу функций, "поручив"
встроенным операциям только самое очевидное, типа конкатенации. Нам такого
щастья и даром не надь... Сложно, громоздко и необозримо даже с помощью
встроенной справочной системы.
В общем оставались у нас две "неокученные" операции: умножение и деление
строки на строку. Вот и решено было, что второй операнд в них — не простая
строка, а специальная — составленная по некоторым правилам. Строка1 / строка2
это будет сопоставление с шаблоном, а строка1 * строка3 — "перекодировка".
Здесь строка3 это последовательность ПАР шаблонов: включающих анализирующий
шаблон и генерирующий. Если первый сопоставился с неким фрагментом строки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 (что значит "подпрограмма":
"sub" это "под", а "routine" — рутина и есть!), но заранее ничего не
объявляется: первый аргумент 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 там означает что таковых пока нет. (Ибо номера
| Предыдущая глава |
↓ Содержание ↓
↑ Свернуть ↑
| Следующая глава |