国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

數(shù)棧產(chǎn)品中的代碼編譯器

這篇具有很好參考價(jià)值的文章主要介紹了數(shù)棧產(chǎn)品中的代碼編譯器。希望對大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

我們是袋鼠云數(shù)棧 UED 團(tuán)隊(duì),致力于打造優(yōu)秀的一站式數(shù)據(jù)中臺產(chǎn)品。我們始終保持工匠精神,探索前端道路,為社區(qū)積累并傳播經(jīng)驗(yàn)價(jià)值。

本文作者:奇銘

前言

目前數(shù)棧的多個(gè)產(chǎn)品中都支持在線編輯 SQL 來生成對應(yīng)的任務(wù)。比如離線開發(fā)產(chǎn)品和實(shí)時(shí)開發(fā)產(chǎn)品。在使用 MonacoEditor 為編輯器的基礎(chǔ)上,我們還支持了如下幾個(gè)重要功能:

  • 多種 SQL 的語法高亮
  • 多種 SQL 的報(bào)錯(cuò)提示(錯(cuò)誤位置飄紅)
  • 多種 SQL 的自動(dòng)補(bǔ)全(智能提示)

本文旨在講解上述功能的實(shí)現(xiàn)思路,對于技術(shù)細(xì)節(jié),由于篇幅原因不會闡述的太詳細(xì)。

Monaco Languages

Monaco Editor 內(nèi)置的 languages

Monaco Editor 內(nèi)置了相當(dāng)多的 languages,比如 javaScriptCSS、Shell 等。
Monaco Editor 依賴包的 ESM 入口文件為 ./esm/vs/editor/editor.main.ts
數(shù)棧產(chǎn)品中的代碼編譯器
而在這個(gè)文件中,Monaco Editor 引入了所有內(nèi)置的 Languages。
數(shù)棧產(chǎn)品中的代碼編譯器
這里 languages 文件可以分為兩類,一類是../language文件夾下的,支持自動(dòng)補(bǔ)全和飄紅提示功能;另一類則是../basic-languages文件夾下的,不支持自動(dòng)補(bǔ)全功能和飄紅提示功能。

使用內(nèi)置的 Language 功能

以使用 typescript 為例

import { editor } from 'monaco-editor';

const container = document.getElementById('container');

editor.create(container, {
    language: 'typescript'
})

此時(shí)我們會發(fā)現(xiàn),我們的編輯器已經(jīng)有語法高亮的功能了,但是瀏覽器控制臺會拋異常,另外也沒有自動(dòng)補(bǔ)全功能和飄紅提示功能,
數(shù)棧產(chǎn)品中的代碼編譯器
這其實(shí)是因?yàn)?,Monaco Editor 無法加載到 language 對應(yīng)的 worker,對應(yīng)的解決辦法看這里: Monaco integrate-esm。
這里我們使用 Using plain webpack的方式,首先將對應(yīng)的 worker 文件設(shè)置為 webpack entry

module.exports = {
    entry: {
        index: path.resolve( __dirname, './src/index.ts'),
        'editor.worker': 'monaco-editor/esm/vs/editor/editor.worker.js',
        'ts.worker': 'monaco-editor/esm/vs/language/typescript/ts.worker.js'
    },
}

另外還需要設(shè)置 Monaco Editor 的全局環(huán)境變量,這主要是為了告訴 Monaco Editor 對應(yīng)的 worker 文件的路徑

import { editor } from 'monaco-editor';

(window as any).MonacoEnvironment = {
	getWorkerUrl: function (_moduleId, label) {
		switch (label) {
			case 'flink': {
				return './flink.worker.js';
			}
			case 'typescript': {
				return './ts.worker.js'
			}
			default: {
				return './editor.worker.js';
			}
		}
	}
};

const container = document.getElementById('container');

editor.create(container, {
    language: 'typescript'
})

這樣一個(gè)具有語法高亮、自動(dòng)補(bǔ)全、飄紅提示 功能的 typescript 編輯器就設(shè)置好了
數(shù)棧產(chǎn)品中的代碼編譯器

小結(jié)分析

首先上文中提到了當(dāng)我們直接從 Monaco Editor 的入口文件中導(dǎo)入時(shí),會自動(dòng)的引入所有內(nèi)置的 Languages,但是實(shí)際上這其中絕大都是我們不需要的,而由于其導(dǎo)入方式,很顯然我們不需要的 languages 也無法被 treeShaking。要解決這個(gè)問題我們可以選擇從 monaco-editor/esm/vs/editor/editor.api 文件中導(dǎo)入Monaco Editor 核心 API,然后通過 monaco-editor-webpack-plugin 來按需導(dǎo)入所需要的功能。另外這個(gè)插件也可以自動(dòng)處理Monaco Editor 內(nèi)置的 worker 文件的打包問題,以及自動(dòng)注入 MonacoEnvironment全局環(huán)境變量。

自定義 Language

注冊Language

Monaco Editor 提供了 monaco.languages.register方法,用來自定義 language

/**
 * Register information about a new language.
 */
export function register(language: ILanguageExtensionPoint): void;

