99

У американцев есть такая считалочка — «песня о пиве», наверняка её многие слышали в американских фильмах. У меня есть хобби — делать вывод этой считалочки на разных языках программирования. Со временем набралась приличная коллекция.


99 бутылок: ПРОЛ-2

Иван Черняков, которого я консультировал в процессе создания фильма о программируемом калькуляторе «Электроника МК-61», выпустил новую увлекательную документалку — про «Буран» и его программное и аппаратное обеспечение.

«Буран» решал задачу многоразового возвращаемого орбитального корабля: вывести аппарат на орбиту, доставить людей или груз, выполнить работу в космосе и вернуть корабль на Землю для потенциального повторного использования.

В фильме упомянуты несколько предметно-ориентированных языков программирования, созданных в СССР специально для написания программ, некогда управлявших полётом «Бурана». Один из них — «ПРОЛ-2», программы на котором Ивану найти не удалось, поэтому он попробовал его реконструировать при помощи нейросети.

Разумеется мне немедленно захотелось найти хоть какие-то подробности про этот язык, чтобы написать на нём свою традиционную «песню про пиво». Вчера облазил весь интернет и на одном из форумов мне повезло найти одну из тестовых программ, написанных на языке «ПРОЛ-2». Она и несколько упоминаний языка в других источниках помогли мне побольше узнать о том, что представлял собой язык, на котором писались программы для «Бурана».

Итак, судя по сохранившемуся тексту, «ПРОЛ‑2» — императивный язык управления для системы реального времени. Современному программисту его запись напомнит Паскаль: присваивание :=, блоки НАЧАЛО и КОНЕЦ, явно объявляемые типы, условия и циклы, но с русскими ключевыми словами.

Помимо обычных вычислений язык поддерживал процессы и исполнителей, выдачу команд оборудованию, ожидание событий, паузы и обработку прерываний. Характерная особенность — в объявлениях нелокальных переменных отдельно перечислялись процедуры, которым разрешалось их читать и изменять: ограничения доступа входили непосредственно в описание данных.

Вот какая «песня» у меня получилась:

% ПЕСНЯ О ПИВЕ. НАПИСАНА СТЕПАНИЩЕВЫМ Е. В. HTTPS://BOLKNOTE.RU
МОДУЛЬ ПИВО ТЕСТ;
ПРОЦЕСС ПИВО;
ПЕСНЯ:1T;
КОНЕЦ
ИСПОЛНИТЕЛЬ ПЕСНЯ;
N:ЦЕЛ(0..99);
НАЧАЛО
    N:=99;
    <<КУПЛЕТ>>
    ПЕЧАТЬ(/,N,' БУТЫЛОК ПИВА НА СТЕНЕ',/);
    ПЕЧАТЬ(N,' БУТЫЛОК ПИВА!',/);
    ПЕЧАТЬ('ВОЗЬМИ ОДНУ, ПУСТИ ПО КРУГУ',/);
    N:=N-1;
    ПЕЧАТЬ(N,' БУТЫЛОК ПИВА НА СТЕНЕ!',/);
    ЕСЛИ N>0 ТО НА КУПЛЕТ;
    ПЕЧАТЬ(/,'НЕТ БУТЫЛОК ПИВА НА СТЕНЕ!',/);
    ПЕЧАТЬ('НЕТ БУТЫЛОК ПИВА!',/);
    ПЕЧАТЬ('ПОЙДИ В МАГАЗИН И КУПИ ЕЩЁ,',/);
    ПЕЧАТЬ('99 БУТЫЛОК ПИВА НА СТЕНЕ!',/);
    ВЫЙТИ ПЕСНЯ;
КОНЕЦ

Почти все строки, думаю, понятны, я бы только одну строку прокомментировал — ПЕСНЯ:1T. Эту конструкцию я перенёс из найденного листинга, но что она точно значит, я сказать не могу. Похоже на выделение тактов для выполнения параллельных подпрограмм. Но так ли это, пока неизвестно.

2 комментария

99 бутылок: Basic в UNIXv4

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

На нём я и решил написать «Песню о пиве».

86. Basic в UNIXv4 работает в двух режимах — интерактивном, в нём операторы вводятся без номера и выполняются сразу же, и программном, где номер строки указывается, а оператор запоминается для последующего выполнения. Этими двумя режимами можно пользоваться вперемешку.

Редактора нет, набивать программу нужно сразу в буфер интерпретатора, либо создавать при помощи стороннего редактора. Важно не ошибиться, так как редактировать программу можно только полной заменой строки, указав новому варианту тот же номер.

Почти во всём это довольно стандартный «Бейсик», но тут есть две особенности.

Во-первых, строки программы можно вызывать, как подпрограммы, прямо по номеру. Причём параметры передаются не через глобальные переменные, как обычно, а получаются при помощи функции arg(…). То есть 2000(i) — это вызов подпрограммы, которая начинается с номера 2000 и заканчивается, когда встретит первый попавшийся return, причём return может вернуть значение, если это требуется.

Во-вторых, логические выражения порождают нули и единицы, их можно использовать в арифметических выражениях и передавать, например, в goto. Так умеет далеко не каждый «Бейсик». А благодаря тому, что операторы вычисляются слева направо, можно писать вот такое: 1 < 3 < 2. Будет ноль.

У меня в коде есть пугающая конструкция goto 2110 + 10 * ((arg(1) > 0) + (arg(1) > 1)). Выглядит страшно, признаюсь, я её специально запутал, так, мне кажется, лучше передаётся дух эпохи, судя по старым программам, так вполне могли тогда написать.

На самом деле тут всё просто: выражение в скобках для нуля даёт ноль, для единицы — единицу, а для значения больше единицы — двойку. Это нужно для условного перехода на разные части подпрограммы, так как строкового типа нет и единственный способ вывести нужную строку — передать её в print как есть.

Кроме числового типа (числа с плавающей точкой тоже поддерживаются), есть массивы, причём даже двумерные с индексами от нуля до 32768.

10 for i=0 98
20 2000(99-i)
30 2100(98-i)
40 print
50 next
60 print "No more bottles of beer on the wall, no more bottles of beer."
70 print "Go to the store and buy some more, 99 bottles of beer on the wall."
80 done

2000 if arg(1) == 1 goto 2030
2010 print arg(1) " bottles of beer on the wall, " arg(1) " bottles of beer."
2020 return
2030 print "1 bottle of beer on the wall, 1 bottle of beer."
2040 return

2100 goto 2110 + 10 * ((arg(1) > 0) + (arg(1) > 1))
2110 print "Take one down and pass it around, no bottles of beer on the wall."
2115 return
2120 print "Take one down and pass it around, 1 bottle of beer on the wall."
2125 return

2130 print "Take one down and pass it around, " arg(1) " bottles of beer on the wall."
2135 return

run

Программу, которая у меня получилась и её вывод я заскриншотил для истории. Интересно, конечно, когда в последний раз на этом «Бейсике» кто-то что-то писал, кроме меня.

Скриншот терминала с программой на BASIC, реализующей песню «99 бутылок пива на стене», и её выводом: цикл от 99 до 0 с фразами «Take one down and pass it around».

2 комментария

«99 бутылок» и «Постгрес»

Недавно моему сайту исполнилось 25 лет и я вспоминал свою первую заметку, с которой всё началось. Она положила начало традиции, которая длится столько, сколько существует этот сайт — писать «песню о пиве», это такая американская считалочка, на различных языках программирования.

Когда я пересматривал ту заметку, обратил внимание насколько сложно написана была «песня о пиве» на языке СУБД «Постгрес». Это был 2000-й год, седьмая версия, не исключено что тогда проще было и нельзя. Но прошедшие десятилетия «Постгрес» сильно изменился, хочу показать насколько проще можно сейчас написать то же самое:

