четверг, 27 января 2011 г.

[Из песочницы] Программируем в dc (desktop calculator)

Я часто люблю отдыхать, изучая новые языки или технологии. И не важно, что я их, возможно, никогда и не применю на практике. Занимаюсь я этим преимущественно из простого любопытства и желания узнать что-то новое.

Отчасти, по этой же причине, возникли эзотерические языки программирования. Brainfuck, Befunge и прочие весьма хорошо разгружают мозг после умственной работы, да и просто позволяют интересно и с пользой провести время. Да, именно с пользой, ведь они развивают нестандартное программистское мышление, ставя его в сложное положение жестких ограничений возможностей языка, операторов, и, как правило, ужасного нечитабельного синтаксиса. Да, они неприменимы в реальной практике, но, тем не менее, большинство из них обладают тьюринговской полнотой, следовательно, вполне могут считаться полноценными языками программирования.
Кстати, в ближайшее время планируется серия постов по некоторым эзотерическим языкам.

Однако, в данный момент меня заинтересовало немного другое. Итак, многим из вас наверняка известен арифметический пакет dc, на всякий случай приведу цитату с википедии:
dc — пакет для арифметических вычислений с произвольной точностью в unix-системах. Обычно он оперирует в десятичной системе счисления с целыми числами, однако можно задать системы счисления для ввода и вывода, а также точность вычислений. Общая структура dc — стековый калькулятор, использующий обратную польскую запись.

В вышеприведенной цитате мы видим волшебное слово – стек. И да, действительно, в dc реализована полноценная стековая машина, с возможностью сохранения и извлечения значений из именованных регистров, условиями, переходами. Хм, а что нам мешает попробовать программировать на этом? Поехали! (© Гагарин Ю. Н.).

dc имеет ограниченный набор команд, перечислять их здесь не вижу смысла, желающие могут ознакомиться с ним все там же.

Итак, как уже было сказано выше, dc использует обратную польскую запись, что значит, что в отличии от привычной инфиксной записи, сначала задаются операнды, а потом операция для них. Парочка примеров обратной польской записи с указанием эквивалентных им инфиксных записей:
2 2 * <=> 2 * 2
5 3 7 + * <=> 5 * (3 + 7)

Думаю, все должно быть понятно.

Вы уже наверняка ознакомились по вышеприведенной ссылке с синтаксическими конструкциями dc. Нетрудно догадаться, что использование [...] совместно с sx, а в дальнейшем lx совместно с x, позволяет нам эмулировать поведение функций. Ну а параметры им можно передавать посредством помещения в стек.

Для выполнения приведенного в посте кода, достаточно в консоли набрать команду
dc -e «код»

Например:
dc -e «2 3^p»

В дальнейшем, буду приводить лишь код. Для удобства, дабы можно было разбивать код на логические блоки и комментировать его, где необходимо, буду приводить код в многострочном виде. Для его выполнения есть несколько способов:
dc -f «имя_файла_куда_сохранили_код»

  1. dc -«2 \\+p»

И да, комментарии я буду оформлять в C-стиле – //. Они, разумеется, в код попасть не должны.

Итак, попробуем, для начала, написать что-нибудь простенькое. Например, выведем числа от 1 до 10.
  1. [ // начало цепочки символов
  2.   lap // печатаем содержимое регистра a
  3.   1+sa // инкрементируем регистр a
  4.   10la!>// сравниваем содержимое регистра a с 10,
  5.     // если меньше или равно десяти - продолжаем выполнение,
  6.     // рекурсивно вызывая на выполнение содержимое регистра f
  7. ]sf // конец символьной цепочки, помещаем ее в регистр f
  8. 1sa // задаем начальное значение регистру a
  9. lfx // выполняем содержимое регистра f

Небольшое примечание. Оператор “!>” эквивалентен “<=”, поскольку “!>” интерпретируется как “не больше”, а раз не больше, то меньше либо равно. В остальном же, все должно быть понятно из комментариев.
Инлайн версия:
[lap1+sa10la!>f]sf1salfx

