
Багато хто з нас схильний ідеалізувати минуле. Чи мрієте ви стати капітаном великого корабля? Звісно, за умови, що вас не лякають харчові комахи, авітаміноз та смерть від інфекції за відсутності антибіотиків. Хочете бути середньовічним лицарем? Ще гірше. Але ж ретрокомп’ютинг, напевно, такий же веселий, як ми його пам’ятаємо, чи не так? Увімкнув комп’ютер, і з’явився BASIC! Готовий для написання власних програм. Жодних тобі графічних інтерфейсів. Звісно, це лише черговий приклад ідеалізованого погляду на минуле.
Навіть якщо вам подобається BASIC або подібна мова сьогодні, все змінилося. У вас є зручний текстовий редактор, швидкий комп’ютер, інструменти для налагодження, а також такі речі, як іменовані функції, відсутність номерів рядків та сучасні структури керування. Нічого з цього не було поширеним у 1980-х роках. Принаймні, не на комп’ютерах для хобі.
Чому я про це думаю? Ну, триває Hackaday Retrocomputing Challenge, і мені спало на думку, що я хотів би попрацювати з молодими студентами на чудовому MBASIC на машині CP/M, яку я зібрав і модифікував за проєктом Hackaday. Ідеально, чи не так? Багато хто з нас починав саме так, то чому б і їм не спробувати?
Але це швидко набридло. Навіть проста програма стає обтяжливою через команди GOTO та GOSUB до таємничих номерів рядків. Це нагадало мені про час на початку 1980-х, або, можливо, навіть наприкінці 1970-х, коли я написав препроцесор BASIC для обробки BASIC без номерів рядків і створення правильного вихідного коду, перетворюючи мітки на номери рядків за два проходи.
Звісно, той код давно втрачено або, принаймні, на дискеті, яку я не намагався прочитати вже кілька десятиліть. Шість років тому я вирішив спробувати ще раз, але все одно не зробив його набагато надійнішим. Наприклад:
PRINT: X=X+10
Це два оператори? Чи мітка? Важко сказати. Моя версія 2020 року використовувала awk. Awk чудово підходить для такого роду завдань завдяки регулярним виразам і циклу обробки вхідних даних. Але він все ще має деякі проблеми з такими речами, як коментарі та рядки. Розглянемо наведений нижче код.
PRINT “HELLO: IS IT ME YOU’RE LOOKING FOR?”
Ймовірно, це змусило б мій препроцесор awk “з’їсти” рядок і створити фальшиву мітку з назвою HELLO.
Історія препроцесорів
Препроцесинг з однієї мови на іншу — це не новинка. RATFOR і RATFIV Брайана Кернігана перетворювали сучасні конструкції на звичайний FORTRAN IV. Навіть C++ почав як програма, що генерувала код C.
Отже, ідея хороша, але є десятки граничних випадків. Коли я готувався знову взятися за цю ідею для BASIC, я зрозумів, що мої попередні версії мали деякі дизайнерські рішення, які ускладнювали процес. Тому я почав з нуля.

