有时候表达式里面需要转义字符串,比如"a\"b"
这种,两个双引号中间有一个双引号,今天打算写这个功能。
思路是,如果遇到反斜杠\
,就进入转义模式。稍微修改一下代码。
Lexer.prototype.readString=function(quote){
var escapeMode=false;
var string='';
this.index++;
while(this.index<this.text.length){
var char = this.text.charAt(this.index);
if(char==quote){
this.tokens.push({
text:string,
value:string
})
this.index++;
return ;
}else if (char==="\\") {
escapeMode=true;
}else if (escapeMode) {
//进行转义操作
}else{
string+=char;
}
this.index++;
}
throw "字符串解析流程出错";
}
其实转义操作就是根据反斜杠后面的那个字符来决定编译的下一个字符串是什么,比如:
'a\'b'
这个表达式,如果按照现有流程去遍历:顺序是:遇到单引号,遇到a,遇到反斜杠,遇到单引号,正好和前面的单引号匹配到,字符串解析结束。后面的那个b就直接忽略了。这样不行。应该的顺序是:
遇到a(当前字符串是string==a);遇到反斜杠进入转义模式;遇到单引号,去查找转义模式里面的单引号是什么字符串,查到了是\'
,把\'
加入当前字符串,退出转义模式,当前字符串变成(a');接着往下走遇到b,把b加入字符串,当前字符串变成a\'b
。转义完成。
开始写代码,先加一个常量对象;
Lexer.EASCAPE={
'n':'\n',
'\"':'\"',
'\'':'\'',
'\\':'\\'
}
再写一个处理流程:
Lexer.prototype.readString=function(quote){
var escapeMode=false;
var string='';
this.index++;
while(this.index<this.text.length){
var char = this.text.charAt(this.index);
if(char==quote){
this.tokens.push({
text:string,
value:string
})
this.index++;
return ;
}else if (char==="\\") {
escapeMode=true;
}else if (escapeMode) {
var replacement = this.EASCAPE[char];
if(replacement){
string+=replacement;
}else{
string+=char;
}
}else{
string+=char;
}
this.index++;
}
throw "字符串解析流程出错";
}
测试案例测一下:
it('编译转义字符串',function(){
var lexer = new Lexer();
var astbuilder = new ASTBuilder(lexer);
var compiler = new Compiler(astbuilder);
var FnA = compiler.compile("'a\\\'b'");
expect(FnA()).toEqual('a\'b');
})
发现不通过,经过跟踪代码以后发现,是if的顺序弄错了,修改代码:
Lexer.prototype.readString=function(quote){
var escapeMode=false;
var string='';
this.index++;
while(this.index<this.text.length){
var char = this.text.charAt(this.index);
if(escapeMode){
var replacement = this.EASCAPE[char];
if(replacement){
string+=replacement;
}else{
string+=char;
}
escapeMode=false;
}else if (char==="\\") {
escapeMode=true;
}else if (char===quote) {
this.tokens.push({
text:string,
value:string
})
this.index++;
return ;
}else{
string+=char;
}
this.index++;
}
throw "字符串解析流程出错";
}
这次流程对了,但是报错Cannot read property ''' of undefined
。难道是找不到那个ESCAPE对象吗?看了一下代码,写的是这个
Lexer.EASCAPE={
'n':'\n',
'\"':'\"',
'\'':'\'',
'\\':'\\'
}
var replacement = this.EASCAPE[char];
原来如此,在Lexer上面直接挂的EASCAPE属性,在被实例后的对象上是读不到的。修改一下
var replacement = Lexer.EASCAPE[char];
可以跑了,但是最后还是报错:
跟踪代码以后发现,原来在最后一步return一个Function的时候,出错了,因为我的EASCAPE对象写的不对,修改一下:
Lexer.EASCAPE={
'n':'\\\n',
'\"':'\\\"',
'\'':'\\\'',
'\\':'\\\\'
}
现在再跑测试案例,通过了,yeah!
重点来了
其实刚才的解决思路是错的,首先重现一下刚才的问题,就是当我解析'a'b'的时候,最后生成的function代码是
function(){
return 'a'b'
}
这个代码不合法,所以我修改了ESCAPE对象,那么修改以后实际上在Lexer阶段其实是考虑了另外一个Compiler类的工作,所以为了照顾Compiler,强行改变了自己的工作标准。把这个思路是不对的,其实这就是一种经常见到的耦合,两个类互相照顾对方的感受,最后搞得两个类必须放在一起才能用,谁离开谁都不行。所以代码还是要改,能不能在编译的时候处理一下这种字符。事实是可以的,我只要把一个字符串里面的所有这种单引号双引号什么的字符强制换成十六进制的unicode码就可以了
比如'a'b'
把中间的单引号换掉:
关于js和unicode有个文章挺不错:Unicode与JavaScript详解
将一个字符转成unicode编码可以用这个方法:
'0000'+(char.charCodeAt(x).toString(16)).slice(-4);
经过这个步骤,单引号被转换为0027
.
照着这个思路写代码:
Compiler.prototype.stringEscapeReg=/[^ a-zA-Z0-9]/g;
Compiler.prototype.changeToUnicode=function(c){
return '\\u'+('0000'+c.charCodeAt(0).toString(16)).slice(-4);
}
Compiler.prototype.escape=function(value){
if(webframe.isString(value)){
return "\'"+
value.replace(this.stringEscapeReg,this.changeToUnicode)
+"\'";
}else{
return value;
}
}
Compiler.prototype.recurse=function(ast){
switch (ast.type) {
case ASTBuilder.Program:
this.state.body.push('return ',this.recurse(ast.body),' ;');
break;
case ASTBuilder.Literal:
return this.escape(ast.value);
break;
}
}
现在编译能通过了。
幸好今天是周末,这些东西已经花了我3个多小时了。但是还没完,还要继续。
如何编译这种unicode字符串?
it('编译unicode字符串',function(){
var expression = "'\\u0027'";
var lexer = new Lexer();
var astbuilder = new ASTBuilder(lexer);
var compiler = new Compiler(astbuilder);
var FnA = compiler.compile(expression);
expect(FnA()).toEqual('\u0027');
})
思路是,遇到反斜杠以后,看看后面的字符是不是u,如果是,就读取u后面的4个字符,转成相应的字符串。
if(escapeMode){
if(char==='u'){
var hex = this.text.substring(this.index+1,this.index+5);
this.index+=4;
string += String.fromCharCode(parseInt(hex, 16));
}else{
var replacement = Lexer.EASCAPE[char];
if(replacement){
string+=replacement;
}else{
string+=char;
}
}
escapeMode=false;
}
最后,解决一下错误检测,如果u后面是不合法的字符,需要抛出异常。
这个很简单,只要检查一下u后面的字符是不是4个字,并且是十六进制的数字就可以了。
var hex = this.text.substring(this.index+1,this.index+5);
if(!hex.match(/[\da-f]{4}/i)){
throw "不合法的unicode字符"
}
this.index+=4;
string += String.fromCharCode(parseInt(hex, 16));
终于完成了。啊!