80幎代スタむルのBASICむンタヌプリタヌを曞く





私は数幎間、「停の゚ミュレヌタヌ」、぀たり、存圚しなかったコンピュヌタヌ甚にJavaScriptで蚘述された゚ミュレヌタヌを䜜成するそしお実際に研究するずいう個人的なプロゞェクトに取り組んできたした。このマシンは、1980幎代ず90幎代の8ビットず16ビットのコンピュヌタヌのメモリぞのオマヌゞュであるず考えられおいたした。



しかし、私はその耇雑さが倧奜きです。このマシンも新しい䞀連の呜什を䜿甚しおいたした。それはその時代に䜿甚されたキットに䌌おいたすが、操䜜が少し簡単です。Retroputerが誕生したした。䜕幎にもわたっお、゚ミュレヌタヌは拡匵および改善されおきたしたが、おそらく「完成」するこずはありたせん結局のずころ、これは個人的な研究プロゞェクトです。@bbcmicrobot



が登堎したずき、Retroputerに䌌たものを䜜りたかった。私のJS開発スキルは䞻にフロント゚ンドに限定されおいたので、これはバック゚ンドの経隓を積むための玠晎らしい蚀い蚳になりたす。問題は1぀だけです。Retroputerは独自のアセンブリ蚀語しか理解できたせん。ただBASICをサポヌトしおいたせん。



そこで私は、80幎代のスタむルで、぀たり完党にアセンブリ蚀語でBASICむンタヌプリタヌを䜜成するこずを思い぀きたした。通垞の抜象化から遠く離れた領域に飛び蟌む必芁がないこずが倚いので、自分の仕事を共有する䟡倀があるず刀断したした。私の日垞のツヌルJavaScriptは、倚くの偎面を些现なものにし、時には魔法のようにさえ思えたす。最も䜎いレベルのプロセスを理解するこずは、これらの抜象化を理解するのに圹立぀こずがよくありたす。



それでは始めたしょう。



Retroputerの特性



  • 16 ROM (KERNEL) c 1 (scratch space)
  • 512 , 64
  • 16- 6516, 512 4 64
  • 4025, ,
  • 1125
  • 9800




Retroputerのアセンブラヌを曞いおいたずき、非垞に䟿利なPegjsツヌルを䜿甚できたした。高速なネむティブアセンブラ構文を提䟛したしたが、残念ながら、RetroputerASMにはこのようなものはありたせん。぀たり、私たちは困難な道を歩たなければなりたせん。



解析自䜓はいく぀かの段階で実行されたす。コンパむラを䜿甚する蚀語は、コヌドを解析しお抜象構文ツリヌASTたたは他の衚珟にし、このツリヌを䜿甚しお完成したネむティブコヌドを生成できたす。したがっお、コンパむルを成功させるには、プログラムが構文的に正しい必芁がありたす。



䞀郚の最新のむンタヌプリタヌにもこの抂念がありたす。これは、゜ヌスコヌドではなく、䞭間ASTを生成しお実行するず䟿利な堎合が倚いためです。



ただし、リ゜ヌスが限られおいるマシン䞊のBASICむンタヌプリタヌの堎合、いく぀かの段階で解析するのが最も効率的であり、その䞀郚は実行時に発生したす。ただし、これは、プログラムを実行しお゚ラヌのあるコヌドの領域に移動するたで、構文゚ラヌを怜出できないこずを意味したす。



Retroputer BASIC解析は、次の3぀の段階で構成されたす。



  1. 文字列の倉換
  2. トヌクン化
  3. ランタむム構文チェック


最初の2぀の段階は、ナヌザヌがプログラムに入るたたはプログラムをダりンロヌドするずきに発生したす。埌者は、プログラムの実行䞭に発生したす。基本的に、最初の2぀は航空機の胎䜓を䜜成したすが、離陞するこずを保蚌するものではありたせん。最埌のレグはテストパむロットです-圌が離陞するこずを願っおいたすが、圌が詊みるたでそれに぀いおはわかりたせん。



泚 Retroputer BASIC゜ヌスコヌド開発䞭はGitHubで入手できたす。



文字列の倉換



これは、プロセス党䜓の䞭で最も単玔な郚分です。ナヌザヌが入力した文字列は倧文字に倉換され、埌続のプロセスが簡玠化および高速化されたす。BASICは倧文字ず小文字を区別しないため、それを利甚できたす。



<pre>print 2+2
' becomes:
PRINT 2+2</pre>


JavaScriptでこれを行うのはずおも簡単ですよね



theLine = theLine.toUpperCase();


しかし、アセンブリ蚀語では、このプロセスはより詳现です。文字を読み、倧文字に倉換しお、どこかに保存する必芁がありたす。



           ld  y,  0                # y is our index
_loop:     ld  al, [d, x, y]        # [d,x] is pointer to string
           cmp al, 97               # is al (char) in range?
           brs n   _continue        # Not a lowercase char; continue
           cmp al, 123              # high portion
           brs !n  _continue        # not a lowercase char; continue
           and al, 0b1101_1111      # uppercase!
           st  [d, x, y], al        # store back (we modify the original)
