數棧產品中的代碼編譯器

来源:https://www.cnblogs.com/dtux/archive/2023/11/15/17833165.html
-Advertisement-
Play Games

我們是袋鼠雲數棧 UED 團隊,致力於打造優秀的一站式數據中台產品。我們始終保持工匠精神,探索前端道路,為社區積累並傳播經驗價值。 本文作者:奇銘 前言 目前數棧的多個產品中都支持線上編輯 SQL 來生成對應的任務。比如離線開發產品和實時開發產品。在使用 MonacoEditor 為編輯器的基礎上, ...


我們是袋鼠雲數棧 UED 團隊,致力於打造優秀的一站式數據中台產品。我們始終保持工匠精神,探索前端道路,為社區積累並傳播經驗價值。

本文作者:奇銘

前言

目前數棧的多個產品中都支持線上編輯 SQL 來生成對應的任務。比如離線開發產品和實時開發產品。在使用 MonacoEditor 為編輯器的基礎上,我們還支持瞭如下幾個重要功能:

  • 多種 SQL 的語法高亮
  • 多種 SQL 的報錯提示(錯誤位置飄紅)
  • 多種 SQL 的自動補全(智能提示)

本文旨在講解上述功能的實現思路,對於技術細節,由於篇幅原因不會闡述的太詳細。

Monaco Languages

Monaco Editor 內置的 languages

Monaco Editor 內置了相當多的 languages,比如 javaScriptCSSShell 等。
Monaco Editor 依賴包的 ESM 入口文件為 ./esm/vs/editor/editor.main.ts
file
而在這個文件中,Monaco Editor 引入了所有內置的 Languages。
file
這裡 languages 文件可以分為兩類,一類是../language文件夾下的,支持自動補全和飄紅提示功能;另一類則是../basic-languages文件夾下的,不支持自動補全功能和飄紅提示功能。

使用內置的 Language 功能

以使用 typescript 為例

import { editor } from 'monaco-editor';

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

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

此時我們會發現,我們的編輯器已經有語法高亮的功能了,但是瀏覽器控制台會拋異常,另外也沒有自動補全功能和飄紅提示功能,
file
這其實是因為,Monaco Editor 無法載入到 language 對應的 worker,對應的解決辦法看這裡: Monaco integrate-esm
這裡我們使用 Using plain webpack的方式,首先將對應的 worker 文件設置為 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'
    },
}

另外還需要設置 Monaco Editor 的全局環境變數,這主要是為了告訴 Monaco Editor 對應的 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'
})

這樣一個具有語法高亮自動補全飄紅提示 功能的 typescript 編輯器就設置好了
file

小結分析

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

自定義 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;
}

第一步,我們需要註冊一個 language, 配置項中 id 對應的就是語言名稱(其他配置項可以暫時不填),這裡自定義的 language 名為 myLang

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

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

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

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

此時可以發現,頁面上的編輯器沒有任何其他附加功能,就是普通的文本編輯器。

設置 Language

通過 monaco.languages.setLanguageConfiguration,可以對 language 進行配置

