用熟悉 javascript 代替 C++ 来写解析器

动机

准备写一个 javascript 的解析器,将 javascript 解析成为一个抽象语法树(AST),准备着手写这个多半是出自兴趣,要写代码首先就须要选择一个语言去实现,本来也是比较理想去用 c++,可是如今时机还不算成熟,对 c++ 语言还不熟练,相比于 c++ 我更熟悉 javascript 来写一个解析器,这里不会依赖于其余 javascript 的库,基本是白手起家。javascript

001.png

即便是处于兴趣也好,娱乐也好,也但愿把这件事作好html

搭建项目

002.png

项目你们很简单,只须要安装当前稳定版本 node 建立一个文件夹后,在文件夹下运行以下命令便可java

npm init -y
复制代码

完成后会在项目目录下,自动建立一个package.json文件,注意这里进行node

{
  "name": "implementation_parser_with_js",
  "type":"module",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
复制代码

先去实现 lexer

编译的第一个步骤称为 Lexer, 词法分析,其功能是将文本输入转为多个 tokens,接下来主要就是去实现这部分代码功能。咱们将把每一个识别出的 token 标记一个类型c++

const input = "";

function lexer(str){

}

console.log(lexer(input));
复制代码

先去实现一个 lexer 一切从简单开始,input 就是读取 code,咱们先以简单开始,用于一个空字符串""做为输入。git

const input = "";

function lexer(str){
    let c = str[0];
    if(c === undefined){
        return {
            type: 'EOF'
        }
    }
}

console.log(lexer(input));
复制代码

这里输入代码为空input="",lexer 函数读取 code 而后逐一去解析,当解析到字符串为空也就是说明已经读取文件完毕,这里返回的 token 的类型(type)就是 EOF.github

解析数值型

这里咱们仍是从简单起见,假设输入为input="7", 而后判读当前字符是否为 7,若是为数值型 7 这返回一个 token。npm

const input = "7";

function* lexer(str){

    for (let cursor = 0; cursor <= str.length; cursor++) {
        const char = str[cursor];
        if(char === undefined){
            yield {
                type: 'EOF'
            }
        }else if(char === '7'){
            yield{
                type:'number',
                value:7
            }
        }
    }
    
}

for(const token of lexer(input)){
    console.log(token)
}

// console.log(lexer(input));
复制代码

输出类型为 number 值为 7 的 token,表示识别出一个数值类型的 token。json

{ type: 'number', value: 7 }
{ type: 'EOF' }
复制代码

对比 if else ifif if

对于分支语句咱们一般有几种选择,第一种也就是咱们在学习 javascript 第一个接触分支语句 if ... else if ... 也能够用于 if 罗列来进行分支判断,固然也能够用switch, 这里就不讨论 switch 结构比较复杂,也不推荐,就是看一看用 if ... else if ... 仍是用 if 罗列进行分支结构。函数

function* lexer(str){

    for (let cursor = 0; cursor <= str.length; cursor++) {
        const char = str[cursor];
        if( trace("checking undefine",(char === undefined))){
            yield {
                type: 'EOF'
            }
        }
        if (trace("checking 7",(char === '7'))){
            yield{
                type:'number',
                value:7
            }
        }
    }
    
}

for(const token of lexer(input)){
    console.log(token)
}

复制代码

简单地去实现一个方法语言输出程序走到某一个分支后输出内容

function trace(name,v){
    console.log(name);
    return v;
}
复制代码
checking undefine
checking 7
{ type: 'number', value: 7 }
checking undefine
checking 7
{ type: 'number', value: 7 }
checking undefine
checking 7
{ type: 'number', value: 7 }
checking undefine
checking 7
{ type: 'number', value: 7 }
checking undefine
checking 7
{ type: 'number', value: 7 }
checking undefine
{ type: 'EOF' }
checking 7
复制代码

对于if ·和 if 这种形式,输出每一次都会确认两次,这是由于 yield 对每一个 if 语句都进检查,而用 if... else if 若是将对数值型检查放在前面以下

const input = "77777";

function trace(name,v){
    console.log(name);
    return v;
}

function* lexer(str){

    for (let cursor = 0; cursor <= str.length; cursor++) {
        const char = str[cursor];
        if (trace("checking 7",(char === '7'))){
            yield{
                type:'number',
                value:7
            }
        }else if( trace("checking undefine",(char === undefined))){
            yield {
                type: 'EOF'
            }
        }
    }
    
}

for(const token of lexer(input)){
    console.log(token)
}

复制代码

这样作的好处显而易见,这一次对于 undefine 和 7 检查,只进行一次检查

checking 7
{ type: 'number', value: 7 }
checking 7
{ type: 'number', value: 7 }
checking 7
{ type: 'number', value: 7 }
checking 7
{ type: 'number', value: 7 }
checking 7
{ type: 'number', value: 7 }
checking 7
checking undefines
{ type: 'EOF' }
复制代码

异常处理

在解析过程当没有批评的状况这应该抛出一个异常,这里异常类型SyntaxError

const input = "78";


function* lexer(str){

    for (let cursor = 0; cursor <= str.length; cursor++) {
        const char = str[cursor];
        if( char === '7'){
            yield{
                type:'number',
                value:7
            }
        }else if (char === undefined){
            
            yield {
                type: 'EOF'
            }
        }else{
            throw new SyntaxError(`unexpected character "${char}"`)
        }
    }
    
}

for(const token of lexer(input)){
    console.log(token)
}

复制代码
SyntaxError: unexpected character "8"
复制代码

一般咱们还须要给出

const input = "777";

function trace(name,v){
    console.log(name);
    return v;
}



function* lexer(str){

    for (let cursor = 0; cursor <= str.length; cursor++) {
        let char = str[cursor];

        function number(){
            let value = ""

            for(; cursor <= str.length; cursor++){
                char = str[cursor];
                if(char === '7'){
                    //TODO
                    value +=7
                }else{
                    break;
                }
            }

            return {
                type:'number',
                value,
            }
        }

        if( char === '7'){
            yield number()
        }else if (char === undefined){
            
            yield {
                type: 'EOF',
                // begin:cursor,
                // end:cursor+1,
            }
        }else{
            throw new SyntaxError(`unexpected character "${char}" at ${cursor+1}`)
        }
    }
    
}

for(const token of lexer(input)){
    console.log(token)
}

复制代码

整理代码

对代码进行整理,将number方法提取出来,随后咱们不只限于数值型 token 还有字符串、对象等等类型,并且将数值型处理为一个总体 777 因此在 number 函数加入一个内循环,内循环将同一类型的值记做一个 token 返回。到如今,程序变得有点复杂,

  • 首先将 cursor 和 char 两个变量移除 for 循环之外来进行初始化
  • 在 number 中进行内循环,若是类型仍是 7(数值型)将其追加到 value 上,在内循环会将 cursor 增长
  • 为抛出语法的异常语句添加定位信息
const input = "777";

function trace(name,v){
    console.log(name);
    return v;
}

function* lexer(str){

    let cursor = 0;
    let char = undefined;

    function number(){
        let value = ""

        for(; cursor <= str.length; cursor++){
            
            char = str[cursor];

            if(char === '7'){
                //TODO
                value += char
            }else{
                break;
            }
        }

        return {
            type:'number',
            value,
        }
    }

    for (;cursor < str.length; ) {
        const token = number()
        if( token ){
            yield token
        // }else if (char === undefined){
            
        // yield {
        // type: 'EOF',
        // }
        }else{
            throw new SyntaxError(`unexpected character "${char}" at ${cursor+1}`)
        }
    }
    
}

for(const token of lexer(input)){
    console.log(token)
}

总结

创做不易,若是对你们有所帮助,但愿你们点赞支持,有什么问题也能够在评论区里讨论😄~

若是你以为这篇文章对你有点用的话,麻烦请给咱们的开源项目点点star:   http://github.crmeb.net/u/defu       不胜感激 !
 
来自 “开源独尊 ” ,连接:   https://ym.baisou.ltd/post/843.html
相关文章
相关标签/搜索