_continue: inc y                    # move our index along
           cmp al, 0                # check for NULL
           brs !z _loop             # No? Go back for more.


䞊蚘のコヌドは、JavaScriptバヌゞョンのコヌドのセマンティクスず完党には䞀臎しおいたせん。重芁な違いは、今日ではUnicodeを䜿甚しおテキストを凊理しおいるため、入力を小文字から倧文字に倉換するのが難しい堎合が倚く、䞍可胜な堎合もありたす蚀語によっお異なりたす。 RetroputerはASCIIの䞖界より正確には、RetSCIIず呌ばれる独自のASCIIバリ゚ヌションの䞖界に存圚したす。぀たり、サポヌトされおいるすべおの文字は8ビットで゚ンコヌドされたす。倚くの蚀語にずっお、これは完党に䞍十分ですが、それは圓時の珟実を満たしおいたす。



これは、かわいいASCII機胜を䜿甚しお小文字から倧文字に倉換できるこずも意味したす。倧文字の「A」はASCIIコヌド65で衚され、小文字の「a」はコヌドで衚されたす。97..。 2の环乗に粟通しおいる堎合は、この違いに気付いたはずです。



したがっお、小文字は小文字の数よりも正確に32倚い数で指定されおいるこずがわかりたす。倀が正しい範囲にあるこずがわかっおいる堎合は、32を匕くだけで枈みたす。



このアプロヌチは機胜したすが、ビットを少し倉曎するだけです。 Retroputerの堎合、これは枛算よりも高速ではありたせんが、枛算がない堎合は、蚈算䞭にキャリヌ/ボロヌフラグに泚意を払う必芁はありたせん。and32倀の代わりにビットワむズを䜿甚しおビットをオフにできるこずがわかりたした。



and al, 0b1101_1111         # turn off bit in 32-place
# versus
clr c                       # clear carry
sub al, 32                  # subtract 32


ただし、埮劙な点が1぀ありたす。すべおを倧文字に倉換できるわけではありたせん。たずえば、ナヌザヌが文字列リテラルを远加した堎合、Retroputer BASICが垞に倧文字でナヌザヌに怒鳎りたくないので、もっず泚意する必芁がありたす。圓時の倚くのコンピュヌタヌには小文字がありたせんでしたが、Retroputerにはありたせんでした。



䟋



print "Hello, World!"
' should become:
PRINT "Hello, World!"
' and not
PRINT "HELLO, WORLD!"


これは、文字列リテラルの途䞭にいるかどうかを远跡する必芁があるこずを意味したす。BASICでは、これに察する指定は2぀だけです。二重匕甚笊です。文字が二重匕甚笊の堎合、フラグを蚭定し、フラグの倀に応じお倧文字に倉換するか、そのたたにしおおくこずができたす。



JavaScriptにはこのための組み蟌み機胜がないこずがわかりたしたが、自分で䜜成するこずはできたす。



const len = theLine.length;
let insideString = false;
for (let i = 0; i < len; i++) {
    const ch = theLine[i];
    if (ch === `"`) insideString = !insideString;
    if (!insideString) {
        const newCh = ch.toUpperCase();
        if (ch !== newCh) theLine[i] = newCh;
    }
}


JSのUnicodeサポヌトを少し利甚したすが、JSのロゞックはアセンブラヌのバヌゞョンずより厳密に䞀臎するようになりたした。



アセンブラのバヌゞョンは次のようになりたす。



           ld  y,  0                # y is our index
           ld  bl, 0                # === insideString (false)
_loop:     ld  al, [d, x, y]        # [d,x] is pointer to string
           cmp al, 34               # is al a double quote?
           brs !z  check_char       # no? should we uppercase it?
           xor bl, 0xFF             # yes? toggle insideString
_check_char:
           cmp bl, 0xFF             # inside a string?
           brs z   _continue        # yes? don't modify it
           cmp al, 97               # is al (char) in range? "a"
           brs n   _continue        # Not a lowercase char; continue
           cmp al, 123              # high portion           "z"
           brs !n  _continue        # not a lowercase char; continue
           and al, 0b1101_1111      # uppercase!
           st  [d, x, y], al        # store back (we modify the original)
_continue: inc y                    # move our index along
           cmp al, 0                # check for NULL
           brs !z _loop             # No? Go back for more.


これたでのずころ、入力テキストの倧文字ぞの倉換のみを行っおきたしたが、別の可胜性ずしお文字列リテラルの定矩を䜿甚できたす。もう1぀の構文チェックを実行できたす



プロセスの最埌にinStringただ真bl = 0xFFが䜕であるかがわかった堎合、゚ラヌを返す可胜性がありたす。これは、文字列のどこかに䞍完党な文字列リテラルがあるこずを意味したす。



泚 BASICの倚くのバヌゞョンは、文字列に閉じ匕甚笊がないこずに察しおかなり寛倧であるこずがわかりたした。私は自分の通蚳を䜜成しおいるずきにこれを芋぀けたした。たずえそうだずしおも、それは私には正しくないように思われるので、RetroputerBASICはそれを蚱可したせん。



トヌクン化



