├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── package.json ├── rollup.config.js ├── src ├── index.ts ├── syntax.grammar └── syntax.grammar.d.ts ├── test ├── cases.txt └── test.js └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | package-lock.json 3 | /dist 4 | /src/*.js 5 | /src/*.d.ts 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /src 2 | /test 3 | /node_modules 4 | rollup.config.js 5 | tsconfig.json 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (C) 2021 by Marijn Haverbeke and others 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CodeMirror 6 language package template 2 | 3 | This is an example repository containing a minimal [CodeMirror](https://codemirror.net/6/) language support package. The idea is to clone it, rename it, and edit it to create support for a new language. 4 | 5 | Things you'll need to do (see the [language support example](https://codemirror.net/6/examples/lang-package/) for a more detailed tutorial): 6 | 7 | * `git grep EXAMPLE` and replace all instances with your language name. 8 | 9 | * Rewrite the grammar in `src/syntax.grammar` to cover your language. See the [Lezer system guide](https://lezer.codemirror.net/docs/guide/#writing-a-grammar) for information on this file format. 10 | 11 | * Adjust the metadata in `src/index.ts` to work with your new grammar. 12 | 13 | * Adjust the grammar tests in `test/cases.txt`. 14 | 15 | * Build (`npm run prepare`) and test (`npm test`). 16 | 17 | * Rewrite this readme file. 18 | 19 | * Optionally add a license. 20 | 21 | * Publish. Put your package on npm under a name like `codemirror-lang-EXAMPLE`. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "codemirror-lang-EXAMPLE", 3 | "version": "0.1.0", 4 | "description": "EXAMPLE language support for CodeMirror", 5 | "scripts": { 6 | "test": "mocha test/test.js", 7 | "prepare": "rollup -c" 8 | }, 9 | "type": "module", 10 | "main": "dist/index.cjs", 11 | "module": "dist/index.js", 12 | "exports": { 13 | "import": "./dist/index.js", 14 | "require": "./dist/index.cjs" 15 | }, 16 | "types": "dist/index.d.ts", 17 | "sideEffects": false, 18 | "dependencies": { 19 | "@codemirror/language": "^6.0.0", 20 | "@lezer/highlight": "^1.0.0", 21 | "@lezer/lr": "^1.0.0" 22 | }, 23 | "devDependencies": { 24 | "@lezer/generator": "^1.0.0", 25 | "mocha": "^9.0.1", 26 | "rollup": "^2.60.2", 27 | "rollup-plugin-dts": "^4.0.1", 28 | "rollup-plugin-ts": "^3.0.2", 29 | "typescript": "^4.3.4" 30 | }, 31 | "license": "MIT" 32 | } 33 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript from "rollup-plugin-ts" 2 | import {lezer} from "@lezer/generator/rollup" 3 | 4 | export default { 5 | input: "src/index.ts", 6 | external: id => id != "tslib" && !/^(\.?\/|\w:)/.test(id), 7 | output: [ 8 | {file: "dist/index.cjs", format: "cjs"}, 9 | {dir: "./dist", format: "es"} 10 | ], 11 | plugins: [lezer(), typescript()] 12 | } 13 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import {parser} from "./syntax.grammar" 2 | import {LRLanguage, LanguageSupport, indentNodeProp, foldNodeProp, foldInside, delimitedIndent} from "@codemirror/language" 3 | import {styleTags, tags as t} from "@lezer/highlight" 4 | 5 | export const EXAMPLELanguage = LRLanguage.define({ 6 | parser: parser.configure({ 7 | props: [ 8 | indentNodeProp.add({ 9 | Application: delimitedIndent({closing: ")", align: false}) 10 | }), 11 | foldNodeProp.add({ 12 | Application: foldInside 13 | }), 14 | styleTags({ 15 | Identifier: t.variableName, 16 | Boolean: t.bool, 17 | String: t.string, 18 | LineComment: t.lineComment, 19 | "( )": t.paren 20 | }) 21 | ] 22 | }), 23 | languageData: { 24 | commentTokens: {line: ";"} 25 | } 26 | }) 27 | 28 | export function EXAMPLE() { 29 | return new LanguageSupport(EXAMPLELanguage) 30 | } 31 | -------------------------------------------------------------------------------- /src/syntax.grammar: -------------------------------------------------------------------------------- 1 | @top Program { expression* } 2 | 3 | @skip { space | LineComment } 4 | 5 | expression { 6 | Identifier | 7 | String | 8 | Boolean | 9 | Application { "(" expression* ")" } 10 | } 11 | 12 | @tokens { 13 | Identifier { $[a-zA-Z_\-0-9]+ } 14 | 15 | String { '"' (!["\\] | "\\" _)* '"' } 16 | 17 | Boolean { "#t" | "#f" } 18 | 19 | LineComment { ";" ![\n]* } 20 | 21 | space { $[ \t\n\r]+ } 22 | 23 | "(" ")" 24 | } 25 | 26 | @detectDelim 27 | -------------------------------------------------------------------------------- /src/syntax.grammar.d.ts: -------------------------------------------------------------------------------- 1 | import {LRParser} from "@lezer/lr" 2 | 3 | export declare const parser: LRParser 4 | -------------------------------------------------------------------------------- /test/cases.txt: -------------------------------------------------------------------------------- 1 | # Booleans 2 | 3 | #t 4 | #f 5 | 6 | ==> 7 | 8 | Program(Boolean, Boolean) 9 | 10 | # Identifiers 11 | 12 | one 13 | Two_Three 14 | 15 | ==> 16 | 17 | Program(Identifier, Identifier) 18 | 19 | # Strings 20 | 21 | "hello" 22 | "one\"\\two" 23 | 24 | ==> 25 | 26 | Program(String, String) 27 | 28 | # Applications 29 | 30 | (begin 31 | (when #t 32 | (print (concat "hello" " " "world"))) 33 | (print "DONE")) 34 | 35 | ==> 36 | 37 | Program(Application( 38 | Identifier, 39 | Application(Identifier, Boolean, Application( 40 | Identifier, Application(Identifier, String, String, String))) 41 | Application(Identifier, String))) 42 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | import {EXAMPLELanguage} from "../dist/index.js" 2 | import {fileTests} from "@lezer/generator/dist/test" 3 | 4 | import * as fs from "fs" 5 | import * as path from "path" 6 | import { fileURLToPath } from 'url'; 7 | let caseDir = path.dirname(fileURLToPath(import.meta.url)) 8 | 9 | for (let file of fs.readdirSync(caseDir)) { 10 | if (!/\.txt$/.test(file)) continue 11 | 12 | let name = /^[^\.]*/.exec(file)[0] 13 | describe(name, () => { 14 | for (let {name, run} of fileTests(fs.readFileSync(path.join(caseDir, file), "utf8"), file)) 15 | it(name, () => run(EXAMPLELanguage.parser)) 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "target": "es6", 5 | "module": "es2020", 6 | "newLine": "lf", 7 | "declaration": true, 8 | "moduleResolution": "node" 9 | }, 10 | "include": ["src/*.ts"] 11 | } 12 | --------------------------------------------------------------------------------