【编译器实现笔记】1. 分词器(Tokenizer、Lexer)

原文地址:https://lisperator.net/pltut/

分词器的作用

通过解析字符流,把里面的 token 提取出来,提取后的格式为:

{ type: "punc", value: "(" }           // punctuation: parens, comma, semicolon etc.
{ type: "num", value: 5 }              // numbers
{ type: "str", value: "Hello World!" } // strings
{ type: "kw", value: "lambda" }        // keywords
{ type: "var", value: "a" }            // identifiers
{ type: "op", value: "!=" }            // operators

每一个 token 都是一个 AST 的节点

分词器的实现原理

  1. 跳过所有的空格
  2. 若输入流已到结尾(input.eof() === true),返回 null。
  3. 若遇到 #,则跳过注释
  4. 若遇到 ",则读取字符串。
  5. 若遇到 数字,则连续读取得到一个数字。
  6. 若遇到 字母,则读取标识符(identifier)或者关键字(keyword)。
  7. 若遇到 标点符号,则返回标点符号 token。
  8. 若遇到 操作符,则返回操作符 token。
  9. 若上面的情况都不符合,则抛出一个异常 input.croak()。

下面的read_next函数作为分词器的核心,实现了上面各描述情况:

function read_next() {
  read_while(is_whitespace); // 1
  if (input.eof()) return null; // 2
  var ch = input.peek();
  if (ch == '#') {
    skip_comment();
    return read_next();
  } // 3
  if (ch == '"') return read_string(); // 4
  if (is_digit(ch)) return read_number(); // 5
  if (is_id_start(ch)) return read_ident(); // 6
  if (is_punc(ch))
    return {
      type: 'punc',
      value: input.next(),
    }; // 7
  if (is_op_char(ch))
    return {
      type: 'op',
      value: read_while(is_op_char),
    }; // 8
  input.croak("Can't handle character: " + ch); // 9
}

注意:没有一次性把输入流全部读掉。每次当解析器需要下一个 token 时,才会读取一个 token。当解析过程发生错误时我们甚至都不会到达流的末尾。

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

  // 将标识符和已知的关键字列表进行对比,如果标识符是一个关键字则会返回一个 "kw" token,否则返回一个 "var" token。
  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;
  }

  // 4
  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;
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值