解析の次のステップは、入力文字列をRetroputerBASICむンタヌプリタヌが実行するためのより効率的なものに倉換するこずです。抜象構文ツリヌの抂念にできるだけ近づけようずしおいたすが、結果はただツリヌではありたせん。しかし、それは実行時に迅速に凊理できるものになりたす。



初期のマむクロコンピュヌタの共通の特城は、非垞に限られたメモリでした。 Retroputerは、圓時のほずんどの暙準的なマシンよりも倚くのメモリを備えおいたすが、それでも最新のコンピュヌタよりもはるかに少ないです。したがっお、長いBASICプログラムは、ナヌザヌ入力䞭に保存するず、メモリを倧量に消費する可胜性がありたす。



スペヌスを節玄するために、キヌワヌドはプログラムがメモリに入力されるずきにトヌクン化されたす..。このプロセスは、キヌワヌドをシングルバむトトヌクンに倉換したす。キヌワヌドは垞に少なくずも2バむトの長さであるため、入力するに぀れおさらに節玄できたす。たた、実行時にルックアップテヌブルを䜿甚しお、適切なアセンブリ蚀語ルヌチンを呌び出すこずができるこずも意味したす。



ただし、Retroputer BASICは、圓時のほずんどのBASICバヌゞョンよりも少し進んでいたす。さらに、数倀をバむナリ圢匏に倉換したり、文字列にマヌクを付けたり、倉数参照を蚈算したりしたす。正盎なずころ、これはより倚くのスペヌスを浪費しおいたすが、速床および実装の容易さの利点はそれを䞊回りたす。



したがっお、ここでは次の手順を䜿甚したす。







  1. , , . , , , , .




  2. , , , . , PRINT "Hello, World" «Hello, World» , .



    , .




  3. , , , . JavaScript, !



    ( ). , PRINT !




  4. Retroputer BASIC ( ). . , , .



    Retroputer BASIC . , . , Retroputer BASIC.


投皿では、アセンブリ蚀語でのこのステヌゞの実装に぀いおは詳しく説明したせんが、将来のために残しおおきたす。しかし、心配しないでください。そこにはたくさんのコヌドがありたす。



実行時の構文の確認



最埌になりたしたが、ランタむム構文チェックはです。コヌドをトヌクン化された圢匏に倉換した埌、それを実装するのは非垞に簡単です。



たず、実行時に、 BASICは珟圚トヌクンを凊理しおいるかどうかを確認したす。すべおのトヌクンで最䞊䜍ビットが有効になっおいたす぀たり、倀が128以䞊です。トヌクンが芋぀かった堎合、ベクトルテヌブルでトヌクンを芋぀けるこずにより、呌び出すサブルヌチンを決定できたす。たた、構文゚ラヌを衚瀺するこずも簡単になりたす。䞀郚のキヌワヌドは挔算子ずしお意味をなさないため、ベクトルテヌブルは単に構文゚ラヌを生成するプロシヌゞャを指したす。



オペレヌタヌトヌクンハンドラヌが呌び出された埌、ハンドラヌはすべおの远加の解析䜜業を匕き受けたす。それはそれらの間の移行を䜿甚するこずができ、トヌクンの堎合やgettok、gettok-raw、peektokなどトヌクンがプロシヌゞャによっお予期されおいないものである堎合、プロシヌゞャは単に゚ラヌコヌドを返したす。構文゚ラヌずタむプマッチング゚ラヌの䞡方が怜出されるのはこの段階です。



オペレヌタヌが匏を評䟡する必芁がある堎合は、解析の別の段階が実行されたす。匏の解析䞭に、別のベクトルルックアップテヌブルが䜿甚されたす。぀たり、数匏に関連しないキヌワヌドをむンタヌセプトしお、察応する゚ラヌを返すこずができたす。たずえば、入力しようずするずPRINT 2+CLS、次に構文゚ラヌが発生したすCLSCLS-これは「画面のクリア」の省略圢です。



泚この衚から、数孊挔算子の優先床ず必芁なパラメヌタヌの数を決定するこずもできたす。これは、匏の実際の評䟡にずっお重芁ですが、ナヌザヌが十分な匕数を提䟛しおいない堎合をキャッチするためにも䜿甚できたす。



トヌクンはベクタヌルックアップテヌブル゚ントリに盎接マップされるため、プログラムは最小限の劎力でかなり迅速に実行できたす。各タむプの挔算子を解析するタスクはハンドラヌ自䜓に委ねられおおり、通垞、これによっお特別な問題が発生するこずはありたせん。ある解析するおそらく最も難しいPRINTずINPUT、ただし、各ステップで䞀床に1぀のトヌクンが取埗されたす。



倚くのチェックはプログラムの実行時たで実行されないため、これは、゚ラヌが発生する前に郚分的な結果が埗られる可胜性があるこずを意味したす。䟋えば



PRINT "Hello";CLS
Hello
?Syntax Error


たた、プログラムがテキストを衚瀺できない状態で画面を離れるず、゚ラヌの陀去に問題が発生する可胜性があるこずを意味したす。構文゚ラヌが衚瀺されおも衚瀺されない堎合は、どうすればよいですか



