0

我正在研究一种可以转换为 javascript 并具有类似语法的语言。但是我想包含一些新类型的块语句。出于语法目的,它们与 IfStatement 相同。如何让 esprima 或 acorn 解析这个程序MyStatement {a=1;}而不抛出错误?如果它称它为 IfStatement,那很好。我宁愿不分叉 esprima。

4

1 回答 1

2

事实证明,橡子的插件功能并没有真正记录在案。似乎分叉橡子将是最简单的路线。在这种情况下,它就像搜索 的出现_if并遵循类似的模式一样简单_MyStatement

但是,可以编写一个插件来完成我想要做的事情。这似乎有点骇人听闻,但这是代码。基本步骤是:

  1. 扩展并添加到第一遍Parse识别的关键字列表中

  2. 为新关键字创建一个 TokenType 并将其添加到Parser.acorn.keywordTypes, 扩展parseStatement中,以便它处理新的 TokenType

  3. 为新的 TokenType 创建一个处理程序,它将根据关键字功能的要求将信息添加到抽象语法树,并使用诸如this.expect(tt.parenR)吃 '(' 或this.parseExpression()处理整个表达式之类的命令来使用令牌。

这是代码:

var program = 
`
  MyStatement {
    MyStatement(true) {
      MyStatement() {
        var a = 1;
      }
    }
    if (1) {
      var c = 0;
    }
  }
`;

const acorn = require("acorn");

const Parser = acorn.Parser;
const tt = acorn.tokTypes; //used to access standard token types like "("
const TokenType = acorn.TokenType; //used to create new types of Tokens.

//add a new keyword to Acorn.
Parser.acorn.keywordTypes["MyStatement"] = new TokenType("MyStatement",{keyword: "MyStatement"});

//const isIdentifierStart = acorn.isIdentifierStart;

function wordsRegexp(words) {
  return new RegExp("^(?:" + words.replace(/ /g, "|") + ")$")
}

var bruceware = function(Parser) {
  return class extends Parser {
    parse(program) {
      console.log("hooking parse.");

      //it appears it is necessary to add keywords here also.
      var newKeywords = "break case catch continue debugger default do else finally for function if return switch throw try var while with null true false instanceof typeof void delete new in this const class extends export import super";
      newKeywords += " MyStatement";
      this.keywords = wordsRegexp(newKeywords);

      return(super.parse(program));
    }

    parseStatement(context, topLevel, exports) {
      var starttype = this.type;
      console.log("!!!hooking parseStatement", starttype);

      if (starttype == Parser.acorn.keywordTypes["MyStatement"]) {
        console.log("Parse MyStatement");
        var node = this.startNode();
        return this.parseMyStatement(node);
      }
      else {
        return(super.parseStatement(context, topLevel, exports));
      }
    }

    parseMyStatement(node) {
      console.log("parse MyStatement");
      this.next();

      //In my language, MyStatement doesn't have to have a parameter. It could be called as `MyStatement { ... }`
      if (this.type == tt.parenL) {
        node.test = this.parseOptionalParenExpression();
      }
      else {
        node.test = 0; //If there is no test, just make it 0 for now (note that this may break code generation later).
      }

      node.isMyStatement = true; //set a flag so we know that this if a "MyStatement" instead of an if statement.

      //process the body of the block just like a normal if statement for now.

      // allow function declarations in branches, but only in non-strict mode
      node.consequent = this.parseStatement("if");
      //node.alternate = this.eat(acornTypes["else"]) ? this.parseStatement("if") : null;
      return this.finishNode(node, "IfStatement")
    };

    //In my language, MyStatement, optionally has a parameter. It can also by called as MyStatement() { ... }
    parseOptionalParenExpression() {
      this.expect(tt.parenL);

      //see what type it is
      console.log("Type: ", this.type);

      //allow it to be blank.
      var val = 0; //for now just make the condition 0. Note that this may break code generation later.
      if (this.type == tt.parenR) {
        this.expect(tt.parenR);
      }
      else { 
        val = this.parseExpression();
        this.expect(tt.parenR);
      }

      return val
    };

  }
}

process.stdout.write('\033c'); //cls

var result2 = Parser.extend(bruceware).parse(program); //attempt to parse

console.log(JSON.stringify(result2,null,' ')); //show the results.
于 2020-01-21T18:06:15.887 回答