99
У американцев есть такая считалочка — «песня о пиве», наверняка её многие слышали в американских фильмах. У меня есть хобби — делать вывод этой считалочки на разных языках программирования. Со временем набралась приличная коллекция.
99 бутылок: ПРОЛ-2
Иван Черняков, которого я консультировал в процессе создания фильма о программируемом калькуляторе «Электроника
«Буран» решал задачу многоразового возвращаемого орбитального корабля: вывести аппарат на орбиту, доставить людей или груз, выполнить работу в космосе и вернуть корабль на Землю для потенциального повторного использования.
В фильме упомянуты несколько
Разумеется мне немедленно захотелось найти хоть
Итак, судя по сохранившемуся тексту, «ПРОЛ‑2» — императивный язык управления для системы реального времени. Современному программисту его запись напомнит Паскаль: присваивание :=, блоки НАЧАЛО и КОНЕЦ, явно объявляемые типы, условия и циклы, но с русскими ключевыми словами.
Помимо обычных вычислений язык поддерживал процессы и исполнителей, выдачу команд оборудованию, ожидание событий, паузы и обработку прерываний. Характерная особенность — в объявлениях нелокальных переменных отдельно перечислялись процедуры, которым разрешалось их читать и изменять: ограничения доступа входили непосредственно в описание данных.
Вот какая «песня» у меня получилась:
% ПЕСНЯ О ПИВЕ. НАПИСАНА СТЕПАНИЩЕВЫМ Е. В. HTTPS://BOLKNOTE.RU
МОДУЛЬ ПИВО ТЕСТ;
ПРОЦЕСС ПИВО;
ПЕСНЯ:1T;
КОНЕЦ
ИСПОЛНИТЕЛЬ ПЕСНЯ;
N:ЦЕЛ(0..99);
НАЧАЛО
N:=99;
<<КУПЛЕТ>>
ПЕЧАТЬ(/,N,' БУТЫЛОК ПИВА НА СТЕНЕ',/);
ПЕЧАТЬ(N,' БУТЫЛОК ПИВА!',/);
ПЕЧАТЬ('ВОЗЬМИ ОДНУ, ПУСТИ ПО КРУГУ',/);
N:=N-1;
ПЕЧАТЬ(N,' БУТЫЛОК ПИВА НА СТЕНЕ!',/);
ЕСЛИ N>0 ТО НА КУПЛЕТ;
ПЕЧАТЬ(/,'НЕТ БУТЫЛОК ПИВА НА СТЕНЕ!',/);
ПЕЧАТЬ('НЕТ БУТЫЛОК ПИВА!',/);
ПЕЧАТЬ('ПОЙДИ В МАГАЗИН И КУПИ ЕЩЁ,',/);
ПЕЧАТЬ('99 БУТЫЛОК ПИВА НА СТЕНЕ!',/);
ВЫЙТИ ПЕСНЯ;
КОНЕЦ
Почти все строки, думаю, понятны, я бы только одну строку прокомментировал — ПЕСНЯ:1T. Эту конструкцию я перенёс из найденного листинга, но что она точно значит, я сказать не могу. Похоже на выделение тактов для выполнения параллельных подпрограмм. Но так ли это, пока неизвестно.
99 бутылок: Basic в UNIXv4
А раз я узнал как запустить UNIXv4, грех не воспользоваться возможностью посмотреть что там за языки программирования есть на борту. Ассемблер и Си меня не заинтересовали, но оказалось, что в комплект входит довольно интересный «Бейсик», маленький, но мощный.
На нём я и решил написать «Песню о пиве».
86. Basic в UNIXv4 работает в двух режимах — интерактивном, в нём операторы вводятся без номера и выполняются сразу же, и программном, где номер строки указывается, а оператор запоминается для последующего выполнения. Этими двумя режимами можно пользоваться вперемешку.
Редактора нет, набивать программу нужно сразу в буфер интерпретатора, либо создавать при помощи стороннего редактора. Важно не ошибиться, так как редактировать программу можно только полной заменой строки, указав новому варианту тот же номер.
Почти во всём это довольно стандартный «Бейсик», но тут есть две особенности.
У меня в коде есть пугающая конструкция 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Программу, которая у меня получилась и её вывод я заскриншотил для истории. Интересно, конечно, когда в последний раз на этом «Бейсике»
«99 бутылок» и «Постгрес»
Недавно моему сайту исполнилось 25 лет и я вспоминал свою первую заметку, с которой всё началось. Она положила начало традиции, которая длится столько, сколько существует этот сайт — писать «песню о пиве», это такая американская считалочка, на различных языках программирования.
Когда я пересматривал ту заметку, обратил внимание насколько сложно написана была «песня о пиве» на языке СУБД «Постгрес». Это был
(
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.';99 бутылок: JOSS
Продолжают писать американскую считалочку «песня о пиве» на всяких странных и не очень языках программирования.
85. JOSS (JOHNNIAC Open Shop System) — язык, созданный Клиффордом Шоу в RAND Corporation в 1963 году для машины JOHNNIAC.
Именно в этом языке впервые появились условные выражения, диапазоны, функции и компактный синтаксис ветвлений, позволявший записывать целые решения в одну строку. Из «Джосса» выросли TELCOMP, STRINGCOMP, CAL, MUMPS, «Фокал» и «Бейсик» — целое поколение языков, унаследовавших его простоту и интерактивность.
Что в «Джоссе» необычного? Из тех языков, с которыми наиболее вероятно столкнуться в реальной жизни, он, разумеется, напоминает
Ну и мелочи: перевод строки обозначается подчёркиванием (_), умножение — интерпунктом (·), «звёздочка» при этом служит для экспоненты, «неравно» записывается отдельным символом (≠). Таких деталей в «Джоссе» много — перечислять можно долго.
* "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.Язык давно уже не используется, поэтому мне не удалось найти, где можно было бы выполнить на нём программу. Обычно в таких случаях я просто пишу код по описанию, не проверяя его, но здесь впервые пошёл другим путём — написал несколько тестов и «завайбкодил» интерпретатор по описанию, взятому из «Википедии».
Колеблюсь, выкладывать его или нет. С одной стороны, кому он нужен — интерпретатор давно почившего языка? С другой стороны, вдруг
99 бутылок: AkRScript
В полку странных языков программирования, на которых я писал «песню о пиве», прибыло.
84. AkRScript — самодельный скриптовый язык программирования, написанный в середине десятых годов владельцем
Он его оживил в эмуляторе и выложил несколько программ, написанных на 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 бутылок: HolyC
История появления языка, на котором написана сегодняшняя «песня о пиве», довольно занятна.
Его автор, Терри Дэвис — гениальный одиночка и бывший инженер компании Ticketmaster, который посвятил более десяти лет созданию собственной операционной системы TempleOS. Страдая от шизофрении, он считал, что действует по воле Бога, который лично поручил ему создать «божественную ОС». Несмотря на тяжёлое психическое состояние, Дэвис самостоятельно написал компилятор, ядро, графическую оболочку и язык программирования — в полном одиночестве и с поразительной продуктивностью.
83. HolyC — тот самый язык, разработанный Терри. Выглядит он довольно интересно. Сильно напоминает Си, но имеет много интересных особенностей, некоторые из которых сразу бросаются в глаза, даже в моей небольшой программе.
Чуть менее заметная деталь — оператор case может не иметь аргумента, в таком случае используется автоикрементное значение, начинающееся с нуля. В 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";
}
99 бутылок: Входной язык ПЭВМ Искра-1256
Сегодня у меня рубрике «99 бутылок» язык настолько редкий, что у него даже отдельного названия нет. Иногда его называют «русскоязычным Бейсиком
В редкой профессиональной литераторе по теме его называют «входной язык…» или «символьный язык ПЭВМ
83. Входной язык ПЭВМ
Машина работала
С языком программирования пришлось разбираться по листингу сохранившихся программ, никаких следов его описания к сожалению, не обнаружилось. Впрочем, если прочитать все нашедшиеся программы, основное понять можно, сложного там ничего нет.
Строки нумеруются подряд, видимо в этом
Номер строки тут, кажется, используется только для удобства набора текста программы, переход же в языке делается не на номер строки, а на метку, причём их минимум два вида — метка для передачи управления, начинающаяся с буквы «М» (например — M01), и метка подпрограммы, которая начинается с «П» (П01).
Операторы записываются русскими буквами. Например ПЕРЕХ П01 — вызов подпрограммы, перед которой стоит метка П01. Оператор перехода часто используется с условным оператором, у которого нет
Переменные так же записываются русскими буквами, их буквенные префиксы значимы. Со всеми префиксами разобраться не удалось, но некоторые я понял: например, буквами «И» и «А» обозначаются переменные, содержащие числа. Разница, кажется, в том, что на вторые
Необычно выглядит присваивание — оно тут записывается как => и присваивает левое значение правому операнду.
Но самое странное — это функции.
Вот какая программа получилась у меня после попытки освоить этот язык. Не факт, что она написана без ошибок, но попробовать, как вы догадываетесь, негде:
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 он чего только не делал».
Добавлено позднее: действительно, оператор СЕЛЕКТ() чего только не делал, например через него можно было производить смену режимов дисплея или указывать задержку.
99 бутылок: калькулятор bc
Разбирал одну старенькую научную статью с некоторым количеством математики, а внутри увидел вычисления на bc.
Не особо с ним знаком, но несколько раз пользовался, даже не вникая что он умеет. Честно говоря, не помню зачем он мне тогда понадобился, наверное надо было
В общем, оказалось, там внутри целый язык программирования, с циклами, ветвлениями, локальными переменными, строками, массивами и числам. Поддерживаются даже пользовательские функции. Решил попробовать написать на нём очередную «песню о пиве».
82. bc — интерактивный интерпретатор
/* 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
Никаких других сложностей с этой программой у меня не возникло, язык простой, хорошо продуманный, лёгок в освоении.
Добавлено позднее: как оказалось, у языка есть свои особенности о которых нелишне знать. Например, подключение математической библиотеки меняет точность, определяемую переменной scale, что влияет, например, на операцию вычисления модуля, она начинает работать иначе.
Другая странность — операции сдвига работают в десятичной системе. Не понимаю какой от этого толк. Например, 1<<2 даст 100.
99 бутылок: язык B («Би»)
В последнее время я довольно много пишу на Си,
При этом ни в одной проштудированной мной статье нет ни одной даже самой простенькой программы на Би, а я человек любопытный. В общем, я решил сам посмотреть и вам показать.
81. B (язык «Би»), после появления Си, был полностью вытеснен последним и сейчас совершенное не используется. В отличие от, например, Кобола, который старше на десять лет, но при этом вполне ещё встречается.
Думаю, причин тут несколько.
Язык Би и правда очень похож на Си, в этом можно убедиться, взглянув на написанную мной программу. Что тут режет глаз сишному программисту? Прежде всего — отсутствие типов у заголовка функции, импорта функции printf и странное ключевое слово extrn внутри функций. extrn означает, что этот символ (в терминах компилятора) пришёл из внешней зоны видимости. Современное extern явно произошло от него, только значение немного поменялось.
Немного непривычно, что все переменные объявлены как auto, но в современном Си так тоже можно. auto, в данном случае, — класс хранения. В современном Си это класс хранения
Впрочем, в стандарте Си23 auto теперь означает автоматический вывод типа, что для совместимости с Би подходит ещё больше, так как, если приглядеться, строковые массивы («строки») в Би не выделены в отдельный тип.
Из того, что заметить чуть сложнее — экранирующий символ в строке не обратный слэш, а «звёздочка» (*), поэтому символ перевода строки не \n, а *n.
Ещё немного странно, что тут цикл while, а не for, но сильно это в глаза не бросается. В «Би» цикла for просто нет, поэтому вот так.
Вообще, ощущение такое, что Си взял от Би буквально всё, но, как ни странно, это не так. В Би есть конструкции ===, =!=, =<= и =>=. Наверное они
Кроме того, другая почти вся стандартная библиотека. Совпадают всего несколько функций, по больше части это
/* 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");
}Я долго пытался найти хоть
В итоге, остановился на
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 — это персональный компьютер, выпускавшийся в начале
Последние пару дней я пытался написать на тамошнем «Паскале» «песню о пиве», но поскольку на моём ноутбуке потребовалась двойная эмуляция, скорость работы встроенного в систему редактора была ниже плинтуса. Писать было очень сложно, а править ошибки — ещё сложнее. Тем не менее, я справился.
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 для каждого числа, выводимого на экран.
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). У обеих команд есть параметр, где указывается имя файла, с которым мы работаем.
Но если мы открывали на редактирование
99 бутылок: bootBASIC
Очередная «песня о пиве», в последнее время я
78. bootBASIC — интерпретатор простенького диалекта Бейсика, занимающий 512 байт. В наше время, когда некоторые программы занимают гигабайты, удивительно видеть
А тут целый язык, со строками, поддержкой математики, ветвлением и даже небольшим редактором кода. Правда я уже писал «песню о пиве» на миниатюрном Си. Он занимал чуть меньше, но и возможности там были
Конечно, при таком размере есть свои ограничения — арифметика только целочисленная, до 65535 и нет операций сравнения, — можно проверить только, что число не равно нулю.
Кроме того, строки кода ограничены по длине,
Обидно, что нет 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. Эти вложенные директории в дальнейшем используются как конечный цикл для следующей команды find. Вложенность при этом можно регулировать параметром -maxdepth.
Мне пришлось дополнительно решить ещё две проблемы.
На самом деле на каждой внешней итерации я пускаю не один, а два внутренних цикла, поскольку мне нужны два числа — текущее значение считаемого «пива» и это же значение, уменьшенное на единицу. Сделать это было просто — ещё один find надо запускать во вложенном директории.
Но в строке перед этим текстом у меня сделано более честно. Как видно, структура директориев у меня такая: 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Программирую под Flipper Zero
Мне месяц назад на день рождения подарили «Флиппер Зеро» — «перочинный ножик для хакера». Довольно известное в узких кругах устройство, очень занятное. Но об этом
Давно подмывало
В интернете есть несколько статей о том как начать, которые мне очень помогли. Довольно быстро я научился собирать первое приложение, которые выводит строчку текста и опрашивает клавиатуру. По пути поразился сколько бойлерплейта (шаблонного почти одинакового для каждого приложения кода) пришлось написать. Очень не хватает
Пока застопорился — не пойму как прокручивать экран и есть ли
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(); // '.'
}«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 бутылок: 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.psql99 бутылок: 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 — записываем результат запроса в переменные, имена которых равны именам столбцов запроса.
У меня есть одна идея, как сделать вычитание, не привлекая эскуэль, надо будет попробовать. А вот с остановом хуже — там никаких идей.
99 бутылок: Старсет
Семьдесят четвёртый выпуск считалочки «песни о пиве», которую я пишу на разных языках программирования, для разнообразия сделаю на языке родом из СССР.
74. Старсет — язык программирования высокого уровня, разработанный в Институте программных систем РАН в 1991 году. К сожалению, мне не удалось обнаружить компиляторов или интерпретаторов этого языка, и даже упоминания, что они вообще
Поэтому программировать пришлось исключительно по описанию языка, почерпнутого из книжки, купленной за двести рублей с рук в интернете.
Язык, кстати говоря, небезынтересный — в наличие имеется развитая работа со множествами и свой язык регулярных выражений («образцов»), который может применяться не только на скалярах, но и на множествах! Кроме того имеются операторы, позволяющие распараллеливать вычисления!
Помимо скаляров и множеств, язык поддерживает «классы» — насколько я понял, своеобразные вычисляемые «срезы» множеств
Например, попробуйте разобраться в этом отрывке («каноническая запись», как утверждается выше процитированного — это когда отброшены все незначащие нули):
Каноническим числовым представлением нуля будем считать слово 0. В частности, каноническим числовым значением слова 1 является оно само, каноническим представлением слова 2 является слово −6, каноническим представлением слова 3 — слово 67000000000000000.
Я, сколько ни пытался, не смог уловить закономерность.
Программы по внешнему виду сильно напоминают «Паскаль» — совпадают некоторые конструкции и принципы, но, конечно же, есть и вещи, которые сильно отличаются.
Выполнение программы выполняется с первой процедуры и завершается с выходом из неё. Возврат значения из функции (тут она называется
/* Считалочка про пиво на языке «Старсет». Написал Евгений Степанищев */
/* 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 раза» преобразуется к двойке */С удовольствием познакомился с языком, благо он не выглядит, как