/**
 * 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 的一些預設行為,比如設置 autoClosingPairs中有一項為一對圓括弧,那麼當輸入左圓括弧後,會自動補全右圓括弧。

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 內置了 Monarch,用於實現語法高亮功能,它本質上是一個有限狀態機,我們可以通過JSON的形式來配置其狀態流轉邏輯,並通過monaco.languages.setMonarchTokensProvider API 應用該配置。關於Monarch 的具體用法可以看一下這篇文章 以及 Monarch Document
配置中最重要的是 tokenizer屬性,意思是分詞器,分詞器會自動對編輯器內部的文本進行分詞處理,每個分詞器都有一個 root state,在 root state 中可以有多條規則,規則內部可以引用其他 state。

下麵是一個簡單的配置示例

import { languages } from "monaco-editor";
export const language: languages.IMonarchLanguage = {
	ignoreCase: true,
	tokenizer: {
		root: [
			{ include: '@comments' }, // 引用下麵的 comments 規則
			{ include: '@whitespace' }, // 引用下麵的 whiteSpace 規則
			{ include: '@strings' },// 引用下麵的 strings 規則
		],
		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 下麵有三條規則分別匹配 註釋(comments)字元串(strings) 以及空白字元(whiteSpace), 每條規則可以大體分為兩部分:

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

比如上述配置中 tokenizer.comments 規則

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

配置瞭如上 Monarch 之後,在編輯器內部輸入註釋或者字元串,那麼Monaco editor 就會根據輸入的內容進行分詞處理
file

可以看到目前字元串和註釋已經被高亮了。這裡有一個新的問題,不同類型的分詞的顏色是怎麼設置的

Monaco Theme

從上圖中右側的 Elements 面板中可以看到,不同類型的分詞,對應的標簽的 className 不同,它們是由 Monarch 配置中的 token 映射而來的。MonacoEditor 內置了一些 Theme,預設的 Theme 是 vs,而預設的 theme 中已經設置了上述 Monarch 中的 token 對應的顏色,所以我們應用上述配置後,對應的分詞直接就有了高亮顏色。
我們可以通過 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"
});

這裡將註釋設置為紅色,字元串設置為藍色,顯示效果如下圖所示
file

飄紅提示

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

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: "語法錯誤",
		severity: MarkerSeverity.Error
	}
])

severity 是標記類型,message 是提示信息,效果如下所示。
file
到此為止,實現了飄紅的功能,但是沒有實現在語法錯誤處飄紅的功能,這需要額外的語法解析器支持,會在下文中講到。

自動補全功能

Monaco Editor 提供了 monaco.languages.registerCompletionItemProvider API 來實現自動補全功能

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: '關鍵字',
    					},
    					{
    						label: "SET",
    						kind: languages.CompletionItemKind.Keyword,
    						insertText: "SET",
    						range: wordRange,
    						detail: '關鍵字',
    					},
    					{
    						label: "SHOW",
    						kind: languages.CompletionItemKind.Keyword,
    						insertText: "SHOW",
    						range: wordRange,
    						detail: '關鍵字',
    					},
    				]
    			})
    		})
	}
})

registerCompletionItemProvider 接受兩個參數,第一個參數是 languageId 也就是 language 名稱,
第二個參數是一個 CompletionItemProviderCompletionItemProvidertriggerCharacters用來配置觸發自動補全的字元有哪些,而 provideCompletionItems則是一個函數,它接收 Monaco Editor 提供的當前的上下文信息,返回自動補全項列表。如上例中返回了三個自動補全項,那麼當我們在編輯器中輸入 S時,就會出現配置的自動補全項候選菜單。
file
通過這個 API 我們可以實現一種語言的關鍵字自動補全,只需要在CompletionItemProvider中返回該語言所有的關鍵字對應的自動補全項即可。
但是registerCompletionItemProvider目前做不到根據語義進行自動補全。
比如用戶寫一段 flinkSQL,當用戶輸入完 CREATE 關鍵字並按下空格後,應該出現的自動補全項應該是只有TABLECATALOGDATABASEFUNCTIONVIEW
再比如當用戶輸入 SELECT * FROM 時,後面應該提示表名而不是其他無關的關鍵字。與上文中的飄紅提示一樣,這些語義信息需要單獨的語法解析器來分析。

小結分析

到此為止,在**自定義 language **這一節中,我們已經瞭解了,在 Monaco Editor 中如何實現自定義語言的 語法高亮錯誤處飄紅提示自動補全
在數棧產品中,本節講到的功能都通過引入 monaco-sql-languages 依賴來實現,這是我們數棧 UED 團隊自研的開源項目,目前已經支持多種 SQL Languages。
由於目前為止沒有實現自定義 language 的語義分析功能,導致目前實現的編輯器不夠智能。 另外,對於第一節中提到的 web worker ,在第二節中也沒有有提到,實際上 Monaco Editor 自帶的 web worker,也都是為了實現 language 的語義分析功能,下一節將闡述這一部分內容。

SQL Parser

要實現語義分析功能,很顯然我們需要一個語法解析器。除了基本的語法解析的基礎功能以外,我們還需要

  • 語法錯誤收集,收集編輯器中文本的語法錯誤信息,用於錯誤飄紅提示功能。
  • 推斷文本中指定位置的候選項列表,對於編輯器來說,指定位置一般就是游標所在位置。候選項是指在游標所在的位置應該要寫什麼。比如 SQL 中 SELECT 關鍵字後面可以跟欄位或者函數,那麼我們所要實現的 sql parser 就應該提示出在 SELECT 關鍵字後面的候選項應該是欄位或者函數。

實現基礎的 SQL Parser

Antlr4 語法文件

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

SELECT * FROM table1;  -- eg1

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

那麼在antlr4中這份語法文件應該這樣寫:

grammar SelectStatement;

/** 語法規則 begin */
program: selectStatement? EOF;

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

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