Чуть усложним пример. Пускай верхнюю границу диапазона выводимых чисел задает пользователь. Таким образом, будем выводить числа в диапазоне [1..n], где n – число, введенное пользователем. Да, и добавим проверку: работаем только если n >= 1. Следует, конечно, вывести еще какое-нибудь сообщение об ошибке, но это сделаем чуть позже.
  1. [
  2.   lap
  3.   1+sa
  4.   lnla!>// теперь сравниваем не с 10, а с содержимым регистра n
  5. ]sf
  6. 1sa
  7. ?sn // помещаем пользовательский ввод в регистр n
  8. laln!<// если n >= a, вызываем f

Инлайн версия:
[lap1+salnla!>f]sf1sa?snlaln!<f

Таки добавим теперь обещанный вывод текстовых сообщений: приглашение к вводу верхней границы выводимых чисел и сообщение об ошибке (если n < a):
  1. [
  2.   lap
  3.   1+sa
  4.   lnla!>f
  5. ]sf
  6.  
  7. [
  8.   [Error! N must be greater or equal than a register value!]P
  9.   10P // перевод на новую строку (как мы помним, \n = 10)
  10.   q // прекращаем выполнение
  11. ]se // «функция» вывода сообщения об ошибке
  12.  
  13. 1sa
  14. [Enter max number for print: ]// печатаем приглашение для ввода
  15. ?sn
  16. laln<// если n < a, вызываем e
  17. lfx // успешно прошли предыдущую проверку, вызываем f

Инлайн версия:
[lap1+salnla!>f]sf[[Error! N must be greater or equal than a register value!]P10Pq]se1sa[Enter max number for print: ]P?snlaln<elfx

Ну, и напоследок, еще один пример. Пусть требуется вывести n первых чисел Фибоначчи, где n, как и в предыдущем примере, задается путем ввода пользователем и должно принадлежать диапазону [0..∞]. Задача поставлена, пишем.
  1. // функция вывода сообщения об ошибке (в случае, если n < 0)
  2. [
  3.   [Error! N must be greater or equal than zero!]P10P // печатаем...
  4.   q // … и выходим
  5. ]se
  6.  
  7. // функция выхода
  8. [
  9.   q
  10. ]sQ
  11.  
  12. // выводит первое число Фибоначчи
  13. [
  14.   0p
  15.   q
  16. ]sZ
  17.  
  18. // выводит два первых числа Фибоначчи
  19. [
  20.   0p1p
  21.   q
  22. ]sO
  23.  
  24. // выводит [2..n] числа Фибоначчи
  25. [
  26.   lfls+// суммируем два предыдущих числа
  27.     // и выводим результат
  28.   // меняем два предыдущих числа
  29.   lssf
  30.   ss
  31.  
  32.   lc1+sc // инкрементируем счетчик
  33.   lnlc<// если не достигли n - рекурсивно вызываем I
  34. ]sI
  35.  
  36. [
  37.   // всякие условия, все очевидно
  38.   0ln=Q
  39.   1ln=Z
  40.   2ln=O
  41.  
  42.   0psf
  43.   1pss
  44.   2sc
  45.   lnlc<I
  46. ]sF
  47.  
  48. [Enter the count of Fibonacci numbers to print: ]// приглашение ввода n
  49. ?sn // считываем пользовательский ввод и сохраняем его в n
  50. 0ln<// если n < 0, выводим соответствующее сообщение
  51. lFx // вызываем F

Инлайн версия:
[[Error! N must be greater or equal than zero!]P10Pq]se[q]sQ[0pq]sZ[0p1pq]sO[lfls+plssfsslc1+sclnlc<I]sI[0ln=Q1ln=Z2ln=O0psf1pss2sclnlc<I]sF[Enter the count of Fibonacci numbers to print: ]P?sn0ln<elFx

Магия, не иначе.

Тема программирования на dc, несомненно, будет продолжена мною, ибо интересна. В ближайшее время также ожидаются посты по эзотерическим языкам Brainfuck и Befunge. Совсем скоро…


Источник: Хабрахабр - Ненормальное программирование
Оригинальная страница: [Из песочницы] Программируем в dc (desktop calculator)

Комментариев нет:

Отправить комментарий