export interface ILanguageExtensionPoint {
  id: string;
  extensions?: string[];
  filenames?: string[];
  filenamePatterns?: string[];
  firstLine?: string;
  aliases?: string[];
  mimetypes?: string[];
  configuration?: Uri;
}

第一步,我們需要注冊一個(gè) language, 配置項(xiàng)中 id 對應(yīng)的就是語言名稱(其他配置項(xiàng)可以暫時(shí)不填),這里自定義的 language 名為 myLang

import { editor, languages } from 'monaco-editor';

languages.register({
    id: "myLang"
});

const container = document.getElementById('container');

editor.create(container, {
    language: 'myLang'
})

此時(shí)可以發(fā)現(xiàn),頁面上的編輯器沒有任何其他附加功能,就是普通的文本編輯器。

設(shè)置 Language

通過 monaco.languages.setLanguageConfiguration,可以對 language 進(jìn)行配置

/**
 * Set the editing configuration for a language.
 */
export function setLanguageConfiguration(
  languageId: string,
  configuration: LanguageConfiguration
): IDisposable;

/**
 * The language configuration interface defines the contract between extensions and
 * various editor features, like automatic bracket insertion, automatic indentation etc.
 */
export interface LanguageConfiguration {
    comments?: CommentRule;
    brackets?: CharacterPair[];
    wordPattern?: RegExp;
    indentationRules?: IndentationRule;
    onEnterRules?: OnEnterRule[];
    autoClosingPairs?: IAutoClosingPairConditional[];
    surroundingPairs?: IAutoClosingPair[];
    colorizedBracketPairs?: CharacterPair[];
    autoCloseBefore?: string;
    folding?: FoldingRules;
}

這些配置會影響 Monaco Editor 的一些默認(rèn)行為,比如設(shè)置 autoClosingPairs中有一項(xiàng)為一對圓括號,那么當(dāng)輸入左圓括號后,會自動(dòng)補(bǔ)全右圓括號。

import { languages } from "monaco-editor";
const conf: languages.LanguageConfiguration = {
  comments: {
    lineComment: "--",
    blockComment: ["/*", "*/"],
  },
  brackets: [
    ["(", ")"],
  ],
  autoClosingPairs: [
    { open: "(", close: ")" },
    { open: '"', close: '"' },
    { open: "'", close: "'" },
  ],
  surroundingPairs: [
    { open: "(", close: ")" },
    { open: '"', close: '"' },
    { open: "'", close: "'" },
  ],
};

languages.setLanguageConfiguration('myLang', conf)

高亮功能

Monarch

Moanco Editor 內(nèi)置了 Monarch,用于實(shí)現(xiàn)語法高亮功能,它本質(zhì)上是一個(gè)有限狀態(tài)機(jī),我們可以通過JSON的形式來配置其狀態(tài)流轉(zhuǎn)邏輯,并通過monaco.languages.setMonarchTokensProvider API 應(yīng)用該配置。關(guān)于Monarch 的具體用法可以看一下這篇文章 以及 Monarch Document。
配置中最重要的是 tokenizer屬性,意思是分詞器,分詞器會自動(dòng)對編輯器內(nèi)部的文本進(jìn)行分詞處理,每個(gè)分詞器都有一個(gè) root state,在 root state 中可以有多條規(guī)則,規(guī)則內(nèi)部可以引用其他 state。

下面是一個(gè)簡單的配置示例

