1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JavaScriptってどうやって動く?①

Posted at

はじめに

JavaScriptはブラウザやNode.jsのJavaScriptエンジンで処理されてコードが動作します。
そんな大枠で理解していたことを正しく理解してみようと思い、いくつかのステップに分けて書いてみようと思います。

JavaScriptが動く流れ

1.ソースコード
2.実行環境へ入力
3.字句解析(レキサー)
4.構文解析(パーサ)
5.中間表現(IR)生成
6.バイトコード生成
7.JITコンパイル
8.実行

1.ソースコード

開発者が記述するソースコードそのものです。
人間が読める文字の状態で記述されます。

function greet(name) {
    console.log("Hello, " + name);
}

2.実行環境へ入力

記述したソースコードがブラウザ(Chrome、Firefox)やNode.jsのJavaScriptエンジンに渡されます。

JavaScriptエンジンとは

JavaScriptコードを処理し、コンピュータが実行できる形式に変換して動作させるためのソフトウェアコンポーネントです。

主要なJavaScriptエンジン

  • V8: Google ChromeやNode.jsで使用されているエンジン
  • SpiderMonkey: Mozilla Firefoxで使用されているエンジン
  • JavaScriptCore(Nitro): AppleのSafariで使用されているエンジン
  • Bun: JavaScriptとTypeScriptを実行するための最新エンジン

3.字句解析(レキサー)

ソースコード(人間の理解できるテキストデータ)を意味のある最小単位(トークン)に分解します。

簡易的なレキサーを作成し、字句解析を行ってみる。

検証環境

Apple clang version 16.0.0 (clang-1600.0.26.4)

ソースコード

let x = 10 + 20;

出力

Token(Type: KEYWORD, Value: "let")
Token(Type: IDENTIFIER, Value: "x")
Token(Type: OPERATOR, Value: "=")
Token(Type: NUMBER, Value: "10")
Token(Type: OPERATOR, Value: "+")
Token(Type: NUMBER, Value: "20")
Token(Type: SEMICOLON, Value: ";")

簡易的なレキサー

#include <iostream>
#include <string>
#include <vector>
#include <regex>

using namespace std;

// トークンの種類を列挙型で定義
enum TokenType {
    KEYWORD,
    IDENTIFIER,
    NUMBER,
    OPERATOR,
    SEMICOLON,
    UNKNOWN
};

// トークンを表現する構造体
struct Token {
    TokenType type;
    string value;
};

// レキサー関数
vector<Token> lex(const string& code) {
    vector<Token> tokens;
    size_t pos = 0;

    // トークンに対応する正規表現
    regex keyword_regex(R"(\b(let|const|var)\b)");
    regex identifier_regex(R"([a-zA-Z_][a-zA-Z0-9_]*)");
    regex number_regex(R"(\b\d+\b)");
    regex operator_regex(R"([=+\-*/])");
    regex semicolon_regex(R"(;)");

    while (pos < code.size()) {
        // 現在の位置以降のコードを取得
        string remaining_code = code.substr(pos);

        smatch match;

        if (regex_search(remaining_code, match, keyword_regex) && match.position(0) == 0) {
            tokens.push_back({KEYWORD, match.str(0)});
        } else if (regex_search(remaining_code, match, identifier_regex) && match.position(0) == 0) {
            tokens.push_back({IDENTIFIER, match.str(0)});
        } else if (regex_search(remaining_code, match, number_regex) && match.position(0) == 0) {
            tokens.push_back({NUMBER, match.str(0)});
        } else if (regex_search(remaining_code, match, operator_regex) && match.position(0) == 0) {
            tokens.push_back({OPERATOR, match.str(0)});
        } else if (regex_search(remaining_code, match, semicolon_regex) && match.position(0) == 0) {
            tokens.push_back({SEMICOLON, match.str(0)});
        } else if (isspace(remaining_code[0])) {
            // 空白文字は無視
            pos++;
            continue;
        } else {
            tokens.push_back({UNKNOWN, string(1, remaining_code[0])});
        }

        // マッチした文字列の長さだけ進める
        pos += match.length(0);
    }

    return tokens;
}

// トークンタイプを文字列で表示する関数
string tokenTypeToString(TokenType type) {
    switch (type) {
        case KEYWORD: return "KEYWORD";
        case IDENTIFIER: return "IDENTIFIER";
        case NUMBER: return "NUMBER";
        case OPERATOR: return "OPERATOR";
        case SEMICOLON: return "SEMICOLON";
        default: return "UNKNOWN";
    }
}

// メイン関数
int main() {
    string code = "let x = 10 + 20;";

    // レキサー実行
    vector<Token> tokens = lex(code);

    // トークンを出力
    for (const auto& token : tokens) {
        cout << "Token(Type: " << tokenTypeToString(token.type)
             << ", Value: \"" << token.value << "\")" << endl;
    }

    return 0;
}

