词法分析是基于字符输入流进行操做的,可是经过peek() 或 next() 返回的是一个特殊对象,即token
. 一个token
中包含两个属性: type
和 value
. 如下是几个例子: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
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就能够了.read_number()
函数中添加处理方法就能够了\n
\t
.下面一节会介绍一下AST
.post