import { languages } from "monaco-editor";
export const language: languages.IMonarchLanguage = {
	ignoreCase: true,
	tokenizer: {
		root: [
			{ include: '@comments' }, // 引用下面的 comments 規(guī)則
			{ include: '@whitespace' }, // 引用下面的 whiteSpace 規(guī)則
			{ include: '@strings' },// 引用下面的 strings 規(guī)則
		],
		whitespace: [[/\s+/, 'white']],
		comments: [
			[/--+.*/, 'comment'],
			[/\/\*/, { token: 'comment.quote', next: '@comment' }]
		],
		comment: [
			[/[^*/]+/, 'comment'],
			[/\*\//, { token: 'comment.quote', next: '@pop' }],
			[/./, 'comment']
		],
		strings: [
			[/'/, { token: 'string', next: '@string' }]
		],
		string: [
			[/[^']+/, 'string'],
			[/''/, 'string'],
			[/'/, { token: 'string', next: '@pop' }]
		],
	}
};

languages.setMonarchTokensProvider("myLang", language);

上面的配置中 root 下面有三條規(guī)則分別匹配 注釋(comments)、字符串(strings) 以及空白字符(whiteSpace), 每條規(guī)則可以大體分為兩部分:

  • 匹配方式,比如說正則
  • 對應(yīng)的 token 類型(任意字符串)

比如上述配置中 tokenizer.comments 規(guī)則

  comments: [
    [/--+.*/, 'comment'], // 左邊是正則表達(dá)式用來匹配文本,右邊是該規(guī)則對應(yīng)的 token 名稱
    [/\/\*/, { token: 'comment.quote', next: '@comment' }] // 左邊是正則表達(dá)式用來匹配文本,右邊顯示聲明對應(yīng)的 token 名稱
  ],

配置了如上 Monarch 之后,在編輯器內(nèi)部輸入注釋或者字符串,那么Monaco editor 就會根據(jù)輸入的內(nèi)容進(jìn)行分詞處理
數(shù)棧產(chǎn)品中的代碼編譯器

可以看到目前字符串和注釋已經(jīng)被高亮了。這里有一個(gè)新的問題,不同類型的分詞的顏色是怎么設(shè)置的?

Monaco Theme

從上圖中右側(cè)的 Elements 面板中可以看到,不同類型的分詞,對應(yīng)的標(biāo)簽的 className 不同,它們是由 Monarch 配置中的 token 映射而來的。MonacoEditor 內(nèi)置了一些 Theme,默認(rèn)的 Theme 是 vs,而默認(rèn)的 theme 中已經(jīng)設(shè)置了上述 Monarch 中的 token 對應(yīng)的顏色,所以我們應(yīng)用上述配置后,對應(yīng)的分詞直接就有了高亮顏色。
我們可以通過 monaco.editor.defineTheme 來定義一種新的 theme,如下例所示:

editor.defineTheme('myTheme', {
    base: 'vs',
    inherit: true,
    rules: [
        { token: 'comment', foreground: 'ff4400' },
        { token: 'string', foreground: '0000ff' }
    ],
    colors: {
    },
});

// xxxx

editor.create(container, {
  language: "myLang",
  theme: "myTheme"
});

這里將注釋設(shè)置為紅色,字符串設(shè)置為藍(lán)色,顯示效果如下圖所示
數(shù)棧產(chǎn)品中的代碼編譯器

飄紅提示

飄紅提示的功能就是在代碼錯(cuò)誤的位置打上標(biāo)記(一般是紅色波浪線),可以通過 monaco.editor.setModelMarkers API 來實(shí)現(xiàn)。比如我們想為 第1行的第1個(gè)字符到第2行的第2個(gè)字符 之間打上錯(cuò)誤標(biāo)記:

const editorIns = editor.create(container, {
  language: "myLang",
  theme: "myTheme",
  value: 
`hello
world`
});

const model = editorIns.getModel();

editor.setModelMarkers(model, 'myLang', [
	{
		startLineNumber: 1,
		startColumn: 1,
		endLineNumber: 2,
		endColumn: 2,
		message: "語法錯(cuò)誤",
		severity: MarkerSeverity.Error
	}
])

severity 是標(biāo)記類型,message 是提示信息,效果如下所示。
數(shù)棧產(chǎn)品中的代碼編譯器
到此為止,實(shí)現(xiàn)了飄紅的功能,但是沒有實(shí)現(xiàn)在語法錯(cuò)誤處飄紅的功能,這需要額外的語法解析器支持,會在下文中講到。

自動(dòng)補(bǔ)全功能

Monaco Editor 提供了 monaco.languages.registerCompletionItemProvider API 來實(shí)現(xiàn)自動(dòng)補(bǔ)全功能

import { editor, languages, MarkerSeverity, Position, CancellationToken, Range  } from "monaco-editor";

languages.registerCompletionItemProvider('myLang', {
	triggerCharacters: ['.', '*'],
	provideCompletionItems(
		model: editor.IReadOnlyModel,
		position: Position,
		context: languages.CompletionContext,
		token: CancellationToken
	){
		const wordInfo = model.getWordUntilPosition(position);
        const wordRange = new Range(
            position.lineNumber,
            wordInfo.startColumn,
            position.lineNumber,
            wordInfo.endColumn
        );
    		return new Promise((resolve) => {
    			resolve({
    				suggestions: [
    					{
    						label: "SELECT",
    						kind: languages.CompletionItemKind.Keyword,
    						insertText: "SELECT",
    						range: wordRange,
    						detail: '關(guān)鍵字',
    					},
    					{
    						label: "SET",
    						kind: languages.CompletionItemKind.Keyword,
    						insertText: "SET",
    						range: wordRange,
    						detail: '關(guān)鍵字',
    					},
    					{
    						label: "SHOW",
    						kind: languages.CompletionItemKind.Keyword,
    						insertText: "SHOW",
    						range: wordRange,
    						detail: '關(guān)鍵字',
    					},
    				]
    			})
    		})
	}
})

registerCompletionItemProvider 接受兩個(gè)參數(shù),第一個(gè)參數(shù)是 languageId 也就是 language 名稱,
第二個(gè)參數(shù)是一個(gè) CompletionItemProvider,CompletionItemProvidertriggerCharacters用來配置觸發(fā)自動(dòng)補(bǔ)全的字符有哪些,而 provideCompletionItems則是一個(gè)函數(shù),它接收 Monaco Editor 提供的當(dāng)前的上下文信息,返回自動(dòng)補(bǔ)全項(xiàng)列表。如上例中返回了三個(gè)自動(dòng)補(bǔ)全項(xiàng),那么當(dāng)我們在編輯器中輸入 S時(shí),就會出現(xiàn)配置的自動(dòng)補(bǔ)全項(xiàng)候選菜單。
數(shù)棧產(chǎn)品中的代碼編譯器
通過這個(gè) API 我們可以實(shí)現(xiàn)一種語言的關(guān)鍵字自動(dòng)補(bǔ)全,只需要在CompletionItemProvider中返回該語言所有的關(guān)鍵字對應(yīng)的自動(dòng)補(bǔ)全項(xiàng)即可。
但是registerCompletionItemProvider目前做不到根據(jù)語義進(jìn)行自動(dòng)補(bǔ)全。
比如用戶寫一段 flinkSQL,當(dāng)用戶輸入完 CREATE 關(guān)鍵字并按下空格后,應(yīng)該出現(xiàn)的自動(dòng)補(bǔ)全項(xiàng)應(yīng)該是只有TABLE、CATALOG、DATABASEFUNCTION、 VIEW。
再比如當(dāng)用戶輸入 SELECT * FROM 時(shí),后面應(yīng)該提示表名而不是其他無關(guān)的關(guān)鍵字。與上文中的飄紅提示一樣,這些語義信息需要單獨(dú)的語法解析器來分析。

小結(jié)分析

到此為止,在**自定義 language **這一節(jié)中,我們已經(jīng)了解了,在 Monaco Editor 中如何實(shí)現(xiàn)自定義語言的 語法高亮、錯(cuò)誤處飄紅提示、自動(dòng)補(bǔ)全。
在數(shù)棧產(chǎn)品中,本節(jié)講到的功能都通過引入 monaco-sql-languages 依賴來實(shí)現(xiàn),這是我們數(shù)棧 UED 團(tuán)隊(duì)自研的開源項(xiàng)目,目前已經(jīng)支持多種 SQL Languages。
由于目前為止沒有實(shí)現(xiàn)自定義 language 的語義分析功能,導(dǎo)致目前實(shí)現(xiàn)的編輯器不夠智能。 另外,對于第一節(jié)中提到的 web worker ,在第二節(jié)中也沒有有提到,實(shí)際上 Monaco Editor 自帶的 web worker,也都是為了實(shí)現(xiàn) language 的語義分析功能,下一節(jié)將闡述這一部分內(nèi)容。

SQL Parser

要實(shí)現(xiàn)語義分析功能,很顯然我們需要一個(gè)語法解析器。除了基本的語法解析的基礎(chǔ)功能以外,我們還需要

  • 語法錯(cuò)誤收集,收集編輯器中文本的語法錯(cuò)誤信息,用于錯(cuò)誤飄紅提示功能。
  • 推斷文本中指定位置的候選項(xiàng)列表,對于編輯器來說,指定位置一般就是光標(biāo)所在位置。候選項(xiàng)是指在光標(biāo)所在的位置應(yīng)該要寫什么。比如 SQL 中 SELECT 關(guān)鍵字后面可以跟字段或者函數(shù),那么我們所要實(shí)現(xiàn)的 sql parser 就應(yīng)該提示出在 SELECT 關(guān)鍵字后面的候選項(xiàng)應(yīng)該是字段或者函數(shù)。

實(shí)現(xiàn)基礎(chǔ)的 SQL Parser

Antlr4 語法文件

我們使用 Antlr4 來實(shí)現(xiàn)一個(gè)基本的 SQL Parser。Antlr4 是一個(gè)強(qiáng)大的解析器生成器,它能根據(jù)用戶自定義的語法文件來生成對應(yīng)的解析器。Antlr4 的語法文件為 .g4文件,內(nèi)部可以包含多條規(guī)則,規(guī)則可以分為詞法規(guī)則和語法規(guī)則,詞法規(guī)則用于生成詞法分析器,語法規(guī)則用于生成語法解析器。
例,我們現(xiàn)在寫一份語法規(guī)則,匹配最簡單的 SELECT 語句(不包括子查詢、別名等規(guī)則),比如

SELECT * FROM table1;  -- eg1

SELECT table2.name, age FROM schema2.table2; -- eg2

那么在antlr4中這份語法文件應(yīng)該這樣寫:

grammar SelectStatement;

/** 語法規(guī)則 begin */
program: selectStatement? EOF;

// 聲明 語句的匹配規(guī)則
selectStatement: KW_SELECT columnGroup KW_FROM tablePath SEMICOLON?;

// 聲明 語句中字段部分的匹配規(guī)則,字段部分可能為 col1, col2 的形式
columnGroup: columnPath (COMMA columnPath)*;

// 聲明 字段名匹配規(guī)則,字段名有可能為 db.table.col 或者 * 的形式
columnPath: dot_id | OP_STAR; 

// 聲明 表名匹配規(guī)則,表名有可能為 db.table 的形式
tablePath: dot_id; 

// 匹配 id.id 形式的標(biāo)識符號
dot_id: IDENTIFIER_LITERAL (DOT IDENTIFIER_LITERAL)*; 
/** 語法規(guī)則 end */ 


/** 詞法規(guī)則 begin */
KW_SELECT:          'SELECT'; // 匹配 SELECT 關(guān)鍵字
KW_FROM:            'FROM'; // 匹配 FROM 關(guān)鍵字
OP_STAR:            '*'; // 匹配 * 
DOT:                '.'; // 匹配 .
COMMA:              ','; // 匹配 ,
SEMICOLON:          ';'; // 匹配 ;
IDENTIFIER_LITERAL: [A-Z_a-z][A-Z_0-9a-z]*; // 匹配標(biāo)識符

WS:                 [ \t\n\r]+ -> skip ; // 忽略空格換行等空白字符
/** 詞法規(guī)則 end */

語法規(guī)則的編寫格式類似于 EBNF。
然后運(yùn)行 antlr4 命令,根據(jù)所寫的語法文件生成對應(yīng)的解析器??梢灾苯邮褂霉俜轿臋n中提供的方式 antlr4 typescript-target doc ,或者直接使用社區(qū)提供的 antlr4ts 包,這里以使用 antlr4ts 為例。
生成的文件結(jié)果如下所示:
數(shù)棧產(chǎn)品中的代碼編譯器

使用 Antlr4 生成的 Parser

在使用Antlr4 的生成的 Parser 之前我們需要安裝,Antlr4 的運(yùn)行時(shí)包。你可以將 Antlr4 的運(yùn)行時(shí)包通過語法文件生成的parser文件之間的關(guān)系,類比為 react 和 react-dom之間的關(guān)系。這里以使用 antlr4ts 為運(yùn)行時(shí)

import { CommonTokenStream, CharStreams } from 'antlr4ts';
import { SelectStatementLexer } from '../lib/selectStatement/SelectStatementLexer';
import { SelectStatementParser } from '../lib/selectStatement/SelectStatementParser';

class SelectParser {
  private createLexer(input: string) {
    const inputStream = CharStreams.fromString(input);
    const lexer = new SelectStatementLexer(inputStream);
    return lexer
  }

  private createParser (input: string) {
    const lexer = this.createLexer(input);
    const tokens = new CommonTokenStream(lexer);
    const parser = new SelectStatementParser(tokens);
    return parser
  }

  parse (sql: string) {
    const parser = this.createParser(sql)
    const parseTree = parser.selectStatement();
    return parseTree;
  }
}
// 試一下效果
const selectParser = new SelectParser();
const parseTree = selectParser.parse('SELECT * FROM table1');

獲取文本中的錯(cuò)誤信息

當(dāng)解析一個(gè)含有錯(cuò)誤的文本時(shí),Antlr4 會輸出錯(cuò)誤信息,例如輸入

selectParser.parse('SELECT id FRO');

控制臺打印
數(shù)棧產(chǎn)品中的代碼編譯器
可以看到錯(cuò)誤信息中包含了文本中的錯(cuò)誤所處的位置,我們可以通過使用 Antlr4 ParserErrorListener 來獲取錯(cuò)誤信息。

聲明一個(gè) ParserErrorListener

import { ParserErrorListener } from 'antlr4ts';

export class SelectErrorListener implements ParserErrorListener {
    private _parserErrorSet: Set<any> = new Set();

    syntaxError(_rec,_ofSym, line, charPosInLine,msg) {
        let endCol = charPosInLine + 1;
        this._parserErrorSet.add({
            startLine: line,
            endLine: line,
            startCol: charPosInLine,
            endCol: endCol,
            message: msg,
        })
    }

    clear () {
        this._parserErrorSet.clear();
    }

    get parserErrors () {
        return Array.from(this._parserErrorSet) 
    }
}

使用 ParserErrorListener 收集錯(cuò)誤信息

import { CommonTokenStream, CharStreams } from 'antlr4ts';
import { SelectStatementLexer } from '../lib/selectStatement/SelectStatementLexer';
import { SelectStatementParser } from '../lib/selectStatement/SelectStatementParser';


class SelectParser {
    private _errorListener = new SelectErrorListener();

    createLexer(input: string) {
        const inputStream = CharStreams.fromString(input);
        const lexer = new SelectStatementLexer(inputStream);
        this._errorListener.clear();
        lexer.removeErrorListeners(); // 移除 Antlr4 內(nèi)置的 ErrorListener
        lexer.addErrorListener(this._errorListener)
        return lexer
    }

    createParser (input: string) {
        const lexer = this.createLexer(input);
        const tokens = new CommonTokenStream(lexer);
        const parser = new SelectStatementParser(tokens);
        parser.removeErrorListeners(); // 移除 Antlr4 內(nèi)置的 ErrorListener
        parser.addErrorListener(this._errorListener);
        return parser
    }

    parse (sql: string) {
        const parser = this.createParser(sql)
        const parseTree = parser.selectStatement();
        console.log(this._errorListener.parserErrors);
        return {
          parseTree,
          errors: this._errorListener.parserErrors,
        };
    }
}
// 試一下效果
const selectParser = new SelectParser();
const { errors } = selectParser.parse('SELECT id FRO');
console.log(errors);

打印結(jié)果
數(shù)棧產(chǎn)品中的代碼編譯器
這樣我們就獲取到了文本中的語法錯(cuò)誤出現(xiàn)的位置,以及錯(cuò)誤信息。
到此為止上文中遺留的第一個(gè)問題就已經(jīng)差不多解決了,我們只需要在合適的時(shí)機(jī)將編輯器的內(nèi)容進(jìn)行解析,拿到錯(cuò)誤信息并且通過 editor.setModelMarkers這個(gè) API 讓錯(cuò)誤的位置飄紅就大功告成了。

自動(dòng)補(bǔ)全功能

對于自動(dòng)補(bǔ)全功能,Antlr4 并沒有直接提供,但是社區(qū)已經(jīng)有了比較優(yōu)秀的解決方案 - antlr-c3 。它的作用是根據(jù)Antlr4 Parser 的解析結(jié)果,分析指定位置填哪些詞法/語法規(guī)則是合法的
antlr4-c3 的使用方式比較簡單。

import { CodeCompletionCore } from "antlr4-c3";

// 這里 parser 是 parser 實(shí)例
let core = new CodeCompletionCore(parser); 
// tokenIndex 是想要自動(dòng)補(bǔ)全的位置,對應(yīng)由編輯器的光標(biāo)位置轉(zhuǎn)換而來
// parserContext 則是解析完之后的返回的 ParserTree 或者 ParserTree 的子節(jié)點(diǎn)(傳入子節(jié)點(diǎn)可以更高效)
let candidates = core.collectCandidates(tokenIndex, parserContext);

那么結(jié)合上文中寫的 SelectParser,代碼應(yīng)該是這樣

import { CodeCompletionCore } from "antlr4-c3";
import { SelectParser } from "./selectParser";

/**
 * input 源文本
 * caretPosition 編輯器光標(biāo)位置
 */
function getSuggestions(input: string, caretPosition) {
    const selectParser = new SelectParser();
    const parserIns = selectParser.createParser(input)
    let core = new CodeCompletionCore(parserIns);

    const parserContext = parserIns.selectStatement();
    // 偽代碼
    const tokenIndex = caretPosition2TokenIndex(caretPosition)

    let candidates = core.collectCandidates(tokenIndex, parserContext);
}

core.collectCandidates 的返回值的數(shù)據(jù)類型如下

interface CandidatesCollection {
    tokens: Map<number, TokenList>;
    rules: Map<number, CandidateRule>;
}

tokens 對應(yīng)的是詞法規(guī)則提示,比如關(guān)鍵字等,rules 對應(yīng)的是語法規(guī)則,比如上述語法文件中的 columnPathtablePath等。
需要注意的是,antlr4-c3 默認(rèn)不收集語法規(guī)則,需要我們手動(dòng)設(shè)置需要收集的語法規(guī)則

import { SelectStatementParser } from '../lib/selectStatement/SelectStatementParser';


let core = new CodeCompletionCore(parserIns);

core.preferredRules= new Set([
    SelectStatementParser.RULE_tablePath,
    SelectStatementParser.RULE_columnPath
])
// 設(shè)置需要收集 tablePath 和 columnPath

這樣我們就收集到了在指定位置的可以填什么。接下來我們需要將結(jié)果進(jìn)行轉(zhuǎn)換成我們需要的數(shù)據(jù)結(jié)果

import { CodeCompletionCore } from "antlr4-c3";
import { SelectParser } from "./selectParser";
import { SelectStatementParser } from '../lib/selectStatement/SelectStatementParser';

/**
 * input 源文本
 * caretPosition 編輯器光標(biāo)位置
 */
export function getSuggestions(input: string, caretPosition?: any) {
    const selectParser = new SelectParser();
    const parserIns = selectParser.createParser(input)
    let core = new CodeCompletionCore(parserIns);

    core.preferredRules= new Set([
        SelectStatementParser.RULE_tablePath,
        SelectStatementParser.RULE_columnPath
    ])

    const parserContext = parserIns.selectStatement();
    const tokenIndex = caretPosition2TokenIndex(caretPosition);

    let candidates = core.collectCandidates(tokenIndex, parserContext);

    const rule = [];
    const keywords = []

    for (let candidate of candidates.rules) {
        const [ruleType] = candidate;
        let syntaxContextType;
        switch (ruleType) {
            case SelectStatementParser.RULE_tablePath: {
                syntaxContextType = 'table';
                break;
            }
            case SelectStatementParser.RULE_columnPath: {
                syntaxContextType = 'column';
                break;
            }
            default:
                break;
        }
        if (syntaxContextType) {
            rule.push(syntaxContextType)
        }
    }

    for (let candidate of candidates.tokens) {
        const symbolicName = parserIns.vocabulary.getSymbolicName(candidate[0]);
        const displayName = parserIns.vocabulary.getDisplayName(candidate[0]);
        if(symbolicName && symbolicName.startsWith('KW_')) {
            const keyword = displayName.startsWith("'") && displayName.endsWith("'")
                ? displayName.slice(1, -1)
                : displayName
            keywords.push(keyword);
        }
    }

    console.log('===== suggest keywords: ',keywords);
    console.log('===== suggest rules:', rule);
}

這樣我們就拿到了要提示的關(guān)鍵字和語法規(guī)則。關(guān)鍵字可以直接用于生成自動(dòng)補(bǔ)全項(xiàng),語法規(guī)則可以用于提示表名、字段名等。

小結(jié)分析

在這一節(jié)中,我們已經(jīng)了解了,如何使用 Antlr4 和 antlr4-c3 來實(shí)現(xiàn)更加智能的飄紅提示以及自動(dòng)補(bǔ)全功能。
這一部分功能,在 monaco-sql-languages 中通過引入數(shù)棧前端團(tuán)隊(duì)自研的開源項(xiàng)目 dt-sql-parser 實(shí)現(xiàn)。
前文中提到的 worker 文件也正是用于運(yùn)行 sql parser,因?yàn)閐t-sql-parser 的解析可能會比較耗時(shí),為了避免用項(xiàng)用戶交互,將 sql parser 放到 web worker 中運(yùn)行顯然是更明智的選擇。

總結(jié)

總的來說

  • 多種 SQL 的語法高亮
  • 多種 SQL 的報(bào)錯(cuò)提示(錯(cuò)誤位置飄紅)
  • 多種 SQL 的自動(dòng)補(bǔ)全(智能提示)

三個(gè)功能大部分都可以通過 MonacoEditor 內(nèi)置的 API 來實(shí)現(xiàn),只是關(guān)鍵的語法解析功能需要使用 Antlr4 實(shí)現(xiàn)。整體上來說大部分的工作在編寫 Antlr4 的語法文件以及方案整合上面。

Github 鏈接

  • demo 項(xiàng)目
  • monaco-sql-languages
  • dt-sql-parser
  • antlr4
  • antlr4-c3

最后

歡迎關(guān)注【袋鼠云數(shù)棧UED團(tuán)隊(duì)】~
袋鼠云數(shù)棧UED團(tuán)隊(duì)持續(xù)為廣大開發(fā)者分享技術(shù)成果,相繼參與開源了歡迎star文章來源地址http://www.zghlxwxcb.cn/news/detail-746166.html

  • 大數(shù)據(jù)分布式任務(wù)調(diào)度系統(tǒng)——Taier
  • 輕量級的 Web IDE UI 框架——Molecule
  • 針對大數(shù)據(jù)領(lǐng)域的 SQL Parser 項(xiàng)目——dt-sql-parser
  • 袋鼠云數(shù)棧前端團(tuán)隊(duì)代碼評審工程實(shí)踐文檔——code-review-practices
  • 一個(gè)速度更快、配置更靈活、使用更簡單的模塊打包器——ko

到了這里,關(guān)于數(shù)棧產(chǎn)品中的代碼編譯器的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 袋鼠云產(chǎn)品功能更新報(bào)告05期|應(yīng)有盡“優(yōu)”,數(shù)棧一大波功能優(yōu)化升級!

    袋鼠云產(chǎn)品功能更新報(bào)告05期|應(yīng)有盡“優(yōu)”,數(shù)棧一大波功能優(yōu)化升級!

    這段時(shí)間,我們對產(chǎn)品本身以及客戶反饋的一些問題進(jìn)行了持續(xù)的更新和優(yōu)化,包括對離線平臺數(shù)據(jù)同步功能的更新,數(shù)據(jù)資產(chǎn)平臺血緣問題的優(yōu)化等,力求滿足不同行業(yè)用戶的更多需求,為用戶帶來極致的產(chǎn)品使用體驗(yàn)。 以下為袋鼠云產(chǎn)品功能更新報(bào)告第五期內(nèi)容,更多探

    2024年02月04日
    瀏覽(42)
  • Java編譯器中的優(yōu)化技術(shù)

    Java編譯器中的優(yōu)化技術(shù)

    ? ????????Java中的熱點(diǎn)代碼主要有兩類,包括: 1、被多次調(diào)用的方法。 2、被多次執(zhí)行的循環(huán)體。 前者很好理解,一個(gè)方法被調(diào)用得多了,方法體內(nèi)代碼執(zhí)行的次數(shù)自然就多,它成為 “ 熱點(diǎn)代 碼 ” 是理所當(dāng)然的。而后者則是為了解決當(dāng)一個(gè)方法只被調(diào)用過一次或少量

    2024年02月15日
    瀏覽(28)
  • 匯編代碼生成和編譯器的后端

    匯編代碼生成和編譯器的后端

    基于SLR(1)分析的語義分析及中間代碼生成程序-CSDN博客 https://blog.csdn.net/lijj0304/article/details/135097554?spm=1001.2014.3001.5501 在前面編譯器前端實(shí)現(xiàn)的基礎(chǔ)上,將所生成的中間代碼翻譯成某種目標(biāo)機(jī)的匯編代碼,實(shí)現(xiàn)編譯器后端實(shí)現(xiàn)的任務(wù)。然后進(jìn)一步實(shí)現(xiàn)程序的輸入是源程序,輸出

    2024年01月21日
    瀏覽(21)
  • C++入門(小白篇1—編譯器安裝-代碼注釋等)

    C++入門(小白篇1—編譯器安裝-代碼注釋等)

    最近想學(xué)一下一下C++看了一些博客內(nèi)容寫的倒是很充實(shí),但是,細(xì)節(jié)不到位,我是有Python基礎(chǔ)的,所以學(xué)習(xí)來蠻快的,但是對于小白的話,有好多小細(xì)節(jié)大多數(shù)博客還是不夠詳細(xì),由此我想寫一份相對細(xì)節(jié)一點(diǎn)的 我選擇的是 【Red Panda Dev-C++】,官網(wǎng):點(diǎn)我 1、這里可以改背景

    2024年02月13日
    瀏覽(29)
  • 【C++】在線編譯器推薦,讓你隨時(shí)隨地編寫代碼

    【C++】在線編譯器推薦,讓你隨時(shí)隨地編寫代碼

    描述 文中所有網(wǎng)址,在 結(jié)論 中有匯總。 環(huán)境 版本號 描述 文章日期 2023-06-14 操作系統(tǒng) Win11 - 21H2 - 22000.1335 Python 3.7.1 frida.exe 15.0.18 Repl.it 官網(wǎng)地址: https://repl.it/languages/cpp 需要登錄 支持shell ,也就是說用戶完全可以使用該linux服務(wù)器。 支持調(diào)試 支持各種其他工具 支持項(xiàng)目

    2024年02月15日
    瀏覽(24)
  • C++代碼性能優(yōu)化的好處與缺點(diǎn)?有哪些編譯器優(yōu)化選項(xiàng)?

    性能優(yōu)化是C++編程中的一個(gè)重要方面,它可以帶來許多好處,但也有一些潛在的缺點(diǎn)。 以下是C++代碼性能優(yōu)化的一些優(yōu)缺點(diǎn): 優(yōu)點(diǎn): 提高執(zhí)行速度 : 優(yōu)化后的代碼可以更快地執(zhí)行,這對于需要處理大量數(shù)據(jù)或需要快速響應(yīng)的應(yīng)用程序尤其重要。 減少資源消耗 : 優(yōu)化可以減少

    2024年03月27日
    瀏覽(41)
  • 鏈接文件學(xué)習(xí)(七):英飛凌MCU Tasking編譯器LSL鏈接文件解析 及代碼變量定位方法

    目錄 ? 1、Tasking的鏈接文件 1.1、DSRAM中的數(shù)據(jù)存放 1.2、PFlash中的代碼存放 1.3、LMU 1.4、PSRAM 1.5、UCB 2、代碼與變量定位

    2024年02月07日
    瀏覽(80)
  • Linux C++性能優(yōu)化秘籍:從編譯器到代碼,探究高性能C++程序的實(shí)現(xiàn)之道

    Linux C++性能優(yōu)化秘籍:從編譯器到代碼,探究高性能C++程序的實(shí)現(xiàn)之道

    隨著大數(shù)據(jù)、人工智能等技術(shù)的飛速發(fā)展,程序性能優(yōu)化的重要性愈發(fā)突出。優(yōu)化性能可以降低資源消耗、提高系統(tǒng)響應(yīng)速度,從而在有限的硬件資源下,實(shí)現(xiàn)更高的吞吐量和處理能力。此外,性能優(yōu)化也有助于降低能耗、減少散熱問題,延長硬件使用壽命。 Linux操作系統(tǒng)具

    2023年04月09日
    瀏覽(47)
  • VS2019編譯器修改背景壁紙(寫代碼背景不再單薄,試試換一張清晰的美女照片)

    VS2019編譯器修改背景壁紙(寫代碼背景不再單薄,試試換一張清晰的美女照片)

    第一步:打開vs2019編譯器,在上方菜單欄找到【擴(kuò)展】-【管理擴(kuò)展】-【聯(lián)機(jī)】,在右方搜索欄搜索“claudiaIDE”,找到ClaudiaIDE 2019,點(diǎn)擊下載 第二步:下載完成后,關(guān)閉所有的VS文件,關(guān)閉VS后回到桌面,發(fā)現(xiàn)有一個(gè)“VSIX Installer”的彈窗,選擇“Modify”進(jìn)行安裝,等到修改完

    2023年04月21日
    瀏覽(29)
  • Java on VS Code 8月更新|反編譯器用戶體驗(yàn)優(yōu)化、新 Maven 項(xiàng)目工作流、代碼高亮穩(wěn)定性提升

    Java on VS Code 8月更新|反編譯器用戶體驗(yàn)優(yōu)化、新 Maven 項(xiàng)目工作流、代碼高亮穩(wěn)定性提升

    作者:Nick Zhu 排版:Alan Wang 大家好,歡迎來到 Visual Studio Code for Java 的 8 月更新!在這篇博客中,我們將為您提供有關(guān)反編譯器支持的更多改進(jìn)。此外,我們將展示如何創(chuàng)建沒有原型的 Maven 項(xiàng)目以及一項(xiàng)重要錯(cuò)誤修復(fù)。讓我們開始吧! 上一篇博客中將強(qiáng)大的 Fernflower 反編譯

    2024年02月10日
    瀏覽(25)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包