«Разве вы не видите, что вся цель новояза состоит в том, чтобы сузить круг мыслей? В конце концов мы сделаем мыслепреступление буквально невозможным, потому что не будет слов, чтобы выразить его». — «1984», Джордж Оруэлл

Предположим, вы хотите иметь возможность использовать ключевое слово fn вместо function в JavaScript, аналогично тому, как это работает в Rust. Как сделать это возможным?

Итак, начнем с желудя.

Крошечный, быстрый анализатор JavaScript, полностью написанный на JavaScript.

Инструменты, которые вы используете каждый день, такие как ESLint или Babel, основаны на Acorn. Это быстрый и простой для вас парсер ESTree.

Его можно использовать следующим образом:

const {Parser} = require("acorn")

const MyParser = Parser.extend(
  require("acorn-jsx")(),
  require("acorn-bigint")
)
console.log(MyParser.parse("// Some bigint + JSX code"))

И даже больше! Его можно расширить:

module.exports = function noisyReadToken(Parser) {
  return class extends Parser {
    readToken(code) {
      console.log("Reading a token!")
      super.readToken(code)
    }
  }
}

И если вы хотите сделать возможным использование такого кода:

fn hello() {
    return 'world';
}

И разобрать его на

function hello() {
    return 'world';
}

Не осторожно! Это намного проще, чем вы думаете. Все, что нужно сделать, это: расширить анализатор Acorn.

Как расширить парсер желудей?

Ну, есть пара вещей, которые вам нужно знать, прежде чем мы начнем.

Все зарезервированные слова, которые мы используем в JavaScript, такие как: if, else, function и т. д., расположены в объекте Parser.acorn.keywordTypes, поэтому, если вы хотите добавить новое ключевое слово, вам нужно создать новый токен:

const {Parser, TokenType} = require('acorn');

function newSpeak(Parser) {
    Parser.acorn.keywordTypes['fn'] = new TokenType('fn', {
        keyword: 'fn',
    });
}
const {parse} = Parser.extend(
    newSpeak,
);

console.log(parse(`
    fn hello() {
        return 'world';
    }
`));
// here you will see the AST

newSpeek — это название нашего воображаемого языка.

Далее следует добавить ключевое слово fn в список ключевых слов, о которых знает Acorn:

function newSpeak(Parser) {
    Parser.acorn.keywordTypes['fn'] = new TokenType('fn', {
        keyword: 'fn',
    });
    
    return class extends Parser {
        parse() {
            this.keywords = addKeyword('fn', this.keywords);
            return super.parse();
        }
    }
}
function addKeyword(keyword, keywords) {
    const str = keywords
        .toString()
        .replace(')$', `|${keyword})$`)
        .slice(1, -1);
    
    return RegExp(str);
}

keywords – этоодно большое регулярное выражение, вот как оно выглядит:

/^(?: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)$/

Так что да, нам нужно добавить наше ключевое слово fn в этот список, чтобы все заработало.

После этого нам нужно переопределить parseStatement и сказать acorn, что это всего лишь fn это всего лишь function.

function newSpeak(Parser) {
    Parser.acorn.keywordTypes['fn'] = new TokenType('fn', {
        keyword: 'fn',
    });
    
    return class extends Parser {
        parse() {
            this.keywords = addKeyword('fn', this.keywords);
            return super.parse();
        }
        parseStatement(context, topLevel, exports) {
            if (this.type == Parser.acorn.keywordTypes['fn']) {
                this.type = Parser.acorn.keywordTypes['function'];
            }
            return super.parseStatement(context, topLevel, exports);
        }
    }
}

Вот и все! Вот весь код:

const {TokenType, Parser} = require('acorn');

function newSpeak(Parser) {
    Parser.acorn.keywordTypes['fn'] = new TokenType('fn', {
        keyword: 'fn',
    });
    
    return class extends Parser {
        parse() {
            this.keywords = addKeyword('fn', this.keywords);
            return super.parse();
        }
        parseStatement(context, topLevel, exports) {
            if (this.type == Parser.acorn.keywordTypes['fn']) {
                this.type = Parser.acorn.keywordTypes['function'];
            }
            return super.parseStatement(context, topLevel, exports);
        }
    }
}

function addKeyword(keyword, keywords) {
    const str = keywords
        .toString()
        .replace(')$', `|${keyword})$`)
        .slice(1, -1);
    
    return RegExp(str);
}

const MyParser = Parser.extend(
    newSpeak,
);

console.log(MyParser.parse(`
    fn hello() {
        return 'world';
    }
`));

Теперь вы можете добавить любое ключевое слово, чтобы сделать язык своей мечты :)!