構文をチェックするこの方法には確かに欠点がありたすが、かなり単玔なむンタヌプリタヌが可胜です。



ナヌザヌ入力のトヌクン化



前述のように、その時代のBASICバヌゞョンではトヌクン化戊術を䜿甚するのが䞀般的でした。スペヌスを節玄し、実行速床を䞊げるために、キヌワヌドはシングルバむトトヌクンたたは、さらにキヌワヌドが必芁な堎合はダブルバむトに「圧瞮」されたした。



画面をクリアしお暙準の挚拶を印刷する簡単なコヌド行があるずしたしょう。



CLS: PRINT "Hello, World"


これ自䜓はあたりメモリを消費したせんが、長いプログラムを䜜成するず、そのプログラムの倚くの単語がキヌワヌドになりたす。それらを圧瞮トヌクン化するず、スペヌスのかなりの郚分を節玄できたす。たずえば、䞊蚘の行をトヌクン化した埌、メモリには次のようなものがありたす。



8A E3 B5 FC Hello, World 00 00


これを元の構成に戻すのはそれほど難しいこずではありたせん。



バむト キヌワヌド ノヌト
8A

CLS

E3 "" 工事終了
32 「」 最倧1぀のスペヌスに保管したす
B5

印刷



FB コヌドの行 次は文字列文字です
こんにちは、ワヌルド、00 文字列はnullで終了したす
00 コヌド行の終わり コヌド行もnullで終了したす


これは倧倉な䜜業だず思われるかもしれたせんが、スペヌスの節玄は重芁です。これはこの䟋では特に目立ちたせんが、そこからでも、節玄されたスペヌスがどれだけ早く蓄積されるかを想像するこずができたす。この堎合、圧瞮された結果は19バむトです。゜ヌステキストは26バむト終了ヌルバむトを含むで構成されたす。



ナヌザヌプログラム甚のRAMが非垞に少ないマシンで実行されるBASICむンタヌプリタヌに関しおは、スペヌスの節玄が重芁になりたす。したがっお、このような圧瞮は、远加の利点がない堎合でも、非垞に重芁で魅力的です。



では、このようなものをどのようにトヌクン化するのでしょうか最初は、JavaScriptでこれを行うのは非垞に簡単なようです。文字列の配列を䜿甚するず、各キヌワヌドを察応するトヌクンにすばやく眮き換えるこずができたす。本圓ですか



これはの仕事のようString#replaceですか玠朎なアプロヌチでは、次のようなこずを詊すこずができたす。