// 聲明 欄位名匹配規則,欄位名有可能為 db.table.col 或者 * 的形式
columnPath: dot_id | OP_STAR; 

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

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


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

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

語法規則的編寫格式類似於 EBNF
然後運行 antlr4 命令,根據所寫的語法文件生成對應的解析器。可以直接使用官方文檔中提供的方式 antlr4 typescript-target doc ,或者直接使用社區提供的 antlr4ts 包,這裡以使用 antlr4ts 為例。
生成的文件結果如下所示:
file

使用 Antlr4 生成的 Parser

在使用Antlr4 的生成的 Parser 之前我們需要安裝,Antlr4 的運行時包。你可以將 Antlr4 的運行時包通過語法文件生成的parser文件之間的關係,類比為 react 和 react-dom之間的關係。這裡以使用 antlr4ts 為運行時

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');

獲取文本中的錯誤信息

當解析一個含有錯誤的文本時,Antlr4 會輸出錯誤信息,例如輸入

selectParser.parse('SELECT id FRO');

控制台列印
file
可以看到錯誤信息中包含了文本中的錯誤所處的位置,我們可以通過使用 Antlr4 ParserErrorListener 來獲取錯誤信息。

聲明一個 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 收集錯誤信息

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 內置的 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 內置的 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);

列印結果
file
這樣我們就獲取到了文本中的語法錯誤出現的位置,以及錯誤信息。
到此為止上文中遺留的第一個問題就已經差不多解決了,我們只需要在合適的時機將編輯器的內容進行解析,拿到錯誤信息並且通過 editor.setModelMarkers這個 API 讓錯誤的位置飄紅就大功告成了。

自動補全功能

對於自動補全功能,Antlr4 並沒有直接提供,但是社區已經有了比較優秀的解決方案 - antlr-c3 。它的作用是根據Antlr4 Parser 的解析結果,分析指定位置填哪些詞法/語法規則是合法的
antlr4-c3 的使用方式比較簡單。

import { CodeCompletionCore } from "antlr4-c3";

// 這裡 parser 是 parser 實例
let core = new CodeCompletionCore(parser); 
// tokenIndex 是想要自動補全的位置,對應由編輯器的游標位置轉換而來
// parserContext 則是解析完之後的返回的 ParserTree 或者 ParserTree 的子節點(傳入子節點可以更高效)
let candidates = core.collectCandidates(tokenIndex, parserContext);

那麼結合上文中寫的 SelectParser,代碼應該是這樣

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

/**
 * input 源文本
 * caretPosition 編輯器游標位置
 */
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 的返回值的數據類型如下

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

tokens 對應的是詞法規則提示,比如關鍵字等,rules 對應的是語法規則,比如上述語法文件中的 columnPathtablePath等。
需要註意的是,antlr4-c3 預設不收集語法規則,需要我們手動設置需要收集的語法規則

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


let core = new CodeCompletionCore(parserIns);

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

這樣我們就收集到了在指定位置的可以填什麼。接下來我們需要將結果進行轉換成我們需要的數據結果

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