プログラムの主要な部分を説明します

インクルード文と名前空間
#include <iostream>
#include <string>
#include <vector>
#include <regex>

using namespace std;
  • #include文を使用してプログラムで必要なライブラリ(入出力処理、文字列操作、コンテナ、正規表現)を取り込んでいます
  • using namespace stdによって標準名前空間を使うことで、コードの記述を簡単にしています
TokenType列挙型
enum TokenType {
    KEYWORD,
    IDENTIFIER,
    NUMBER,
    OPERATOR,
    SEMICOLON,
    UNKNOWN
};
  • トークンの種類をTokenTypeという列挙型で定義しています
    • KEYWORD:予約語(letconstverなど)
    • IDENTIFIER:識別子(変数など)
    • NUMBER:数値
    • OPERATOR:演算子(+-*/=など)
    • SEMICOLON:セミコロン(;)
    • UNKNOWN:上記以外のもの
Token構造体
struct Token {
    TokenType type;
    string value;
};
  •  トークンを表現するToken構造体を定義しています。Tokenはトークンの種類(type)とその値(value)を保持します
レキサー関数
vector<Token> lex(const string& code) {
    vector<Token> tokens;
    size_t pos = 0;

    // トークンに対応する正規表現
    regex keyword_regex(R"(\b(let|const|var)\b)");
    regex identifier_regex(R"([a-zA-Z_][a-zA-Z0-9_]*)");
    regex number_regex(R"(\b\d+\b)");
    regex operator_regex(R"([=+\-*/])");
    regex semicolon_regex(R"(;)");
  • lex関数は文字列codeを受け取り、トークンに分解する処理を行います
  • regexを使用して、それぞれのトークンに対応する正規表現を定義します
レキシングのループ処理
    while (pos < code.size()) {
        string remaining_code = code.substr(pos);
        smatch match;

        if (regex_search(remaining_code, match, keyword_regex) && match.position(0) == 0) {
            tokens.push_back({KEYWORD, match.str(0)});
        } else if (regex_search(remaining_code, match, identifier_regex) && match.position(0) == 0) {
            tokens.push_back({IDENTIFIER, match.str(0)});
        } else if (regex_search(remaining_code, match, number_regex) && match.position(0) == 0) {
            tokens.push_back({NUMBER, match.str(0)});
        } else if (regex_search(remaining_code, match, operator_regex) && match.position(0) == 0) {
            tokens.push_back({OPERATOR, match.str(0)});
        } else if (regex_search(remaining_code, match, semicolon_regex) && match.position(0) == 0) {
            tokens.push_back({SEMICOLON, match.str(0)});
        } else if (isspace(remaining_code[0])) {
            pos++;
            continue;
        } else {
            tokens.push_back({UNKNOWN, string(1, remaining_code[0])});
        }

        pos += match.length(0);
    }

  • whileを使って、文字列codeの最初から順にトークン化の処理を行います
  • regex_searchで各トークンの正規表現に一致するか確認し、一致した場合は該当するTokenTypeを持つトークンをtokensに追加します
  • 正規表現で一致した文字の分だけposを進め、次の処理を行います
  • 空白文字は無視し、一致する正規表現がない文字はUNKNOWNとして扱います
トークンタイプを文字列に変換する処理
string tokenTypeToString(TokenType type) {
    switch (type) {
        case KEYWORD: return "KEYWORD";
        case IDENTIFIER: return "IDENTIFIER";
        case NUMBER: return "NUMBER";
        case OPERATOR: return "OPERATOR";
        case SEMICOLON: return "SEMICOLON";
        default: return "UNKNOWN";
    }
}
  • トークンの情報を表示するため、トークンの種類を文字列として出力します
メイン関数
int main() {
    string code = "let x = 10 + 20;";

    // レキサー実行
    vector<Token> tokens = lex(code);

    // トークンを出力
    for (const auto& token : tokens) {
        cout << "Token(Type: " << tokenTypeToString(token.type)
             << ", Value: \"" << token.value << "\")" << endl;
    }

    return 0;
}
  • codeという変数に"let x = 10 + 20;"を設定します
  • lex関数を呼び出し、コードをトークンに分解します
  • 分解されたトークンを出力します

まとめ

長くなったので、ここで区切って続きは別記事にしようと思います。

  • ソースコードはブラウザやNode.jsなどのJavaScriptエンジンで実行される
  • 字句解析ではソースコードをトークンという単位に分解して、それぞれの構成要素に意味を与え、分類する。このプロセスには正規表現を使用されることが多い
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?