(
    WITH phrase(ph) AS (
        SELECT COALESCE(NULLIF(beer, 0)::text, 'no') || ' bottle' ||
            CASE beer WHEN 1 THEN '' ELSE 's' END || ' of beer'
        FROM generate_series(99, 0, -1) AS c(beer)
    )
    SELECT ph || ' on the wall, ' || ph || '.' || e'\n' ||
        'Take one down and pass it around, ' ||
        LEAD(ph) OVER() || e' on the wall.\n' AS " "
    FROM phrase LIMIT 99
)
UNION ALL SELECT
'No more bottles of beer on the wall, no more bottles of beer.
Go to the store and buy some more, 99 bottles of beer on the wall.';

7 комментариев

99 бутылок: JOSS

Продолжают писать американскую считалочку «песня о пиве» на всяких странных и не очень языках программирования.

85. JOSS (JOHNNIAC Open Shop System) — язык, созданный Клиффордом Шоу в RAND Corporation в 1963 году для машины JOHNNIAC.

Именно в этом языке впервые появились условные выражения, диапазоны, функции и компактный синтаксис ветвлений, позволявший записывать целые решения в одну строку. Из «Джосса» выросли TELCOMP, STRINGCOMP, CAL, MUMPS, «Фокал» и «Бейсик» — целое поколение языков, унаследовавших его простоту и интерактивность.

Что в «Джоссе» необычного? Из тех языков, с которыми наиболее вероятно столкнуться в реальной жизни, он, разумеется, напоминает какой-нибудь простенький «Бейсик», но при этом содержит конструкции, которые редко встречаются в современных языках программирования.

Во-первых, строки здесь нумеруются с точкой. Это позволяет наглядно группировать подпрограммы — принцип, без изменений перекочевавший в «Фокал», с которым я сталкивался в детстве. Кстати, многие операторы «Фокал» так же позаимствовал у «Джосса».

Во-вторых, интересно устроена запись функций. Такого я, кажется, нигде больше не видел: в моём коде функция b(n) принимает один аргумент и возвращает значение в зависимости от того, какая проверка сработает.

В-третьих, в языке используется постфиксная запись операторов — for и if ставятся после выражения. Похожие конструкции существуют в «Перле» — не самом популярном сегодня, но всё ещё используемом языке.

Ну и мелочи: перевод строки обозначается подчёркиванием (_), умножение — интерпунктом (·), «звёздочка» при этом служит для экспоненты, «неравно» записывается отдельным символом (≠). Таких деталей в «Джоссе» много — перечислять можно долго.

