Обратите внимание, что эта статья несколько сложна и не подходит для чтения новичкам. Иногда ее читают люди с некоторым опытом программирования. Это полная ерунда, ок, начнем.
Будь то внешние исследования и разработки или внутренние исследования, вы столкнетесь с DSL — это аббревиатура доменно-специфического языка, то есть предметно-ориентированного языка. DSL — это язык, специально разработанный для решения проблем в определенной области. Его синтаксис и семантика специфичны для этой области и не являются универсальными. DSL можно разделить на внутренний DSL и внешний DSL. Внутренний DSL — это DSL, построенный на общем языке программирования, а внешний DSL — это независимый DSL. Эта статья была составлена потому, что на встрече по обмену интерфейсными технологиями, проведенной Tencent в Шэньчжэне, кто-то упомянул DSL, а затем увидел проблемы, которые они решают с помощью DSL, и мне это показалось очень интересным, поэтому я позже изучил DSL.
Итак, что такое DSL? Например, мы знаем, что SQL — это DSL, язык, используемый для работы с базами данных. Его синтаксис и семантика предназначены для операций с базами данных. Другой пример: регулярное выражение также является своего рода DSL. Это язык, используемый для сопоставления строк. Его синтаксис и семантика направлены на сопоставление строк. DSL может помочь нам решить проблемы в некоторых конкретных областях и повысить эффективность нашей разработки. Фактически, HTML, CSS и JavaScript, которые мы используем во внешнем интерфейсе, также можно рассматривать как своего рода DSL. Все они представляют собой языки, используемые для создания веб-приложений. Их синтаксис и семантика специфичны для веб-приложений. не универсальный. Даже популярные интерфейсные фреймворки, такие как React, Vue, Angular и т. д., можно рассматривать как своего рода DSL. Все они представляют собой фреймворки, используемые для создания веб-приложений, и их синтаксис и семантика специфичны для веб-приложений.
DSL Можно разделить на внутренние помещения. DSL и внешний DSL, внутренний DSL построен на языке программирования общего назначения DSL, а внешний DSL является независимым ДСЛ. внутренний DSL Преимущество состоит в том, что он может использовать преимущества общих языков программирования, таких как проверка типов, завершение кода, отладка и т. д., но его синтаксис и семантика ограничены общими языками программирования. Здесь я приведу вам пример, чтобы вы могли легко его понять. Например, проходим. JavaScript построить ДСЛ, это DSL Может использоваться для описания простого калькулятора, например, это наш DSL:
const calculator = {
add: (a, b) => a + b,
sub: (a, b) => a - b,
mul: (a, b) => a * b,
div: (a, b) => a / b
};
console.log(calculator.add(1, 2)); // 3
console.log(calculator.sub(3, 2)); // 1
console.log(calculator.mul(2, 3)); // 6
console.log(calculator.div(6, 2)); // 3
Этот DSL построен на JavaScript. Его синтаксис и семантика ориентированы на калькуляторы, но на него распространяются ограничения JavaScript. Например, он не может определять новые правила синтаксиса, новые семантические правила и т. д. Внешний DSL не имеет этих ограничений.
Преимущество внешнего DSL заключается в том, что синтаксис и семантика могут быть настроены в соответствии с потребностями предметной области, но затраты на его разработку и обслуживание выше. В реальной разработке мы можем выбрать подходящий DSL в соответствии с нашими потребностями, чтобы повысить эффективность разработки. Недостатком является то, что затраты на разработку и обслуживание DSL высоки и требуют определенного технического уровня. Но не паникуйте, я надеюсь, что эта статья поможет вам лучше понять и применить внешний DSL в вашем проекте.
DSL может применяться во многих областях, таких как файлы конфигурации, механизмы шаблонов, механизмы правил, моделирование предметной области и т. д. В реальной разработке мы можем выбрать подходящий DSL в соответствии с нашими потребностями, чтобы повысить эффективность разработки. Ниже я буду использовать пример, встречающийся в реальных исследованиях и разработках, чтобы проиллюстрировать применение внешнего DSL. Прежде чем продолжить, нам, возможно, потребуется сначала разобраться с одним или двумя инструментами. Один из них — это парсер DSL, написанный на js, который называется Nearley. Другой тоже написан на js и называется jison. Оба инструмента используются для анализа DSL, и вы можете выбрать подходящий инструмент в соответствии с вашими потребностями.
Хорошо, позвольте мне привести вам практический пример.
В реальных исследованиях и разработках мы столкнемся с некоторыми конкретными проблемами в конкретных областях. Если мы будем использовать общий язык программирования для решения этих проблем, это может оказаться более громоздким. В настоящее время мы можем использовать DSL для решения этих проблем и повышения эффективности нашей разработки. Ниже я буду использовать практический пример, чтобы проиллюстрировать применение внешнего DSL.
Предположим, мы крупная телекоммуникационная компания, и наши клиенты в основном делятся на две категории: стандартные клиенты (стандарт) и премиальные клиенты (премиум). Компания предлагает два основных продукта: «Товар1» и «Товар2». Всякий раз, когда клиент покупает любой из этих двух продуктов, нам необходимо отправить ему контракт.
Содержание контракта генерируется на основе стандартного шаблона («contract_template»), подписывается автоматически («auto») или вручную («manuel») и использует печать компании («company_seal»). Контракт действителен в течение одного года («один_год»), условия оплаты — помесячно («ежемесячно»).
Однако мы не отправляем контракты всем клиентам. Мы отправим контракт только при выполнении следующих условий, предполагая сценарий:
Клиент является премиальным клиентом; кредитный рейтинг клиента больше или равен 700; клиент приобрел «продукт2». В этом сценарии мы можем использовать приведенный выше DSL для описания этого сценария, например:
send_contract {
recipient: {
name: "customer.name"
email: "customer.email"
id_card: "customer.id_card"
phone: "customer.phone"
}
template: "contract_template"
signature_method: "auto"
seal_setting: "company_seal"
valid_period: "one_year"
payment_terms: "monthly"
send if customer.type in ["premium"] and customer.credit_rating >= 700 and customer.product in ["product2"]
}
Хорошо, приведенный выше DSL нельзя запустить напрямую. Для его запуска вам все равно понадобится программа. Мы можем использовать Nearley или Jison для написания правил синтаксиса DSL. Здесь мы возьмем jison в качестве примера для написания грамматических правил DSL.
jison — это генератор синтаксического анализатора JavaScript, который генерирует синтаксический анализатор из описания грамматики в стиле BNF. Вот упрощенное синтаксическое правило jison, основанное на предоставленном вами примере DSL:
/* Lexical rules */
%lex
%%
\s+ /* skip whitespace */
"send_contract" { return 'SEND_CONTRACT'; }
"\{" { return '{'; }
"\}" { return '}'; }
"\:" { return ':'; }
"\"" { return '"'; }
[a-zA-Z_][a-zA-Z0-9_]* { return 'IDENTIFIER'; }
"and" { return 'AND'; }
"or" { return 'OR'; }
"in" { return 'IN'; }
">=" { return 'GE'; } // Сопоставление здесь является жадным, вы можете обратиться к jison Документация
"<=" { return 'LE'; }
"<" { return 'LT'; }
">" { return 'GT'; }
"=" { return 'EQ'; }
[0-9]+ { return 'NUMBER'; }
. { return 'INVALID'; }
/lex
/* Operator precedence */
%start expressions
%left OR
%left AND
%left GE LE GT LT EQ
%left IN
%% /* language grammar */
expressions
: contract
;
contract
: SEND_CONTRACT '{' contract_body '}' { $$ = { type: 'contract', body: $3 }; }
;
contract_body
: recipient template signature_method seal_setting valid_period payment_terms send_condition
;
recipient
: "recipient" ':' '{' recipient_body '}' { $$ = { type: 'recipient', body: $4 }; }
;
recipient_body
: IDENTIFIER ':' '"' IDENTIFIER '"' { $$ = { [$1]: $4 }; }
;
template
: "template" ':' '"' IDENTIFIER '"' { $$ = { type: 'template', name: $4 }; }
;
signature_method
: "signature_method" ':' '"' IDENTIFIER '"' { $$ = { type: 'signature_method', method: $4 }; }
;
seal_setting
: "seal_setting" ':' '"' IDENTIFIER '"' { $$ = { type: 'seal_setting', setting: $4 }; }
;
valid_period
: "valid_period" ':' '"' IDENTIFIER '"' { $$ = { type: 'valid_period', period: $4 }; }
;
payment_terms
: "payment_terms" ':' '"' IDENTIFIER '"' { $$ = { type: 'payment_terms', terms: $4 }; }
;
send_condition
: "send" "if" condition { $$ = { type: 'send_condition', condition: $3 }; }
;
condition
: IDENTIFIER IN '[' IDENTIFIER_list ']' { $$ = { type: 'in_condition', variable: $1, values: $4 }; }
| IDENTIFIER GE NUMBER { $$ = { type: 'ge_condition', variable: $1, value: $3 }; }
| IDENTIFIER AND IDENTIFIER { $$ = { type: 'and_condition', left: $1, right: $3 }; }
;
IDENTIFIER_list
: IDENTIFIER ',' IDENTIFIER_list { $$ = [$1].concat($3); }
| IDENTIFIER { $$ = [$1]; }
;
Если вы хотите узнать больше о грамматических и лексических правилах, рекомендуется прочитать книги о принципах компиляции, которые я не буду здесь вдаваться в подробности.
Этот файл определяет лексические правила (в %lex
и /lex
между) и грамматические правила (между %%
и между концом файла). Лексические правила определяют ваше DSL Различные символы (например, ключевые слова, идентификаторы и операторы) в , а также правила синтаксиса определяют, как эти символы объединяются в допустимые выражения.
Этот файл можно скомпилировать в файл JavaScript с помощью инструмента командной строки jison, который затем можно использовать в своем коде для анализа DSL. И какой результат после анализа? Результатом анализа является абстрактное синтаксическое дерево (AST), которое представляет собой древовидную структуру, используемую для представления синтаксической структуры вашего DSL.
{
type: 'contract',
body: {
recipient: {
name: 'customer.name',
email: 'customer.email',
id_card: 'customer.id_card',
phone: 'customer.phone'
},
template: { type: 'template', name: 'contract_template' },
signature_method: { type: 'signature_method', method: 'auto' },
seal_setting: { type: 'seal_setting', setting: 'company_seal' },
valid_period: { type: 'valid_period', period: 'one_year' },
payment_terms: { type: 'payment_terms', terms: 'monthly' },
send_condition: {
type: 'send_condition',
condition: {
type: 'and_condition',
left: {
type: 'and_condition',
left: { type: 'in_condition', variable: 'customer.type', values: ['premium'] },
right: { type: 'ge_condition', variable: 'customer.credit_rating', value: 700 }
},
right: { type: 'in_condition', variable: 'customer.product', values: ['product2'] }
}
}
}
}
У нас может быть функция, которая выполняет этот AST, например:
class ContractSender {
constructor() {
this.recipient = {};
this.template = '';
this.signatureMethod = '';
this.sealSetting = '';
this.validPeriod = '';
this.paymentTerms = '';
this.sendCondition = null;
}
setRecipient(info) {
this.recipient = info;
}
setTemplate(template) {
this.template = template;
}
setSignatureMethod(method) {
this.signatureMethod = method;
}
setSealSetting(setting) {
this.sealSetting = setting;
}
setValidPeriod(period) {
this.validPeriod = period;
}
setPaymentTerms(terms) {
this.paymentTerms = terms;
}
setSendCondition(condition) {
this.sendCondition = condition;
}
send() {
if (this.sendCondition()) {
console.log(`Sending contract to ${this.recipient.name}...`);
// Here you would actually send the contract.
} else {
console.log(`Not sending contract to ${this.recipient.name}.`);
}
}
}
function execute(ast) {
const sender = new ContractSender();
sender.setRecipient(ast.body.recipient);
sender.setTemplate(ast.body.template.name);
sender.setSignatureMethod(ast.body.signature_method.method);
sender.setSealSetting(ast.body.seal_setting.setting);
sender.setValidPeriod(ast.body.valid_period.period);
sender.setPaymentTerms(ast.body.payment_terms.terms);
// Here we assume that the send condition is a simple function that returns a boolean.
// In a real application, you would probably want to convert the send condition from the AST into a function.
sender.setSendCondition(() => true);
sender.send();
}
// Parse your DSL into an AST.
const ast = parse(myDSL);
// Execute the AST.
execute(ast);
Таким образом, наш администратор контракта может отправить контракт по DSL. Конечно, вы можете сказать, что этот DSL кажется относительно простым. На самом деле в реальных сценариях он гораздо сложнее, чем этот. Однако, чтобы упростить его и облегчить понимание, я опустил здесь слишком многое. значит надеяться, что вы сможете понять основные принципы DSL. Затем напишите свой собственный DSL, исходя из ваших собственных потребностей.
Нет, когда мы пишем код, если нет раскраски синтаксиса, автодополнения кода и подсказок кода, эффективность нашей разработки будет значительно снижена. Чтобы реализовать раскраску синтаксиса и завершение кода, нам может потребоваться создать собственный язык, чтобы редактор мог нас распознавать. Этот процесс называется языковой поддержкой. Монако Editor и Ace Editor Все они поддерживают поддержку пользовательских языков, и вы можете реализовать поддержку пользовательских языков в соответствии со своими потребностями. Здесь я использую Monaco Editor Возьмите пример, чтобы проиллюстрировать, как реализовать поддержку пользовательского языка.
Monaco Editor — это редактор кода на основе браузера, разработанный Microsoft. Он предоставляет множество мощных функций, включая подсветку синтаксиса, автозаполнение кода, подсказки кода и т. д. Вы можете заставить Monaco Editor поддерживать ваш DSL, определив собственную языковую поддержку.
Вот основные шаги по использованию редактора Monaco для реализации поддержки пользовательских языков:
Во-первых, вам необходимо определить свой DSL грамматические правила. Это можно сделать, создав файл, содержащий tokenizer
объект недвижимости для реализации。tokenizer
Атрибут — массив, каждый элемент — это массив, содержащий два элемента: регулярное выражение и тип токена. Например:
const myDSL = {
tokenizer: {
root: [
[/send_contract/, "keyword"],
[/"/, "string"],
[/:/, "delimiter"],
[/[{}]/, "delimiter.bracket"],
[/[a-zA-Z_][a-zA-Z0-9_]*/, "identifier"],
[/[0-9]+/, "number"],
[/\s+/, "white"]
],
},
};
Тогда вам нужно позвонить monaco.languages.register
и monaco.languages.setMonarchTokensProvider
Приходите и зарегистрируйте свой ДСЛ. Вы также можете позвонить monaco.editor.defineTheme
определить свой DSL тема. Например:
monaco.languages.register({ id: "ct" });
monaco.languages.setMonarchTokensProvider("ct", myDSL);
monaco.editor.defineTheme("ctTheme", {
base: "vs",
inherit: true,
rules: [
{ token: "keyword", foreground: "880000", fontStyle: "bold" },
{ token: "string", foreground: "008800", fontStyle: "italic" },
{ token: "number", foreground: "000088" },
],
});
Наконец, вы можете позвонить monaco.editor.create
чтобы создать экземпляр редактора и установить его язык и тему. Например:
const editor = monaco.editor.create(document.getElementById("container"), {
value: "",
language: "ct",
theme: "ctTheme",
});
Вышеупомянутое — это лишь основные шаги по реализации подсветки синтаксиса.,Если вы также хотите реализовать автозаполнение кода и подсказки кода,Возможно, вам придется использовать monaco.languages.registerCompletionItemProvider
и monaco.languages.registerHoverProvider
ждать API. Конкретная реализация зависит от вашего DSL сложность и ваши конкретные потребности. Я не буду здесь вдаваться в подробности, я отвлекся. Если вам интересно, вы можете это проверить. Monaco Editor Для получения дополнительной информации ознакомьтесь с официальной документацией.
Рассмотрим процесс решения требований DSL в целом: