Chat Monaco Editor-Monarch of Custom Languages

JavaScript

Что такое монарх

Monarch — это библиотека подсветки синтаксиса, которая поставляется с редактором Monaco Editor, с помощью которой мы можем использовать синтаксис, аналогичный Json, для достиженияПользовательский языкизПодсветка синтаксиса. В этой статье мы познакомимся с использованием Monarch, написав простой пользовательский язык журналов (далее именуемый log).

Начинать

инициализация

monaco.languages.register({ id: 'log' });

monaco.languages.setMonarchTokensProvider('log', monarchObj);

setMonarchTokensProvidermonarchObjЭто содержимое Json, указанное Monarch, которое нам нужно заполнить.

Monarch

Monarch состоит из серии пар ключ-значение Json, имеет множество свойств, наиболее важным из которых являетсяtokenizer属性,我们描述语法的代码就写在这里面。 Начнем с простого примера:

monaco.languages.setMonarchTokensProvider('log', {
	tokenizer: {
		root:[
			[/\d+/,{token:"keyword"}],
			[/[a-z]+/,{token:"string"}]
		],
	}
});

Мы определяем токенизатор вrootАтрибуты,rootдаtokenizerодин изstate, что мы используем для написания правил синтаксического анализа (rule), кудаrule, мы можем написать соответствующий текстрегулярное выражениеИ текст предоставляется для выполнения операции сопоставленияaction,существуетactiontoken class.

В нашем примере мы устанавливаем два правила в корне, которые используются для сопоставленияколичествоа такжеписьмо, после успешного совпадения выполняется соответствующее действие Наконец, в действии мы устанавливаем класс токена, соответствующий тексту:keywordа такжеstring. Окончательный эффект выглядит следующим образом:

У некоторых здесь возникнут вопросы, является ли класс токенов классом css? По сути, класс токена фактически устанавливает класс в css, однако ключевое слово !=.keyword, в Monarch будет слойПерепискаОтдел, ключевое слово соответствует css.mtk8, а строка соответствует css в.mtk5. Следующие классы токенов встроены в Monarch:

identifier         entity           constructor
operators          tag              namespace
keyword            info-token       type
string             warn-token       predefined
string.escape      error-token      invalid
comment            debug-token
comment.doc        regexp
constant           attribute

delimiter .[curly,square,parenthesis,angle,array,bracket]
number    .[hex,octal,binary,float]
variable  .[name,value]
meta      .[content]

Однако проблема с выделенным выше кодом все еще существует.

Мы обнаружили, что верхний регистр не распознается. В настоящее время мы можем усовершенствовать регулярное выражение правила для следующих совпадающих строк.

tokenizer: {
	root:[
	[/\d+/,{token:"keyword"}],
	[/[a-zA-Z]+/,{token:"string"}]
	],
}

Если наш язык игнорирует регистр, то мы можем напрямую добавитьignoreCaseАтрибуты.

monaco.languages.setMonarchTokensProvider('log', {
    ignoreCase: true,
    tokenizer: {
	    root:[
            [/\d+/, {token: "keyword"}],
            [/[a-z]+/, {token: "string"}]
	    ],
    }
});

Окончательный эффект выглядит следующим образом:

Разобравшись с базовой структурой Monarch, давайте начнем формально писать язык журналов.

язык журнала

Мы хотим, чтобы язык журнала в основном использовался для различения отображения различных типов журналов, общий эффект выглядит следующим образом:

мы начинаем с[error], [info], [warning]Как начало строки, представляющей уровень лога. как показано на рисунке,errorЛоги после все будуткрасныйДо следующего уровня журнала.

отметить уровень журнала

Прежде всего, давайте отметим это.[error],[info]

tokenizer: {
	root: [
		[/^\[error\]/, { token: "custom-error" }],
		[/^\[info\]/, { token: "custom-info" }],
		[/^\[warning\]/, { token: "custom-warning" }]
	]
}
//设置含有custom-error等token class的主题
monaco.editor.defineTheme('logTheme', {
    base: 'vs',
    inherit: true,
    rules: [
        { token: 'custom-info', foreground: '808080' },
        { token: 'custom-error', foreground: 'ff0000', fontStyle: 'bold' },
        { token: 'custom-warning', foreground: 'FFA500' }
    ]
});
monaco.editor.create(document.getElementById("container"), {
    theme: 'logTheme',
    value: getCode(),
    language: 'log'
});

Написали три правила соответственно[error]отметить какcustom-error,[info]отметить какcustom-info,[warning]отметить какcustom-warning. Мы обнаружили, что эти правила похожи, поэтому мы можем найти способ их объединить.