* "99 Bottles of Beer," written in JOSS on Oct 16, 2025
* by Evgeny Stepanishchev (https://bolknote.ru)

Let b(n)=[n=1:" bottle"; n≠1:" bottles"].

1.1  Set n=99.
1.2  Do part 2 for i=n(-1)1.
1.3  Do part 3.
1.4  Stop.

2.1  Set j=i-1.
2.2  Type i, b(i), " of beer on the wall, ", i, b(i), " of beer.".
2.3  Type "Take one down and pass it around, no more bottles of beer on the wall." if j=0.
2.4  Type "Take one down and pass it around, ", j, b(j), " of beer on the wall." if j>0.
2.5  Type _.

3.1  Type "No more bottles of beer on the wall, no more bottles of beer.".
3.2  Type "Go to the store and buy some more, 99 bottles of beer on the wall.".

Go.

Язык давно уже не используется, поэтому мне не удалось найти, где можно было бы выполнить на нём программу. Обычно в таких случаях я просто пишу код по описанию, не проверяя его, но здесь впервые пошёл другим путём — написал несколько тестов и «завайбкодил» интерпретатор по описанию, взятому из «Википедии».

Колеблюсь, выкладывать его или нет. С одной стороны, кому он нужен — интерпретатор давно почившего языка? С другой стороны, вдруг кому-то нужен, но в этом случае его надо бы, по хорошему, поддерживать, а мне, если честно, не хочется.

Текст песни «10 бутылок пива на стене» в терминале: отсчёт от 10 до 0, затем «пойти в магазин и купить ещё, 99 бутылок на стене»; внизу строка «bolck: JOSS $ _

5 комментариев

99 бутылок: AkRScript

В полку странных языков программирования, на которых я писал «песню о пиве», прибыло.

84. AkRScript — самодельный скриптовый язык программирования, написанный в середине десятых годов владельцем телеграм-канала «Notes from the Dark Side of the Moon», когда он ещё был школьником.

Он его оживил в эмуляторе и выложил несколько программ, написанных на AkRScript, что и позволило мне разобраться в том как этот язык устроен.

Синтаксис, как это часто бывает у самодельных языков, совершенно вырвиглазный. Зато есть все основные примитивные типы данных — строка, число, массив, хеш и даже лямбды. Программирование процедурное, функции возможны только в виде лямбд. У меня в коде тоже есть одна такая, её легко узнать — они всегда начинаются с тильды и заканчиваются обратным апострофом; параметры передаются подстановками $A1, $A2 и так далее.

Процедуры можно описывать двумя способами — через ключевое слово subp и при помощи subp opcode. Отличия существенные — обычную процедуру приходится вызывать через оператор csp, а опкод вызывается как оператор. Кроме того, в обычную процедуру параметры передаются через глобальные переменные, тогда как опкод может получать их через стек, снимая с него значения оператором pop.

Дополнено: автор написал, что процедуры, вызываемые через csp тоже, вроде бы, могут забирать параметры через pop. Звучит логично, но проверять это я уже не буду.

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

С пробелами у языка сложные отношения — в параметрах они могут срезаться даже внутри строк, поэтому, чтобы не мучиться с ними, я завёл себе переменную sp, куда поместил неразрывный пробел и расставлял его везде, где сталкивался с трудностями.

-- "99 Bottles of Beer," written in AkrScript on August 9, 2025
-- by Evgeny Stepanishchev (https://bolknote.ru)

clrs

-- тут в скобках стоит неразрывный пробел (код 0xA0)
assign sp " "

subp opcode plural
assign _s s
assign _b `pop`

chk `cmp b 1` ~assign _s ""`
cno b ~assign _b No`
assign out `add _b sp bottle _s sp "of beer"`

mkill "_s"
mkill "_b"
ret

assign b 99

plural b

whi `gt b 0`

prnt out sp "on the wall, " out "!"
prnt "Take one down, pass it around,"

assign b `sub b 1`

plural b

prnt out sp "on the wall!"
prnt
ret

prnt "No more bottles of beer on the wall, no more bottles of beer."
prnt "Go to the store and buy some more, 99 bottles of beer on the wall."
Текст песни «99 бутылок пива на стене» с обратным отсчётом и фрагмент кода: «0029: prnt "No more bottles of beer", 002A: prnt "Go to the store and buy», 002B:

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

99 бутылок: HolyC

История появления языка, на котором написана сегодняшняя «песня о пиве», довольно занятна.

Его автор, Терри Дэвис — гениальный одиночка и бывший инженер компании Ticketmaster, который посвятил более десяти лет созданию собственной операционной системы TempleOS. Страдая от шизофрении, он считал, что действует по воле Бога, который лично поручил ему создать «божественную ОС». Несмотря на тяжёлое психическое состояние, Дэвис самостоятельно написал компилятор, ядро, графическую оболочку и язык программирования — в полном одиночестве и с поразительной продуктивностью.

83. HolyC — тот самый язык, разработанный Терри. Выглядит он довольно интересно. Сильно напоминает Си, но имеет много интересных особенностей, некоторые из которых сразу бросаются в глаза, даже в моей небольшой программе.

Во-первых, конечно, это собственная система типов, простая и понятная — значение либо выводится (auto), либо сразу видно сколько бит оно занимает, а так же знаковое оно или нет — U8, I8, U16 и так далее. Тип U0, как легко догадаться — аналог void.

Во-вторых, упрощённый вывод на экран — всё отдельностоящие строки сразу выводятся, всё остальное можно вывести через операцию «запятая», указав формат и значения — одно или несколько.

Чуть менее заметная деталь — оператор case может не иметь аргумента, в таком случае используется автоикрементное значение, начинающееся с нуля. В HolyC вообще довольно много интересного синтаксического сахара.

Поражает, как много может создать в одиночку человек, заражённый какой-то идеей. Работы проделано довольно много, причём, работы проделанной очень хорошо. Я не смотрел внутренности TempleOS, но язык HolyC выглядит как нечто цельное и продуманное. Некоторые конструктивные особенности я был бы рад видеть и в современном Си, например, значения по-умолчанию для параметров функций.

// "99 Bottles of Beer," written in HolyC on July 9, 2025
// by Evgeny Stepanishchev (https://bolknote.ru)

auto Bottle(U8 b)
{
  U8 *bottles, *ret;

  switch (b) {
    case:
      bottles = StrNew("No bottles");
      break;
    case:
      bottles = StrNew("1 bottle");
      break;
    default:
      bottles = StrPrint(NULL, "%d bottles", b);
      break;
  }

  ret = StrMerge(bottles, " of beer");
  Free(bottles);
  return ret;
}

U0 Main()
{
  U8 *bottles, b = 99;

  while (b) {
    bottles = Bottle(b);
    "%s on the wall, %1$s!\n", bottles;
    Free(bottles);

    bottles = Bottle(--b);
    "Take one down, pass it around,\n";
    "%s on the wall!\n\n", bottles;
    Free(bottles);
  }

  "No more bottles of beer on the wall,\n";
  "No more bottles of beer!\n";
  "Go to the store and buy some more,\n";
  "99 bottles of beer on the wall!\n";
}

2 комментария

99 бутылок: Входной язык ПЭВМ Искра-1256

Сегодня у меня рубрике «99 бутылок» язык настолько редкий, что у него даже отдельного названия нет. Иногда его называют «русскоязычным Бейсиком Искры-1256», но это вряд ли верно — на какой-либо Бейсик он едва ли похож. Там где на нём много программировали, конечно, выдумывали какие-то локальные названия — например, «Дуб» за дубовость синтаксиса или «Кузик».

В редкой профессиональной литераторе по теме его называют «входной язык…» или «символьный язык ПЭВМ „Искра-1256“».

83. Входной язык ПЭВМ «Искра-1256» — язык программирования с операторами на русском языке, который использовался на отечественных портативных (по тем временам) ЭВМ «Искра-1256», эта машина выпускалась на заводе «Счётмаш» в Курске с 1981 по 1989 год.

Машина работала с алфавитно-цифровым дисплеем, загружала программы с кассетного магнитофона и выпускалась в нескольких модификациях — с оперативной памятью от 4 до 64 килобайт.

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

Страница руководства с описанием программ 8 и 9: вычисление НОК двух чисел и генератор псевдослучайных чисел, включая формулы, код и примеры использования.

Строки нумеруются подряд, видимо в этом кто-то углядел сходство с Бейсиком, но дальше идут сплошные различия.

Номер строки тут, кажется, используется только для удобства набора текста программы, переход же в языке делается не на номер строки, а на метку, причём их минимум два вида — метка для передачи управления, начинающаяся с буквы «М» (например — M01), и метка подпрограммы, которая начинается с «П» (П01).

Операторы записываются русскими буквами. Например ПЕРЕХ П01 — вызов подпрограммы, перед которой стоит метка П01. Оператор перехода часто используется с условным оператором, у которого нет какого-то явно обозначенного начала. Это просто сравнение с переходом, причём оператор перехода записывается впритык, без пробела: И12=0ПЕРЕХ П01.

Переменные так же записываются русскими буквами, их буквенные префиксы значимы. Со всеми префиксами разобраться не удалось, но некоторые я понял: например, буквами «И» и «А» обозначаются переменные, содержащие числа. Разница, кажется, в том, что на вторые каким-то образом отображается массивы, которые имею префикс «АИ». Помимо того есть префиксы «СА» и «СИ» — так обозначаются символьные переменные, второе, думаю, тоже массив.

Необычно выглядит присваивание — оно тут записывается как => и присваивает левое значение правому операнду.

Но самое странное — это функции.

Во-первых, тут они почему-то записываются английскими буквами. Во-вторых, могут стоять после аргумента. В-третьих, тут есть функции, которым, прямо как в Перле, можно присваивать значение: СИ01=>STR(СА00, И02, 1). В-четвёртых, и это самое, наверное, странное, их можно записывать подряд без пробела: (1.234567↑2+8.76543-0.551122)ABSINTLNEXP.

Вот какая программа получилась у меня после попытки освоить этот язык. Не факт, что она написана без ошибок, но попробовать, как вы догадываетесь, негде:

1 ПЕЧАТЬ(HEX(03),,)
2 ЦИКЛ(И01,0,98)
3 И01-99=>A01
4 ПЕРЕХ П02
5 ПЕЧАТЬ(" ON THE WALL, ",,)
6 ПЕРЕХ П02
7 ПЕЧАТЬ("!",HEX(0A0D),"TAKE ONE DOWN, PASS IT AROUND,",)
8 А01-1=>А01
9 ПЕРЕХ П02
10 ПЕЧАТЬ(" ON THE WALL!",)
11 ЦИКЛ*
12 ПЕЧАТЬ("NO MORE BOTTLES OF BEER ON THE WALL,",)
13 ПЕЧАТЬ("NO MORE BOTTLES OF BEER!",)
14 ПЕЧАТЬ("GO TO THE STORE AND BUY SOME MORE,",)
15 ПЕЧАТЬ("99 ON THE WALL!",)
16 П01
17 "S"=>СА01
18 А01=1ПЕРЕХ М01
19 ""=>СА01
20 М01
21 П02
22 A01=0ПЕРЕХ М02
23 ПЕЧАТЬ(А01(2.0),,)
24 ПЕРЕХ М03
25 М02
26 ПЕЧАТЬ("NO",,)
27 М03
28 ПЕРЕХ П01
29 ПЕЧАТЬ(" BOTTLE",СА01," OF BEER",,)

Разобраться в языке мне помогли следующие книги, найденные в интернете. В них нашлось какое-то количество программ, по которому удалось понять что из себя представлял этот язык:

  • «Комплекс вычислительный „Искра 1256“. Комплект контрольных тест-программ. 1.320.118 Д15»;
  • «Процессор интерпретирующий „Искра-1256“. Библиотека программ. 3.050.187 Д14»;
  • «Задачник по аналитической химии», Н. Ф. Клещев, Е. А. Алферов и другие, М., «Химия», 1993;
  • Журнал «Вычислительная техника и её применение», №3, 1988.

Самым большим тёмным пятном остался оператор СЕЛЕКТ(). Без чёткого описания понять его функцию мне кажется невозможным. Всё, что я смог о нём узнать, заключено в цитате, найденной в одном из обсуждений: «там половину языка составлял оператор СЕЛЕКТ(n, параметры). В зависимости от этого n он чего только не делал».

Добавлено позднее: действительно, оператор СЕЛЕКТ() чего только не делал, например через него можно было производить смену режимов дисплея или указывать задержку.

3 комментария

99 бутылок: калькулятор bc

Разбирал одну старенькую научную статью с некоторым количеством математики, а внутри увидел вычисления на bc.

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

В общем, оказалось, там внутри целый язык программирования, с циклами, ветвлениями, локальными переменными, строками, массивами и числам. Поддерживаются даже пользовательские функции. Решил попробовать написать на нём очередную «песню о пиве».

82. bc — интерактивный интерпретатор Си-подобного языка, появившийся ещё в 1975 году как интерфейс для утилиты dc, на котором «песню о пиве» я уже писал. Утилита dc выполняла вычисления с произвольной точностью, используя обратную польскую запись, а bc предоставила более привычный Си-подобный синтаксис, транслируя его в команды dc. В 1991 году утилита bc была стандартизирована POSIX, и сегодня существует несколько её реализаций.

/* Beer song. Evgeny Stepanischev, Jan 2025 */

define void bottles(b) {
    if (b) print b else print "No"
    " bottle"
    if (b-1) print "s"
    " of beer"
}

for(beer = 99; beer;) {
    bottles(beer); " on the wall, "
    bottles(beer); print "!\n"

    print "Take one down, pass it around,\n"
    bottles(--beer); print " on the wall!\n\n"
}

print "No more bottles of beer on the wall,\n"
print "No more bottles of beer!\n"
print "Go to the store and buy some more,\n"
bottles(99); " on the wall!"

Единственное, что вызвало у меня затруднение — это как сделать так, чтобы функция ничего не возвращала. Дело в том, что функции в bc по-умолчанию возвращают число. Если не указано какое, вернётся ноль. А в глобальном контексте то, что возвращается из функции будет напечатано на экране. Поэтому у меня выводился лишний ноль. Чтобы это исправить, нужно в определение функции добавить ключевое слово void.

Никаких других сложностей с этой программой у меня не возникло, язык простой, хорошо продуманный, лёгок в освоении.

Добавлено позднее: как оказалось, у языка есть свои особенности о которых нелишне знать. Например, подключение математической библиотеки меняет точность, определяемую переменной scale, что влияет, например, на операцию вычисления модуля, она начинает работать иначе.

Другая странность — операции сдвига работают в десятичной системе. Не понимаю какой от этого толк. Например, 1<<2 даст 100.

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

99 бутылок: язык B («Би»)

В последнее время я довольно много пишу на Си, как-то так получилось. А поскольку мне хочется углубить свои знания этого языка, иногда что-нибудь ищу и читаю по теме. Так как авторы статей, по всей видимости, любят исторические экскурсы, примерно в каждой третьей написано, что язык Си произошёл от Би.

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

81. B (язык «Би»), после появления Си, был полностью вытеснен последним и сейчас совершенное не используется. В отличие от, например, Кобола, который старше на десять лет, но при этом вполне ещё встречается.

Думаю, причин тут несколько.

Во-первых, это нацеленность на одну платформу — указатели в Би работают с машинными словами, а не байтами. Во-вторых, неудачные решения в синтаксисе, например, a =* 10 и a = *10 — это разные операции. Первая — умножение переменной a на десять, вторая — чтение в переменную a значения по адресу 10. В-третьих, у Си явно больше возможностей, а учитывая, что программы переписываются довольно легко (кроме части с адресацией, где легко наделать ошибок), переход выглядит очень оправданным.

Язык Би и правда очень похож на Си, в этом можно убедиться, взглянув на написанную мной программу. Что тут режет глаз сишному программисту? Прежде всего — отсутствие типов у заголовка функции, импорта функции printf и странное ключевое слово extrn внутри функций. extrn означает, что этот символ (в терминах компилятора) пришёл из внешней зоны видимости. Современное extern явно произошло от него, только значение немного поменялось.

Немного непривычно, что все переменные объявлены как auto, но в современном Си так тоже можно. auto, в данном случае, — класс хранения. В современном Си это класс хранения по-умолчанию, поэтому его не указывают. При этом, если отсутствует указание типа, Си по-умолчанию подставит int. Как раз для совместимости с Би. То есть auto beer Си даже правильно поймёт, но выдаст предупреждение.

Впрочем, в стандарте Си23 auto теперь означает автоматический вывод типа, что для совместимости с Би подходит ещё больше, так как, если приглядеться, строковые массивы («строки») в Би не выделены в отдельный тип.

Из того, что заметить чуть сложнее — экранирующий символ в строке не обратный слэш, а «звёздочка» (*), поэтому символ перевода строки не \n, а *n.

Ещё немного странно, что тут цикл while, а не for, но сильно это в глаза не бросается. В «Би» цикла for просто нет, поэтому вот так.

Вообще, ощущение такое, что Си взял от Би буквально всё, но, как ни странно, это не так. В Би есть конструкции ===, =!=, =<= и =>=. Наверное они не очень-то нужны, но выглядят интересно. Например, a === 5 означает «если переменная a равна 5, записать в a число 1, иначе — 0».

Кроме того, другая почти вся стандартная библиотека. Совпадают всего несколько функций, по больше части это ввод-вывод. Например, в Би можно объединить несколько строк функцией concat, а char не тип, а функция получения символа строки по индексу.

/* Beer song. Evgeny Stepanischev, Dec 2024 */
print_bottles(b, tail) {
  extrn printf;
  auto s;

  s = "s";

  switch (b) {
    case 0: printf("No"); break;
    case 1: printf("%d", b); s = ""; break;
    default: printf("%d", b);
  }

  printf(" bottle%s of beer%s", s, tail);
}

main() {
  extrn printf, print_bottles;
  auto beer;

  beer = 99;
  while (beer) {
    print_bottles(beer, " on the wall, ");
    print_bottles(beer, "!*n");

    beer--;

    printf("Take one down, pass it around.*n");
    print_bottles(beer, " on the wall!*n*n");
  }

  printf("No more bottles of beer on the wall, ");
  printf("no more bottles of beer.*n");
  printf("Go to the store and buy some more,*n");
  printf("99 bottles of beer on the wall...*n");
}

Я долго пытался найти хоть какой-нибудь работающий под современными платформами компилятор, перебрал около десятка, но везде были свои сложности — некоторые не собирались, другие падали на совершенно валидном коде (например, printf("%c", 't')).

В итоге, остановился на онлайн-версии ybc (Yasha’s B Compiler).

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

99 бутылок: препроцессор Си

Те программисты, кто каким-либо образом сталкивался с языком Си, знают, что у него есть так называемый препроцессор. Его директивы начинаются с символа решётки и выполняются до компиляции программы. Обычно его используют, чтобы включить одни файлы в другие, определить макрос или проверить значение каких-либо внутренних флагов компилятора.

С тех пор как мне много лет назад кто-то показал как можно на нём писать программы, меня не оставляла идея написать на нём «песню о пиве».

80. Препроцессор Си, разумеется, не задумывался как что-то на чём будут писать программы, поэтому, чтобы добиться цели, приходится использовать россыпь хитростей и разнообразных хаков. Не думаю, что имеет смысл их описывать, тем более не я их придумал. В интернете есть достаточно подробных статей по этой теме, как на английском, так и на русском.

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

Вопреки обыкновению, вывод программы я обрабатываю другой утилитой (см. строку запуска). Это приходится делать, так как в этом языке нет простого способа вывести перевод строки. Можно было бы сделать это ANSI-кодами, но их не будет видно в листинге кода, что меня не устраивает.

#if 0
Bolknote.ru, 2024.11.05
To run:
gcc -P -E - < 99 | sed 's/\\n */\n/g'
#endif

#define CAT(a, b) a##b
#define SECOND(a, b, ...) b
#define TEST(...) SECOND(__VA_ARGS__, 0)
#define LF \n

#define DEC(n) CAT(DEC_,n)
#define DEC_0 9
#define DEC_1 0
#define DEC_2 1
#define DEC_3 2
#define DEC_4 3
#define DEC_5 4
#define DEC_6 5
#define DEC_7 6
#define DEC_8 7
#define DEC_9 8

#define DEC_X0(a, b) IF_ELSE(ISZERO(b)) (DEC(a)) (a)

#define ISEND(a, b) TEST(ISEND_ ## a ## b)
#define ISEND_01 ,1

#define ISZERO(n) TEST(ISZERO_ ## n)
#define ISZERO_0 ,1

#define ISONE(n) TEST(ISONE_ ## n)
#define ISONE_1 ,1

#define IF_ELSE(b) CAT(IF_, b)
#define IF_0(i) ELSE_0
#define IF_1(i) i ELSE_1
#define ELSE_0(e) e
#define ELSE_1(e)

#define PLUR(n) IF_ELSE(ISONE(n)) (bottle) (bottles)

#define PRINT_PLUR(a, b) IF_ELSE(ISZERO(a)) (b PLUR(b)) (CAT(a, b) bottles)

#define PRINT_B(a, b) IF_ELSE(ISZERO(b)) \
        (IF_ELSE(ISZERO(a)) \
            (No bottles) \
            (PRINT_PLUR(a, b)) \
        ) \
        (PRINT_PLUR(a, b)) of beer

#define PRINT_ROW(a, b) \
    PRINT_B(a, b) on the wall, PRINT_B(a, b)! LF \
    Take on down, pass it around, LF \
    PRINT_B(DEC_X0(a, b), DEC(b))! LF LF


#define STOP(...) No more bottles of beer on the wall, LF \
No more bottles of beer! LF \
Go to the store and buy some more, LF \
PRINT_B(9, 9) on the wall!

#define EMPTY()
#define DEFER(...) __VA_ARGS__ EMPTY()

#define CROSS1(a, b) PRINT_ROW(a, b) \
    DEFER(IF_ELSE(ISEND(a, b)) (STOP) (CROSS2)) (DEC_X0(a, b), DEC(b))

#define CROSS2(a, b) PRINT_ROW(a, b) DEFER(CROSS1) (DEC_X0(a, b), DEC(b))

#define MUL0(...) MUL1(MUL1(MUL1(MUL1(__VA_ARGS__))))
#define MUL1(...) MUL2(MUL2(MUL2(MUL2(__VA_ARGS__))))
#define MUL2(...) MUL3(MUL3(MUL3(MUL3(MUL3(__VA_ARGS__)))))
#define MUL3(...) __VA_ARGS__

#define RUN(a, b) MUL0(CROSS1(a, b))

RUN(9, 9)

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

«99 бутылок пива» на PERQ Pascal

Мои читатели знают, что последние несколько дней я возился с эмулятором PERQ. PERQ — это персональный компьютер, выпускавшийся в начале 1980-х годов, первый коммерчески успешный ПК с графическим интерфейсом. Такой олдскул мил моему сердцу, поэтому я потратил несколько вечеров, чтобы попробовать хоть на базовом уровне разобраться что из себя представляла операционная система тех компьютеров.

Последние пару дней я пытался написать на тамошнем «Паскале» «песню о пиве», но поскольку на моём ноутбуке потребовалась двойная эмуляция, скорость работы встроенного в систему редактора была ниже плинтуса. Писать было очень сложно, а править ошибки — ещё сложнее. Тем не менее, я справился.

79. PERQ Pascal — диалект некогда широко распространённого в учебных учреждениях языка «Паскаль». Компилируемый язык, в какой-то степени можно назвать его конкурентом Си, которому он проиграл вчистую.

Не думаю, что кто-то, кроме редких энтузиастов языка, сейчас выберет «Паскаль» для чего-то нового, но со старыми проектами на нём я иногда сталкиваюсь. Например, в начале этого года видел электронный документооборот, написанный на «Делфи» (это визуальная среда программирования и название диалекта «Турбо Паскаля»).

{$MESSAGE Written by Evgeny Stepanischev, 2024}
PROGRAM BOTTLES_OF_BEER;

const
    MAX_BOTTLES = 99;

Function BotStr(count: integer): string;
begin
    if count = 1 then
        BotStr := ' bottle of beer'
    else
        BotStr := ' bottles of beer';
end;

Procedure SingSong;
var
    i: integer;
    b: string;
begin
    for i := MAX_BOTTLES downto 1 do
    begin
        b := BotStr(i);
        writeln(i:0, b, ' on the wall,');
        writeln(i:0, b, '!');
        writeln('Take one down, pass it around,');
        if i > 1 then
        begin
            b := BotStr(i-1);
            writeln((i - 1):0, b, ' on the wall!')
        end
        else
            writeln('No more bottles of beer on the wall!');
        writeln;
    end;
    
    writeln('No more bottles of beer on the wall,');
    writeln('No more bottles of beer!');
    writeln('Go to the store and buy some more,');
    b := BotStr(MAX_BOTTLES);
    writeln(MAX_BOTTLES:0, b, ' on the wall!');
end;

begin
    SingSong;
end.

Из того, с чем мне пришлось столкнуться, когда я писал программу.

Больше всего ушло времени на ошибку Error 116: Error in type of standard procedure parameter. Речь тут, вроде, идёт о том, что я какой-то процедуре передаю параметры не того типа. Несмотря на то, что в ошибке был указан номер строки, мне всё равно не удавалось понять, что я делаю не так.

Оказалось, что вызов функции BotStr нельзя напрямую поместить во writeln, надо обязательно использовать промежуточную переменную (у меня это b). Учитывая, что скорость работы с эмулятором не позволяет быстро попробовать разные варианты, пришлось изрядно поскрипеть мозгом, чтобы дойти до сути.

Вторая вещь оказалась проще для исправления. В этом диалекте «Паскаля» вывод чисел происходит с отступом. В моём случае выглядит это неаккуратно. К счастью убрать его просто — нужно добавить параметр :0 для каждого числа, выводимого на экран.

В какой-то момент я так устал редактировать код со скоростью улитки, стал использовать обычный редактор своего ноутбука. Потом я просто запускал небольшую программу, написанную на «Пайтоне», которая печатала код за меня прямо внутрь окна с эмулятором PERQ:

import pyautogui, time

FILE_PATH = '99.pas'

time.sleep(5)

with open(FILE_PATH, 'r') as file:
    for line in file:
        for x in line.lstrip():
            pyautogui.typewrite(x)
            time.sleep(.6)

        time.sleep(1)

Вчера ночью всё наконец запустилось и я смог посмотреть как работает моя первая (и наверняка единственная) программа для PERQ.

Кстати, вот ещё одна интересная деталь.

Чтобы получить исполняемый файл, надо исходный код программы сначала скомпилировать командой COMPILE в объектный файл (.SEG), потом скомпоновать командой LINK в исполняемый (.RUN). У обеих команд есть параметр, где указывается имя файла, с которым мы работаем.

Но если мы открывали на редактирование какой-то файл, его имя указывать не надо — операционная система его где-то запоминает и команды COMPILE и LINK знают с каким файлом мы работаем. Очень удобно.

Текст песни «99 бутылок пива на стене» в терминале: от 14 до 1, затем «нет бутылок», «пойди купи ещё», снова 99. Заголовок: sys:User>Guest>, POS F.1 a-boot, 08 Aug 08 00:22:40.
Результат запуска «Песни о пиве» под эмулятором компьютера PERQ

2 комментария

99 бутылок: bootBASIC

Очередная «песня о пиве», в последнее время я что-то прям зачастил.

78. bootBASIC — интерпретатор простенького диалекта Бейсика, занимающий 512 байт. В наше время, когда некоторые программы занимают гигабайты, удивительно видеть что-то настолько крошечное.

А тут целый язык, со строками, поддержкой математики, ветвлением и даже небольшим редактором кода. Правда я уже писал «песню о пиве» на миниатюрном Си. Он занимал чуть меньше, но и возможности там были в чём-то скромнее.

Текст песни «11 бутылок пива на стене» в терминале QEMU, начинающийся с 11 бутылок и заканчивающийся покупкой 99 новых.

Конечно, при таком размере есть свои ограничения — арифметика только целочисленная, до 65535 и нет операций сравнения, — можно проверить только, что число не равно нулю.

Кроме того, строки кода ограничены по длине, но из-за несовершенства интерпрератора можно хитрить — пропускать пробелы и опускать закрывающую кавычку. За счёт этого в строке можно уместить чуть больше, но редактору от такого плохеет — команда list начинает выдавать мусор и отредактировать некоторые строки не удаётся. Правда работе программы это не мешает.

Обидно, что нет gosub для перехода к подпрограмме, вместо этого приходится использовать goto на переменную. Ладно хоть такая возможность есть. Комментариев нет, но можно использовать для этого команду print""; — дальше неё интерпретатор в строке ничего не видит.

1 print""; Written by Evgeny Stepanischev https://bolknote.ru
10 b=99
20 r=30
21 goto 200

30 print"of beer on ";
31 print"the wall, ";

40 r=50
41 goto 200

50 print"of beer."
60 b=b-1

70 print"Take one do";
71 print"wn and pass";
72print" it around, ";

80 r=90
81 goto 200

90 print"of beer on ";
91 print"the wall."

100 if b goto 20
110 goto 900

200 if b goto 203
201 print"no";
202 goto 210
203 print b;
210 print" bottle";
220 if b-1 goto 250
230 print" ";
240 goto r
250 print"s ";
299 goto r

900print"No more bott";
901print"les of beer ";
902print"on the wall,";
903print" no more bot";
904print"tles of beer."
905print"Go to the st";
906print"ore and buy ";
907print"some more, 9";
908print"9 bottles of";
909print" beer on the";
910print" wall.

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

99 бутылок: mkdir + find

77. mkdir + find — тут некий японец придумал как писать программы, используя только утилиты mkdir и find. Мне такое очень нравится, поэтому и я решил что-нибудь написать на связке этих утилит. Сразу вспомнил, что давно у меня не было «песни о пиве». Есть у меня такое развлечение — писать её на всём подряд.

Программу вы можете видеть на экране, а как она работает расскажу чуть ниже.

# Written by Evgeny Stepanischev https://bolknote.ru

rm -rf bottles; mkdir bottles
find bottles -maxdepth 98 -execdir mkdir bottles/bottles \;
find bottles -empty -name bottles -execdir mkdir bottles/bottle \;

find bottles \
        -empty -printf "1 bottle of beer on the wall, 1 bottle of beer.
Take one down and pass it around, no bottles of beer on the wall.

No more bottles of beer on the wall, no more bottles of beer.
Go to the store and buy some more, 99 bottles of beer on the wall.\n" -o \
        -regex '.*/bottles/bottles' \
        -execdir find . \
                -empty -printf "%d bottles of beer on the wall, %d bottles of beer.\n" \; \
        -execdir find bottles \
                -empty -printf "Take one down and pass it around, %d %f of beer on the wall.\n\n" \;

# Clean up
rm -rf bottles

Прежде всего — что за две команды тут используются? rm не в счёт, она для очистки мусора, можно без неё.

Команда mkdir, наверное, многим знакома — это создание директория, а find — утилита для поиска файлов и директориев по различным критериям. Причём с найденным она может делать некоторые простые действия.

Чтобы писать программы, автор оригинальной статьи использует несколько находок.

Во-первых, создание вложенных директориев командой find с параметром -execdir mkdir имя.

Параметр запускает указанную команду в директории, который сейчас просматривает find. Эти вложенные директории в дальнейшем используются как конечный цикл для следующей команды find. Вложенность при этом можно регулировать параметром -maxdepth.

Во-вторых, параметр -printf поддерживает аргумент %d, который позволяет вывести текущий уровень вложенности и полезен в тех же циклах.

В-третьих, фильтры команды find, а так же директивы -o («ИЛИ») и -and («И») можно использовать для организации условий.

Мне пришлось дополнительно решить ещё две проблемы.

Во-первых, цикл у меня обратный. Чтобы его получить, я запускаю две команды find — одну в другой. Первая считает директории в одну сторону по возрастанию, вторая запускается на каждом шаге и итерируется по остатку директориев до конца. Поскольку это тоже цикл, а мне нужно только последнее значение, я использую параметр -empty, чтобы получить только последний директорий — тот, в котором пусто.

На самом деле на каждой внешней итерации я пускаю не один, а два внутренних цикла, поскольку мне нужны два числа — текущее значение считаемого «пива» и это же значение, уменьшенное на единицу. Сделать это было просто — ещё один find надо запускать во вложенном директории.

Во-вторых, мне нужно поддержать окончание множественного числа. Тут я немного смухлевал и слегка замёл эту проблему в конец — там я вывожу дополнительные строки «1 bottle of beer on the wall, 1 bottle of beer. / Take one down and pass it around, no bottles of beer on the wall».

Но в строке перед этим текстом у меня сделано более честно. Как видно, структура директориев у меня такая: 98 раз созданы вложенные bottles, а конечная директория называется bottle.

Это мне позволяет в одном из «циклов», огранизованных через find, использовать аргумент %f параметра -printf, который выводит имя текущего директория. Таким образом в самом конце я получаю bottle вместо bottles.

Добавлено: мне подсказали, что обратный цикл можно огранизовать проще, через ключ -depth, переделал программу под него:

# Written by Evgeny Stepanischev https://bolknote.ru

find start -delete 2>&-
mkdir -p start/bottle/bottles
find start/bottle -maxdepth 97 -name bottles -execdir mkdir bottles/bottles \;

find start -mindepth 1 -depth \
        -empty -printf "%d %f of beer on the wall, %d %f of beer.\n" -o \
        ! -empty -printf "Take one down and pass it around, %d %f of beer on the wall.\n\n" \
        -printf "%d %f of beer on the wall, %d %f of beer.\n" \
        -name bottle -printf "Take one down and pass it around, no bottles of beer on the wall.

No more bottles of beer on the wall, no more bottles of beer.
Go to the store and buy some more, 99 bottles of beer on the wall.\n"

rm -rf start

4 комментария

Программирую под Flipper Zero

Мне месяц назад на день рождения подарили «Флиппер Зеро» — «перочинный ножик для хакера». Довольно известное в узких кругах устройство, очень занятное. Но об этом как-нибудь в другой раз.

Давно подмывало что-нибудь под него написать, но всё как-то занят по уши был всякими неотложными делами — то сериалы надо посмотреть, то ленту в соцсетях полистать. Но наконец освободился, сегодня утром начал.

Белый многофункциональный инструмент Flipper Zero с оранжевым экраном, на котором написано «99 bottles of beer on the wall», лежит на деревянной поверхности.
Попробую написать «Песню о пиве»

В интернете есть несколько статей о том как начать, которые мне очень помогли. Довольно быстро я научился собирать первое приложение, которые выводит строчку текста и опрашивает клавиатуру. По пути поразился сколько бойлерплейта (шаблонного почти одинакового для каждого приложения кода) пришлось написать. Очень не хватает какого-нибудь фреймворка.

Пока застопорился — не пойму как прокручивать экран и есть ли что-то вообще для этого. Исчерпывающей документации по АПИ я что-то не нашёл, а в статьях сторонних разработчиков рекомендуют читать исходники других программ. Что ж, уже начал, но пока решение мне не попалось.

4 комментария

99 бутылок: SectorC

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

Пора возобновить.

76. SectorC — очень маленький компилятор подмножества языка Си. Помещается в один сектор — 512 байт, отсюда и название. Язык очень сильно урезанный, но всё равно меня поражает как автор запихнул имеющееся в такой малый объём.

В языке нет строк и операторов ввода-вывода, но в стандартной библиотечке языка (часть которая написана в машинных кодах), есть функции, позволяющие выводить символы, используя их коды.

Из-за этого моя программа было сильно распухла, но я немного заморочился и закодировал строки в целых шестнадцатибитных числах — это единственный тип, помимо указателя из доступных. В итоге объём сильно сократился по сравнению с первой версией.

Самые распространённые в выходном тексте буквы я кодирую полубайтом от 0x1 до 0xE, а более редкие — байтом от 0xF1 до 0xFC. Завтра попробую описать это чуть подробнее.

Немного так же печалит, что нет локальных переменных, параметров и возвращаемых значений — всё передаётся через глобальные переменные, как в ранних Бейсиках. Кроме того, немного по-особенному расставляются пробелы. Так надо для упрощения разбиения компилятором программы на токены.

// Written by Evgeny Stepanischev https://bolknote.ru
// SectorC

int c; // input
int shift; int x;

void x() { if( c == ( x >> 8 ) ){ print_ch = x & 255; } }
void p() { print_ch = c; print_char(); }

void c2c() {
    c = c + shift;
    x =  288; x(); x =  613; x(); x =  879; x(); x = 1140; x(); x = 1388; x();
    x = 1634; x(); x = 1902; x(); x = 2145; x(); x = 2419; x(); x = 2674; x();
    x = 2918; x(); x = 3191; x(); x = 3338; x(); x = 3684; x(); x = 4200; x();
    x = 4396; x(); x = 4654; x(); x = 4981; x(); x = 5204; x(); x = 5481; x();
    x = 5739; x(); x = 6000; x(); x = 6253; x(); x = 6471; x(); x = 6734; x();
    x = 7033; x();

    if( c != 15 ){ print_char(); }
    shift = 0; if( c == 15 ){ shift = 15; }
}

int s; // input
void ps() {
    while( s ){
        c = s & 15;
        c2c();
        s = s >> 4;
        if( s == ( 0 - 1 ) ){ s = 0; } // signed workaround
    }
}

void bottle()  { s = 17249; ps(); s = 596; ps(); }
void of_beer() { s = 6961; ps(); s = 41510; ps(); }
void on_wall() { of_beer(); s = 5937; ps(); s = 8692; ps(); s = 22721; ps(); print_char(); }
void o_more()  { s = 40723; ps(); s = 675; ps(); }

int b; // input
void bottle_b() {
    if( b == 0 ){ s = 55; ps(); }
    if( b > 0 ){ print_num = b; print_u16(); }
    bottle();
    if( b != 1 ){ c = 115; p(); } // s
}

void main() {
    shift = 0; b = 99;

    while( b > 0 ){
        bottle_b(); on_wall();

        s = 303; ps(); /* ', ' */ bottle_b(); of_beer();
        c = 46; p(); // '.'
        print_newline();
        b = b - 1;

        s = 2143; ps(); s = 4735; ps(); s = 4723; ps(); s = 31806; ps();
        s = 59265; ps(); s = 35057; ps(); s = 409; ps(); s = 5231; ps();
        s = 936; ps(); s = 59215; ps(); s = 303; ps();

        bottle_b(); on_wall(); c = 46; p(); // '.'
        print_newline(); print_newline();
    }

    c = 78;  p(); /* 'N' */ o_more(); bottle(); c = 115; p(); // 's'
    on_wall();
    s = 28975; ps(); /* ', n' */ o_more(); bottle(); c = 115; p(); // 's'
    on_wall();
    c = 46;  p(); // '.'
    print_newline(); 

    s = 5039; ps(); s = 16692; ps(); s = 4639; ps(); s = 41801; ps();
    s = 30738; ps(); s = 1566; ps(); s = 53071; ps(); s = 913; ps();
    s = 4767; ps(); s = 41887; ps(); s = 4850; ps();
    b = 99; bottle_b(); on_wall(); c = 46; p(); // '.'
}

1 комментарий

«99 бутылок» на баше без цикла

Когда я только начал программировать, все интерпретаторы, которые мне попадались в то далёкое время, интерпретировали программу построчно. Недавно разговаривали на эту тему с одним из приятелей, который буквально был в шоке, когда случайно обнаружил, что популярная оболочка bash работает именно таким образом.

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

#!/bin/bash
len=$(wc -c<"$0"); : https://bolknote.ru 2021
echo -e "99 bottles of beer on the wall, 99 bottles of beer.
Take one down and pass it around, 98 bottles of beer on the wall.\n"
: ${i:=99}; ((--i)) && (grep -B5 -m1 ^G "$0" |
sed "s/9[8]/$(($i-1))/g;s/9[9]/$i/g;s/ [0] / no /;s/\([1] b[^s]*\)s/\\1/g" >> "$0") ||
(echo 'No more bottles of beer on the wall, no more bottles of beer.
Go to the store and buy some more, 99 bottles of beer on the wall.';truncate -s "$len" "$0")
:;

Пока разговаривали, у меня возникла смешная идея — попробовать написать считалочку «99 бутылок пива…» без циклов, на принципе дописывания кода, чтобы строки добавлялись в файл программы по мере интерпретации.

В общем-то оказалось, что это не так и сложно — берёшь из листинга несколько строк, заменяешь в них цифры и копируешь вниз, проблем почти не возникло.

Результат чуть выше. Так как bash местами довольно сложен для понимания, попробую позже (если не заленюсь) сделать разбор странно выглядящих мест этой программы отдельным постом.

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

Мои «99 бутылок» на клоне Эпла-I

Два года назад я написал на ассемблере первого компьютера компании Эпл (сейчас он условно называется «Эпл I») считалочку «99 бутылок пива на стене». Писал под эмулятор, но не оставлял надежду увидеть когда-нибудь как моя программа работает на «железе».

Ради этого я даже повторно посетил музей Эпл в Москве, но к сожалению, искомый компьютер в момент моего посещения не работал. К счастью, подкупом удалось договориться с владельцем музея, чтобы тот позже запустил мою программу и прислал видео.

Запуск моей реализации считалочки «99 бутылок пива на стене» на клоне Эпла-I

Увы, позже ни на одно из моих писем владелец не ответил, зато один из энтузиастов ретро-техники в переписке пообещал, что если когда-нибудь оживит свой клон Элпа-I, то обязательно пришлёт мне доказательство того, что моя программа способна запуститься не только под эмулятором. Сегодня я получил от него видео.

Зелёная плата с чёрными микросхемами — это и есть сам компьютер. Программа, как можно заметить, вводится в виде аудиофайла через звуковой вход (через разъём справа к компьютеру подключен смартфон), это гораздо быстрее, чем набирать её на клавиатуре.

Посмотрите немного, оцените быстродействие, при такой низкой скорости работы тогдашних компьютеров, софт, конечно, разнообразием не блистал. Даже интересно — существовали ли под этот компьютер какие-нибудь игры, кроме тестовых квестов? Надо будет погуглить.

2 комментария

99 бутылок: Psql (переработанная версия)

Я уже публиковал сегодня одну попытку написать «песню про пиво» на языке утилиты psql, но, к сожалению, результат получился не совсем честный — для математики и сравнения мне пришлось использовать вставку на эскуэеле.

Чисто интуитивно чувствовалось, что какое-то подобие арифметики можно сделать безо всякого эскуэля и эта мысль не давала мне покоя. Сегодня ездил в больницу — кашель никак не проходит и, пока ждал врача, в голове появилась конкретная идея.

Реализовать задуманное получилось через таблицу замен (об этом ниже) и кодогенерацию — psql позволяет писать в файлы произвольное содержимое, а потом эти же файлы подключать. К сожалению, после этого остаётся немного мусора во временной папке, но тут уж ничего не попишешь.

Таблицу замен я реализовал через девять переменных с числовыми именами, значением которых является число на единицу меньше имени. Например: \set 9 8 — тут переменная с именем 9 получает значение 8.

Чтобы реализовать вычитание единицы, пришлось разделить счётчик цикла на две переменные — beer_h (старший разряд) и beer_l (младший разряд), вычитание производится для каждой цифры отдельно. На диск записывается код, в который помещается имя переменной и её новое значение, используя текущее значение, как имя переменной, из которой надо взять следующее. Понимаю, что запутанно, надеюсь на примере понять будет проще.

Вот этот кусок: \qecho '\\set beer_h :':beer_h. Если текущее значение переменной beer_h равно 9, то на диск будет помещен скрипт с содержимым \set beer_h :9, после его подключения код выполнится, а так как значение переменной 9 — 8, то beer_h получит значение 8.

Одновременно я таким же способом создаю логические переменные beer_разряд_value_значение со значением 1, которые я использую потом как флаги, чтобы остановить цикл или отключить использование множественного числа, когда число «бутылок» доходит до единицы.

С кодогенерацией, кстати, работать веселее — сгенерированный код потом можно использовать как некий аналог подпрограмм, экономя размер кода.

В общем, в результате получилось реализовать «песню про пиво» на чистом языке утилиты psql, не привлекая другие языки программирования.

\if 0
    99.psql
    Psql beer song v.2
    Written by Evgeny Stepanischev https://bolknote.ru
\endif

\if :{?beer_l} \else
    \set 9 8 \set 8 7 \set 7 6 \set 6 5 \set 5 4 \set 4 3 \set 3 2 \set 2 1 \set 1 0
    \set print /tmp/beer_print.sql
    \set minus /tmp/beer_minus.psql

    \out :print
    \qecho '\\if :{?beer_h_value_0} \\echo -n :beer_l'
    \qecho '\\else \\echo -n :beer_h:beer_l \\endif'

    \set beer_h 9
    \set beer_l 9
    \set s s
\endif

\include :print
\echo -n ' bottle':s' of beer on the wall, '
\include :print
\echo ' bottle':s' of beer.'

\if :{?beer_l_value_0}
    \unset beer_l_value_0
    \set beer_l 9
    \out :minus
    \qecho '\\set beer_h :':beer_h '\\set beer_h_value_:':beer_h 1
    \include :minus
\else
    \out :minus
    \qecho '\\set beer_l :':beer_l '\\set beer_l_value_:':beer_l 1
    \include :minus
\endif

\if :{?beer_h_value_0}
    \if :{?beer_l_value_1}
        \set s
    \endif
    \if :{?beer_l_value_0}
        \echo Take one down and pass it around, no bottles of beer.'\n'
        \echo No more bottles of beer on the wall, no more bottles of beer.
        \echo Go to the store and buy some more, 99 bottles of beer on the wall.
        \q
    \endif
\else
    \unset beer_l_value_1
\endif

\echo -n 'Take one down and pass it around, '
\include :print
\echo ' bottle':s' of beer.\n'

\include_relative 99.psql

6 комментариев

99 бутылок: Psql

Давно чесались руки написать очередную «песню о пиве» на каком-нибудь языке программирования, но что-то никак не мог определиться с выбором. В конечном счёте выбрал psql.

75. Psql — не совсем язык программирования, это название клиентской программы, распространяемая вместе с СУБД «Постгрес». У неё есть довольно скромный встроенный язык, в который относительно недавно добавили условный блок.

Тем не менее на этом языке можно требуемое, если привлечь для математики и сравнения эскуэль — сам psql такого не умеет. В качестве цикла пришлось применить включение файла в самого себя, повезло, что цикл относительно короткий, а то бы я упёрся в лимит на количество открытых файлов в системе.

Добавлено позднее: мне всё же удалось реализовать «песню о пиве» без использования эскуэля.

\if 0
    99.psql
    Psql beer song
    Written by Evgeny Stepanischev https://bolknote.ru
\endif 

\if :{?beer} \else \set beer 99 \endif
\echo :beer bottles of beer on the wall, :beer bottles of beer.

select :beer-1 beer, :beer > 2 notend \g /dev/null
\gset

\if :notend \set s s \else \set s \endif

\echo Take one down and pass it around, :beer bottle:s of beer.'\n'

\if :notend
    \include_relative 99.psql
\else
    \echo 1 bottle of beer on the wall, 1 bottle of beer.
    \echo Take one down and pass it around, no bottle:s of beer.'\n'
    \echo No more bottles of beer on the wall, no more bottles of beer.
    \echo Go to the store and buy some more, 99 bottles of beer on the wall.
\endif

Вроде код понятен почти без объяснений, но несколько моментов без знакомства с утилитой вряд ли возможно расшифровать самостоятельно, поэтому я их вкратце хочу объяснить.

:{?beer} — синтаксис проверки существования переменной, это нужно чтобы понять запускаем ли мы программу в первый раз или включили её в себя же, чтобы имитировать цикл.

select :beer-1 beer, :beer > 2 notend — эскуэль-запрос, которым делается математика и сравнение — вычитается единица из переменной и проверяется не пора ли выходить из цикла.

\g /dev/null — запуск запроса в выводом результата в /dev/null (то есть в никуда), поскольку его результат на экране нам не нужен.

\gset — записываем результат запроса в переменные, имена которых равны именам столбцов запроса.

У меня есть одна идея, как сделать вычитание, не привлекая эскуэль, надо будет попробовать. А вот с остановом хуже — там никаких идей.

2 комментария

99 бутылок: Старсет

Семьдесят четвёртый выпуск считалочки «песни о пиве», которую я пишу на разных языках программирования, для разнообразия сделаю на языке родом из СССР. Как-то я мало такому уделяю внимания, а ведь это тоже история!

74. Старсет — язык программирования высокого уровня, разработанный в Институте программных систем РАН в 1991 году. К сожалению, мне не удалось обнаружить компиляторов или интерпретаторов этого языка, и даже упоминания, что они вообще когда-либо были реализованы.

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

Оранжевая книга «Язык программирования СТАРГЕТ» М.М. Гилгула и А.П. Столбоушкина (Академия наук СССР, Наука) на фоне советской газеты с заголовками о границе и ТАРСС.
Книга «Язык программирования Старсет», М. М. Гилула, А. П. Столбоушкин, М., «Наука», 1991

Язык, кстати говоря, небезынтересный — в наличие имеется развитая работа со множествами и свой язык регулярных выражений («образцов»), который может применяться не только на скалярах, но и на множествах! Кроме того имеются операторы, позволяющие распараллеливать вычисления!

Помимо скаляров и множеств, язык поддерживает «классы» — насколько я понял, своеобразные вычисляемые «срезы» множеств по какому-либо условию (вроде того как это делается в numpy), но могу и ошибаться, книга написана очень сухим, академическим языком и иногда вообще не удаётся понять, что имеется ввиду.

Например, попробуйте разобраться в этом отрывке («каноническая запись», как утверждается выше процитированного — это когда отброшены все незначащие нули):

Каноническим числовым представлением нуля будем считать слово 0. В частности, каноническим числовым значением слова 1 является оно само, каноническим представлением слова 2 является слово −6, каноническим представлением слова 3 — слово 67000000000000000.

Я, сколько ни пытался, не смог уловить закономерность.

Программы по внешнему виду сильно напоминают «Паскаль» — совпадают некоторые конструкции и принципы, но, конечно же, есть и вещи, которые сильно отличаются.

Выполнение программы выполняется с первой процедуры и завершается с выходом из неё. Возврат значения из функции (тут она называется «процедура-функция») осуществляется через переменную, совпадающую с именем функции. Все переменные, включая переменную для возврата, должны быть задекларированы в специальном блоке. Скалярные переменные декларируются в секции word, множества — в set, классы — в секции class.

/* Считалочка про пиво на языке «Старсет». Написал Евгений Степанищев */
/* https://bolknote.ru */

Func Бутылки(колво)
    Word: Бутылки, колво;

    Choice
        Case колво = 1 Do Бутылки := '1 bottle' End
        Case колво = 0 Do Бутылки := 'no bottles' End
        Otherwise Do Бутылки := колво^' bottles' End
Endfunc

Proc Песня()
    Word: колво, бутылок;

    колво := 99;
    бутылок := Бутылки(колво);

    While колво > 0 Do
        Write бутылок^' of beer on the wall, '^бутылок^' of beer.';
        колво := колво - 1;
        бутылок := Бутылки(колво);
        Write 'Take one down and pass it around, '^бутылок^' of beer on the wall.';
        Write ''
    End

    Write 'No more bottles of beer on the wall, no more bottles of beer.';
    Write 'Go to the store and buy some more, 99 bottles of beer on the wall.'
Endproc

Все строки заключаются в апострофы, которые в книге имеют непривычный вид (’), я не стал их тут использовать, вряд ли это какая-то особенность языка, скорее уж такой символ просто поставили при наборе.

Ещё необычно выглядит операция конкатенации — ^ и повторения строки — ^^. Из-за того, что в языке есть неявные преобразования, можно делать вот такое:

Write 'МА' ^^ '2 раза' /* Будет выведено «МАМА», так  как «2 раза» преобразуется к двойке */

С удовольствием познакомился с языком, благо он не выглядит, как что-то, сделанное «в стол», для «галочки» или исключительно для обучения школьников или студентов.

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

← 1 2 3 4 →