UNPKG

cm-tarnation

Version:

An alternative parser for CodeMirror 6

186 lines (150 loc) 5.73 kB
/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { defineLanguageFacet, Language, languageDataProp, LanguageDescription, LanguageSupport } from "@codemirror/language" import type { Extension, Facet } from "@codemirror/state" import { NodeProp, NodeSet, NodeType } from "@lezer/common" import type { ChunkBuffer } from "./compiler/buffer" import { Autocompleter } from "./completion/autcomplete" import { NodeID, NODE_ERROR_ADVANCE, NODE_ERROR_INCOMPLETE } from "./constants" import type * as DF from "./grammar/definition" import { Grammar } from "./grammar/grammar" import { ParserFactory } from "./parser" import type { ParserConfiguration, TarnationLanguageDefinition } from "./types" import { removeUndefined } from "./util" /** * Tarnation language. Use the `load` method to get the extension needed to * load the language into CodeMirror. If you need a `LanguageDescription`, * the `description` property will hold one. */ export class TarnationLanguage { /** CodeMirror language data. */ declare languageData: Record<string, any> /** The grammar definition (or a function that returns one). */ declare grammarData: DF.Grammar | (() => DF.Grammar) /** Extra configuration for the parser. */ declare configure: ParserConfiguration /** List of extensions to load with the language. */ declare extensions: Extension[] /** `LanguageDescription` instance for the language. */ declare description: LanguageDescription /** * Languages that are supported for nesting. Can also be a facet that * provides the list. */ declare nestLanguages: LanguageDescription[] | Facet<LanguageDescription> // only shows up after loading /** Fully resolved grammar instance. Requires the language to have been loaded. */ declare grammar?: Grammar /** * The CodeMirror top node for the language. Requires the language to * have been loaded. */ declare topNode?: NodeType /** * All of the node types used by the language. Requires the language to * have been loaded. */ declare nodeTypes?: NodeType[] /** * CodeMirror `NodeSet` for the language. Requires the language to have * been loaded. */ declare nodeSet?: NodeSet /** * The `NodeProp` that points to the `ChunkBuffer` data. Will be assigned * to the top node of the language. Requires the language to have been loaded. */ declare stateProp?: NodeProp<ChunkBuffer> /** * Autocompleter instance. Requires the language to have been loaded, but * will only be there if there are any autocompletion handlers. */ declare autocompleter?: Autocompleter /** * `LanguageSupport` instance for the language. Requires the language to * have been loaded. */ declare support?: LanguageSupport /** * CodeMirror `language` instance for the language. Requires the language * to have been loaded. */ declare language?: Language /** Will be true if the langauge has been loaded once. */ loaded = false /** Will be set to the number of milliseconds the last parse time took. */ performance = 0 constructor({ name, grammar, nestLanguages = [], configure = {}, alias, extensions, languageData = {}, supportExtensions = [] }: TarnationLanguageDefinition) { const dataDescription = removeUndefined({ name, alias, extensions }) this.languageData = { ...dataDescription, ...languageData } this.nestLanguages = nestLanguages this.grammarData = grammar this.configure = configure this.extensions = supportExtensions this.description = LanguageDescription.of({ ...dataDescription, load: async () => this.load() }) } /** * Loads and processes the language. Calling this function repeatedly * will just return the previously loaded language. */ load() { // setup grammar data if (this.description?.support) return this.description.support const def = typeof this.grammarData === "function" ? this.grammarData() : this.grammarData this.grammar = new Grammar(def, this.configure.variables) // merge data from the grammar Object.assign(this.languageData, this.grammar.data) // setup node data this.stateProp = new NodeProp<ChunkBuffer>({ perNode: true }) const facet = defineLanguageFacet(this.languageData) this.topNode = NodeType.define({ id: NodeID.TOP, name: this.description.name, top: true, props: [[languageDataProp, facet]] }) const nodeTypes = this.grammar.repository.nodes().map(n => n.type) // unshift our special nodes into the list // (NodeType.none always has an ID of 0) nodeTypes.unshift( NodeType.none, this.topNode, NODE_ERROR_ADVANCE, NODE_ERROR_INCOMPLETE ) let nodeSet = new NodeSet(nodeTypes) if (this.configure.props) nodeSet = nodeSet.extend(...this.configure.props) this.nodeTypes = nodeTypes this.nodeSet = nodeSet if (this.configure.autocomplete) { this.autocompleter = new Autocompleter(this) this.languageData.autocomplete = this.autocompleter.handle.bind(this.autocompleter) } // setup language support this.language = new Language(facet, new ParserFactory(this)) this.support = new LanguageSupport(this.language, this.extensions) this.loaded = true console.log(this) return this.support } }