/**
 * input 源文本
 * caretPosition 編輯器游標位置
 */
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);
}

這樣我們就拿到了要提示的關鍵字和語法規則。關鍵字可以直接用於生成自動補全項,語法規則可以用於提示表名、欄位名等。

小結分析

在這一節中,我們已經瞭解了,如何使用 Antlr4 和 antlr4-c3 來實現更加智能的飄紅提示以及自動補全功能。
這一部分功能,在 monaco-sql-languages 中通過引入數棧前端團隊自研的開源項目 dt-sql-parser 實現。
前文中提到的 worker 文件也正是用於運行 sql parser,因為dt-sql-parser 的解析可能會比較耗時,為了避免用項用戶交互,將 sql parser 放到 web worker 中運行顯然是更明智的選擇。

總結

總的來說

  • 多種 SQL 的語法高亮
  • 多種 SQL 的報錯提示(錯誤位置飄紅)
  • 多種 SQL 的自動補全(智能提示)

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

Github 鏈接

最後

歡迎關註【袋鼠雲數棧UED團隊】~
袋鼠雲數棧UED團隊持續為廣大開發者分享技術成果,相繼參與開源了歡迎star


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在本文中,我們將介紹GaussDB資料庫中的用戶定義函數重載的概念、用法以及示例。用戶定義函數是 SQL 中常用的“編程工具”,允許我們自定義函數來處理和操作數據。 ...
  • 單機GreatSQL/MySQL調整架構為多副本複製的好處有哪些?為什麼要調整? 性能優化:如果單個GreatSQL伺服器的處理能力達到瓶頸,可能需要通過主從複製、雙主複製或MGR,以及其他高可用方案等來提高整體性能。通過將讀請求分發到多個伺服器,可以大大提高併發處理能力。 高可用性:如果您的應用程 ...
  • 本文利用向量的點積和叉積來判斷點是否線上段上。 基礎知識補充 從零開始的高中數學——向量、向量的點積、帶你一次搞懂點積(內積)、叉積(外積)、Unity游戲開發——向量運算(點乘和叉乘 說明 點積可以用來判斷兩個向量的夾角,如果這個夾角是0或者180度,說明這個點在直線上; 叉積可以用來判斷一個點到 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 一、故事的開始 最近產品又開始整活了,本來是毫無壓力的一周,可以小摸一下魚的,但是突然有一天跟我說要做一個在網頁端截屏的功能。 作為一個工作多年的前端,早已學會了儘可能避開麻煩的需求,只做增刪改查就行! 我立馬開始了我的反駁,我的理由是市 ...
  • 本篇文章從數據中心,事件中心如何協議工作、不依賴環境對vue2.x、vue3.x都可以支持、投產頁面問題定位三個方面進行分析。 ...
  • 介面數據類型與表單提交數據類型,在大多數情況下,大部分屬性的類型是相同的,但很少能做到完全統一。我在之前的工作中經常為了方便,直接將介面數據類型復用為表單內數據類型,在遇到屬性類型不一致的情況時會使用any強制忽略類型錯誤。後來經過自省與思考,這種工作模式會引起各種隱藏bug,一定有更好的工程解決方... ...
  • 1、提示 由於國內註冊 https://api.openai.com 比較麻煩,直接購買的第三方介面和key 淘寶購買,幾塊錢1個月 3、自己娛樂夠用 2、前端框架 Vant 移動端使用 axios 3、創建攔截器,api/request.js /* * @Descripttion: 文件說明 * ...
  • 跨域這兩個字就像一塊狗皮膏藥一樣黏在每一個前端開發者身上,無論你在工作上或者面試中無可避免會遇到這個問題。如果在網上搜索跨域問題,會出現許許多多方案,這些方案有好有壞,但是對於闡述跨域的原理和在什麼情況下需要用什麼方案,缺少系統性的說明。大家在工作中可能因為大佬們已經配置好了,不會產生跨域,但是作為... ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...