Upgrade to Pro — share decks privately, control downloads, hide ads and more …

JavaScript実装の自作プログラミング言語をTypeScript実装に移行した話

Sponsored · SiteGround - Reliable hosting with speed, security, and support you can count on.

 JavaScript実装の自作プログラミング言語をTypeScript実装に移行した話

Avatar for Keisuke Ikeda

Keisuke Ikeda

May 22, 2026

More Decks by Keisuke Ikeda

Other Decks in Technology

Transcript

  1. tsconfigに向き合おう! 03/14 ͜ͷൃදͰߦ͏͜ͱ ✅ ͢Δ͜ͱ ɾJS → TSҠߦͷ࣮ମݧ ɾݴޠ࣮૷ͱ͍͏υϝΠϯͰͷTSͷخ͔ͬͨ͠ɺͭ·͍ͮͨ࿩ 🚫

    ɾࣗ࡞ͨ͠ݴޠɺࣗ࡞ͷϞσϧʹͨ͠ݴޠɺݴޠॲཧܥͷৄ͍͠࿩ ɾTypeScript ͷجຊతͳ࢖͍ํͷઆ໌ !JLF@LFJDIBO
  2. 前提知識 05/14 ɾ1958೥ John McCarthy ͞Μͷ࿦จͰൃද ɾؔ਺ܕϓϩάϥϛϯάݴޠͷϧʔπ ɾߏจ͕ඇৗʹγϯϓϧɿS ࣜʢલஔه๏ʣ ɹˠ

    ࣗ࡞ϓϩάϥϛϯάݴޠɾݴޠॲཧܥͷೖ໳ʹ࠾༻͞Ε΍͍͢ ࣗ࡞ݴޠͷϞσϧ-JTQʹ͍ͭͯ !JLF@LFJDIBO (+ 1 2) (* (+ 3 4) (- 10 (* 2 3)))
  3. 前提知識 06/14 ίʔυ͕ಈ͘·Ͱ !JLF@LFJDIBO ιʔεจࣈྻ (* (+ 3 4) (-

    10 (* 2 3))) ࣈ۟ղੳʢLexerʣ τʔΫϯྻ [ *, (, +, 3, 4, ), (, -, 10, ... ] ߏจղੳʢParserʣ ASTʢ໦ߏ଄ʣ * + - 3 4 10 ... ධՁʢEvaluatorʣ ஋ 28
  4. JSで苦しんだ話 07/14 +4Ͱۤ͠Μͩ࿩ index.js const eval = (node, env) =>

    { // node は以下のいずれか: // number | string | symbol(識別子)| list | nil // // JS は引数に型を持たない // 型の検証はすべて実行時 };
  5. JSで辛かった話 08/14 ܕͷෆҰக͕఻೻ index.js const eval = (node, env) =>

    { if (node.type === "number") return node.value; if (node.type === "list") return apply(evaluate(node.car, env), node.cdr); // symbol(識別子)の処理が抜けている // return 文なし → undefined を返す // → apply の第1引数が undefined に // → 再帰の数段後で TypeError };
  6. TSに移行して 09/14 !JLF@LFJDIBO ܕʹΑΔදݱ // BNF <value> ::= <number> |

    <string> | <symbol> | <list> | nil // TypeScript type LispValue = number | string | Symbol | Cons | null; ɾBNFʢBackus-Naur Formʣɿݴޠͷจ๏Λܗࣜతʹهड़͢Δه๏ ɾBNF ͷ"|"΋ TypeScript ͷϢχΦϯܕ΋௚࿨ܕʢSum TypeʣΛදݱ ɾݴޠ࣮૷ͱ͍͏ෳࡶͳυϝΠϯʹ΋ରԠ͠΍͍͢
  7. TSに移行して 10/14 !JLF@LFJDIBO 5ZQF4DSJQUͷ/BSSPXJOHʢ௨ৗ࣌ʣ index.ts // ローカル変数:TypeScript が正確に追跡できる let token:

    string | null = null; token = "hello"; token.toUpperCase(); // ✓ string と確定 token = null; token.toUpperCase(); // ✗ コンパイルエラー:null の可能性
  8. TSに移行して 11/14 !JLF@LFJDIBO 5ZQF4DSJQUͷ/BSSPXJOHʢDMBTT಺ʣ index.ts class Lexer { private token:

    string | null = null; private readNext(): void { this.token = "hello"; // フィールドを書き換える副作用 } next(): void { this.token = null; // TypeScript:token は null と記録 // ... // readNext() の中身は解析しない // → this.token が書き換わることを TypeScript は追跡できない } }
  9. TSに移行して 12/14 !JLF@LFJDIBO 54Ͱۤ͠Μͩ࿩ index.ts class Parser { private current:

    LispValue = null; // 完成したトークンを格納 private state: number = 0; private consume(): void { // 1文字読み進め、トークンが完成したら this.current に格納 } nextToken(): LispValue { this.current = null; // ① TypeScript:current は null と記録 while (!this.done()) { if (this.current !== null) break; // ② TypeScript:常に false と警告 this.consume(); // ③ this.current を書き換えるが追跡されない } return this.current; } }
  10. TSに移行して 13/14 !JLF@LFJDIBO 54ͱͯ͠ਖ਼͍͠001ઃܭ index.ts class Parser { private current:

    LispValue = null; // 完成したトークンを格納 private state: number = 0; private consume(): void { // 1文字読み進め、トークンが完成したら this.current に格納 } nextToken(): LispValue { this.current = null; // ① TypeScript:current は null と記録 while (!this.done()) { if (this.current !== null) break; // ② TypeScript:常に false と警告 this.consume(); // ③ this.current を書き換えるが追跡されない } return this.current; } }