Как токенизировать фрагмент кода с помощью грамматики Textmate в Node - PullRequest
2 голосов
/ 18 октября 2019

Я пытаюсь выделить синтаксис фрагментов кода на веб-сайте моей библиотеки. Я пробовал Highlight.js и Prism, но ни один из них не использовал токенизированный код правильно (это Ruby), поэтому в итоге код не выделен синтаксисом должным образом. Это потому, что они оба реализуют свои собственные регулярные выражения токенизации, что является подходом, который обязательно имеет недостатки.

Затем я обнаружил, что GitHub , Atom и VSCode все используют TextMate грамматики для их токенизации. Для меня это звучит как правильный подход - хранить языковые грамматики в одном месте, чтобы другие инструменты могли затем использовать их вместо того, чтобы определять свои собственные.

Мой вопрос таков: как токенизироватьстрока кода, использующая грамматику TextMate в Node? Моя цель - получить что-то вроде:

const codeSnippet = `
class Foo
  def bar
    puts "baz"
  end
end
`

const tokenized = tokenizeCode(codeSnippet, 'ruby')

tokenized // some kind of array of tokens, e.g:
// [
//   ['keyword', 'class'],
//   ['whitespace', ' '],
//   ['class', 'Foo'],
//   ...
// ]

Я пробовал vscode-textmate , что, как кажется, VSCodeиспользовать для собственной подсветки синтаксиса. Однако я не мог понять, как использовать его для достижения вышеуказанной функциональности.

В конечном итоге я хочу закончить с HTML, который я могу выделить синтаксисом:

<pre>
  <code>
    <span class="token kewyord">class</span> <span class="token class">Foo</span>
    <!-- ... -->
  

Опять же, я попробовал highlight.js и Prism, но они оба неправильно маркируют даже самый простой код Ruby.

Edit

Вот несколько примеров, где Prism и Highlight.jsнеправильно токенизировать код Ruby:

  • Highlight.js - не токенизировать Post как "константу"

    const hljs = require("highlight.js/lib/highlight.js");
    hljs.registerLanguage('ruby', require('highlight.js/lib/languages/ruby'));
    
    const rubyCode = `Post.create(params[:post])`
    const html = hljs.highlight('ruby', rubyCode).value
    
    console.log(html)
    // Post.create(params[<span class="hljs-symbol">:post</span>])
    
  • Prism -не маркирует foo: как "символ"

    const Prism = require('prismjs');
    const loadLanguages = require('prismjs/components/');
    loadLanguages(['ruby']);
    
    const rubyCode = `{ foo: "bar" }`
    const html = Prism.highlight(rubyCode, Prism.languages.ruby, 'ruby')
    
    console.log(html)
    // <span class="token punctuation">{</span> foo<span class="token punctuation">:</span> <span class="token string">"bar"</span> <span class="token punctuation">}</span>
    

Ответы [ 2 ]

1 голос
/ 19 октября 2019

После публикации моего комментария я попробовал еще раз и на этот раз был успешным. В следующем примере показано, как использовать vscode-textmate с официальным TypeScript.tmLanguage, но основы должны быть применимы к другим языкам.

  1. Сначала убедитесь, что у вас установлен Python 2.7 (не 3.X)на вашем компьютере и в Windows в переменной PATH.
  2. Установите vscode-textmate, используя npm или yarn, что вызовет требуемый интерпретатор Python во время установки.
  3. Получите грамматику XML (обычно заканчивающуюся * 1011). *) и поместите его в корень проекта.
  4. Используйте плагин vscode-textmate следующим образом:
import * as fs from "fs";
import { INITIAL, parseRawGrammar, Registry } from "vscode-textmate";

const registry = new Registry({
    // eslint-disable-next-line @typescript-eslint/require-await
    loadGrammar: async (scopeName) => {
        if (scopeName === "source.ts") {
            return new Promise<string>((resolve, reject) =>
                fs.readFile("./grammars/TypeScript.tmLanguage", (error, data) =>
                    error !== null ? reject(error) : resolve(data.toString())
                )
            ).then((data) => parseRawGrammar(data));
        }
        console.info(`Unknown scope: ${scopeName}`);
        return null;
    },
});

registry.loadGrammar("source.ts").then(
    (grammar) => {
        fs.readFileSync("./samples/test.ts")
            .toString()
            .split("\n")
            .reduce((previousRuleStack, line) => {
                console.info(`Tokenizing line: ${line}`);
                const { ruleStack, tokens } = grammar.tokenizeLine(line, previousRuleStack);
                tokens.forEach((token) => {
                    console.info(
                        ` - ${token.startIndex}-${token.endIndex} (${line.substring(
                            token.startIndex,
                            token.endIndex
                        )}) with scopes ${token.scopes.join(", ")}`
                    );
                });
                return ruleStack;
            }, INITIAL);
    },
    (error) => {
        console.error(error);
    }
);

Имейте в виду, что строка source.ts не относится кфайл, это имя области из файла грамматики. Скорее всего, это будет source.ruby в вашем случае. Кроме того, фрагмент кода не оптимизирован и едва читаем, но вы должны получить представление о том, как использовать плагин.

После извлечения токенов вы можете отобразить их соответствующим образом в соответствии с вашими требованиями.

Вывод в моем фрагменте выглядит следующим образом:

Output Sample

0 голосов
/ 21 октября 2019

Я обнаружил пакет Highlights в организации Atom, который использует грамматики TextMate и производит разметку разметки. Он также имеет синхронный API, который мне нужен для интеграции с Замечательно .

<code>const Highlights = require("highlights")

const highlighter = new Highlights()

const html = highlighter.highlightSync({
  fileContents: 'answer = 42',
  scopeName: 'source.ruby',
})

html //=>
// <pre class="editor editor-colors">
//   <div class="line">
//     <span class="source ruby">
//       <span>answer&nbsp;</span>
//       <span class="keyword operator assignment ruby">
//         <span>=</span>
//       </span>
//       <span>&nbsp;</span>
//       <span class="constant numeric ruby">
//         <span>42</span>
//       </span>
//     </span> 
//   </div>
// 

Под капотом используется First Mate длятокенизация, которая является альтернативой vscode-texmate, но с гораздо более простым использованием:

const { GrammarRegistry } = require('first-mate')

const registry = new GrammarRegistry()
const grammar = registry.loadGrammarSync('./ruby.cson')

const tokens = grammar.tokenizeLines('answer = 42') // does all the work

tokens[0] //=>
// [ { value: 'answer ', scopes: [ 'source.ruby' ] },
//   { value: '=',
//     scopes: [ 'source.ruby', 'keyword.operator.assignment.ruby' ] },
//   { value: ' ', scopes: [ 'source.ruby' ] },
//   { value: '42',
//     scopes: [ 'source.ruby', 'constant.numeric.ruby' ] } ]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...