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

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

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

JavaScript実装の自作プログラミング言語をTypeScript実装に移行した話(Keisuke Ikeda) https://2026.tskaigi.org/talks/48

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; } }