[譯]用javascript實現一門編程語言-詞法分析

目錄

  1. 用javascript實現一門編程語言-前言
  2. 用javascript實現一門編程語言-語言構想
  3. 用javascript實現一門編程語言-寫一個解析器
  4. 用javascript實現一門編程語言-字符輸入流
  5. 用javascript實現一門編程語言-詞法分析

詞法分析(the token input stream)

詞法分析是基於字符輸入流進行操做的,可是經過peek() 或 next() 返回的是一個特殊對象,即token. 一個token中包含兩個屬性: typevalue. 如下是幾個例子:javascript

{ type: "punc", value: "(" }           // 標點符號(punctuation): 括號(parens), 逗號(comma), 分號(semicolon) etc.
{ type: "num", value: 5 }              // 數字
{ type: "str", value: "Hello World!" } // 字符串
{ type: "kw", value: "lambda" }        // 關鍵字(keywords)
{ type: "var", value: "a" }            // 變量名(identifiers)
{ type: "op", value: "!=" }            // 操做(operators)
複製代碼

空白和註釋會被直接跳過,沒有token返回.java

爲了完成詞法分析器,咱們須要對語法瞭解的很詳細.咱們須要對peek()返回的當前字符進行處理,返回token,有如下幾點須要注意:git

  • 跳過空格
  • 若是到達末尾,返回null
  • 若是碰見#,跳過註釋,即本行後面全部內容
  • 若是是引號,讀入字符串
  • 若是是數字,讀入數字
  • 若是是一個單詞,按關鍵字或者變量處理
  • 若是是標點符號,返回標點符號的token
  • 若是是操做符,返回操做符的token
  • 若是不匹配上面任何一個,輸出錯誤input.croak()

下面是詞法分析的核心代碼-讀取下一個:編程

function read_next() {
    read_while(is_whitespace);
    if (input.eof()) return null;
    var ch = input.peek();
    if (ch == "#") {
        skip_comment();
        return read_next();
    }
    if (ch == '"') return read_string();
    if (is_digit(ch)) return read_number();
    if (is_id_start(ch)) return read_ident();
    if (is_punc(ch)) return {
        type  : "punc",
        value : input.next()
    };
    if (is_op_char(ch)) return {
        type  : "op",
        value : read_while(is_op_char)
    };
    input.croak("Can't handle character: " + ch);
}
複製代碼

這是一個分發函數,他會決定何時調用next()來得到下一個token.這裏面用到了不少工具函數,例如read_string(), read_number()等等.咱們不必在這裏就把這些函數寫出來增長複雜度.bash

另外一個須要注意的是,咱們不會一會兒就去拿到全部的輸入流,每次解析器只會讀取下一個token,這樣便於咱們去定位錯誤(有時由於語法錯誤,解析器不用繼續解析).編程語言

read_ident()函數會盡量多的讀取能夠做爲變量名稱的字符做爲變量名.變量名必須以字母,λ或_開頭,能夠包含字母,數字或者?!-<>=.所以foo-bar不會做爲3個token讀入,而是做爲一個變量.定義這個規則的緣由是爲了讓我定義is-pair這樣的變量.ide

固然,read_ident()函數也會去檢查讀入的名稱是否是一個關鍵字.若是是關鍵字將會返回kw token, 不然返回var token.函數

如下是TokenStream的全部代碼:工具

function TokenStream(input) {
    var current = null;
    var keywords = " if then else lambda λ true false ";
    return {
        next  : next,
        peek  : peek,
        eof   : eof,
        croak : input.croak
    };
    function is_keyword(x) {
        return keywords.indexOf(" " + x + " ") >= 0;
    }
    function is_digit(ch) {
        return /[0-9]/i.test(ch);
    }
    function is_id_start(ch) {
        return /[a-zλ_]/i.test(ch);
    }
    function is_id(ch) {
        return is_id_start(ch) || "?!-<>=0123456789".indexOf(ch) >= 0;
    }
    function is_op_char(ch) {
        return "+-*/%=&|<>!".indexOf(ch) >= 0;
    }
    function is_punc(ch) {
        return ",;(){}[]".indexOf(ch) >= 0;
    }
    function is_whitespace(ch) {
        return " \t\n".indexOf(ch) >= 0;
    }
    function read_while(predicate) {
        var str = "";
        while (!input.eof() && predicate(input.peek()))
            str += input.next();
        return str;
    }
    function read_number() {
        var has_dot = false;
        var number = read_while(function(ch){
            if (ch == ".") {
                if (has_dot) return false;
                has_dot = true;
                return true;
            }
            return is_digit(ch);
        });
        return { type: "num", value: parseFloat(number) };
    }
    function read_ident() {
        var id = read_while(is_id);
        return {
            type  : is_keyword(id) ? "kw" : "var",
            value : id
        };
    }
    function read_escaped(end) {
        var escaped = false, str = "";
        input.next();
        while (!input.eof()) {
            var ch = input.next();
            if (escaped) {
                str += ch;
                escaped = false;
            } else if (ch == "\\") {
                escaped = true;
            } else if (ch == end) {
                break;
            } else {
                str += ch;
            }
        }
        return str;
    }
    function read_string() {
        return { type: "str", value: read_escaped('"') };
    }
    function skip_comment() {
        read_while(function(ch){ return ch != "\n" });
        input.next();
    }
    function read_next() {
        read_while(is_whitespace);
        if (input.eof()) return null;
        var ch = input.peek();
        if (ch == "#") {
            skip_comment();
            return read_next();
        }
        if (ch == '"') return read_string();
        if (is_digit(ch)) return read_number();
        if (is_id_start(ch)) return read_ident();
        if (is_punc(ch)) return {
            type  : "punc",
            value : input.next()
        };
        if (is_op_char(ch)) return {
            type  : "op",
            value : read_while(is_op_char)
        };
        input.croak("Can't handle character: " + ch);
    }
    function peek() {
        return current || (current = read_next());
    }
    function next() {
        var tok = current;
        current = null;
        return tok || read_next();
    }
    function eof() {
        return peek() == null;
    }
}
複製代碼
  • next()並非每次都會調用read_next(),由於可能提早調用過read_next(),因此,在current存在的時候就直接返回current就能夠了.
  • 咱們只支持十進制數字,不支持科學計數法,不支持16進制,8進制.若是咱們須要這些的話,就在read_number()函數中添加處理方法就能夠了
  • 與javascript不一樣的是,字符串中不能包含引號自己和反斜槓,可是咱們不會影響經常使用的轉義字符\n \t.

下面一節會介紹一下AST.post

原文連接: lisperator.net/pltut/parse…

相關文章
相關標籤/搜索