https://segmentfault.com/a/1190000040712964
https://medium.com/better-programming/create-a-custom-web-editor-using-typescript-react-antlr-and-monaco-editor-bcfc7554e446
# antlr4
https://github.com/amazzalel-habib/TodoLangEditor
https://medium.com/better-programming/create-a-custom-web-editor-using-typescript-react-antlr-and-monaco-editor-bcfc7554e446
作者在最後有稍微提示要做自動補足要如何做,我們來補起來
直接run 專案可能會出錯
這邊我給修正過後的package.json
```json=
{
"name": "todolangeditor",
"version": "1.0.0",
"description": "TodoLang editor",
"scripts": {
"start": "webpack-dev-server --hot --open",
"antlr4ts": "antlr4ts ./TodoLangGrammar.g4 -o ./src/ANTLR"
},
"author": "Open",
"license": "ISC",
"dependencies": {
"antlr4ts": "^0.5.0-alpha.3",
"monaco-editor-core": "^0.18.1",
"react": "^16.8.6",
"react-dom": "^16.8.6"
},
"devDependencies": {
"@types/node": "7.0.7",
"@types/react": "^16.8.12",
"@types/react-dom": "^16.8.3",
"@webpack-cli/serve": "^1.7.0",
"antlr4ts-cli": "^0.5.0-alpha.3",
"css-loader": "^3.3.0",
"html-webpack-plugin": "^3.2.0",
"style-loader": "^1.0.1",
"ts-loader": "^5.3.3",
"typescript": "^4.8.4",
"webpack": "^4.29.6",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.11.1"
}
}
```
我們從setupLanguage();進入修改
![](https://i.imgur.com/xcBNPyl.png)
新增TodoLangAutoCompletionProvider2
```nodejs=
import * as monaco from "monaco-editor-core";
import { WorkerAccessor } from "./setup";
export default class TodoLangAutoCompletionProvider2 {
private worker: WorkerAccessor;
TodoLangAutoCompletionProvider2(worker: WorkerAccessor) {
this.worker = worker;
}
// resolveCompletionItem?(model: editor.ITextModel, position: Position, item: CompletionItem, token: CancellationToken): ProviderResult<CompletionItem>{
public async get_label(resource: monaco.Uri): Promise<Array<string | undefined> > {
let suggestions = [];
// 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.get_label();
console.log(formattedCode);
return formattedCode;
}
}
```
回到setup.ts新增
```node.js=
export function setupLanguage() {
(window as any).MonacoEnvironment = {
getWorkerUrl: function (moduleId, label) {
if (label === languageID)
return "./todoLangWorker.js";
return './editor.worker.js';
}
}
monaco.languages.register(languageExtensionPoint);
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);
let tw = new TodoLangAutoCompletionProvider2();
tw.TodoLangAutoCompletionProvider2(worker);
// triggerCharacters.push('C');
// let tmp =new TodoLangAutoCompletionProvider(worker);
monaco.languages.registerDocumentFormattingEditProvider(languageID, new TodoLangFormattingProvider(worker));
// monaco.languages.registerCompletionItemProvider(languageID, new TodoLangAutoCompletionProvider(worker));
monaco.languages.registerCompletionItemProvider(languageID, {
triggerCharacters: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'],
provideCompletionItems: async function (model, position, context) {
let get_label;
let suggestions=[];
console.log(get_label);
console.log(context.triggerCharacter);
get_label = await tw.get_label(model.uri);
for (let index = 0; index < get_label.length; ++index) {
const element = get_label[index];
// console.log(element);
if (element != undefined)
if (element.indexOf(context.triggerCharacter) > 0) {
console.log(element);
suggestions.push({
label: element,
insertText: element,
kind: 1,
});
}
}
return { suggestions };
},
});
});
}
export type WorkerAccessor = (...uris: monaco.Uri[]) => Promise<TodoLangWorker>;
```
主要透過get_label
去跟worker.languageService去拿我們的ast define name
```js=
public get_label():Promise<Array<string | undefined>> {
const code = this.getTextDocument();
return Promise.resolve(this.languageService.get_label(code));
}
```
```js=
import { TodoExpressionsContext, AddExpressionContext, CompleteExpressionContext } from "../ANTLR/TodoLangGrammarParser";
import { parseAndGetASTRoot, parseAndGetSyntaxErrors, parseAndGetLable } from "./parser";
import { ITodoLangError } from "./TodoLangErrorListener";
export default class TodoLangLanguageService {
public input:string;
validate(code: string): ITodoLangError[] {
const syntaxErrors: ITodoLangError[] = parseAndGetSyntaxErrors(code);
const ast: TodoExpressionsContext = parseAndGetASTRoot(code);
const get_label: Array<string | undefined> = parseAndGetLable(code);
// console.log(ast.children);
// console.log(syntaxErrors);
// console.log(get_label);
return syntaxErrors.concat(checkSemanticRules(ast, get_label));
}
get_label(code: string): Array<string | undefined> {
const get_label: Array<string | undefined> = parseAndGetLable(code);
return get_label;
}
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;
}
}
function checkSemanticRules(ast: TodoExpressionsContext, get_label: Array<string | undefined>): ITodoLangError[] {
const errors: ITodoLangError[] = [];
const definedTodos: string[] = [];
// console.log(definedTodos);
// errors.forEach(element => {
// let serach =element.input;
// get_label.forEach(element2 => {
// if( element2.indexOf(serach) >0)
// console.log(element2);
// // some.indexOf("fmpe"); //11
// // some.indexOf("ldaspojpoe"); //-1
// });
// });
ast.children.forEach(node => {
if (node instanceof AddExpressionContext) {
// if a Add expression : ADD TODO "STRING"
const todo = node.STRING().text;
// console.log(todo);
// 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,
input: ""
});
} else {
definedTodos.push(todo);
}
} else if (node instanceof CompleteExpressionContext) {
const todoToComplete = node.STRING().text;
// console.log(todoToComplete);
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,
input: ""
});
}
}
})
return errors;
}
```
```js=
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[],get_lable:Array<string | undefined>} {
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 get_lable =parser.get_label;
const errors: ITodoLangError[] = todoLangErrorsListner.getErrors();
return {ast, errors,get_lable};
}
export function parseAndGetASTRoot(code: string): TodoExpressionsContext {
const {ast} = parse(code);
return ast;
}
export function parseAndGetSyntaxErrors(code: string): ITodoLangError[] {
const {errors} = parse(code);
return errors;
}
export function parseAndGetLable(code: string):Array<string | undefined> {
const {get_lable} = parse(code);
return get_lable;
}
```
這邊TodoLangGrammarParser重寫一個fucntion出來,得到public get_label: Array<string | undefined>
![](https://i.imgur.com/5VhURTh.png)
整體大概是邏輯觸發錯誤時去抓define_lable出來
registerCompletionItemProvider 每次觸發會透過TodoLangAutoCompletionProvider2撈取存在worker的lable.
這邊要注意非同步async
provideCompletionItems: async function (model, position, context)
![](https://i.imgur.com/2S712qP.png)
用index 抓取關鍵字也就是get_label的Array
![](https://i.imgur.com/iKD1c1G.png)
![](https://i.imgur.com/NpXIA6T.png)
到這邊就完成作者的自動補全。
![](https://i.imgur.com/284rQo3.png)
其他功能也能正常WORK.
在稍微改裝一點我們換成vscode 風格,如果在加上前面的riscv模擬器,就可以變成線上ide
目前寫好按鈕假設,搞清楚input ouput我們就可以模擬在線上ide執行riscv指令。
https://github.com/x213212/TodoLangEditor?organization=x213212&organization=x213212
![](https://i.imgur.com/1cIhNqM.png)