tokenizer: {
	root: [
		[/^\[(\w+)\]/, { token: "custom-$1" }]
	]
}

Здесь мы используем знак доллара$, который представляет количество совпадений с регулярным выражением,$0Представляет все совпадающие элементы (например: [ошибка]),$1Представляет получение первого совпадения (например, ошибка). Вышеприведенный код передает тип журнала в качестве класса токена параметра, который аналогиченcustom-Сделайте строчку, которые составляют конечный класс токена, например,custom-error.

error,info,warning[debug],[test]cases.

{ cases: { guard1: action1, ..., guardN: actionN } }

casesи обычныйif ,else ifСинтаксис такой же, вы можете написать несколько условий оценки (guard), то согласноguardвыполнить соответствующиеaction.

guardПодобно регулярным выражениям, функция должна сопоставлять текст, если он не начинается с@или$В начале он обычное регулярное выражение, но когда он начинается с@или$В начале он былВ прямом смыслеОхранник.

защита имеет фиксированную структуру[pat][op]match,patпредставлятьсоответствующий текст,opпредставляеткомпаратор,matchЭто длячто сравнивать.

patк$$1$#$0От имени всего подходящего текста. Кроме того, мы также можем использовать$Sn$S0От имениroot.

opа такжеmatchНемного сложнее, это может быть несколько элементов

  • ~regex or !~regex: соответствует/не соответствует обычному
  • @attribute or !@attribute: соответствует/не соответствует атрибуту.Атрибут определяется на корневом уровне Monarch, который может быть массивом, строкой или обычным.
  • ==str or !=str: сопоставить/не сопоставить строку
  • @default: соответствует регистру по умолчанию
  • @eos: конец строки, совпадение успешно

С помощью этих инструментов мы можем затем написать наш выделенный код

{
    keywords: ['error', 'info', 'warning'],
    tokenizer: {
        root: [
            [/^\[(\w+)\]/, {
                cases: {
                    "$1@keywords": { token: 'custom-$1' },
                    "@default": { token: "string" }
                }
            }]
        ]
    }
}

Здесь мы использовали$1@keywordsсудитьтип журнала ($1)Существует ли вkeywordsВ массиве он также используется.@default来匹配未定义的日志类型。 Окончательный эффект выглядит следующим образом:

Здесь, и мы наконец закончилиЖурналы типов выделения, то можно приступать к обработке лога

пометить журнал

tokenizer: {
        root: [
            [/^\[(\w+)\]/, {
                cases: {
                    "$1@keywords": {token:'custom-$1', next:"@text.$1"},
                    "@default":'string'
                }
            }],
        ],
        text:[
            [/^\[(\w+)\]/,{token:"@rematch",next:"@pop"}],
            [/.*/,{token:"custom-$S2"}]
        ]
}

Здесь впервые былоnext: "@text.$1", Значениетекущее корневое состояниепрыгать вtext state, и положиКорневое состояние помещается в стек токенизатора,существуетtext state, мы можем пройтиnext:@popвернуться к первому в стекеstate, который является нашимroot state.

Вот еще один@rematch, что означает, что текущий текст совпал, но ничего не делать, пусть последующийruleснова совпадать.

Подводя итоги, логика вышеуказанного кодасоответствовать типу журналаПосле этого мыВступил вtext state,существуетtext stateМы будемПоследующий текст (.*)отмечены как иТот же класс токена типа журналаТег типа журналаПосле этого используйте@rematchа также@popВернуться кroot stateПроведите матч еще раз. Эффект следующий:

Сделав еще один шаг, мы можем немного упростить код.

{
    keywords: ['error', 'warning', 'info'],
    header: /\[(\w+)\]/,
    tokenizer: {
        root: [
            [/^@header/, {
                cases: {
                    "$1@keywords": { token: 'custom-$1', next: "@text.$1" },
                    "@default": 'string'
                }
            }],
        ],
        text: [
            [/^@header/, { token: "@rematch", next: "@pop" }],
            [/.*/, { token: "custom-$S2" }]
        ]
    }
}

мы будемРегулярное выражение для соответствия типу журналаизвлечь как единое целоеheader, то через@来嵌入。 Но здесь@а такжеguardиз@другой, онПоддерживаются только регулярные выражения,а такжеТип массива не поддерживается.

конец

В этой статье представлены основные понятия и использование Monarch, но объем ограничен. В этой статье не могут быть представлены другие функции, предоставляемые Monarch, такие как сопоставление скобок, встраивание языка и т. д., и есть много деталей, которые не перечислены. заинтересованы, хотите углубленно изучить, можете.