Чешский Наука и Образование
03.09.2026 Читать источник
Разработка программ для Atari 8-битных компьютеров: от ассемблера до ручного редактирования кода в мониторе
В статье описывается методика создания и отладки приложений для старых компьютеров Atari с использованием примитивных инструментов, таких как Turbo Monitor, вместо современных IDE. Автор демонстрирует, как вручную встраивать циклы и точки останова в машинный код для визуализации изменений и проверки работоспособности программ.
Перевод ИИ
Текст статьи доступен на языке оригинала.
Нажмите кнопку «Перевести статью» выше, чтобы сгенерировать перевод с помощью ИИ.
Оригинальный контент
V předchozích dvou článcích o vývoji aplikací pro osmibitové domácí mikropočítače Atari [1] [2] jsme si (i když jen na poměrně malých programech) ukázali, jak probíhal vývoj a ladění aplikací s využitím původního Atari Assembler/Editoru či novějšího a mnohem rychlejšího assembleru MAC/65 doplněného o debugger nazvaný DDT (oba tyto nástroje dodávala společnost OSS).
Z pohledu dnešního vývojáře sice mohou všechny tři zmíněné vývojové nástroje vypadat velmi spartánsky, ovšem pro situaci před cca 40–45 lety se ve skutečnosti jednalo o uživatelsky přívětivé nástroje (další revoluce nastala až s příchodem integrovaných vývojových prostředí Turbo Pascalu a dalších programovacích jazyků – to je však jiná oblast informatiky, která se už netýká osmibitových počítačů Atari).
Nicméně vyvíjet (a do jisté míry i ladit) aplikace bylo možné i s využitím ještě jednodušších prostředků, než jsou assemblery a debuggery. Příkladem může být tuzemský Turbo Monitor, což byl program používaný mj. pro úpravu a převod programů do formátu Turbo 2000 (jak již ostatně naznačuje jeho název). Ovšem díky tomu, že tento nástroj obsahuje kromě klasických operací, které si s monitory spojujeme, i disassembler a navíc i možnost spouštět strojový kód umístěný na libovolné adrese, lze ho použít i pro další činnosti – úpravu her pro nekonečný počet životů, ladění existujících programů, ale i (a to je již poměrně hardcode i na osmdesátá léta minulého století) pro vývoj nových programů „na zelené louce“.
Co je tedy pro takový vývoj zapotřebí? Ve skutečnosti jen zcela minimální prostředky, o to však větší znalosti (tedy naprostý opak k vibe codingu):
Vzhledem k tomu, že Turbo Monitor neobsahuje plnohodnotný assembler, který by dokázal převádět symbolická jména instrukcí a symbolické adresy (návěští) do strojového kódu, je nutné tuto činnosti provádět ručně a vrátit se tak vlastně na začátek padesátých let minulého století, kdy ještě plnohodnotné assemblery vlastně neexistovaly. U moderních architektur mikroprocesorů, které obsahují tisíce různých variant instrukcí a nepřeberné množství jejich kódování (adresovací režimy, prefixy atd.), by takový přístup byl velmi složitý, ovšem osmibitový mikroprocesor MOS 6502 je v tomto ohledu mnohem jednodušší. Dokáže totiž vykonat pouze 56 strojových instrukcí a i když mnohé instrukce podporují různé adresovací režimy, je jejich kódování do určité míry pravidelné, což zjednodušuje ruční zápis strojového kódu.
Nejprve si uveďme seznam všech podporovaných instrukcí:
Následuje seznam všech podporovaných adresovacích režimů. Právě adresovací režimy odlišují mikroprocesor MOS 6502 od naprosté většiny ostatních mikroprocesorů a umožňují použít odlišný styl programování založený na efektivním použití nulté stránky paměti a jednoho z index registrů (každý z nich má nepatrně odlišné možnosti). Existuje celkem třináct (!) adresovacích režimů, ovšem žádná instrukce nevyužívá všechny dostupné režimy. Některé adresovací režimy jsou určeny pouze pro skoky, další pro implicitní operandy atd.:
V případě, že všechny možné existující kombinace instrukcí a adresovacích režimů zapíšeme do často používané mřížky 16×16 buněk, budeme moci zjistit, jestli jsou instrukce stejného typu seskupeny podle vyššího nibblu a/nebo nibblu nižšího (nibblem se označuje čtveřice bitů). Získáme tedy tabulku se všemi instrukčními kódy, které jsou na mikroprocesoru MOS 6502 podporovány. Mnohé instrukce ovšem (zcela pochopitelně) za instrukčním kódem očekávají další jeden až dva bajty s konstantou či adresou:
Výše uvedená tabulka naznačuje, že kódy nebyly instrukcím přiřazeny zcela náhodně, ale že zde skutečně existují nějaká pravidla. Je to ostatně logické, protože pravidelná a do určité míry ortogonální instrukční sada umožňuje zjednodušit dekodér instrukcí (ten na MOS 6502 zabírá přibližně jednu třetinu plochy čipu). Z výše uvedené tabulky lze tato pravidla odvodit. Informace uvedené ve zbytku této kapitoly byly získány především ze stránky The 6502/65C02/65C816 Instruction Set Decoded, která se zabývá (a to do všech detailů) právě tímto tématem.
Operační kód instrukce, což je vždy jen jeden bajt, lze reprezentovat třemi skupinami bitů aaa, bbb a cc, které tvoří výsledný bajt ve formátu aaabbbcc. Bity ze skupin aaa a cc určují typ instrukce (například LDA), zatímco bity ze skupiny bbb slouží ke specifikaci adresního režimu (například (ZP,X)).
Na základě hodnoty bitů ze skupiny cc je možné instrukce rozdělit do tří množin (teoretická čtvrtá množina je prázdná):
Odlišné kódování mají pouze instrukce pro podmíněné skoky. Bity instrukčního kódu mají vzorek xxy10000, kde xx určuje příznakový bit a y určuje, zda se skok provede v případě, že je příznakový bit vynulován či naopak nastaven.
Pro programátora, který používá Turbo Monitor, nemusí být výše uvedená tabulka se všemi instrukcemi příliš přehledná. Praktičtější je instrukce rozdělit logicky, ideálně na základě bitů ze skupiny cc zmíněné v předchozím textu, do několika menších tabulek, které vlastně mají oproti předchozí tabulce „inverzní“ strukturu (v původní tabulce jsou uloženy kódy instrukcí, kdežto typ instrukce a adresovací režim je zapsán v hlavičkách sloupců a řádků). Výsledky mohou vypadat následovně:
Operace s akumulátorem, tedy instrukce s bity cc nastavenými na hodnotu 01, mají ortogonální strukturu a tabulka tak pokrývá největší skupinu instrukcí, které podporují shodné adresovací režimy:
Operace s index registry, bitové posuny a rotace:
Operace s index registry a skoky:
Instrukce skoků s podmínkou:
Zbylé instrukce, které nezapadají do žádného schématu:
Nyní již máme k dispozici tabulky instrukcí, takže je možné se začít zabývat samotným Turbo Monitorem. Po jeho spuštění by měla obrazovka tohoto nástroje vypadat následovně:
Obrázek 1: Úvodní obrazovka Turbo Monitoru.
Autor: tisnik, podle licence: Rights Managed
Ovládání Turbo Monitoru se provádí jednopísmennými příkazy popř. kombinací Ctrl+klávesa. Příkazy lze rozdělit na práci se zařízeními a práci s pamětí:
Turbo monitor umožňuje prohlížení obsahu paměti, a to několika způsoby. Pro programátory a hackery (v původním smyslu tohoto slova) je nejužitečnější výpis paměti zkombinovaný se zpětným dekódováním instrukcí. Po zadání příkazu D (disassemble) se monitor zeptá na počáteční adresu (opět – pozor na rozdíl mezi decimálními a hexadecimálními adresami) a poté se po každém stisku Enteru vypíše další instrukce:
Obrázek 2: Prohlížení obsahu paměti se zpětným dekódováním instrukcí.
Autor: tisnik, podle licence: Rights Managed
Snadno si ověříme, že šestá paměťová stránka je prázdná a můžeme do ní zapisovat naše demonstrační příklady:
Obrázek 3: Prohlížení obsahu paměti se zpětným dekódováním instrukcí.
Autor: tisnik, podle licence: Rights Managed
Druhý způsob výpisu obsahu paměti je spuštěn příkazem M neboli monitor. Opět je nutné zadat počáteční adresu. Monitor postupně vypisuje obsah paměti a očekává buď potvrzení klávesou Enter nebo zadání nových hodnot, které se ihned do paměti zapíšou. Nejprve si ukažme pouhý výpis obsahu paměti:
Obrázek 4: Prohlížení obsahu paměti odlišným způsobem (hexa výpis) s možností úpravy hodnot.
Autor: tisnik, podle licence: Rights Managed
Nyní se dostáváme k prvnímu demonstračnímu příkladu. V něm se pouze nastavuje barva pozadí textu na černou. V assembleru vypadá výpis takového kódu jednoduše:
*= $0600 LDA #0 STA 710 BRK
Ovšem my dnes nemáme k dispozici assembler, ale jen monitor. Musíme tedy s využitím tabulek zmíněných výše provést ruční převod symbolických jmen instrukcí do strojového kódu:
A9 00 LDA #0 8D C6 02 STA 710 00 BRK
Pouze strojový kód tedy vypadá takto:
A9 00 8D C6 02 00
A přesně tuto sekvenci bajtů (tak jak je uvedena, tedy ve stejném pořadí) zapíšeme v režimu monitoru na adresy $0600 až $0605:
Obrázek 5: Zápis strojového kódu v monitoru.
Autor: tisnik, podle licence: Rights Managed
Stiskneme Reset pro ukončení režimu monitoru a výběrem příkazu D (disassembler) a zadáním adresy 0600 si ověříme, že zapsaná sekvence šesti bajtů skutečně odpovídá požadovaným instrukcím:
Obrázek 6: Kontrolní výpis kódu, který jsme do paměti zapsali.
Autor: tisnik, podle licence: Rights Managed
Výborně, vše se zdá být zcela korektní. Proto opustíme režim disassembleru (například opět Resetem) a příkazem G provedeme spuštění programu zapsaného v šesté stránce paměti. Program je sice ukončen (konkrétně v našem případě instrukcí BRK), ale obrazovka se zdánlivě vůbec nezměnila – pozadí je stále zelené a nikoli černé. A po dokončení programu se vypíše obsah všech registrů. Z tohoto výpisu lze ověřit, že program skončil na adrese $0605 (správně) a akumulátor A je vynulován (opět správně):
Obrázek 7: Po spuštění programu příkazem G se vypíšou obsahy registrů, ale zdánlivě se nic dalšího nestalo.
Autor: tisnik, podle licence: Rights Managed
Program ve skutečnosti změnil barvu pozadí, ovšem po dokončení jeho běhu si Turbo monitor obnovil vlastní display list i obsah barvových registrů. To je rozumné, protože například při ladění hry je vhodné se vždy vrátit k čitelnému textovému režimu. Ovšem jak dosáhnout toho, aby byly změny provedené programem skutečně vizuálně viditelné? Je to snadné – program zacyklíme – nikdy se tedy nevykoná instrukce BRK. Pro jednoduchost použijeme instrukci nepodmíněného skoku JMP a (opět pro jednoduchost – nechce se mi provádět výpočty adres) budeme skákat na začátek programu, tedy na adresu $0600:
LDA #0 STA 710 JMP 0600
Ruční překlad do assembleru (pozor na zápis adresy dolní/horní bajt):
A9 00 LDA #0 8D C6 02 STA 710 4C 00 06 JMP 0600
Výsledný strojový kód:
A9 00 8D C6 02 4C 00 06
Opět se přepneme do režimu monitoru a program přepíšeme novými instrukcemi:
Obrázek 8: Takto vypadá náš kód s přidaným nepodmíněným skokem.
Autor: tisnik, podle licence: Rights Managed
Disassemblerem si ověříme, že byly jak kódy instrukcí, tak i konstanta a dvě adresy zapsány korektně:
Obrázek 9: Kontrolní výpis upraveného programu.
Autor: tisnik, podle licence: Rights Managed
Po spuštění programu příkazem G 0600 by se skutečně měla změnit barva pozadí obrazovky. Následně se program zacyklí a vzhledem k tomu, že nikdy neprovede instrukci BRK, zůstane obrazovka v tomto stavu:
Obrázek 10: Po spuštění upraveného programu se už "něco děje".
Autor: tisnik, podle licence: Rights Managed
V předchozím demonstračním příkladu jsem schválně použil instrukci pro absolutní nepodmíněný skok JMP, protože bylo dopředu známé, na jakou adresu se skok provede. Ovšem všechny skoky s podmínkou (je jich celkem osm, vždy dvě instrukce pro každý příznak) používají relativní adresu:
Tato adresa je uložena v jediném bajtu, který reprezentuje osmibitovou hodnotu se znaménkem, tj. v rozsahu –128 až 127. Ovšem otázkou zůstává, od jaké přesné adresy se tato relativní adresa počítá? Každá architektura mikroprocesorů k tomuto problému přistupuje různě – například se provádí výpočet od adresy instrukce skoku nebo dokonce od adresy za kódem této instrukce. V případě mikroprocesoru MOS 6502 je situace snadnější – adresa se počítá od instrukce, která následuje za instrukcí podmíněného skoku.
Co to znamená v praxi?:
Pokusme se nyní demonstrační příklad upravit do takové podoby, aby se v něm použila dvojice podmíněných skoků. První z těchto instrukcí provádí skok vpřed – přeskočí se instrukce BRK (ta by totiž program ihned ukončila). A druhá z těchto instrukcí realizuje nekonečnou smyčku, tj. skok se provede na tu samou instrukci (opět: pokud by tato část byla napsána špatně, provede se BRK). Vycházím se přitom z faktu, že do akumulátoru je vložena nulová hodnota a tím pádem instrukce LDA nastaví příznak zero na jedničku (pozor – to je specifikum čipu MOS 6502; další mikroprocesory se takto nechovají):
CLC BCC NEXT BRK NEXT LDA #0 STA 710 REPT BEQ 60 BRK
Ruční překlad do strojového kódu:
18 CLC 90 01 BCC NEXT 00 BRK A9 00 NEXT LDA #0 8D C6 02 STA 710 F0 FE REPT BEQ 60 00 BRK
Pouze strojový kód:
18 90 01 00 A9 00 8D C6 02 F0 FE 00
Program ve strojovém kódu přepíšeme v monitoru do paměti:
Obrázek 11: Zápis strojových instrukcí nového programu s instrukcemi pro podmíněné skoky.
Autor: tisnik, podle licence: Rights Managed
A disassemblerem zkontrolujeme výsledek, zejména to, zda skoky používají korektní cílové adresy (disassembler je dokáže spočítat):
Obrázek 12: Disassembling testovacího programu s instrukcemi pro podmíněné skoky.
Autor: tisnik, podle licence: Rights Managed
Takto upravený demonstrační příklad spustíme běžným způsobem, tj. s využitím G 0600 (opět: pozor na hexadecimální zápis adresy). Program by nikdy neměl vykonat instrukci BRK, ale měl by skončit v nekonečné smyčce. Obrazovka by měla zčernat a vypadat následovně:
Obrázek 13: Po spuštění opět uvidíme tuto obrazovku. To je důkaz, že se neprovedla žádná z instrukcí BRK.
Autor: tisnik, podle licence: Rights Managed
Jak si však u programu běžícího v nekonečné smyčce ověříme, co vlastně provádí? Pokud program spouštíme v emulátoru počítačů Atari, je to relativně snadné. Klávesou F8 přejdeme do monitoru, ovšem nikoli do Turbo Monitoru, ale do monitoru, který je zabudován přímo v emulátoru. Měla by se zobrazit informace o tom, že se provádí instrukce BEQ $0609 umístěná na stejné adrese:
0 2 A=00 X=FF Y=04 S=FB P=--*---Z- PC=0609: F0 FE BEQ $0609
Příkazem d 0600 (vidíte tu podobnost s Turbo Monitorem?) si necháme zobrazit instrukce uložené od adresy $0600:
> d 0600 0600: 18 CLC 0601: 90 01 BCC $0604 0603: 00 BRK 0604: A9 00 LDA #$00 0606: 8D C6 02 STA $02C6 ;COLOR2 0609: F0 FE BEQ $0609 060B: 00 BRK 060C: 00 BRK 060D: 00 BRK 060E: 00 BRK 060F: 00 BRK 0610: 00 BRK
Vše se tedy zdá být v naprostém pořádku.
Vzhledem k tomu, že při práci Turbo Monitoru je ROM „přiklopena“ (je namapována do adresového prostoru čipu MOS 6502), můžeme si prohlédnout různé subrutiny, které jsou zde uloženy. Příkladem je subrutina pro kreslení využívaná mj. i Atari BASICem:
Obrázek 14: Prohlížení části ROM, zde konkrétně subrutin pro kreslení,
Autor: tisnik, podle licence: Rights Managed
Jen v krátkosti se podívejme na způsob disassemblingu části paměti s tím, že se výsledek nezobrazí přímo na obrazovce, ale pošle se do zvoleného zařízení:
Obrázek 15: Export (zápis, tisk) disassemblované části RAM či ROM na zvolené zařízení.
Autor: tisnik, podle licence: Rights Managed
Výsledkem bude soubor s následujícím obsahem, který ukazuje limity tohoto přístupu (neznámé instrukce atd.):
$FCFC: STA $3F $85 $3F $FCFE: LDA #$01 $A9 $01 $FD00: JSR $FDFC $20 $FC $FD $FD03: BMI $FD2E $30 $29 $FD05: LDA #$34 $A9 $34 $FD07: STA $D302 $8D $02 $D3 $FD0A: LDX $62 $A6 $62 $FD0C: LDY $FE93,X $BC $93 $FE $FD0F: LDA $FE91,X $BD $91 $FE $FD12: TAX $AA $FD13: ??? $F2 $FD14: TAY $A8 $FD15: STA $022A $8D $2A $02 $FD18: JSR $E45C $20 $5C $E4 $FD1B: LDA $022A $AD $2A $02 $FD1E: BNE $FD1B $D0 $FB $FD20: LDA #$80 $A9 $80 $FD22: STA $3D $85 $3D $FD24: STA $028A $8D $8A $02 $FD27: JMP $FD77 $4C $77 $FD $FD2A: LDY #$80 $A0 $80 $FD2C: DEC $11 $C6 $11 $FD2E: LDA #$00 $A9 $00 $FD30: STA $0289 $8D $89 $02 $FD33: RTS $60 $FD34: LDA #$80 $A9 $80 $FD36: STA $0289 $8D $89 $02 $FD39: LDA #$02 $A9 $02 $FD3B: JSR $FDFC $20 $FC $FD $FD3E: BMI $FD2E $30 $EE $FD40: LDA #$CC $A9 $CC $FD42: STA $D204 $8D $04 $D2 $FD45: LDA #$05 $A9 $05 $FD47: STA $D206 $8D $06 $D2 $FD4A: LDA #$60 $A9 $60 $FD4C: STA $0300 $8D $00 $03 $FD4F: JSR $E468 $20 $68 $E4 $FD52: LDA #$34 $A9 $34 $FD54: STA $D302 $8D $02 $D3 $FD57: LDX $62 $A6 $62 $FD59: LDY $FE8F,X $BC $8F $FE $FD5C: LDA $FE8D,X $BD $8D $FE $FD5F: TAX $AA $FD60: ??? $F2 $FD61: LDA #$20 $A9 $20 $FD63: ??? $5C $FD64: CPX $A9 $E4 $A9 $FD66: ??? $FF $FD67: STA $022A $8D $2A $02 $FD6A: LDA $11 $A5 $11 $FD6C: BEQ $FD2A $F0 $BC $FD6E: LDA $022A $AD $2A $02 $FD71: BNE $FD6A $D0 $F7 $FD73: LDA #$00 $A9 $00 $FD75: STA $3D $85 $3D $FD77: LDY #$01 $A0 $01 $FD79: RTS $60 $FD7A: LDA $3F $A5 $3F $FD7C: BMI $FDB1 $30 $33 $FD7E: LDX $3D $A6 $3D $FD80: CPX $028A $EC $8A $02 $FD83: BEQ $FD8D $F0 $08 $FD85: LDA $0400,X $BD $00 $04 $FD88: INC $3D $E6 $3D $FD8A: LDY #$01 $A0 $01 $FD8C: RTS $60 $FD8D: LDA #$52 $A9 $52 $FD8F: JSR $FE3F $20 $3F $FE $FD92: TYA $98 $FD93: BMI $FD8C $30 $F7 $FD95: LDA #$00 $A9 $00 $FD97: STA $3D $85 $3D $FD99: LDX #$80 $A2 $80 $FD9B: LDA $03FF $AD $FF $03 $FD9E: CMP #$FE $C9 $FE $FDA0: BEQ $FDAF $F0 $0D $FDA2: CMP #$FA $C9 $FA $FDA4: BNE $FDA9 $D0 $03 $FDA6: LDX $047F $AE $7F $04 $FDA9: STX $028A $8E $8A $02 $FDAC: JMP $FD7A $4C $7A $FD $FDAF: DEC $3F $C6 $3F $FDB1: LDY #$88 $A0 $88 $FDB3: RTS $60 $FDB4: LDX $3D $A6 $3D $FDB6: STA $0400,X $9D $00 $04 $FDB9: INC $3D $E6 $3D $FDBB: LDY #$01 $A0 $01 $FDBD: CPX #$7F $E0 $7F $FDBF: BEQ $FDC2 $F0 $01 $FDC1: RTS $60 $FDC2: LDA #$FC $A9 $FC $FDC4: JSR $FE7C $20 $7C $FE $FDC7: LDA #$00 $A9 $00 $FDC9: STA $3D $85 $3D $FDCB: RTS $60 $FDCC: LDY #$01 $A0 $01 $FDCE: RTS $60 $FDCF: LDA $0289 $AD $89 $02 $FDD2: BMI $FDDC $30 $08 $FDD4: LDY #$01 $A0 $01 $FDD6: LDA #$3C $A9 $3C $FDD8: STA $D302 $8D $02 $D3 $FDDB: RTS $60 $FDDC: LDX $3D $A6 $3D $FDDE: BEQ $FDEA $F0 $0A $FDE0: STX $047F $8E $7F $04 $FDE3: LDA #$FA $A9 $FA $FDE5: JSR $FE7C $20 $7C $FE $FDE8: BMI $FDD6 $30 $EC $FDEA: LDX #$7F $A2 $7F $FDEC: LDA #$00 $A9 $00 $FDEE: STA $0400,X $9D $00 $04 $FDF1: DEX $CA $FDF2: BPL $FDEE $10 $FA $FDF4: LDA #$FE $A9 $FE $FDF6: JSR $FE7C $20 $7C $FE $FDF9: JMP $FDD6 $4C $D6 $FD $FDFC: STA $40 $85 $40 $FDFE: LDA $14 $A5 $14 $FE00: CLC $18
Podle názoru autora článku není vývoj aplikací přímo v monitoru prakticky možný, alespoň ne pro běžného vývojáře (zcela jistě se ovšem najdou geniální výjimky). Jakýkoli program delší než cca padesát strojových instrukcí je tedy mnohem lepší nejdříve navrhnout na papíře, provést zde první ruční refaktoring, přepsat výsledek refaktoringu do monitoru (resp. do paměti), uložit na paměťové médium, spustit a ověřit funkcionalitu (samozřejmě lze vynechat fázi ukládání, ostatně co by se tak mohlo pokazit, že?). Výsledkem mnohdy bývá strojový kód s mnoha „poslepovanými“ částmi (vyplněné NOPy atd.), ten je ovšem většinou možné v poslední fázi (zcela mechanicky) přepsat do strukturovanější podoby. Samotné ladění spočívá ve vkládání instrukcí BRK na místa, ve kterých by se v debuggeru použily breakpointy. Taktéž se tedy jedná o poměrně hardcore přístup, ovšem stále může být (pro jednodušší programy i hry) udržitelný.
Všechny demonstrační příklady, s nimiž jsme se v předchozích článcích i v článku dnešním seznámili a které jsou určeny pro překlad s využitím assembleru ca65, jsou dostupné, jak je zvykem, na GitHubu. V tabulce níže jsou uvedeny odkazy na jednotlivé zdrojové kódy příkladů psané v assembleru i „listingy“ vygenerované samotným assemblerem, ze kterých je patrné, jakým způsobem se jednotlivé příklady přeložily do výsledného XEX souboru:
Objednejte si upozornění na nově vydané články do vašeho mailu. Žádný článek vám tak neuteče.
Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.
Аналитика ИИ и парсинга
Технические детали обнаружения, сопоставления и связывания статьи
Поисковый запрос
«Atari 8 бит программирование отладка Turbo Monitor»
Запрос, сгенерированный ИИ для поиска статьи в веб-источниках
Связанные результаты поиска
Тем не менее, по-настоящему отличает программирование для такой системы от современной разработки ПО, разумеется, не сама система, а радикально меньший список возможных проектов.