const tokens = ["CLS", "PRINT", ":" ];
function tokenize (inStr) {
    const newStr = inStr;
    tokens.forEach((token, idx) => newStr = newStr.replace(
        new RegExp(token, "g"), String.fromCharCode(128+idx)
    );
    return newStr;
}


残念ながら、文字列のリテラルをトヌクンに眮き換えるこずはできないこずにすぐに気付きたした。これは、キヌワヌドではない芁玠を圧瞮しないように、コンテキストを考慮しお、文字ごずに凊理を行う必芁があるこずを意味したす。



この新しいアルゎリズムは、Retroputerのアセンブリ蚀語コヌドに近いものですが、JSを䜿甚するず䜜業がはるかに簡単になりたす。これがJSコヌドの始たりですこの投皿で埐々に拡匵しおいきたす



const tokens = ["CLS", "PRINT", ":" ];

function tokenize(inStr) {
    let out = [];                    // return value is "crunched" array
    let idx = 0;                     // index into inStr
    let ch = "";                     // current character
    while (idx < inStr.length) {
        ch = inStr.charCodeAt(idx);  // get the current character code
        
        // our logic is going to go here

        out.push(ch);                // for now push the character
        idx++;                       // and keep going (next char)
    }
    out.push(0);                     // we end with NUL
    return out;
}


テヌブル党䜓をこの圢匏で衚瀺したくないため長く、Retroputerは実際にはJSファむルからトヌクンテヌブルを䜜成したす、トヌクンの非垞に単玔化されたリストから始めたすが、私たちの目的にはこれで十分です。ここでの原則は、トヌクンを芋぀けたら、そのむンデックスを出力配列に曞き蟌むこずです。



この時点で、今のずころ、文字列を同等の文字コヌドに倉換するだけの関数がありたす。特に有甚ではありたせんが、䟿利なフレヌムワヌクずしお機胜したす。



アセンブリ蚀語のバヌゞョンは、䞊蚘のバヌゞョンず非垞によく䌌おいたす。



  do {
    call _get-source-index     # get next character index (in y)
    dl := <BP+source>,y        # read next char from our source string (ch)
    call _adv-source-index     # advance our source index
    call _get-target-index     # get position in target   ("out" in JS)
    <BP+target>,y := dl        # store to target          (out[y] = ch)
    call _adv-target-index     # move it along
    cmp dl, 0                  # was char 0? if so, break
  } while !z


䞊蚘の䟋_get-source-indexや他の関数には、タスクが名前から明らかであるため、含たれおいたせん。それらは、゜ヌスむンデックスずタヌゲットむンデックスを取埗、蚭定し、それらをナビゲヌトするだけです。JSずは異なり、アセンブリ蚀語には動的に割り圓おられた配列がないため、このアルゎリズムは十分なサむズのバッファヌを割り圓おたす。入力行を䞋に移動するずき、タヌゲットバッファの次のトヌクンをどこに曞き蟌むか、およびタヌゲットむンデックスが䞊蚘で䜕を行っおいるかを知る必芁がありたす。䞊蚘の各関数は、でむンデックスを返したすY。たずえば、この関数_adv-target-indexはタヌゲットむンデックスを1぀むンクリメントしたす同等y++。



リテラルに泚意しおください



泚意する必芁がありたす。文字列リテラルはトヌクナむザヌを混乱させる可胜性がありたす。キヌワヌドに䞀臎するすべおの文字文字列をトヌクン倀に単玔に眮き換えるこずはできたせん。「CLS」を含む文字列リテラルが衚瀺された堎合、それをトヌクン化しようずすべきではありたせん。実行可胜であっおはならず、出力するず...その代わりにトヌクンに察応するバむトが出力されたす。これは、開発者が意図したものではない可胜性がありたす。



䞀方、BASICには数倀キヌワヌドがないため、数倀リテラルにはこの問題はありたせん。それでも、数字に盎面したずきにキヌワヌドを怜玢しおも意味がありたせん-なぜ時間を無駄にするのですか



文字列リテラルのトヌクン化



それで、最初に行が始たるかどうかを確認したしょう-JSではこれを行うこずは特に難しいこずではありたせん



if (ch === 34) {
  out.push (0xFB);             // string-in-code token
  idx++;
  ch = inStr.charCodeAt(idx);  // get next character after the quote
  while (ch !== 34 && idx < inStr.length) {
    out.push(ch);
    idx++;
    ch = inStr.charCodeAt(idx);
  };
  idx++;                       // go past the quote
  out.push(0);                 // end of string
  continue;
}


二重匕甚笊は文字コヌド34で衚されたす。他のプログラミング蚀語では、さらに倚くのスタむルの匕甚笊JSやCなどを認識できたすが、BASICを䜿甚するず、二重匕甚笊を䜿甚するか、䜕も䜿甚しないかずいう単玔なものになりたす。



文字列が始たるのを確認したら、残りの文字を取埗しお、別の匕甚笊が芋぀かるたで出力配列に远加するだけです。



Retroputer BASICはCスタむルの文字列を䜿甚するため、文字列党䜓を読み取った埌、れロバむトを远加する必芁がありたす。Pascalスタむルの文字列を䜿甚する堎合は、カりンタヌを栌玍しお文字列リテラルの長さを挿入できたす。いずれにせよ、これは特に問題にはなりたせん。 nullで終了する文字列を䜿甚した唯䞀の理由は、アセンブリ蚀語での操䜜が非垞に簡単であるためです。カりンタを栌玍するのではなく、nullバむトず比范するだけで枈みたす。



したがっお、JavaScriptはそれほど難しくありたせんでした。ほずんどの人は、通垞の衚珟のように、もっず抜象的なものを䜿うこずに決めるず思いたす。このコヌドは私たちの意図をより明確にするだろうず思いたす。



if (ch === 34) {
  out.push (0xFB);             // string-in-code token
  const stringLiteral = inStr.substr(idx+1).match(/^[^"]*/)[0];
  idx += stringLiteral.length+1;
  out.push(...Array.from(stringLiteral, ch => ch.charCodeAt(0)));
  idx++;                       // go past the quote
  out.push(0);                 // end of string
  continue;
}


䞊蚘のコヌドはほが同じですが、文字ごずの怜蚌の代わりに、JSにを実行させmatchたす。䞀臎するこずがわかっおいるので文字列内にありたす、䞀臎が成功したかどうかを確認する必芁もありたせん。次に、文字列の長さだけむンデックスをむンクリメントし、文字を出力配列にコピヌしたす。私の意芋では、このコヌドははるかに理解しやすいですただし、正芏の匏、たずえばES6構文の特性、 および矢印関数の理解を意味したす。



もちろん、アセンブリ蚀語では、JSが行うすべおの䜜業を手動で行う必芁がありたす。結果は、JSコヌドを䜜成する最初の詊みず非垞によく䌌おいたす。



  cmp dl, constants.QUOTE         # is dl a quote?
  brs !Z not-a-string             # nope; not a string

  call _get-target-index          # get next position in crunch target
  dl := brodata.TOK_CODE_STRING   # "String" token
  <bp+target>,y := dl             # Store it into the crunch target
  call _adv-target-index

still-a-string:
  call _get-source-index
  dl := <bp+source>,y             # get string character
  call _adv-source-index
  cmp dl, constants.QUOTE         # if it's a quote, we can zero it
  if Z { 
    dl := 0 
  }
  call _get-target-index
  <bp+target>,y := dl             # write to target
  call _adv-target-index
  cmp dl, 0                       # are we done?
  brs !Z still-a-string           # no, keep going
  continue                        # next!
not-a-string:


Retroputerアセンブリ蚀語パヌサヌに぀いおのメモを远加する䟡倀がありたす-ブロックやルヌプなどの高レベルの構造を基本的にサポヌトしおいたす。したがっお、if Z {
}れロフラグが蚭定されおいる堎合、ブロック内のコンテンツを実行し、ブロックcontinueの先頭に分岐するために䜿甚できたすブロックbreakを終了するためにも䜿甚されたす。アセンブラはこれをさたざたな比范および分岐呜什に倉換するため、CPUはコヌドの高レベルの郚分を認識したせんが、コヌドを少し簡単に蚘述できたす。



トヌクン化番号



たた、キヌワヌドのリストで数字を怜玢しようずしおも意味がないため、スキップするこずができたす。 BASICのほずんどのバヌゞョンは、䞊蚘の文字列操䜜ず同様のこずを行いたす。読み取られた文字が数字の堎合、出力に連結された埌、コンプレッサヌが匕き継ぎたす。



Retroputer BASICおよびAtari BASICなどの他のバヌゞョンのBASICは、さらに䞀歩進んで、数倀を適切なバむナリ圢匏に倉換したす。これを行うのは非垞に簡単です。数字に出䌚った堎合、アキュムレヌタに10を掛けお数字を加算し、数字が出珟し続ける限り続けたす。 ただし、Retroputer BASICは敎数倀でのみ機胜したすが、浮動小数点数の远加はすでに私のToDoリストに含たれおいるこずに泚意しおください。



珟圚、Retroputer BASICは、笊号付きか笊号なしかに関係なく、敎数オヌバヌフロヌが発生しおも䜕もしたせん。浮動小数点数を远加するず、Retroputerも浮動小数点に倉換されたす。



RetroputerBASICはさらに䞀歩進んでいたす。 たた、16進数を認識し、それらを同等の2進数に倉換したす。これは0xJSのようにそのような番号の指定ずしお䜿甚され、蚀語自䜓には、耇数の文字が指定された堎合に゚ラヌが返されるようにするための远加のロゞックがありたすx。



あなたは、文字コヌドの間であるこずを確認する必芁がありたす。文字は難しいが、それは比范のカップルを必芁ずするこずが数字ではありたせんされおいるかどうかをチェックするアセンブリ蚀語では、0x30ず0x39..。 これらは文字コヌド「0」ず「9」です。



文字桁を受け取ったら、文字セットの別の䟿利なプロパティを䜿甚できたす。0x30-これは文字コヌド「0」、0x31-コヌド「1」などです。必芁に応じお枛算するこずもできたす0x30が、もっず簡単な方法がありたす。最䞊䜍4ビットを砎棄するだけです。



and dl, 0b0000_1111


残念ながら、16進数を凊理する必芁がある堎合、これは機胜したせん。この堎合、枛算しお10ず比范し、コヌドが10より倧きい堎合は、再床7ず぀枛らすこずができたす16進数が倧文字の「A」-「Z」の堎合。



JSでは、通垞の匏を再床䜿甚できたす。その埌、番号が䞀臎したずきにを呌び出すこずができたすNumber()。これにより、別の利点Number()が埗られ0xたす。番号が。で始たる堎合は自動的に倉換されるため、16進数も簡単に倉換されたす。



JavaScriptではどのように芋えたすか



if (ch >= 48 && ch <= 57) {
  out.push (0xFD);           // number token
  const numberLiteralStr = inStr.substr(idx).match(/^[\d|A-F|a-f|x|X]+/)[0];
  idx += numberLiteralStr.length;
  const numberLiteral = Number(numberLiteralStr);
  const bytes = new Uint8Array(new Uint16Array([numberLiteral]).buffer);
  out.push(...bytes)
  continue;
}


通垞の匏では、数字たたは16進数の任意の組み合わせを䜿甚できたすさらにx、16進数モヌドに切り替えるこずもできたす。この匏は理想的ではありたせん。たずえば、耇数を䜿甚できたすxが、今のずころこれで十分です。



䞊蚘のコヌドでは、数倀をバむトに倉換するこずは疑わしい堎合がありたす。Number()数字の文字列をJavaScriptが凊理できる数字に倉換するずいう倧倉な䜜業をすべお行いたしたが、今床はそれをバむトのシヌケンスずしお衚す必芁がありたす。これを行うには、数孊を䜿甚できたす。



const hiByte = (numberLiteral & 0xFF00) >> 8;
const loByte = (numberLiteral & 0x00FF);


...そしおそれは敎数に察しお行うのは簡単です。しかし、JSの䜿甚のおかげで、あなたはすべおのこれらの蚈算を避けるが、同時に私たちは、単に眮き換えたす将来的には浮動小数点数を扱うために準備するこずができ、配列を入力Uint16ArrayしおFloat64Array。



このタスクのアセンブリ蚀語コヌドは少し長くなりたすが、少し長くなりたす。Retroputerはもう1぀の最適化を䜿甚したす。数倀が小さい堎合は、小さい圢匏で保存されたす。これは、0〜255を1バむトに栌玍できるのに察し、長い数倀は2を占めるこずを意味したす。



キヌワヌドを怜玢する



そのため、かなりの䜜業を行いたしたが、これたでのずころ、単䞀のキヌワヌドを圧瞮しおいたせん。数字ず文字列のリテラルを扱っおきたので、他のすべおがキヌワヌドたたは倉数名のいずれかであるず確信できたす。 たたはスペヌスですが、これは簡単に確認できたす。



BASICでは、キヌワヌドは必ずしもアルファベット文字で始たるずは限りたせん。挔算子ず区切り文字もトヌクンず芋なされたす。ただし、倉数もアルファベット文字で始たりたす。したがっお、キヌワヌドず倉数のどちらを圧瞮するかをすぐに決定するこずはできたせん。これは私たちに適しおいたす-トヌクンのリストに䞀臎するものが芋぀からない堎合、これは倉数であるず芋なされたす。



では、朜圚的なキヌワヌドが実際にキヌワヌドであるこずをどのように確認するのでしょうか。 JavaScriptで蚘述しおいる堎合は、を䜿甚できたすArray#findIndex。



const tokenMatch = tokens.findIndex(token => 
  inStr.substr(idx).startsWith(token));
if (tokenMatch > -1) {
  out.push(tokenMatch + 128);
  idx += tokens[tokenMatch].length;
  continue;
}


メ゜ッドArray#findIndexは配列tokensを反埩凊理し、チェックしおいるトヌクンでinStr珟圚の配列で開始するかどうかをチェックできidxたす。トヌクンの簡略化されたリストを䜿甚しお、次のようなこずを行いたすず仮定したすinStr.substr(idx)===”PRINT”。



トヌクン .startsWithトヌクン むンデックス
CLS false 0
印刷 true 1


泚indexOfJSず同様に、䜕も芋぀からない堎合は、を取埗し-1たす。



䞀臎するものが芋぀かった堎合は、返された配列にそのむンデックスを保存できたす。しかし、埌でトヌクンずシンボルをどのように区別するのでしょうか。簡単最䞊䜍ビットをオンにしたす。これは、トヌクン倀に128を远加するこずで実行できたす



泚128より倚くのトヌクンが必芁な堎合、䞀郚のトヌクンでは2バむトを䜿甚する必芁がありたす。これは少し耇雑になりたすが、それほど耇雑ではありたせん。 BASICの䞀郚のバヌゞョンは、たずえば、さたざたなMicrosoft BASICでこれ



を実行したす。JavaScriptは終了したしたが、アセンブリ蚀語でどのように実行したすか



ほが同じであるこずがわかりたすが、コヌドははるかに長くなりたす。



search-keywords:
  bl := [d, x]                 # get first character of current token
  cmp bl, constants.NUL        # if it's NUL, we've exhausted the list
  brs Z exit-keyword-search    # so we're clearly not a keyword
  clr Z                        # compare strings, but with partial equality
  call [vectors.STRCMP]        # so that our source doesn't need NUL between
                               # tokens; c will now be how many chars got 
                               # compared
  if !Z {
    do {                       # no match; advance past our token in the list
      inc x                    # character by character
      bl := [d, x]             # tokens are terminated with NULs
      cmp bl, constants.NUL    # so if we see it, we can stop
    } while !z
    inc x                      # go past the token #
    inc x                      # in the lookup table
    brs search-keywords        # and keep looking for a match
  }
  clr c                        # found the match!
  add x, c                     # c is the length of the match
  inc x                        # past the NUL
  bl := [d, x]                 # bl is now the token #
  call _get-target-index
  <bp+target>,y := bl          # write the token #
  call _adv-target-index
  call _get-source-index       # advance past the token in the source
  clr c
  add y, c                     # by adding the character count
  dec y                        # back off by one (we'll advance again later)
  call _set-source-index
  continue


たあ、それほど悪くはないようです。アルゎリズムはほずんど同じですが、アセンブリ蚀語のトヌクンテヌブルの構造が少し異なりたす。これは次のようになりたす。



CLS   00 80
PRINT 00 81
:     00 82


各キヌワヌドは、nullバむトずそれに続くトヌクン番号で終了したす。



ただし、ここでは重芁なこずが欠けおいたす。この文字列の比范はどのように行われるのでしょうか。RetroputerのコアにSTRCMPは䜿甚できる手順がありたすが、どのように芋えたすか



strcmp: {
  enter 0x00
  push a
  push b
  push d
  push y
  pushf
  if Z {
    bl := 0x00                  # Checking for full equality
  } else {
    bl := 0x01                  # only checking for partial equality
  }
_main:
  y := 0                        # start of string
top:
  cl := [d, x, y]               # character in string A
  al := <bp+4>,y                # character in string B
  cmp bl, 0x01                  # check if we're doing full equality
  if Z {
    cmp cl, 0                   # we're not, so check for an early nul
                                # in string b
    brs Z strings-are-equal     # if it's NUL, we calling them equal
  }
  cmp cl, al                    # check character
  if Z {
    cmp cl, 0                   # equal, but check for NUL
    brs Z strings-are-equal     # NUL reached, strings are equal
    inc y                       # next character
    brs top                     # not NUL, so keep going...
  }
 # if here, the strings aren't equal
  if N {
    popf                        # string is less than
    set N
    clr Z
    brs _out
  } else {
    popf                        # string is greater than
    clr N
    clr Z
    brs _out
  }
strings-are-equal:
  popf
  clr N                         # Not less than
  set Z                         # but Equal
_out:
  c := y                        # make sure we know how many chars
                                # were compared
  pop y
  pop d
  pop b
  pop a
  exit 0x00
  ret
}


あなたのこずはわかりたせんが、String#startsWithJSメ゜ッドがたすたす奜きになり始めおいたす。はい、同じこずをしたすが、すべおの内郚ロゞックを蚘述する必芁はありたせん



可倉凊理



これでキヌの圧瞮が完了したので、終了できたす。しかし、Retroputer BASICは、倉数のトヌクン化ずいう別の䞀歩を螏み出したした。メモリが限られおいる状況では有害である可胜性があるため、80幎代ず90幎代のBASICのいく぀かのバヌゞョンのみがこれを行ったように思われたす。ただし、メモリが倚い堎合は、倉数のトヌクン化によっおパフォヌマンスが向䞊したす。



RetroputerBASICの機胜は次のずおりです。



  • 倉数名の最初の2文字たでを読み取りたす。これは、メモリの制玄のために、圓時のBASICバヌゞョンの暙準的な䞍䟿でした。
  • これらの2぀の文字から、倉数のむンデックスを決定したす。「A」は倉数0、「A0」は倉数53などです。方皋匏は非垞に単玔ですが、今は興味がありたせん。
  • BASIC . , $ BASIC . .
  • BASIC , . !


泚Retroputer BASICが倉数にメモリを動的に割り圓おるこずを孊習するず、むンデックスを倉数ぞのポむンタヌに眮き換えたす。ToDoリストの別の項目です



これにより、実行時に倉数を非垞に高速に芋぀けるこずができたす。倉数名を解析しおむンデックスを毎回蚈算する必芁はありたせん。倉数に出䌚ったずき。連続サむクルでは、倧幅な節玄になりたす。ただし、これにはコストがかかりたす。ポむンタず倉数名を䞀緒に栌玍する必芁があるため、発生する倉数ごずに、出力に4バむトを远加する必芁がありたす。



それが䟡倀があるかどうかを決めるのはあなた次第です。



ずはいえ、JavaScriptでは、文字ストリヌムの残りの郚分が倉数であるかどうかを刀断するのも簡単です。



const variableMatch = inStr.substr(idx).match(/^[A-Z]+[A-Z0-9]*[\$]*/);
if (variableMatch) {
  const variableName = variableMatch[0];
  idx += variableName.length;
  // tokenize it (omitted; nothing new here)
  continue;
}


Retroputerが倉数をトヌクン化するために珟圚䜿甚 しおいるコヌドに぀いおは詳しく説明したせん。冗長すぎたす。倉数に動的なメモリ割り圓おを远加するず、それに戻りたす。



トヌクン化が完了したした



JSでコヌドをテストするず、RetroputerBASICが内郚で䜿甚するものず同様のバむト配列が埗られたす。



console.log(toHex(tokenize(`CLS: PRINT "Hello, World"`)));
// 80 82 20 81 20 FB 48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 00 00 


うわヌ、数バむトを節玄するのに䜕ず倧倉な䜜業です。しかし、空きメモリが数キロバむトしかない堎合は、それだけの䟡倀がありたす。ただし、ナヌザヌが入力したテキストを圧瞮する利点はこれだけではありたせん。プログラムの実行も高速化されたす。



これが発生する理由を説明するには、Retroputerがコヌドを実行する方法を理解する必芁がありたす。ここでは詳しく説明したせんが、簡単に蚀うず、コヌドを実行するには次のものが必芁です。



  • メモリから1バむトを取埗したす
  • バむトの最䞊䜍ビットがオンになっおいる堎合、それはトヌクンです。それ以倖の堎合は、構文゚ラヌたたはNUL。いずれの堎合も、これがプログラム行になりたすです。
  • 配列内のトヌクンハンドラヌを探しおいたす-この配列には、トヌクンを凊理する関数自䜓ぞのポむンタヌが含たれおいたす
  • ハンドラヌを呌び出したす匕数などの受信を担圓したす
  • もう䞀床繰り返したす


これは、キヌワヌドをトヌクン化するもう1぀の理由です。キヌワヌドのロゞックを実行するのは簡単で、配列内のアドレスを芋぀けお呌び出すだけです。



トヌクン化はスペヌスを節玄するために重芁ですが、実行速床も向䞊するこずを匷調したいず思いたす。



たずえば、JS実行ルヌプは次のようになりたす。



const handlers = [ cls, print, endOfStmt ];
bytes.forEach(byte => handlers[byte] && handlers[byte]());


もちろん、実際にはすべおがそれほど単玔ではなく、はるかに単玔ですが、これはすでに別の投皿のトピックです



リ゜ヌス








広告



DDoS保護ず最新のハヌドりェアを備えた匷力なVPS。これはすべお、私たちの壮倧なサヌバヌに関するものです。最倧構成は、128 CPUコア、512 GB RAM、4000 GBNVMeです。






All Articles