По-перше, я відмовився від ідеї просто мати мітки, як в інших мовах. Замість цього вони стали б частиною коментаря і їх було б важко сплутати. Це спрощує парсинг, а також дозволяє залишати їх для довідки. Я також відмовився від ідеї автоматичного розгортання міток. Потрібен спосіб зробити їх унікальними. Ось що я вирішив:
‘:TOPLABEL
GOTO @TOPLABEL
План полягав у тому, щоб пройтися по вихідному коду один раз, призначаючи номери рядків. Коли зустрічається мітка, вона додається до таблиці символів. Потім другий прохід фактично пише вихідний код, замінюючи @TOPLABEL значенням із таблиці символів. Звісно, ви все ще хочете, щоб цей рядок не викликав розгортання мітки:
PRINT “Send messages to @JTKIRK”
Я вирішив продовжити роботу з awk, але моєю кінцевою метою було переписати все це на BASIC, використовуючи той самий синтаксис. Тоді ви могли б перетворити сам транслятор за допомогою версії awk, а потім запустити його на старому комп’ютері, використовуючи BASIC, навіть якщо ви хотіли б перекласти сам транслятор. Збочено, чи не так? Але це дозволяє зберегти автентичний досвід розробки. Вам не потрібно переходити на ПК для обробки коду, якщо ви просто не хочете.
Awk як у “незграбний”
Тож я вирішив краще попрацювати над частиною awk з цією новою схемою. Однак у певний момент ви втрачаєте деякі переваги. Потім почалося розширення функціоналу.
Ймовірно, я підсвідомо згадував RATFOR. Я вирішив додати невелику кількість сучасних структур керування. Знову ж таки, я хотів щось, що легко виявити у вихідному файлі, тому я зупинився на цьому:
IF X=0 THEN! PRINT “There is no X!” X=10 ENDIF!
Також є WHILE!, DO!, EXIT! та CONTINUE!
Звичайно, це не зупинилося. Я вирішив додати спосіб включення або виключення частин вихідного коду (схоже на #if у C, але простіше), разом із включенням вихідного коду та кількома іншими чудовими опціями, такими як числові константи, умовні блоки вихідного коду, помилки компіляції та навіть невеликий числовий стек із PUSH! та POP!.
Включення вихідного коду може працювати з awk, але це виглядає негарно. Я вирішив, що краще перейти до іншої мови, але оскільки я не хотів починати з нуля, я просто попросив LLM перетворити мій awk на Python, що він зробив без жодних проблем. Потім я додав ще трохи функціоналу в Python, але переконався, що не використовую ці нові функції в самому трансляторі, щоб версія awk могла продовжувати обробляти вхідний файл.
Отже, хоча початковий план полягав у розробці та тестуванні в awk, а потім у впровадженні подібного коду в MBASIC, тепер у мене було три версії: заморожений скрипт awk, версія Python та версія MBASIC.
Це стало занадто складно тестувати. Я попросив LLM створити документацію, додаткові коментарі та тести. Було особливо приємно переконатися, що всі три версії — awk, Python та BASIC — робили одне й те саме для своїх спільних функцій. LLM добре впорався з виконанням тестів і пошуком граничних випадків. Він навіть запускав тести в сеансі RunCPM на версії MBASIC.
BASIC
Як ви й очікували, версія BASIC дещо заплутаніша. Однак використання міток і структур керування робить її набагато легшою для написання, читання та підтримки.
Написання транслятора на MBASIC наклало деякі дуже старомодні обмеження. Немає словників або динамічних списків, тому мітки та стан блоку зберігаються у масивах фіксованого розміру. Включені файли вимагають явно керованого стека, а розбір рядків і коментарів доводиться виконувати символ за символом. Це не так компактно, як версія Python, але це звичайний MBASIC, і він може працювати на цільовій машині CP/M. Обмеження також вплинули на деякі функції, які були б можливі в Python, але майже неможливі в програмі MBASIC.
Що ще краще, lblbasic.bal залишається в межах підмножини, яку розумів оригінальний транслятор awk. Це забезпечує шлях завантаження: awk генерує перший lblbas.bas, після чого транслятор MBASIC може обробляти власний вихідний код BAL.
Кілька зразків
Один із проєктів, який я мав на увазі, — це керування модулем світлодіодного дисплея. Я написав бібліотеку, а потім і тестову програму нижче. Це не має значення, але я використав розширення файлу .bal.
REM TM1637 TEST – PORT 3, BIT 2=DIO, BIT 3=CLK REM Uses the TM1637.BAL library with LBLBASIC
STACK! 8 ‘ Required by the library’ Confirm that the library preserves I while initializing its data table.
I=3141 gosub @init print “I is now:”;I PRINT “Starting number: “; INPUT CT PRINT “1-Up, 0-Down: “; INPUT UD OFFSET=-1 IF UD0 THEN OFFSET=1 ‘
:CDLOOP
WHILE! CT>=0 and CT<=9999 NUM=CT GOSUB @sendnum4 CT=CT+OFFSET WEND!
CT=0 IF OFFSET=-1 THEN CT=9999 GOTO @CDLOOP END’ Include the library after the main program.
‘INCLUDE! TM1637.BAL
Код BASIC, включно з кількома рядками бібліотеки, читати набагато складніше:
10 DIM LBLBSTACK#(8):LBLBSP=0
20 I=3141
30 gosub 230
40 print “I is now:”;I
50 PRINT “Starting number: “;
60 INPUT CT
70 PRINT “1-Up, 0-Down: “;
80 INPUT UD
90 OFFSET=-1
100 IF UD0 THEN OFFSET=1
110 ‘ :CDLOOP
120 IF CT>=0 and CT<=9999 THEN
140
130 GOTO 180
140 NUM=CT
150 GOSUB 330
160 CT=CT+OFFSET
170 GOTO 120
180 CT=0
190 IF OFFSET=-1 THEN CT=9999
200 GOTO 120
210 END
220 ‘ :init
230 IF LBLBSP+1>64 THEN PRINT “BAL stack overflow”:STOP
240 LBLBSTACK#(LBLBSP+1)=I:LBLBSP=LBLBSP+1
250 DIM D(9)
260 FOR I=0 to 9: READ D(I): NEXT I
270 DATA 63,6,91,79,102,109,125,7,127,111
280 X=12:OUT 3,X
290 IF LBLBSP<1 THEN PRINT "BAL stack underflow":STOP
300 I=LBLBSTACK#(LBLBSP):LBLBSP=LBLBSP-1
310 return
320 ‘ :sendnum4
330 GOSUB 500
340 B=64:GOSUB 610

Якщо ви раніше не користувалися TM1637, він використовує послідовний протокол з тактовою лінією та лінією даних і є дуже стійким до таймінгу. Швидкість оновлення дисплея досить низька, частково тому, що MBASIC не такий швидкий, а частково тому, що чіп Z80 спілкується із зовнішнім світом через інший мікроконтролер, що працює через шину I2C. Але код, не BAL, був би таким же повільним на тому ж комп’ютері. Ви помітите, однак, що я прибрав усі затримки, і код все ще працює добре.
Більший приклад, однак, — це HILO.BAL. Це дозволяє встановити кілька констант часу компіляції, які дають змогу вибрати, які частини програми будуть зібрані. Він також добре використовує цикли керування. Звісно, якщо ви справді хочете заглибитися, lblbasic.bal використовує значну частину синтаксису, сумісного з awk, і є суттєвою програмою: близько 1300 рядків вихідного коду BAL, які генерують майже 900 рядків звичайного BASIC завдяки видаленню коментарів та пробілів.
Висновок
Чи перетворює це MBASIC на сучасну мову? Звісно, ні. Згенерована програма все ще має номери рядків, машина все ще крихітна, а реалізація містить компроміси, які жахнули б будь-кого, хто пише справжній компілятор. Але це усуває достатньо перешкод, щоб програмування старої машини знову стало приємним, особливо з WordStar як редактором.
Що ще важливіше, транслятор може жити на машині, для якої він призначений. Прототип awk може один раз перекласти lblbasic.bal, створивши звичайний MBASIC. З цього моменту машина CP/M може перекладати програми BAL — включно із самим транслятором — без допомоги Python або сучасного комп’ютера.
Тож, можливо, урок не в тому, що ретрокомп’ютинг був кращим, ніж ми пам’ятаємо. А в тому, що за допомогою невеликого стратегічного “шахрайства” він може бути майже таким же веселим, як ми його пам’ятаємо.
