使用 TypeScript,React,ANTLR 和 Monaco Editor 创建一个自定义 Web 编辑器(二)
欢迎阅读如何使用 TypeScript, React, ANTLR4, Monaco Editor 创建一个自定义 Web 编辑器系列的第二章节, 在这之前建议您阅读使用 TypeScript, React, ANTLR4, Monaco Editor 创建一个自定义 Web 编辑器(一)
在本文中, 我将介绍如何实现语言服务, 语言服务在编辑器中主要用来解析键入文本的繁重工作, 我们将使用通过Parser生成的抽象语法树(AST)来查找语法或词法错误, 格式文本, 针对用户键入文本对TODOS语法做只能提示(本文中我不会实现语法自动完成), 基本上, 语言服务暴露如下函数:
format(code: string): string
validate(code: string): Errors[]
autoComplete(code: string, currentPosition: Position): string[]
Add ANTLER, Generate Lexer and Parser From the Grammar
我将引入ANTLR库并增加一个根据TODOLang.g4
语法文件生Parser和Lexer的脚本, 首先引入两个必须的库:antlr4ts和antlr4ts-cli, antlr4 Typescript 目标生成的解析器对antlr4ts包有运行时依赖, 另一方面, 顾名思义antlr4ts-cli 就是CLI我们将使用它生成该语言的Parser和Lexer
npm add antlr4ts
npm add -D antlr4ts-cli
在根路径创建包含TodoLang
语法规则的文件TodoLangGrammar.g4
grammar TodoLangGrammar;
todoExpressions : (addExpression)* (completeExpression)*;
addExpression : ADD TODO STRING;
completeExpression : COMPLETE TODO STRING;
ADD : 'ADD';
TODO : 'TODO';
COMPLETE: 'COMPLETE';
STRING: '"' ~ ["]* '"';
EOL: [\r\n] + -> skip;
WS: [ \t] -> skip;
现在我们在package.json
文件里增加通过antlr-cli生成Parser和Lexer的脚本
"antlr4ts": "antlr4ts ./TodoLangGrammar.g4 -o ./src/ANTLR"
让我们执行一下antlr4ts脚本,就可以在./src/ANTLR
目录看到生成的解析器的typescript源码了
npm run antlr4ts
正如我们看到的那样, 这里有一个Lexer 和 Parser, 如果你查看Parser文件, 你会发现它导出 TodoLangGrammarParser
类, 该类有个构造函数constructor(input: TokenStream)
, 该构造函数将TodoLangGrammarLexer
为给定代码生成的TokenStream
作为参数, TodoLangGrammarLexer
有一个以代码作为入参的构造函数 constructor(input: CharStream)
Parser文件包含了public todoExpressions(): TodoExpressionsContext
方法,该方法会返回代码中定义的所有TodoExpressions
的上下文对象, 猜想一下TodoExpressions
在哪里可以追踪到,其实它是源于我们语法规则文件的第一行语法规则:
todoExpressions : (addExpression)* (completeExpression)*;
TodoExpressionsContext
是AST
的根基, 其中的每个节点都是另一个规则的另一个上下文, 它包含了终端和节点上下文,终端拥有最终令牌(ADD 令牌, TODO 令牌, todo 事项名称的令牌)
TodoExpressionsContext
包含了addExpressions
和completeExpressions
表达式列表, 来源于以下三条规则
todoExpressions : (addExpression)* (completeExpression)*;
addExpression : ADD TODO STRING;
completeExpression : COMPLETE TODO STRING;
另一方面, 每个上下文类都包含了终端节点, 它基本包含以下文本(代码段或者令牌, 例如:ADD, COMPLETE, 代表 TODO 的字符串), AST的复杂度取决于你编写的语法规则
让我们来看看TodoExpressionsContext, 它包含了ADD
, TODO
和STRING
终端节点, 对应的规则如:
addExpression : ADD TODO STRING;
STRING
终端节点保存了我们要加的Todo
文本内容, 先来解析一个简单的TodoLang
代码以来了解AST如何工作的,在./src/language-service
目录建一个包含以下内容的文件parser.ts
import { TodoLangGrammarParser, TodoExpressionsContext } from "../ANTLR/TodoLangGrammarParser";
import { TodoLangGrammarLexer } from "../ANTLR/TodoLangGrammarLexer";
import { ANTLRInputStream, CommonTokenStream } from "antlr4ts";
export default function parseAndGetASTRoot(code: string): TodoExpressionsContext {
const inputStream = new ANTLRInputStream(code);
const lexer = new TodoLangGrammarLexer(inputStream);
const tokenStream = new CommonTokenStream(lexer);
const parser = new TodoLangGrammarParser(tokenStream);
// Parse the input, where `compilationUnit` is whatever entry point you defined
return parser.todoExpressions();
}
parser.ts
文件导出了parseAndGetASTRoot(code)
方法, 它接受TodoLang
代码并且生成相应的AST, 解析以下TodoLang
代码:
parseAndGetASTRoot(`
ADD TODO "Create an editor"
COMPLETE TODO "Create an editor"
`)
Implementing Lexical and Syntax Validation
在本节中, 我将引导您逐步了解如何向编辑器添加语法验证, ANTLR开箱即用为我们生成词汇和语法错误, 我们只需要实现ANTLRErrorListner
类并将其提供给Lexer和Parser, 这样我们就可以在 ANTLR解析代码时收集错误
在./src/language-service
目录下创建TodoLangErrorListener.ts
文件, 文件导出实现ANTLRErrorListner
接口的TodoLangErrorListener
类
import { ANTLRErrorListener, RecognitionException, Recognizer } from "antlr4ts";
export interface ITodoLangError {
startLineNumber: number;
startColumn: number;
endLineNumber: number;
endColumn: number;
message: string;
code: string;
}
export default class TodoLangErrorListener implements ANTLRErrorListener<any>{
private errors: ITodoLangError[] = []
syntaxError(recognizer: Recognizer<any, any>, offendingSymbol: any, line: number, charPositionInLine: number, message: string, e: RecognitionException | undefined): void {
this.errors.push(
{
startLineNumber:line,
endLineNumber: line,
startColumn: charPositionInLine,
endColumn: charPositionInLine+1,//Let's suppose the length of the error is only 1 char for simplicity
message,
code: "1" // This the error code you can customize them as you want
}
)
}
getErrors(): ITodoLangError[] {
return this.errors;
}
}
每次 ANTLR 在代码解析期间遇到错误时, 它将调用此TodoLangErrorListener
, 以向其提供有关错误的信息, 该监听器会返回包含解析发生错误的代码位置极错误信息, 现在我们尝试把TodoLangErrorListener
绑定到parser.ts
的文件的Lexer和Parser里, eg:
import { TodoLangGrammarParser, TodoExpressionsContext } from "../ANTLR/TodoLangGrammarParser";
import { TodoLangGrammarLexer } from "../ANTLR/TodoLangGrammarLexer";
import { ANTLRInputStream, CommonTokenStream } from "antlr4ts";
import TodoLangErrorListener, { ITodoLangError } from "./TodoLangErrorListener";
function parse(code: string): {ast:TodoExpressionsContext, errors: ITodoLangError[]} {
const inputStream = new ANTLRInputStream(code);
const lexer = new TodoLangGrammarLexer(inputStream);
lexer.removeErrorListeners()
const todoLangErrorsListner = new TodoLangErrorListener();
lexer.addErrorListener(todoLangErrorsListner);
const tokenStream = new CommonTokenStream(lexer);
const parser = new TodoLangGrammarParser(tokenStream);
parser.removeErrorListeners();
parser.addErrorListener(todoLangErrorsListner);
const ast = parser.todoExpressions();
const errors: ITodoLangError[] = todoLangErrorsListner.getErrors();
return {ast, errors};
}
export function parseAndGetASTRoot(code: string): TodoExpressionsContext {
const {ast} = parse(code);
return ast;
}
export function parseAndGetSyntaxErrors(code: string): ITodoLangError[] {
const {errors} = parse(code);
return errors;
}
在./src/language-service
目录下创建LanguageService.ts
, 以下是它导出的内容
import { TodoExpressionsContext } from "../ANTLR/TodoLangGrammarParser";
import { parseAndGetASTRoot, parseAndGetSyntaxErrors } from "./Parser";
import { ITodoLangError } from "./TodoLangErrorListener";
export default class TodoLangLanguageService {
validate(code: string): ITodoLangError[] {
const syntaxErrors: ITodoLangError[] = parseAndGetSyntaxErrors(code);
//Later we will append semantic errors
return syntaxErrors;
}
}
不错, 我们实现了编辑器错误解析, 为此我将要创建上篇文章讨论过的web worker
, 并且添加worker
服务代理, 该代理将调用语言服务区完成编辑器的高级功能
Creating the web worker
首先, 我们调用 monaco.editor.createWebWorker 来使用内置的 ES6 Proxies 创建代理TodoLangWorker
, TodoLangWorker
将使用语言服务来执行编辑器功能,在web worker
中执行的那些方法将由monaco代理,因此在web worker
中调用方法仅是在主线程中调用被代理的方法。
在./src/todo-lang
文件夹下创建TodoLangWorker.ts
包含以下内容:
import * as monaco from "monaco-editor-core";
import IWorkerContext = monaco.worker.IWorkerContext;
import TodoLangLanguageService from "../language-service/LanguageService";
import { ITodoLangError } from "../language-service/TodoLangErrorListener";
export class TodoLangWorker {
private _ctx: IWorkerContext;
private languageService: TodoLangLanguageService;
constructor(ctx: IWorkerContext) {
this._ctx = ctx;
this.languageService = new TodoLangLanguageService();
}
doValidation(): Promise<ITodoLangError[]> {
const code = this.getTextDocument();
return Promise.resolve(this.languageService.validate(code));
}
private getTextDocument(): string {
const model = this._ctx.getMirrorModels()[0];
return model.getValue();
}
我们创建了language service
实例 并且添加了doValidation
方法, 进一步它会调用language service
的validate
方法, 还添加了getTextDocument
方法, 该方法用来获取编辑器的文本值, TodoLangWorker
类还可以扩展很多功能如果你想要支持多文件编辑等, _ctx: IWorkerContext
是编辑器的上下文对象, 它保存了文件的 model 信息
现在让我们在./src/todo-lang
目录下创建 web worker 文件todolang.worker.ts
import * as worker from 'monaco-editor-core/esm/vs/editor/editor.worker';
import { TodoLangWorker } from './todoLangWorker';
self.onmessage = () => {
worker.initialize((ctx) => {
return new TodoLangWorker(ctx)
});
};
我们使用内置的worker.initialize
初始化我们的 worker,并使用TodoLangWorker
进行必要的方法代理
那是一个web worker
, 因此我们必须让webpack
输出对应的worker
文件
// webpack.config.js
entry: {
app: './src/index.tsx',
"editor.worker": 'monaco-editor-core/esm/vs/editor/editor.worker.js',
"todoLangWorker": './src/todo-lang/todolang.worker.ts'
},
output: {
globalObject: 'self',
filename: (chunkData) => {
switch (chunkData.chunk.name) {
case 'editor.worker':
return 'editor.worker.js';
case 'todoLangWorker':
return "todoLangWorker.js"
default:
return 'bundle.[hash].js';
}
},
path: path.resolve(__dirname, 'dist')
}
我们命名worker
文件为todoLangWorker.js
文件, 现在我们在编辑器启动函数里面增加getWorkUrl
(window as any).MonacoEnvironment = {
getWorkerUrl: function (moduleId, label) {
if (label === languageID)
return "./todoLangWorker.js";
return './editor.worker.js';
}
}
这是 monaco 如何获取web worker
的 URL 的方法, 请注意, 如果worker
的 label 是TodoLang
的 ID, 我们将返回用于在 Webpack 中打包输出的同名worker,
如果现在构建项目, 则可能会发现有一个名为todoLangWorker.js
的文件(或者在 dev-tools 中, 您将在线程部分中找到两个worker
)
现在创建一个用来管理worker
创建和获取代理worker
客户端的 WorkerManager
import * as monaco from "monaco-editor-core";
import Uri = monaco.Uri;
import { TodoLangWorker } from './todoLangWorker';
import { languageID } from './config';
export class WorkerManager {
private worker: monaco.editor.MonacoWebWorker<TodoLangWorker>;
private workerClientProxy: Promise<TodoLangWorker>;
constructor() {
this.worker = null;
}
private getClientproxy(): Promise<TodoLangWorker> {
if (!this.workerClientProxy) {
this.worker = monaco.editor.createWebWorker<TodoLangWorker>({
moduleId: 'TodoLangWorker',
label: languageID,
createData: {
languageId: languageID,
}
});
this.workerClientProxy = <Promise<TodoLangWorker>><any>this.worker.getProxy();
}
return this.workerClientProxy;
}
async getLanguageServiceWorker(...resources: Uri[]): Promise<TodoLangWorker> {
const _client: TodoLangWorker = await this.getClientproxy();
await this.worker.withSyncedResources(resources)
return _client;
}
}
我们使用createWebWorker
创建monaco代理的web worker
, 其次我们获取返回了代理的客户端对象, 我们使用workerClientProxy
调用代理的一些方法, 让我们创建DiagnosticsAdapter
类, 该类用来连接 Monaco 标记 Api 和语言服务返回的 error,为了让解析的错误正确的标记在monaco上
import * as monaco from "monaco-editor-core";
import { WorkerAccessor } from "./setup";
import { languageID } from "./config";
import { ITodoLangError } from "../language-service/TodoLangErrorListener";
export default class DiagnosticsAdapter {
constructor(private worker: WorkerAccessor) {
const onModelAdd = (model: monaco.editor.IModel): void => {
let handle: any;
model.onDidChangeContent(() => {
// here we are Debouncing the user changes, so everytime a new change is done, we wait 500ms before validating
// otherwise if the user is still typing, we cancel the
clearTimeout(handle);
handle = setTimeout(() => this.validate(model.uri), 500);
});
this.validate(model.uri);
};
monaco.editor.onDidCreateModel(onModelAdd);
monaco.editor.getModels().forEach(onModelAdd);
}
private async validate(resource: monaco.Uri): Promise<void> {
const worker = await this.worker(resource)
const errorMarkers = await worker.doValidation();
const model = monaco.editor.getModel(resource);
monaco.editor.setModelMarkers(model, languageID, errorMarkers.map(toDiagnostics));
}
}
function toDiagnostics(error: ITodoLangError): monaco.editor.IMarkerData {
return {
...error,
severity: monaco.MarkerSeverity.Error,
};
}
onDidChangeContent
监听器监听model
信息, 如果model
信息变更, 我们将每隔 500ms 调用webworker
去验证代码并且增加错误标记;setModelMarkers
通知monaco增加错误标记, 为了使得编辑器语法验证功能完成,请确保在setup
函数中调用它们,并注意我们正在使用WorkerManager来获取代理worker
monaco.languages.onLanguage(languageID, () => {
monaco.languages.setMonarchTokensProvider(languageID, monarchLanguage);
monaco.languages.setLanguageConfiguration(languageID, richLanguageConfiguration);
const client = new WorkerManager();
const worker: WorkerAccessor = (...uris: monaco.Uri[]): Promise<TodoLangWorker> => {
return client.getLanguageServiceWorker(...uris);
};
//Call the errors provider
new DiagnosticsAdapter(worker);
});
}
export type WorkerAccessor = (...uris: monaco.Uri[]) => Promise<TodoLangWorker>;
现在一切准备就绪, 运行项目并且输入错误的TodoLang
代码, 你会发现错误被标记在代码下面
Implementing Semantic Validation
现在往编辑器增加语义校验, 记得我在上篇文章提到的两个语义规则
- 如果使用 ADD TODO 说明定义了 TODO ,我们可以重新添加它。
- 在 TODO 中应用中,COMPLETE 指令不应在尚未使用声明 ADD TODO 前
要检查是否定义了 TODO,我们要做的就是遍历 AST 以获取每个 ADD 表达式并将其推入definedTodos
.然后我们在definedTodos
中检查 TODO 的存在. 如果存在, 则是语义错误, 因此请从 ADD 表达式的上下文中获取错误的位置, 然后将错误推送到数组中, 第二条规则也是如此
function checkSemanticRules(ast: TodoExpressionsContext): ITodoLangError[] {
const errors: ITodoLangError[] = [];
const definedTodos: string[] = [];
ast.children.forEach(node => {
if (node instanceof AddExpressionContext) {
// if a Add expression : ADD TODO "STRING"
const todo = node.STRING().text;
// If a TODO is defined using ADD TODO instruction, we can re-add it.
if (definedTodos.some(todo_ => todo_ === todo)) {
// node has everything to know the position of this expression is in the code
errors.push({
code: "2",
endColumn: node.stop.charPositionInLine + node.stop.stopIndex - node.stop.stopIndex,
endLineNumber: node.stop.line,
message: `Todo ${todo} already defined`,
startColumn: node.stop.charPositionInLine,
startLineNumber: node.stop.line
});
} else {
definedTodos.push(todo);
}
}else if(node instanceof CompleteExpressionContext) {
const todoToComplete = node.STRING().text;
if(definedTodos.every(todo_ => todo_ !== todoToComplete)){
// if the the todo is not yet defined, here we are only checking the predefined todo until this expression
// which means the order is important
errors.push({
code: "2",
endColumn: node.stop.charPositionInLine + node.stop.stopIndex - node.stop.stopIndex,
endLineNumber: node.stop.line,
message: `Todo ${todoToComplete} is not defined`,
startColumn: node.stop.charPositionInLine,
startLineNumber: node.stop.line
});
}
}
})
return errors;
}
现在调用checkSemanticRules
函数, 在language service
的validate
方法中将语义和语法错误合并返回, 现在我们编辑器已经支持语义校验
Implementing Auto-Formatting
对于编辑器的自动格式化功能, 您需要通过调用Monaco API registerDocumentFormattingEditProvider
提供并注册 Monaco 的格式化提供程序. 查看 monaco-editor 文档以获取更多详细信息. 调用并遍历 AST 将为你展示美化后的代码
// languageService.ts
format(code: string): string{
// if the code contains errors, no need to format, because this way of formating the code, will remove some of the code
// to make things simple, we only allow formatting a valide code
if(this.validate(code).length > 0)
return code;
let formattedCode = "";
const ast: TodoExpressionsContext = parseAndGetASTRoot(code);
ast.children.forEach(node => {
if (node instanceof AddExpressionContext) {
// if a Add expression : ADD TODO "STRING"
const todo = node.STRING().text;
formattedCode += `ADD TODO ${todo}\n`;
}else if(node instanceof CompleteExpressionContext) {
// If a Complete expression: COMPLETE TODO "STRING"
const todoToComplete = node.STRING().text;
formattedCode += `COMPLETE TODO ${todoToComplete}\n`;
}
});
return formattedCode;
}
在todoLangWorker
中添加format
方法, 该format
方法会使用language service
的format
方法
现在创建TodoLangFomattingProvider
类去实现``DocumentFormattingEditProvider`接口
import * as monaco from "monaco-editor-core";
import { WorkerAccessor } from "./setup";
export default class TodoLangFormattingProvider implements monaco.languages.DocumentFormattingEditProvider {
constructor(private worker: WorkerAccessor) {
}
provideDocumentFormattingEdits(model: monaco.editor.ITextModel, options: monaco.languages.FormattingOptions, token: monaco.CancellationToken): monaco.languages.ProviderResult<monaco.languages.TextEdit[]> {
return this.format(model.uri, model.getValue());
}
private async format(resource: monaco.Uri, code: string): Promise<monaco.languages.TextEdit[]> {
// get the worker proxy
const worker = await this.worker(resource)
// call the validate methode proxy from the langaueg service and get errors
const formattedCode = await worker.format(code);
const endLineNumber = code.split("\n").length + 1;
const endColumn = code.split("\n").map(line => line.length).sort((a, b) => a - b)[0] + 1;
console.log({ endColumn, endLineNumber, formattedCode, code })
return [
{
text: formattedCode,
range: {
endColumn,
endLineNumber,
startColumn: 0,
startLineNumber: 0
}
}
]
}
}
TodoLangFormattingProvider
通过调用worker
提供的format
方法, 并借助editor.getValue()
作为入参, 并且向monaco提供各式后的代码及想要替换的代码范围, 现在进入setup
函数并且使用Monaco registerDocumentFormattingEditProvider
API注册formatting provider
, 重跑应用, 你能看到编辑器已支持自动格式化了
monaco.languages.registerDocumentFormattingEditProvider(languageID, new TodoLangFormattingProvider(worker));
尝试点击Format document 或Shift + Alt + F, 你能看到如图的效果:
Implementing Auto-Completion
若要使自动完成支持定义的 TODO, 您要做的就是从 AST 获取所有定义的 TODO, 并提供completion provider
通过在setup
中调用registerCompletionItemProvider
。completion provider
为您提供代码和光标的当前位置,因此您可以检查用户正在键入的上下文,如果他们在完整的表达式中键入 TODO,则可以建议预定义的 TO DOs。 请记住,默认情况下,Monaco-editor 支持对代码中的预定义标记进行自动补全,您可能需要禁用该功能并实现自己的标记以使其更加智能化和上下文化
译者信息
使用 TypeScript,React,ANTLR 和 Monaco Editor 创建一个自定义 Web 编辑器(二)的更多相关文章
- 创建一个maven web project
几经周折总算是找到了和高杨学长一样的web project的方法.感谢学长的一语点醒.我之前以为,既是maven又是web project的项目得要是通过dynamic web project转换到 ...
- 如何创建一个自定义jQuery插件
简介 jQuery 库是专为加快 JavaScript 开发速度而设计的.通过简化编写 JavaScript 的方式,减少代码量.使用 jQuery 库时,您可能会发现您经常为一些常用函数重写相同的代 ...
- Java web 开发填坑记 2 -如何正确的创建一个Java Web 项目
转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/72566261 本文出自[赵彦军的博客] Java web 开发填坑记 1-如何正确 ...
- Eclipse创建一个Maven Web项目
在这篇文章中,我们将演示如何在Eclipse IDE中使用maven创建一个动态Web项目. 使用的工具和技术 - Eclipse Jee Oxygen Maven 3.3.3 JavaSE 1.8 ...
- springmvc在处理请求过程中出现异常信息交由异常处理器进行处理,自定义异常处理器可以实现一个系统的异常处理逻辑。为了区别不同的异常通常根据异常类型自定义异常类,这里我们创建一个自定义系统异常,如果controller、service、dao抛出此类异常说明是系统预期处理的异常信息。
springmvc在处理请求过程中出现异常信息交由异常处理器进行处理,自定义异常处理器可以实现一个系统的异常处理逻辑. 1.1 异常处理思路 系统中异常包括两类:预期异常和运行时异常RuntimeEx ...
- struts2官方 中文教程 系列一:创建一个struts2 web Application
先贴了本帖地址,以免被爬 http://www.cnblogs.com/linghaoxinpian/p/6898779.html 本教程将会通过安装struts2框架来创建一个简单的应用程序.虽然 ...
- 创建一个动态Web项目:
开始你的Eclipse,然后进入“文件”>“新建”>“动态Web项目,然后输入项目名称为HelloWorldStruts2和设置其他的选项,在下面的屏幕: 选择在屏幕上的所有默认选项,最后 ...
- 带你使用Visual Studio 2019创建一个MVC Web应用
工欲善其事必先利其器,我们既然有Visual Studio2019这样的IDE为什么不用?学.Net Core而不用Visual Studio进行开发可谓是多么另类呀!既然你已经安装了VS2019的话 ...
- maven 学习---用Eclipse创建一个Maven Web项目
下面是使用 Eclipse 来创建一个Maven Web项目的说明.这是相当简单的. 现在让我们开始吧! 1: 启动 Eclipse, 点击 File->New->Other 2: 在弹出 ...
随机推荐
- zabbix学习笔记:zabbix监控之短信报警
zabbix学习笔记:zabbix监控之短信报警 zabbix的报警方式有多种,除了常见的邮件报警外,特殊情况下还需要设置短信报警和微信报警等额外方式.本篇文章向大家介绍短信报警. 短信报警设置 短信 ...
- Jenkins远程代码执行漏洞
于一个月前,进行服务器巡检时,发现服务器存在不明进程,并且以Jenkins用户身份来运行.当时进行了处理并修复了漏洞.在此补上修复过程 第一反应是Jenkins存在漏洞,于是Google Jenkin ...
- Linux_yum命令详解
一.yum命令语法 yum [options] [command] [package ...] 二.yum命令常用的选项: yum options -y //自动回答为"yes" ...
- gpgj-19.高级课总结
19.高级课总结 1.高级课最终站 如何对公司估值 找到公司未来的真实盈利*回本年限=估值 如何计算公司未来的盈利 ROIC 资本回报率 零售公司 每平米净现金流 经营面积 高速公路 每 ...
- Linux中级之负载均衡(lvs,nginx,haproxy)、中间件
一.负载均衡的概念 1.系统的扩展方式: scale up:向上扩展 scale out:向外扩展 2.集群类型: LB(Load Balancing).HA(high availability) ...
- STM32定时器配置
void TIM1_Int_Init(u16 arr,u16 psc) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; NVIC_InitTypeDe ...
- crontab 的简要介绍
1.概述: crontab 用于周期性被执行的指令,该指令从标准设备输入指令,并将指令存放在crontab文件中,供之后读取和执行. 与crontab相关的文件一共有三个: /etc/crontab ...
- 源码篇:Flutter Provider的另一面(万字图文+插件)
前言 阅读此文的彦祖,亦菲们,附送一枚Provider模板代码生成插件! 我为啥要写这个插件呢? 此事说来话短,我这不准备写解析Provider源码的文章,肯定要写这框架的使用样例啊,然后再哔哔源码呀 ...
- 如何挑选深度学习 GPU?
如何挑选深度学习 GPU? 深度学习是一个对计算有着大量需求的领域,从一定程度上来说,GPU的选择将从根本上决定深度学习的体验.因此,选择购买合适的GPU是一项非常重要的决策.那么2020年,如何选择 ...
- deeplearning搜索空间
deeplearning搜索空间 搜索空间是神经网络搜索中的一个概念.搜索空间是一系列模型结构的汇集, SANAS主要是利用模拟退火的思想在搜索空间中搜索到一个比较小的模型结构或者一个精度比较高的模型 ...