├── .eslintignore ├── logo.png ├── .gitattributes ├── .gitignore ├── tsconfig.json ├── .vscode ├── settings.json ├── tasks.json └── launch.json ├── .vscodeignore ├── .eslintrc.yml ├── src ├── index.ts ├── language │ ├── completion.ts │ ├── folding.ts │ ├── utilities.ts │ ├── index.ts │ ├── hover.ts │ ├── resources.ts │ ├── color.ts │ └── features │ │ └── character.ts ├── commands │ ├── setInstallationDirectory.ts │ ├── index.ts │ ├── generateSyntax.ts │ └── formatEncodings.ts ├── syntax │ ├── depSet.ts │ ├── index.ts │ ├── macroParser.ts │ ├── minifySyntax.ts │ ├── mergeSyntax.ts │ └── traverser.ts ├── utilities │ ├── vsc-utils.ts │ └── index.ts └── scopes │ ├── textmate.ts │ ├── theme.ts │ ├── index.ts │ ├── workspace.ts │ ├── extension.ts │ ├── utilities.ts │ └── document.ts ├── syntaxes ├── xml-template.yaml ├── smart-comments.yaml ├── type-inference.yaml ├── simplest.yaml └── basic.yaml ├── language.json ├── LICENSE ├── demo ├── type-inference.wl └── basic-syntax.wl ├── README.md ├── docs ├── yaml-schema.md └── syntax-overview.md ├── CHANGELOG.md └── package.json /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | out -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigma/vscode-wl/HEAD/logo.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | *.vsix 4 | test.* 5 | /dist 6 | /out 7 | TODOS.md 8 | # IDE 9 | *.idea 10 | *.iml -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2018", 5 | "rootDir": "src", 6 | "outDir": "out", 7 | }, 8 | "exclude": [ 9 | "node_modules" 10 | ] 11 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "package-lock.json": true, 4 | "node_modules": true, 5 | "**/*.js.map": true, 6 | }, 7 | "[markdown]": { 8 | "editor.wordWrap": "off" 9 | } 10 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | .gitattributes 4 | .gitignore 5 | TODOS.md 6 | test.* 7 | build/** 8 | src/** 9 | dist/** 10 | *.js.map 11 | .eslintignore 12 | .eslintrc.yml 13 | tsconfig.json 14 | package-lock.json -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "npm", 6 | "script": "watch", 7 | "problemMatcher": "$tsc-watch", 8 | "isBackground": true, 9 | "presentation": { 10 | "reveal": "never" 11 | }, 12 | "group": { 13 | "kind": "build", 14 | "isDefault": true 15 | } 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | es6: true 3 | node: true 4 | 5 | extends: eslint:recommended 6 | 7 | parserOptions: 8 | ecmaVersion: 2018 9 | 10 | rules: 11 | indent: 12 | - error 13 | - 2 14 | - SwitchCase: 1 15 | quotes: 16 | - warn 17 | - single 18 | semi: 19 | - error 20 | - never 21 | eqeqeq: 22 | - error 23 | - always 24 | no-console: off 25 | no-useless-escape: off -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import * as scopes from './scopes' 3 | import * as language from './language' 4 | import * as commands from './commands' 5 | 6 | export function activate(context: vscode.ExtensionContext) { 7 | scopes.activate(context) 8 | language.activate(context) 9 | commands.activate(context) 10 | } 11 | 12 | export function deactivate() { 13 | scopes.deactivate() 14 | } 15 | -------------------------------------------------------------------------------- /src/language/completion.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import { namespace, dictionary } from './resources' 3 | 4 | export default { 5 | provideCompletionItems(document, position, token, context) { 6 | return namespace.map(name => { 7 | const completion = new vscode.CompletionItem(name) 8 | if (name.startsWith('System`')) name = name.slice(7) 9 | completion.documentation = dictionary[name].documentation 10 | return completion 11 | }) 12 | } 13 | } as vscode.CompletionItemProvider 14 | -------------------------------------------------------------------------------- /src/language/folding.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import { getWatcher } from '../scopes' 3 | 4 | export default { 5 | async provideFoldingRanges(document, context, token) { 6 | const watcher = await getWatcher(document) 7 | const result: vscode.FoldingRange[] = [] 8 | const ranges = watcher.getRangeByRegex(/^\(\* ::([a-zA-Z\d]+::)+ \*\)(\r?\n\(\*.*\*\))+$/m) 9 | for (const range of ranges) { 10 | result.push(new vscode.FoldingRange(range.start.line, range.end.line, vscode.FoldingRangeKind.Region)) 11 | } 12 | return result 13 | } 14 | } as vscode.FoldingRangeProvider 15 | -------------------------------------------------------------------------------- /src/commands/setInstallationDirectory.ts: -------------------------------------------------------------------------------- 1 | import { executeCode } from '../utilities' 2 | import * as vscode from 'vscode' 3 | 4 | export function setInstallationDirectory(global?: boolean) { 5 | const config = vscode.workspace.getConfiguration('wolfram') 6 | executeCode('$InstallationDirectory', (error, stdout, stderr) => { 7 | if (error) throw error 8 | if (stderr) throw new Error(stderr) 9 | config.update('installationDirectory', stdout.trim(), global) 10 | }) 11 | } 12 | 13 | // const config = vscode.workspace.getConfiguration('wolfram') 14 | // if (!config.get('installationDirectory')) setInstallationDirectory(true) 15 | -------------------------------------------------------------------------------- /src/language/utilities.ts: -------------------------------------------------------------------------------- 1 | export const WORD_PATTERN = /([$a-zA-Z]+[$0-9a-zA-Z]*`)*[$a-zA-Z]+[$0-9a-zA-Z]*/ 2 | 3 | export function isIdentifierScope(scope: string) { 4 | return scope.startsWith('support.function') 5 | || scope.startsWith('support.undocumented') 6 | || scope.startsWith('variable.parameter') 7 | || scope.startsWith('constant') 8 | || scope === 'variable.other.wolfram' 9 | || scope === 'variable.other.context.wolfram' 10 | } 11 | 12 | export function identifierRegex(symbols: string[]) { 13 | return new RegExp(`(?) 9 | name: comment.line.xml-template.wolfram 10 | captures: !raw 11 | 1: punctualation.definition.comment.begin.wolfram 12 | 2: punctualation.definition.comment.end.wolfram 13 | - begin: 14 | end: 15 | beginCaptures: !all punctualation.definition.comment.begin.wolfram 16 | endCaptures: !all punctualation.definition.comment.end.wolfram 17 | name: comment.block.xml-template.wolfram 18 | - !embed-in-string external.text.xml 19 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that launches the extension inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "runtimeExecutable": "${execPath}", 13 | "args": [ 14 | "--extensionDevelopmentPath=${workspaceFolder}" 15 | ], 16 | "outFiles": [ 17 | "${workspaceFolder}/out/**/*.js" 18 | ], 19 | "preLaunchTask": "npm: watch" 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /src/language/index.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | 3 | import hoverProvider from './hover' 4 | import colorProvider from './color' 5 | import foldingProvider from './folding' 6 | import completionProvider from './completion' 7 | 8 | import * as character from './features/character' 9 | 10 | export function activate(context: vscode.ExtensionContext) { 11 | character.activate(context) 12 | context.subscriptions.push( 13 | vscode.languages.registerHoverProvider('wolfram', hoverProvider), 14 | vscode.languages.registerColorProvider('wolfram', colorProvider), 15 | vscode.languages.registerFoldingRangeProvider('wolfram', foldingProvider), 16 | vscode.languages.registerCompletionItemProvider('wolfram', completionProvider), 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /src/language/hover.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import * as utils from './utilities' 3 | import { getWatcher } from '../scopes' 4 | import { dictionary } from './resources' 5 | 6 | export default { 7 | async provideHover(document, position, token) { 8 | const watcher = await getWatcher(document) 9 | const scopeToken = watcher.getScopeAt(position) 10 | if (scopeToken && !utils.isIdentifierScope(scopeToken.scopes[scopeToken.scopes.length - 1])) return 11 | const range = document.getWordRangeAtPosition(position, utils.WORD_PATTERN) 12 | let word = document.getText(range) 13 | if (word.startsWith('System`')) word = word.slice(7) 14 | if (!dictionary[word]) return 15 | return new vscode.Hover(dictionary[word], range) 16 | } 17 | } as vscode.HoverProvider 18 | -------------------------------------------------------------------------------- /src/syntax/depSet.ts: -------------------------------------------------------------------------------- 1 | interface DepItem { 2 | name: string 3 | count: number 4 | } 5 | 6 | export default class DepSet { 7 | private _list: DepItem[] = [] 8 | public checked: boolean = false 9 | 10 | public get(name: string) { 11 | return this._list.find(item => item.name === name) 12 | } 13 | 14 | public add(name: string, count: number = 1) { 15 | const item = this.get(name) 16 | if (item) { 17 | item.count += count 18 | } else { 19 | this._list.push({ name, count }) 20 | } 21 | } 22 | 23 | public count(name: string) { 24 | const item = this.get(name) 25 | return item ? item.count : 0 26 | } 27 | 28 | public each(callback: (name: string, count: number, index: number) => void) { 29 | this._list.forEach((item, index) => { 30 | callback(item.name, item.count, index) 31 | }) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/commands/index.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import { generateSyntax, checkSyntax } from './generateSyntax' 3 | import { formatWithUTF8, formatWithASCII } from './formatEncodings' 4 | import { setInstallationDirectory } from './setInstallationDirectory' 5 | 6 | export function activate(context: vscode.ExtensionContext) { 7 | function registerCommand(command: string, callback: (...args: any[]) => any) { 8 | context.subscriptions.push(vscode.commands.registerCommand(command, callback)) 9 | } 10 | 11 | registerCommand('wolfram.formatWithUTF8', formatWithUTF8) 12 | registerCommand('wolfram.formatWithASCII', formatWithASCII) 13 | registerCommand('wolfram.generateSyntax', generateSyntax) 14 | registerCommand('wolfram.setInstallationDirectory', setInstallationDirectory) 15 | 16 | context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(checkSyntax)) 17 | } 18 | -------------------------------------------------------------------------------- /src/syntax/index.ts: -------------------------------------------------------------------------------- 1 | import mergeSyntax from './mergeSyntax' 2 | import MacroParser from './macroParser' 3 | import Traverser from './traverser' 4 | 5 | export { 6 | mergeSyntax, 7 | MacroParser, 8 | Traverser, 9 | } 10 | 11 | export type Captures = Record 15 | 16 | export interface Rule { 17 | match?: string 18 | begin?: string 19 | end?: string 20 | name?: string 21 | contentName?: string 22 | include?: string 23 | captures?: Captures 24 | beginCaptures?: Captures 25 | endCaptures?: Captures 26 | patterns?: SlotRule[] 27 | } 28 | 29 | export type SlotRule = Rule | string 30 | 31 | export interface Syntax { 32 | _name: string 33 | repository: Record 34 | } 35 | 36 | export interface BaseSyntax extends Syntax { 37 | patterns: Rule[] 38 | _plugins: string[] 39 | } 40 | 41 | export type Repository = Record 42 | -------------------------------------------------------------------------------- /src/utilities/vsc-utils.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | 3 | export function showMessage(message: string, callback?: () => void) { 4 | if (callback) { 5 | return vscode.window.showInformationMessage(message, 'Yes', 'No') 6 | .then(answer => answer === 'Yes' && callback()) 7 | } else { 8 | return vscode.window.showInformationMessage(message) 9 | } 10 | } 11 | 12 | export function showError(error: string | Error) { 13 | vscode.window.showErrorMessage(error.toString()) 14 | } 15 | 16 | export function editorCommand(callback: (editor: vscode.TextEditor) => void) { 17 | return function() { 18 | const editor = vscode.window.activeTextEditor 19 | if (!editor) return 20 | if (editor.document.languageId !== 'wolfram') { 21 | showMessage('This file is probably not written in Wolfram Language.\nAre you sure you want to continue?', () => { 22 | callback(editor) 23 | }) 24 | } else { 25 | callback(editor) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /language.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | "blockComment": [ "(*", "*)" ] 4 | }, 5 | "brackets": [ 6 | ["{", "}"], 7 | ["[[", "]]"], 8 | ["[", "]"], 9 | ["(", ")"], 10 | ["<|", "|>"], 11 | ["\\!\\(", "\\)"], 12 | ["\\(", "\\)"] 13 | ], 14 | "autoClosingPairs": [ 15 | { "open": "{", "close": "}" }, 16 | { "open": "[", "close": "]" }, 17 | { "open": "\\(", "close": "\\)", "notIn": ["string", "comment"] }, 18 | { "open": "(", "close": ")" }, 19 | { "open": "<|", "close": "|>", "notIn": ["string", "comment"] }, 20 | { "open": "\"", "close": "\"", "notIn": ["string"] }, 21 | { "open": "(*", "close": "*", "notIn": ["string"] } 22 | ], 23 | "surroundingPairs": [ 24 | ["{", "}"], 25 | ["[[", "]]"], 26 | ["[", "]"], 27 | ["(", ")"], 28 | ["<|", "|>"], 29 | ["\"", "\""], 30 | ["(*", "*)"], 31 | ["\\!\\(", "\\)"], 32 | ["\\(", "\\)"] 33 | ], 34 | "wordPattern": "([$a-zA-Z]+[$0-9a-zA-Z]*`)*[$a-zA-Z]+[$0-9a-zA-Z]*" 35 | } -------------------------------------------------------------------------------- /src/scopes/textmate.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs' 2 | import * as path from 'path' 3 | import { vscRequire } from '../utilities' 4 | import * as extension from './extension' 5 | import * as Textmate from 'vscode-textmate/release/main' 6 | export * from 'vscode-textmate/release/main' 7 | 8 | const textmate: typeof Textmate = vscRequire('vscode-textmate') 9 | 10 | /** reload textmate grammars */ 11 | export function reload() { 12 | const grammars = extension.getResources('grammars', true) 13 | return new textmate.Registry({ 14 | loadGrammar(scopeName: string) { 15 | const [grammar] = grammars.filter(g => g.scopeName === scopeName) 16 | if (!grammar) return 17 | const filepath = path.join(grammar.extensionPath, grammar.path) 18 | return new Promise((resolve, reject) => { 19 | fs.readFile(filepath, 'utf8', (error, data) => { 20 | if (error) reject(error) 21 | resolve(textmate.parseRawGrammar(data, filepath)) 22 | }) 23 | }) 24 | } 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /src/syntax/macroParser.ts: -------------------------------------------------------------------------------- 1 | type Variables = Record 2 | 3 | interface MacroItem { 4 | regex: RegExp 5 | target: string 6 | } 7 | 8 | export default class MacroParser { 9 | macros: MacroItem[] 10 | 11 | constructor(variables: Variables = {}) { 12 | this.macros = [] 13 | this.push(variables) 14 | } 15 | 16 | push(variables: Variables): this { 17 | for (const key in variables) { 18 | this.macros.push({ 19 | regex: new RegExp(`{{${key}}}`, 'g'), 20 | target: this.resolve(variables[key]), 21 | }) 22 | } 23 | return this 24 | } 25 | 26 | resolve(source: string): string { 27 | let output = source 28 | for (const macro of this.macros) { 29 | output = output.replace(macro.regex, macro.target) 30 | } 31 | return output 32 | } 33 | 34 | clone(variables: Variables = {}): MacroParser { 35 | const parser = new MacroParser() 36 | parser.macros = this.macros.slice() 37 | return parser.push(variables) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/scopes/theme.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import * as extension from './extension' 3 | import * as textmate from './textmate' 4 | 5 | interface ThemeItem { 6 | scope: string, 7 | settings: textmate.IRawThemeSetting["settings"] 8 | } 9 | 10 | let themes: ThemeItem[] 11 | 12 | function* parseThemeData(theme: extension.ThemeData): IterableIterator { 13 | for (const { scope, settings } of theme.tokenColors) { 14 | if (!scope) continue 15 | const scopes = typeof scope === 'string' ? scope.split(/, */g) : scope 16 | for (const scope of scopes) { 17 | yield { scope, settings } 18 | } 19 | } 20 | } 21 | 22 | export function reload() { 23 | const label = vscode.workspace.getConfiguration().get('workbench.colorTheme') as string 24 | themes = Array.from(parseThemeData(extension.getTheme(label).data)) 25 | } 26 | 27 | export function getTokenColor(...scopes: string[]) { 28 | for (const scope of scopes.reverse()) { 29 | const result = themes.find(t => scope.startsWith(t.scope)) 30 | if (result) return result.settings 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Shigma 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 all 13 | 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 THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /demo/type-inference.wl: -------------------------------------------------------------------------------- 1 | Map[func, list] 2 | Map[fu nc, list] 3 | 4 | SortBy[func] 5 | SortBy[fu nc] 6 | SortBy[list, func] 7 | SortBy[list, fu nc] 8 | 9 | Block[{x, y}z, w] 10 | Block[{x = 1, y, {x}}] 11 | 12 | Function[a] 13 | Function[a, v] 14 | Function[{a, v}] 15 | Function[{a, {c}, b}, a] 16 | Function[{a, {c}, b}d, a] 17 | 18 | Compile[{{a, b}, {{c}}, {d}, e}] 19 | Compile[{a, b}c] 20 | 21 | Limit[a, b -> c, d -> e] 22 | Limit[a, {b, c} -> {d, e}] 23 | Limit[a, {b, c}x -> {d, e}] 24 | Limit[a, {b -> c}, d -> e] 25 | Limit[a, {b -> c}y, d -> e] 26 | 27 | AsymptoticEquivalent[a, b -> c, d -> e, f -> g] 28 | AsymptoticEquivalent[a, b, {c -> d, e -> f}] 29 | AsymptoticEquivalent[a, b, {c, d} -> {e, f}] 30 | AsymptoticEquivalent[a, b, {c -> d, e -> f}x] 31 | AsymptoticEquivalent[a, b, {c, d}y -> {e, f}] 32 | 33 | Solve[a, x, b] 34 | Solve[a, {x, y}, b] 35 | Solve[a, {x, y}z, b] 36 | 37 | FourierTransform[a, b, {c, d + e}, f * g , {h}i, j] 38 | 39 | Grad[func, x, p] 40 | Grad[func, {x, y, z}t, p] 41 | Grad[x ^ 2, {x, y, z}, p] 42 | 43 | Plot[a, {x, b}, {y, c, d}, z] 44 | NDSolve[a, a, {x, b}, {y, c, d}] 45 | Sum[func, a, {b, c}, d, {e, f}] 46 | 47 | Manipulate[expr, {u, u1, u2}, {{v, v0}, {x, y, z}}, w] 48 | -------------------------------------------------------------------------------- /src/scopes/index.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import * as theme from './theme' 3 | import * as workspace from './workspace' 4 | import * as extension from './extension' 5 | import { showError } from '../utilities/vsc-utils' 6 | 7 | let activated = false 8 | function wrapScopeAPI any>(callback: T) { 9 | return ((...args) => { 10 | try { 11 | if (!activated) throw new Error('API havn\'t been activated.') 12 | return callback(...args) 13 | } catch (error) { 14 | showError(error) 15 | } 16 | }) as T 17 | } 18 | 19 | export const getWatcher = wrapScopeAPI(workspace.getWatcher) 20 | 21 | export const getGrammar = wrapScopeAPI(extension.getGrammar) 22 | export const getScopeForLanguage = wrapScopeAPI(extension.getScopeForLanguage) 23 | 24 | export function activate(context: vscode.ExtensionContext) { 25 | context.subscriptions.push( 26 | vscode.workspace.onDidOpenTextDocument(d => workspace.open(d)), 27 | vscode.workspace.onDidCloseTextDocument(d => workspace.close(d)), 28 | vscode.workspace.onDidChangeConfiguration(() => theme.reload()), 29 | ) 30 | 31 | theme.reload() 32 | workspace.reload() 33 | activated = true 34 | } 35 | 36 | export function deactivate() { 37 | activated = false 38 | workspace.unload() 39 | } 40 | -------------------------------------------------------------------------------- /syntaxes/smart-comments.yaml: -------------------------------------------------------------------------------- 1 | include: true 2 | 3 | contexts: 4 | slot:comments: 5 | # paclet info 6 | - begin: '(\(\*) +(:\w[ \w-]*\w:)\s+' 7 | beginCaptures: !raw 8 | 1: punctualation.definition.comment.begin.wolfram 9 | 2: storage.type.paclet-info.wolfram 10 | name: comment.block.paclet-info.wolfram 11 | contentName: variable.other.paclet-info.wolfram 12 | end: \*\) 13 | endCaptures: !all punctualation.definition.comment.end.wolfram 14 | patterns: !push comment-block 15 | 16 | # cell styles 17 | - !cell-style 18 | target: Section|Subsection|Subsubsection|Title|Subtitle|Subsubtitle|Chapter|Subchapter|Subsubchapter 19 | contentName: markup.heading.wolfram 20 | - !cell-style 21 | target: Input 22 | patterns: !push expressions.in-comment 23 | # - !cell-style 24 | # target: '{{alnum}}+' 25 | # contentName: variable.other.text.wolfram 26 | 27 | # special comment syntaxes cannot be embedded in strings and comments 28 | slot:comments.in-string: [] 29 | slot:comments.in-comment: [] 30 | 31 | newline-escape.in-comment: 32 | - match: (\\)(\*\))\r?\n(\(\*) 33 | captures: !raw 34 | 1: constant.character.escape.wolfram 35 | 2: punctualation.definition.comment.begin.wolfram 36 | 3: punctualation.definition.comment.end.wolfram 37 | -------------------------------------------------------------------------------- /src/language/resources.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | 3 | const systemSymbols: string[] = require('../resources/system') 4 | const addonsSymbols: string[] = require('../resources/addons') 5 | const usages: Record = require('../resources/usages') 6 | export const dictionary: Record = {} 7 | 8 | export const namespace: string[] = [ 9 | ...systemSymbols, 10 | ...systemSymbols.map(name => 'System`' + name), 11 | ...addonsSymbols, 12 | ] 13 | 14 | class UsageItem extends Array { 15 | readyForNextBlock = false 16 | documentation = new vscode.MarkdownString() 17 | 18 | constructor() { 19 | super(new vscode.MarkdownString()) 20 | } 21 | 22 | append(...texts: string[]) { 23 | texts.forEach(text => { 24 | if (text.startsWith('code: ')) { 25 | if (this.readyForNextBlock) { 26 | this.push(new vscode.MarkdownString()) 27 | this.readyForNextBlock = false 28 | } 29 | this.documentation.appendCodeblock(text.slice(6), 'wolfram') 30 | this[this.length - 1].appendCodeblock(text.slice(6), 'wolfram') 31 | } else { 32 | if (text.startsWith('text: ')) text = text.slice(6) 33 | this.documentation.appendMarkdown(text) 34 | this[this.length - 1].appendMarkdown(text) 35 | this.readyForNextBlock = true 36 | } 37 | }) 38 | } 39 | } 40 | 41 | for (const name in usages) { 42 | const usage = new UsageItem() 43 | const context = name.match(/[\w`]+`/) 44 | if (context) usage.append(`From package: **${context[0]}**.\n\n`) 45 | usage.append(...usages[name]) 46 | dictionary[name] = usage 47 | } 48 | -------------------------------------------------------------------------------- /src/utilities/index.ts: -------------------------------------------------------------------------------- 1 | import * as cp from 'child_process' 2 | import * as path from 'path' 3 | import * as fs from 'fs' 4 | 5 | export function fullPath(...filenames: string[]) { 6 | return path.resolve(__dirname, '../..', ...filenames) 7 | } 8 | 9 | export function vscPath(...filenames: string[]) { 10 | let basePath: string 11 | if (process.argv0.endsWith(path.join('Microsoft VS Code', 'Code.exe'))) { 12 | basePath = path.join(require.main.filename, '../../../..') 13 | } else { 14 | let paths = process.env.PATH.match(/(;|^)[^;]+Microsoft VS Code[\\/]bin(;|$)/g) 15 | if (!paths) return 16 | paths = paths.map(str => str.replace(/;/g, '')) 17 | basePath = paths.find(str => str.startsWith(process.env.LOCALAPPDATA)) || paths[0] 18 | } 19 | return path.resolve(basePath, 'resources/app', ...filenames) 20 | } 21 | 22 | export function vscRequire(filename: string) { 23 | return require(vscPath('node_modules.asar', filename)) 24 | } 25 | 26 | export function mkdir(...filenames: string[]) { 27 | const filepath = fullPath(...filenames) 28 | if (fs.existsSync(filepath)) return 29 | fs.mkdirSync(filepath) 30 | } 31 | 32 | type executeCallback = (error: cp.ExecException, stdout: string, stderr: string) => void 33 | 34 | function wrapScript(script: string) { 35 | return '"' + script.replace(/"/g, '\\"') + '"' 36 | } 37 | 38 | export function executeCode(script: string, callback: executeCallback) { 39 | return cp.exec(`wolframscript -c ${wrapScript(script)}`, callback) 40 | } 41 | 42 | export function executeFile(filename: string, args: string[], callback: executeCallback) { 43 | return cp.exec(`wolframscript -f "${fullPath(filename)}" ` + args.map(wrapScript).join(' '), callback) 44 | } 45 | -------------------------------------------------------------------------------- /src/syntax/minifySyntax.ts: -------------------------------------------------------------------------------- 1 | import DepSet from './depSet' 2 | import Traverser from './traverser' 3 | import { Rule, Repository } from '.' 4 | 5 | export default function minifySyntax(patterns: Rule[], repository: Repository) { 6 | /** dependencies for all repositories */ 7 | const deps: Record = {} 8 | 9 | for (const key in repository) { 10 | const { include, patterns } = repository[key] 11 | deps[key] = new DepSet() 12 | 13 | if (include && include[0] === '#') { 14 | deps[key].add(include.slice(1)) 15 | continue 16 | } 17 | 18 | if (!patterns) continue 19 | repository[key].patterns = new Traverser({ 20 | * onString() {}, 21 | onInclude(include) { 22 | if (include[0] === '#') deps[key].add(include.slice(1)) 23 | return include 24 | }, 25 | }).traverse(patterns) 26 | } 27 | 28 | /** dependencies for root patterns */ 29 | const rootDep = new DepSet() 30 | 31 | function collectDep(context: string, count: number = 1) { 32 | const depSet = deps[context] 33 | if (!depSet) return 34 | rootDep.add(context, count) 35 | if (depSet.checked) return 36 | depSet.checked = true 37 | depSet.each(collectDep) 38 | } 39 | 40 | for (const rule of patterns) { 41 | if (rule.include && rule.include[0] === '#') { 42 | collectDep(rule.include.slice(1)) 43 | } 44 | } 45 | 46 | return new Traverser({ 47 | onContext: name => rootDep.count(name) > 1, 48 | onInclude(include) { 49 | const name = include.slice(1) 50 | const count = rootDep.count(name) 51 | if (!count) return 52 | return count === 1 ? this.repository[name] : include 53 | }, 54 | }).traverseAll(repository) 55 | } 56 | -------------------------------------------------------------------------------- /src/language/color.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import { getWatcher } from '../scopes' 3 | 4 | const colorMap = { 5 | Red: new vscode.Color(1, 0, 0, 1), 6 | Green: new vscode.Color(0, 1, 0, 1), 7 | Blue: new vscode.Color(0, 0, 1, 1), 8 | Black: new vscode.Color(0, 0, 0, 1), 9 | White: new vscode.Color(1, 1, 1, 1), 10 | Gray: new vscode.Color(.5, .5, .5, 1), 11 | Cyan: new vscode.Color(0, 1, 1, 1), 12 | Magenta: new vscode.Color(1, 0, 1, 1), 13 | Yellow: new vscode.Color(1, 1, 0, 1), 14 | Orange: new vscode.Color(1, .5, 0, 1), 15 | Pink: new vscode.Color(1, .5, .5, 1), 16 | Purple: new vscode.Color(.5, 0, .5, 1), 17 | Brown: new vscode.Color(.6, .4, .2, 1), 18 | LightRed: new vscode.Color(1, .85, .85, 1), 19 | LightGreen: new vscode.Color(.85, 1, .85, 1), 20 | LightBlue: new vscode.Color(.85, .85, 1, 1), 21 | LightGray: new vscode.Color(.85, .85, .85, 1), 22 | LightCyan: new vscode.Color(.9, 1, 1, 1), 23 | LightMagenta: new vscode.Color(1, .9, 1, 1), 24 | LightYellow: new vscode.Color(1, 1, .9, 1), 25 | LightOrange: new vscode.Color(1, .9, .8, 1), 26 | LightPink: new vscode.Color(1, .925, .925, 1), 27 | LightPurple: new vscode.Color(.94, 0.88, .94, 1), 28 | LightBrown: new vscode.Color(.94, .91, .88, 1), 29 | Transparent: new vscode.Color(0, 0, 0, 0), 30 | } as Record 31 | 32 | export default { 33 | provideColorPresentations(color, { document, range }, token) { 34 | return null 35 | }, 36 | async provideDocumentColors(document, token) { 37 | const result: vscode.ColorInformation[] = [] 38 | const watcher = await getWatcher(document) 39 | for (const range of watcher.getRangeByScope('constant.language.wolfram')) { 40 | const color = colorMap[document.getText(range)] 41 | if (color) result.push({ range, color }) 42 | } 43 | return result 44 | }, 45 | } as vscode.DocumentColorProvider 46 | -------------------------------------------------------------------------------- /src/commands/generateSyntax.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import mergeSyntax from '../syntax/mergeSyntax' 3 | import { showMessage } from '../utilities/vsc-utils' 4 | 5 | function getCurrentPlugins() { 6 | const plugins: string[] = [] 7 | const config = vscode.workspace.getConfiguration('wolfram') 8 | if (config.get('syntax.simplestMode')) return 9 | if (config.get('syntax.xmlTemplate')) plugins.push('xml-template') 10 | if (config.get('syntax.typeInference')) plugins.push('type-inference') 11 | if (config.get('syntax.smartComments')) plugins.push('smart-comments') 12 | return plugins 13 | } 14 | 15 | function isSyntaxUpdated(plugins: string[]) { 16 | delete require.cache[require.resolve('../syntax')] 17 | const syntax = require('../syntax') 18 | if (!plugins) return syntax._name === 'simplest' 19 | if (syntax._name === 'simplest') return false 20 | const _plugins = new Set(syntax._plugins) 21 | return _plugins.size === plugins.length && plugins.every(name => _plugins.has(name)) 22 | } 23 | 24 | export function generateSyntax(forced = false) { 25 | const plugins = getCurrentPlugins() 26 | if (!forced && isSyntaxUpdated(plugins)) { 27 | showMessage('The syntax file is consistent with your configuration. There is no need to regenerate.') 28 | return 29 | } 30 | if (plugins) { 31 | mergeSyntax(require('../syntaxes/basic'), plugins.map(name => require('../syntaxes/' + name))) 32 | } else { 33 | mergeSyntax(require('../syntaxes/simplest')) 34 | } 35 | showMessage('The syntax file has just been regenerated and will take effect after reload.\nDo you want to reload vscode now?', () => { 36 | vscode.commands.executeCommand('workbench.action.reloadWindow') 37 | }) 38 | } 39 | 40 | export function checkSyntax() { 41 | if (!isSyntaxUpdated(getCurrentPlugins())) { 42 | showMessage('The syntax file currently in use is not consistent with some configurations.\nDo you want to regenerated syntax file now?', () => { 43 | generateSyntax(true) 44 | }) 45 | } 46 | } 47 | 48 | checkSyntax() 49 | -------------------------------------------------------------------------------- /src/scopes/workspace.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import * as textmate from './textmate' 3 | import DocumentWatcher from './document' 4 | import { getScopeForLanguage } from './extension' 5 | 6 | let registry: textmate.Registry 7 | let target = null 8 | export const documents = new Map() 9 | 10 | export function filter(languages: string[] | null) { 11 | target = languages 12 | vscode.workspace.textDocuments.forEach(d => open(d, false)) 13 | } 14 | 15 | export async function open(document: vscode.TextDocument, refresh = true) { 16 | const { uri, languageId } = document 17 | const watcher = documents.get(uri) 18 | const valid = target ? target.includes(languageId) : true 19 | if (watcher) { 20 | if (valid) { 21 | if (refresh) watcher.refresh() 22 | } else { 23 | watcher.dispose() 24 | documents.delete(uri) 25 | } 26 | } else { 27 | if (!valid) return 28 | const scopeName = getScopeForLanguage(languageId) 29 | if (!scopeName) return 30 | if (!registry) throw new Error('No textmate registry.') 31 | const grammar = await registry.loadGrammar(scopeName) 32 | const watcher = new DocumentWatcher(document, grammar) 33 | documents.set(uri, watcher) 34 | return watcher 35 | } 36 | } 37 | 38 | export function close(document: vscode.TextDocument) { 39 | const watcher = documents.get(document.uri) 40 | if (!watcher) return 41 | watcher.dispose() 42 | documents.delete(document.uri) 43 | } 44 | 45 | export function unload() { 46 | for (const watcher of documents.values()) { 47 | watcher.dispose() 48 | } 49 | documents.clear() 50 | } 51 | 52 | export function reload() { 53 | registry = textmate.reload() 54 | unload() 55 | vscode.workspace.textDocuments.forEach(d => open(d)) 56 | } 57 | 58 | function wrapAPI any>(callback: T) { 59 | return async ( 60 | document: vscode.TextDocument, 61 | ...args: (T extends (watcher: DocumentWatcher, ...args: infer R) => any ? R : any) 62 | ) => { 63 | const watcher = documents.get(document.uri) || await open(document) 64 | return callback(watcher, ...args) as ReturnType 65 | } 66 | } 67 | 68 | export async function getWatcher(document: vscode.TextDocument) { 69 | return documents.get(document.uri) || await open(document) 70 | } 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wolfram Language 2 | 3 | [Wolfram Language](https://reference.wolfram.com/language) support for [Visual Studio Code](https://code.visualstudio.com/). 4 | 5 | ## Features 6 | 7 | - All syntaxes from Wolfram Language. 8 | - Updated documentations and completions. 9 | - Color provider, diagnostics, ... 10 | 11 | Demostration files can be seen under the *demo* folder. 12 | 13 | ### Supported Symbols 14 | 15 | This extension uses specified crawler to get symbols and their usage from [Mathematica](http://www.wolfram.com/mathematica). 16 | 17 | | Category | System Symbols | AddOns Symbols | 18 | |:--------:|:--------------:|:--------------:| 19 | | Function | 4169 | 953 | 20 | | Constant | 437 | 1030 | 21 | | Option | 973 | 260 | 22 | | Undocumented | 611 | 408 | 23 | | Total | 6190 | 2651 | 24 | 25 | ### Syntax Plugins 26 | 27 | (*Invented in v1.3*) This extension also provided some syntax plugins which will enrich the coloring behaviour. These plugins can be found in settings: 28 | 29 | - `wolfram.syntax.xmlTemplate`: Support XML template syntax in special functions (experimental). 30 | - `wolfram.syntax.typeInference`: Support type inference in special functions. It uses recursive subpattern, which may cause regular expression denial of service, although it is not common. 31 | 32 | Once configuration was changed, this extension will notify you to regenerate the syntax file. You can also use command `wolfram.generateSyntaxFile` to generate syntax file. 33 | 34 | ### Simplest Mode 35 | 36 | (*Invented in v1.4*) If you don't like the coloring for built-in symbols, or if you find a coloring problem in some files and don't know what caused the problem, please have a look at the simplest mode. Set `wolfram.syntax.simplestMode` to `true`, the extension will regenerate the simplest syntax definitions for you. 37 | 38 | | Syntax Package | Minified Size | 39 | |:--------------:|:-------------:| 40 | | Simplest Mode | 8.46 KB | 41 | | Basic Syntax | 226.63 KB | 42 | | Type Inference Plugin | 36.17 KB | 43 | | XML Template Plugin | 29.86 KB | 44 | 45 | We also provide a well-commented [source file](src/syntaxes/simplest.yaml) and a [syntax overview](docs/syntax-overview.md) for the simplest mode. 46 | 47 | ## Documentations 48 | 49 | - [Syntax Overview for Wolfram Language](docs/syntax-overview.md) 50 | - [Extended YAML Schema for Syntax Definitions](docs/yaml-schema.md) 51 | 52 | ## TODOS 53 | 54 | - Add some code snippets. 55 | - A better usage document. 56 | - Add syntax definition for `.tm` and `.tr` files. 57 | - Add icons for all wolfram language related file extensions. 58 | 59 | Any issues or pull requests are welcomed! 60 | -------------------------------------------------------------------------------- /demo/basic-syntax.wl: -------------------------------------------------------------------------------- 1 | 2 | (* Numbers and constants *) 3 | 4 | (* Base form, precision, accuracy and scientific notations *) 5 | 6 | 11^^1a.a1`11*^-11 7 | 8 | (* Arithmetic operators *) 9 | 10 | 1 + 1 - 1 * 1 / 1 ^ 1 11 | 12 | (* Logical and comparison operators *) 13 | 14 | 1 > 2 || 3 <= 4 && 5 =!= 6 15 | 16 | (* Built-in constants and symbols *) 17 | 18 | True 19 | Center 20 | $Failed 21 | 22 | 23 | (* Strings and boxes *) 24 | 25 | (* Raw String with named, escaped, encoded and invalid characters *) 26 | 27 | "This is a \`string` (* not \a comment *)" 28 | "\[Alpha] \:abcd \.ef \123 \[foo] \678 \:012 \.3x" 29 | 30 | (* String templates in messages and spectified functions *) 31 | 32 | foo::bar::lang = "Here is an `argument`." 33 | StringTemplate["Value `a`: <* Range[#n] *>."][<|"a" -> 1234, "n" -> 3|>] 34 | 35 | (* String operators and expressions *) 36 | 37 | "foo" <> "bar" ~~ "baz" 38 | 39 | (* Strings inside RegularExpression will be specially displayed *) 40 | 41 | RegularExpression["^a\n.+?\\^[^a-f*]{2,}"] 42 | RegularExpression["(?=[^[:alpha:]](\\d*))\\g1\\2"] 43 | 44 | (* Box representation are also supported *) 45 | 46 | "box 1: \!\(x\^2\); box 2: \(y\^3\) " 47 | 48 | \(x + \*GridBox[{ 49 | {"a", "b"}, 50 | {"c", \(d \^ 2\)} 51 | }]\) 52 | 53 | 54 | (* Variables and patterns *) 55 | 56 | (* A variable may contain backquotes and dollars *) 57 | 58 | my`context12`$foo$bar 59 | 60 | (* Using Put and Get *) 61 | 62 | << EquationTrekker` 63 | >>> OutputFile.mx 64 | 65 | (* Patterns with Default, Blank, Optional and PatternTest *) 66 | 67 | _.; _h; __h; ___h 68 | var: patt ? EvenQ : foo 69 | 70 | (* Built-in options are also displayed like parameters *) 71 | 72 | Image[Red, Interleaving -> True] 73 | 74 | 75 | (* Assignments and rules *) 76 | 77 | (* Declaration for functions *) 78 | 79 | f[x_ ? TrueQ, y_ /; Negative[y], z, OptionsPattern[]] := 2x /; y > 0; 80 | 81 | (* Rules *) 82 | 83 | foo /. { foo :> 1, bar :> 2 } 84 | foo //. { foo -> 1, bar -> 2 } 85 | Graph[{ 1 <-> 2, 2 -> 3, 3 -> 1 }] 86 | 87 | 88 | (* Brackets *) 89 | 90 | <| |> 91 | [ ] 92 | { } 93 | ( ) 94 | [[ ]] 95 | 96 | (* Parts should not be confused with function calls *) 97 | 98 | list[[]] 99 | func[ []] 100 | 101 | 102 | (* Functions *) 103 | 104 | (* Functional operators can change the color of neighbouring variables *) 105 | 106 | foo @ bar; foo @@ bar; foo @@@ bar; foo // bar 107 | bar /@ {}; foo //@ {}; foo /* bar; foo @* bar 108 | foo ~ bar ~ foo ~ Plus ~ foo 109 | 110 | (* But built-in functions are always displayed as usual *) 111 | 112 | cos[[Pi]]; cos[Pi] 113 | Cos[Pi]; System`Cos[Pi] 114 | 115 | 116 | (* Other notations *) 117 | 118 | (* Out *) 119 | 120 | %%%; %12 121 | 122 | (* Definition *) 123 | 124 | ?foo; ??bar 125 | 126 | (* comment (* another comment *) *) 127 | -------------------------------------------------------------------------------- /src/language/features/character.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import { getWatcher } from '../../scopes' 3 | 4 | const collection = vscode.languages.createDiagnosticCollection('wolfram.characters') 5 | 6 | async function updateDiagnostics(document: vscode.TextDocument) { 7 | if (document.languageId !== 'wolfram') return 8 | const watcher = await getWatcher(document) 9 | collection.set(document.uri, [ 10 | ...Array.from(watcher.getRangeByScope('invalid.character.encoding.wolfram')).map(range => { 11 | let message: string, code: string 12 | switch (document.getText(range)[1]) { 13 | case '.': message = 'Invalid encoded character: 2 hexadecimal digits are expected.'; code = '001'; break 14 | case ':': message = 'Invalid encoded character: 4 hexadecimal digits are expected.'; code = '002'; break 15 | default: message = 'Invalid encoded character: 3 octal digits are expected.'; code = '000' 16 | } 17 | return { 18 | code, 19 | range, 20 | message, 21 | source: 'wolfram', 22 | severity: vscode.DiagnosticSeverity.Error, 23 | } 24 | }), 25 | ...Array.from(watcher.getRangeByScope('invalid.character.built-in.wolfram')).map(range => ({ 26 | code: '003', 27 | range, 28 | message: 'Invalid named character: cannot recognize character name.', 29 | source: 'wolfram', 30 | severity: vscode.DiagnosticSeverity.Error, 31 | })), 32 | ]) 33 | } 34 | 35 | export const codeActionProvider: vscode.CodeActionProvider = { 36 | provideCodeActions(document, range, { diagnostics }, token) { 37 | const actions: vscode.CodeAction[] = [] 38 | diagnostics.forEach(diagnostic => { 39 | const code = Number(diagnostic.code) 40 | if (code < 3) { 41 | const edit = new vscode.WorkspaceEdit() 42 | const range = diagnostic.range 43 | const text = document.getText(range).slice(1) 44 | const newText = code === 0 45 | ? '\\' + text.padStart(3, '0') 46 | : code === 1 47 | ? '\\.' + text.slice(1).padStart(2, '0') 48 | : '\\:' + text.slice(1).padStart(4, '0') 49 | edit.set(document.uri, [new vscode.TextEdit(range, newText)]) 50 | actions.push({ 51 | edit, 52 | title: `Do you mean "${newText}"?`, 53 | kind: vscode.CodeActionKind.QuickFix, 54 | diagnostics: [diagnostic], 55 | }) 56 | } 57 | }) 58 | return actions 59 | } 60 | } 61 | 62 | export function activate(context: vscode.ExtensionContext) { 63 | if (vscode.window.activeTextEditor) { 64 | updateDiagnostics(vscode.window.activeTextEditor.document) 65 | } 66 | 67 | context.subscriptions.push( 68 | vscode.languages.registerCodeActionsProvider('wolfram', codeActionProvider), 69 | vscode.workspace.onDidChangeTextDocument(e => updateDiagnostics(e.document)), 70 | ) 71 | } 72 | -------------------------------------------------------------------------------- /src/commands/formatEncodings.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import { editorCommand } from '../utilities/vsc-utils' 3 | 4 | const characters = require('../resources/characters') as [number, string][] 5 | 6 | interface Replacement { 7 | start: number 8 | end: number 9 | code: number 10 | } 11 | 12 | export const formatWithUTF8 = editorCommand(editor => { 13 | const document = editor.document 14 | const replacements: Replacement[] = [] 15 | let text = document.getText(), end = 0 16 | 17 | while (text.length) { 18 | const match = text.match(/(\\+)(:[\da-fA-F]{4}|\.[\da-fA-F]{2}|\d{3}|\[\w+\])/) 19 | if (!match) break 20 | const start = end + match.index 21 | end = start + match[0].length 22 | text = text.slice(match.index + match[0].length) 23 | if (match[1].length % 2 === 0) continue 24 | 25 | let code: number 26 | switch (match[2][0]) { 27 | // named characters 28 | case '[': { 29 | const name = match[2].slice(1, -1) 30 | const item = characters.find(item => item[1] === name) 31 | if (!item) continue 32 | code = item[0] 33 | break 34 | } 35 | 36 | // hexidecimal unicode characters 37 | case ':': 38 | case '.': 39 | code = parseInt(match[2].slice(1), 16) 40 | break 41 | 42 | // octal unicode characters 43 | default: 44 | code = parseInt(match[2], 8) 45 | } 46 | 47 | replacements.push({ start, end, code }) 48 | } 49 | 50 | editor.edit(builder => { 51 | replacements.forEach(({ start, end, code }) => { 52 | // control characters don't escape 53 | if (code <= 0x1f || code >= 0x7f && code <= 0x9f) return 54 | const range = new vscode.Range(document.positionAt(start), document.positionAt(end)) 55 | builder.replace(range, String.fromCharCode(code)) 56 | }) 57 | }) 58 | }) 59 | 60 | export const formatWithASCII = editorCommand(editor => { 61 | const document = editor.document 62 | const config = vscode.workspace.getConfiguration() 63 | const useNamedCharacters = config.get('wolfram.formatter.namedCharacters') 64 | const extendedAsciiMethod = config.get('wolfram.formatter.extendedAscii') 65 | 66 | editor.edit(builder => { 67 | document.getText().split('').forEach((char, index) => { 68 | const code = char.charCodeAt(0) 69 | // basic ascii characters don't escape 70 | if (code <= 0x7f) return 71 | const range = new vscode.Range(document.positionAt(index), document.positionAt(index + 1)) 72 | 73 | // use named characters if possible 74 | if (useNamedCharacters) { 75 | const item = characters.find(item => item[0] === code) 76 | if (item) { 77 | builder.replace(range, '\\[' + item[1] + ']') 78 | return 79 | } 80 | } 81 | 82 | // 4-digit hexidecimal for big unicode characters 83 | if (code > 0xff) { 84 | builder.replace(range, '\\:' + code.toString(16).padStart(4, '0')) 85 | return 86 | } 87 | 88 | // extended ascii characters 89 | switch (extendedAsciiMethod) { 90 | case '3-digit octal': return builder.replace(range, '\\' + code.toString(8)) 91 | case '2-digit hexidecimal': return builder.replace(range, '\\.' + code.toString(16)) 92 | case '4-digit hexidecimal': return builder.replace(range, '\\:00' + code.toString(16)) 93 | } 94 | }) 95 | }) 96 | }) 97 | -------------------------------------------------------------------------------- /src/scopes/extension.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs' 2 | import * as path from 'path' 3 | import * as vscode from 'vscode' 4 | import * as textmate from './textmate' 5 | import * as StripComments from 'strip-json-comments' 6 | import { vscRequire } from '../utilities' 7 | 8 | const stripComments: typeof StripComments = vscRequire('strip-json-comments') 9 | 10 | namespace Contributes { 11 | export interface Grammar { 12 | language?: string 13 | scopeName?: string 14 | path?: string 15 | injectTo?: string[] 16 | embeddedLanguages?: { 17 | [scopeName: string]: string 18 | } 19 | } 20 | 21 | export interface Language { 22 | id: string 23 | configuration: string 24 | } 25 | 26 | export interface Theme { 27 | label: string 28 | uiTheme: string 29 | path: string 30 | } 31 | } 32 | 33 | interface ContributeMap { 34 | languages: Contributes.Language 35 | grammars: Contributes.Grammar 36 | themes: Contributes.Theme 37 | } 38 | 39 | interface DataMap { 40 | languages: {} 41 | grammars: textmate.IRawGrammar 42 | themes: ThemeData 43 | } 44 | 45 | export interface ThemeData { 46 | name: string 47 | type: string 48 | colors: Record 49 | tokenColors: textmate.IRawThemeSetting[] 50 | } 51 | 52 | type ContributeItem = (ContributeMap[K] & { 53 | extensionPath?: string 54 | data?: DataMap[K] 55 | }) 56 | 57 | export type Language = ContributeItem<"languages"> 58 | export type Grammar = ContributeItem<"grammars"> 59 | export type Theme = ContributeItem<"themes"> 60 | 61 | export interface Extension extends vscode.Extension { 62 | packageJSON: { 63 | contributes?: { 64 | [key in keyof ContributeMap]: ContributeMap[key][] 65 | } 66 | } 67 | } 68 | 69 | const contributes: { [K in keyof ContributeMap]?: ContributeItem[] } = {} 70 | 71 | export function getResources(key: K, forced = false) { 72 | if (!forced && key in contributes) return contributes[key] 73 | const resources: ContributeItem[] = [] 74 | for (const extension of vscode.extensions.all) { 75 | const { extensionPath, packageJSON } = extension as Extension 76 | if (!packageJSON || !packageJSON.contributes || !packageJSON.contributes[key]) continue 77 | resources.push(...(packageJSON.contributes[key] as ContributeMap[K][]).map(resource => { 78 | return Object.assign(resource, { extensionPath }) 79 | })) 80 | } 81 | return (contributes[key] as ContributeItem[]) = resources 82 | } 83 | 84 | export function getScopeForLanguage(languageId: string): string { 85 | const languages = getResources('grammars').filter(g => g.language === languageId) 86 | return languages[0] && languages[0].scopeName 87 | } 88 | 89 | export function getGrammar(scopeName: string) { 90 | const [grammar] = getResources('grammars').filter(g => g.scopeName === scopeName) 91 | if (!grammar) return 92 | const filepath = path.join(grammar.extensionPath, grammar.path) 93 | grammar.data = JSON.parse(stripComments(fs.readFileSync(filepath, 'utf8'))) 94 | return grammar 95 | } 96 | 97 | export function getTheme(label: string) { 98 | const [theme] = getResources('themes').filter(g => g.label === label) 99 | if (!theme) return 100 | const filepath = path.join(theme.extensionPath, theme.path) 101 | theme.data = JSON.parse(stripComments(fs.readFileSync(filepath, 'utf8'))) 102 | return theme 103 | } 104 | -------------------------------------------------------------------------------- /docs/yaml-schema.md: -------------------------------------------------------------------------------- 1 | # Extended YAML Schema for Syntax Definitions 2 | 3 | The syntax definition for Wolfram Language is very complicated. It is hard to write such a big JSON file with expansibility and maintainability, so I use YAML to define all the syntaxes and use some special [tags](https://yaml.org/spec/1.2/spec.html#id2761292) to simplify the writing process. This page will tell you how the extended schema works. 4 | 5 | All the source files can be found under directory [*build/types*](../build/types). 6 | 7 | ## Glossary 8 | 9 | Every tag has its own environment and parameter type. In this schema, there are 4 environments and 3 parameter types. 10 | 11 | ### Tag Environments 12 | 13 | 1. regex: used under `match`, `begin` and `end`. 14 | 2. capture: used under `captures`, `beginCaptures` and `endCaptures`. 15 | 3. rule: used as a wrapper of a rule under a list of patterns. 16 | 4. context: used under `patterns` or as the root of a context. 17 | 18 | ### Tag Parameter Types 19 | 20 | 1. scalar: parameters are written in a line separated by whitespaces after the tag name. 21 | 2. sequence: parameters are witten in a list under the tag name. 22 | 3. mapping: parameters are witten in an object under the tag name. 23 | 24 | ## Basic Types 25 | 26 | There are some basic types in the schema. These types are all very simple and easy to use. [*src/syntaxes/simplest.yaml*](../src/syntaxes/simplest.yaml) only uses tags from this category so it may serves as a perfect example of how to use these tags. 27 | 28 | ### raw 29 | 30 | *(capture/mapping)* It traverses all the captures and turn every string value into objects. For example: 31 | 32 | ```yaml 33 | endCaptures: !raw 34 | 1: '#function-identifier' 35 | 2: keyword.operator.call.wolfram 36 | ``` 37 | will be transpiled into 38 | ```json 39 | { 40 | "endCaptures": { 41 | "1": { "patterns": [{ "include": "#function-identifier" }] }, 42 | "2": { "name": "keyword.operator.call.wolfram" } 43 | } 44 | } 45 | ``` 46 | 47 | ### all 48 | 49 | *(capture/scalar)* It turns the parameter into the only capture of the regex. For Example: 50 | 51 | ```yaml 52 | beginCaptures: !all keyword.operator.call.wolfram 53 | ``` 54 | will be transpiled into 55 | ```json 56 | { 57 | "beginCaptures": { 58 | "0": { "name": "keyword.operator.call.wolfram" } 59 | } 60 | } 61 | ``` 62 | 63 | ### push 64 | 65 | *(context/scalar)* It turns the parameter into the only rule of the context. The parameter will serves as the include target. For Example: 66 | 67 | ```yaml 68 | patterns: !push expressions 69 | ``` 70 | will be transpiled into 71 | ```json 72 | { 73 | "patterns": [{ "include": "#expressions" }] 74 | } 75 | ``` 76 | 77 | ### bracket 78 | 79 | *(rule/scalar)* It generates a rule for bracketing by the name of bracket given by the parameter. For Example: 80 | 81 | ```yaml 82 | - !bracket association 83 | ``` 84 | will be transpiled into 85 | ```json 86 | [{ 87 | "begin": "<\\|", 88 | "beginCaptures": { 89 | "0": { "name": "punctuation.section.association.begin.wolfram" } 90 | }, 91 | "end": "\\|>", 92 | "endCaptures": { 93 | "0": { "name": "punctuation.section.association.end.wolfram" } 94 | }, 95 | "name": "meta.association.wolfram", 96 | "patterns": [{ "include": "#expressions" }] 97 | }] 98 | ``` 99 | 100 | ## General Types 101 | 102 | ### builtin 103 | 104 | ### function 105 | 106 | ### string-function 107 | 108 | ### cell-style 109 | 110 | ## Type-Inference Types 111 | 112 | See [type inference](). 113 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## v1.7 4 | 5 | - **Feature:** Support color decorators. 6 | - **Feature:** Support folding cells in Wolfram Packages. 7 | - **Feature:** Support diagnostics and code actions on characters. 8 | 9 | ## v1.6 10 | 11 | - **Command:** `wolfram.formatWithASCII`: Format file with ASCII encoding. 12 | - **Config:** `wolfram.syntax.smartComments`: Better coloring for comments, support paclet info and styled cells. 13 | - **Config:** `wolfram.formatter.namedCharacters`: Always use named characters to format files if possible. 14 | - **Config:** `wolfram.formatter.extendedAscii`: The default method to format extended ASCII characters. 15 | - **Enhance:** Hovering over symbols in comments and strings will not trigger documentation any more. 16 | - **Enhance:** Support more named characters in syntax definitions. (1008 -> 1048) 17 | - **Enhance:** Optimize documentation layout. 18 | 19 | ## v1.5 20 | 21 | - **Command:** `wolfram.formatWithUTF8`: Format file with UTF-8 encoding. 22 | - **Command:** Rename command `wolfram.generateSyntaxFile` into `wolfram.generateSyntax`. 23 | - **Syntax:** Support SlotSequence syntax: `##`, `##2`, etc. 24 | - **Syntax:** Support highlighting for paclet informations. 25 | - **Syntax:** Support operator `|` (Alternatives), `'` (Derivative), `**` (NonCommutativeMultiply) and `//@` (MapAll). 26 | - **Docs:** Add documentation: extended YAML schema for TMLanguage. 27 | - **Enhance:** Recognize shorthand syntax `%%%` (Out) as a whole. 28 | 29 | ## v1.4 30 | 31 | - **Feature:** Support coloring and documentation for symbols from official addons. 32 | - **Config:** `wolfram.syntax.typeInference`: Use the simplest syntax definition for Wolfram Language. 33 | - **Syntax:** Support operator `.` (Dot). 34 | - **Syntax:** Support default context `` ` ``. 35 | - **Syntax:** Support coloring for shebang. 36 | - **Docs:** Add documentation: wolfram language syntax overview. 37 | - **Enhance:** Add detailed scope names including: 38 | - `punctualation.definition.comment.(begin|end).wolfram` for the beginning and ending marks of comment blocks. 39 | 40 | ## v1.3 41 | 42 | - **Feature:** Some syntaxes are now treated as plugins. 43 | - **Command:** `wolfram.generateSyntaxFile`: Generate syntax file. 44 | - **Config:** `wolfram.syntax.xmlTemplate`: Support XML template syntax in special functions. 45 | - **Config:** `wolfram.syntax.typeInference`: Support type inference in special functions. 46 | - **Syntax:** [#4](https://github.com/Shigma/vscode-wl/issues/4) Fix unexpected behaviour when colorizing expressions after Get/Put/PutAppend. 47 | - **Enhance:** Add some detailed scope names including: 48 | - `.context.wolfram` suffix for `constant.language`, `constant.numeric`, `support.undocumented` and `variable.parameter.option`. 49 | 50 | ## v1.2 51 | 52 | - **Revert:** Remove declaration syntax. 53 | - **Language:** Add supported extension `nbp`. 54 | - **Syntax:** Embedded xml language support in `XMLTemplate`. 55 | - **Syntax:** Fix coloring for some undocumented symbols. 56 | - **Syntax:** Support nested expressions in strings. 57 | - **Syntax:** Advanced coloring in different types of functions. 58 | - **Syntax:** [#3](https://github.com/Shigma/vscode-wl/issues/3) Support operator `//.` (ReplaceAll). 59 | - **Enhance:** [#3](https://github.com/Shigma/vscode-wl/issues/3) Add some detailed scope names including: 60 | - `variable.parameter.slot.wolfram` for slot parameters. 61 | - `constant.language.attribute.wolfram` for attribute names. 62 | - `.context.wolfram` suffix for `variable.other`, `entity.name.function` and `support.function`. 63 | 64 | ## v1.1 65 | 66 | - **Feature:** Document display on hover and on completion. 67 | - **Config:** Set default config `wordWrap` and `wordSeparators`. 68 | - **Syntax:** Initial support for XML templates. 69 | - **Syntax:** Support for function `Function` and `Compile`. 70 | - **Syntax:** Support coloring for `\` before newlines. 71 | - **Syntax:** Support operators `^=` (UpSet), `^:=` (UpSetDelayed) and `=.` (Unset). 72 | - **Syntax:** Fix unexpected behaviour when colorizing expressions like `SortBy[ func]`. -------------------------------------------------------------------------------- /src/syntax/mergeSyntax.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs' 2 | import * as util from '../utilities' 3 | import * as Syntax from '.' 4 | import minifySyntax from './minifySyntax' 5 | import Traverser, { TraverseOptions } from './traverser' 6 | 7 | interface EmbedOptions { 8 | name: string 9 | repository: Syntax.Repository 10 | onRegex: TraverseOptions["onRegex"] 11 | } 12 | 13 | class EmbedTraverser extends Traverser { 14 | constructor(options: EmbedOptions) { 15 | const insertion = '.' + options.name 16 | super({ 17 | onRegex: options.onRegex, 18 | onName: name => name.replace( 19 | /(meta\.[\w.]+)(\.\w+)/g, 20 | (_, $1, $2) => $1 + insertion + $2, 21 | ), 22 | onInclude(name) { 23 | if (name.endsWith(insertion) || !name.startsWith('#')) return name 24 | return options.repository[name.slice(1) + insertion] ? name + insertion : name 25 | }, 26 | }) 27 | } 28 | } 29 | 30 | export default function mergeSyntax(base: Syntax.BaseSyntax, syntaxes: Syntax.Syntax[] = [], isDev = false) { 31 | base._plugins = [] 32 | 33 | // Step 1: merge all the plugins 34 | syntaxes.forEach(syntax => { 35 | base._plugins.push(syntax._name) 36 | Object.assign(base.repository, syntax.repository) 37 | }) 38 | 39 | // Step 2: generate embedded contexts 40 | const stringEmbedder = new EmbedTraverser({ 41 | name: 'in-string', 42 | repository: base.repository, 43 | onRegex(source, key) { 44 | let result = source.replace(/"/g, '\\\\"') 45 | if (key === 'end') result += `|(?=")` 46 | return result 47 | }, 48 | }) 49 | 50 | const commentEmbedder = new EmbedTraverser({ 51 | name: 'in-comment', 52 | repository: base.repository, 53 | onRegex(source, key) { 54 | return key === 'end' ? source + `|(?=\\*\\)(\\r?\\n|\\Z))` : source 55 | }, 56 | }) 57 | 58 | function parseExternalInclude(this: Traverser, name: string): Syntax.Rule { 59 | const extPath = util.vscPath('extensions', name.match(/\.(\w+)$/)[1]) 60 | try { 61 | const { contributes } = require(extPath + '/package.json') 62 | const lang = contributes.grammars.find(({ scopeName }) => scopeName === name) 63 | const syntax = require(extPath + '/' + lang.path) 64 | 65 | const externalTraverser = new Traverser({ 66 | onRegex: stringEmbedder.onRegex, 67 | onInclude: include => { 68 | if (include.startsWith('#')) { 69 | return include.startsWith('#external.') 70 | ? include 71 | : `#external.${name}:${include.slice(1)}` 72 | } else { 73 | return parseExternalInclude.call(this, include) 74 | } 75 | }, 76 | }) 77 | 78 | for (const key in syntax.repository) { 79 | const patterns = externalTraverser.traverse([syntax.repository[key]]) 80 | this.repository[`external.${name}:${key}`] = { patterns } 81 | } 82 | 83 | return { patterns: externalTraverser.traverse(syntax.patterns) } 84 | } catch (error) { 85 | return { patterns: [] } 86 | } 87 | } 88 | 89 | base.repository = new Traverser({ 90 | * onString(name) { 91 | if (name.startsWith('embed-in-string:')) { 92 | const target = name.slice(16) 93 | if (target.startsWith('external.')) { 94 | yield parseExternalInclude.call(this, target.slice(9)) 95 | } else if (this.repository[target]) { 96 | yield* stringEmbedder.traverse([this.repository[target]]) as Syntax.Rule[] 97 | } 98 | } else if (name.startsWith('embed-in-comment:')) { 99 | const target = name.slice(17) 100 | yield* commentEmbedder.traverse([this.repository[target]]) as Syntax.Rule[] 101 | } 102 | }, 103 | }).traverseAll(base.repository) 104 | 105 | // Step 3: minify the syntax definition 106 | base.repository = minifySyntax(base.patterns, base.repository) 107 | 108 | fs.writeFileSync( 109 | util.fullPath('out/syntax.json'), 110 | isDev ? JSON.stringify(base, null, 2) : JSON.stringify(base), 111 | ) 112 | } 113 | -------------------------------------------------------------------------------- /src/scopes/utilities.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | 3 | const lineEndingRE = /([^\r\n]*)(\r\n|\r|\n)?/ 4 | 5 | export interface RangeDelta { 6 | start: vscode.Position 7 | end: vscode.Position 8 | linesDelta: number 9 | /** delta for positions on the same line as the end position */ 10 | endCharactersDelta: number 11 | } 12 | 13 | /** get position by offset */ 14 | function positionAt(text: string, offset: number): vscode.Position { 15 | if (offset > text.length) offset = text.length 16 | let line = 0 17 | let lastIndex = 0 18 | while (true) { 19 | const match = lineEndingRE.exec(text.substring(lastIndex)) 20 | if (lastIndex + match[1].length >= offset) { 21 | return new vscode.Position(line, offset - lastIndex) 22 | } 23 | lastIndex += match[0].length 24 | ++line 25 | } 26 | } 27 | 28 | /** 29 | * @returns the lines and characters represented by the text 30 | */ 31 | export function toRangeDelta(oldRange: vscode.Range, text: string): RangeDelta { 32 | const newEnd = positionAt(text,text.length) 33 | let charsDelta: number 34 | if (oldRange.start.line == oldRange.end.line) 35 | charsDelta = newEnd.character - (oldRange.end.character-oldRange.start.character) 36 | else 37 | charsDelta = newEnd.character - oldRange.end.character 38 | 39 | return { 40 | start: oldRange.start, 41 | end: oldRange.end, 42 | linesDelta: newEnd.line-(oldRange.end.line-oldRange.start.line), 43 | endCharactersDelta: charsDelta 44 | } 45 | } 46 | 47 | export function rangeDeltaNewRange(delta: RangeDelta): vscode.Range { 48 | let x: number 49 | if (delta.linesDelta > 0) 50 | x = delta.endCharactersDelta 51 | else if (delta.linesDelta < 0 && delta.start.line == delta.end.line + delta.linesDelta) 52 | x = delta.end.character + delta.endCharactersDelta + delta.start.character 53 | else 54 | x = delta.end.character + delta.endCharactersDelta 55 | return new vscode.Range(delta.start, new vscode.Position(delta.end.line + delta.linesDelta, x)) 56 | } 57 | 58 | function positionRangeDeltaTranslate(pos: vscode.Position, delta: RangeDelta): vscode.Position { 59 | if(pos.isBefore(delta.end)) 60 | return pos 61 | else if (delta.end.line == pos.line) { 62 | let x = pos.character + delta.endCharactersDelta 63 | if (delta.linesDelta > 0) 64 | x = x - delta.end.character 65 | else if (delta.start.line == delta.end.line + delta.linesDelta && delta.linesDelta < 0) 66 | x = x + delta.start.character 67 | return new vscode.Position(pos.line + delta.linesDelta, x) 68 | } 69 | else // if(pos.line > delta.end.line) 70 | return new vscode.Position(pos.line + delta.linesDelta, pos.character) 71 | } 72 | 73 | function positionRangeDeltaTranslateEnd(pos: vscode.Position, delta: RangeDelta): vscode.Position { 74 | if(pos.isBeforeOrEqual(delta.end)) 75 | return pos 76 | else if (delta.end.line == pos.line) { 77 | let x = pos.character + delta.endCharactersDelta 78 | if (delta.linesDelta > 0) 79 | x = x - delta.end.character 80 | else if (delta.start.line == delta.end.line + delta.linesDelta && delta.linesDelta < 0) 81 | x = x + delta.start.character 82 | return new vscode.Position(pos.line + delta.linesDelta, x) 83 | } 84 | else // if(pos.line > delta.end.line) 85 | return new vscode.Position(pos.line + delta.linesDelta, pos.character) 86 | } 87 | 88 | export function rangeTranslate(range: vscode.Range, delta: RangeDelta) { 89 | return new vscode.Range( 90 | positionRangeDeltaTranslate(range.start, delta), 91 | positionRangeDeltaTranslateEnd(range.end, delta) 92 | ) 93 | } 94 | 95 | export function rangeContains(range: vscode.Range, pos: vscode.Position, exclStart=false, inclEnd=false) { 96 | return range.start.isBeforeOrEqual(pos) 97 | && (!exclStart || !range.start.isEqual(pos)) 98 | && ((inclEnd && range.end.isEqual(pos)) || range.end.isAfter(pos)) 99 | } 100 | 101 | export function maxPosition(x: vscode.Position, y: vscode.Position) { 102 | if(x.line < y.line) 103 | return x 104 | if(x.line < x.line) 105 | return y 106 | if(x.character < y.character) 107 | return x 108 | else 109 | return y 110 | } 111 | -------------------------------------------------------------------------------- /src/syntax/traverser.ts: -------------------------------------------------------------------------------- 1 | import * as Syntax from '.' 2 | 3 | type CapturesType = 'captures' | 'beginCaptures' | 'endCaptures' 4 | type NameType = 'name' | 'contentName' | CapturesType 5 | type RegexType = 'begin' | 'match' | 'end' 6 | 7 | export interface TraverseOptions { 8 | onName?(this: Traverser, name: string, key: NameType): string 9 | onRegex?(this: Traverser, regex: string, key: RegexType): string 10 | onString?(this: Traverser, source: string): IterableIterator 11 | onInclude?(this: Traverser, include: string): string | Syntax.Rule 12 | onContext?(this: Traverser, name: string): boolean 13 | } 14 | 15 | /** a textmate language patterns traverser */ 16 | export default class Traverser { 17 | onName: (name: string, key: NameType) => string 18 | onRegex: (regex: string, key: RegexType) => string 19 | onString: (source: string) => IterableIterator 20 | onInclude: (include: string) => string | Syntax.Rule 21 | onContext: (name: string) => boolean 22 | 23 | repository: Syntax.Repository 24 | 25 | constructor(options: TraverseOptions = {}) { 26 | this.onName = options.onName 27 | this.onRegex = options.onRegex 28 | this.onString = options.onString 29 | this.onInclude = options.onInclude 30 | this.onContext = options.onContext 31 | } 32 | 33 | private getName(name: string, key: NameType): string { 34 | if (!name) return 35 | if (!this.onName) return name 36 | return this.onName(name, key) 37 | } 38 | 39 | private getCaptures(captures: Syntax.Captures, key: CapturesType): Syntax.Captures { 40 | if (!captures) return 41 | const result = {} 42 | for (const index in captures) { 43 | const capture = result[index] = Object.assign({}, captures[index]) 44 | capture.name = this.getName(capture.name, key) 45 | capture.patterns = this.traverse(capture.patterns) 46 | } 47 | return result 48 | } 49 | 50 | private* getRules(rules: Syntax.SlotRule[]): IterableIterator { 51 | for (let rule of rules) { 52 | if (typeof rule === 'string') { 53 | if (this.onString) { 54 | yield* this.onString(rule) 55 | } else { 56 | yield rule 57 | } 58 | continue 59 | } 60 | 61 | rule = Object.assign({}, rule) 62 | 63 | if (rule.include) { 64 | if (this.onInclude) { 65 | const include = this.onInclude(rule.include) 66 | if (!include) continue 67 | if (typeof include !== 'string') { 68 | yield* this.getRules([include]) 69 | continue 70 | } 71 | rule.include = include 72 | } 73 | yield rule 74 | continue 75 | } 76 | 77 | rule.patterns = this.traverse(rule.patterns) 78 | if (rule.patterns && !(rule.begin || rule.end || rule.match)) { 79 | yield* rule.patterns 80 | continue 81 | } 82 | 83 | if (this.onRegex) { 84 | for (const key of ['begin', 'match', 'end']) { 85 | if (rule[key]) rule[key] = this.onRegex(rule[key], key as RegexType) 86 | } 87 | } 88 | rule.name = this.getName(rule.name, 'name') 89 | rule.contentName = this.getName(rule.contentName, 'contentName') 90 | rule.captures = this.getCaptures(rule.captures, 'captures') 91 | rule.endCaptures = this.getCaptures(rule.endCaptures, 'endCaptures') 92 | rule.beginCaptures = this.getCaptures(rule.beginCaptures, 'beginCaptures') 93 | yield rule 94 | } 95 | } 96 | 97 | public traverse(rules: Syntax.SlotRule[]): Syntax.SlotRule[] { 98 | if (!rules) return 99 | return Array.from(this.getRules(rules)) 100 | } 101 | 102 | public traverseAll(contexts: Syntax.Repository): Syntax.Repository { 103 | const result = {} 104 | this.repository = new Proxy({}, { 105 | get: (_, key) => contexts[key as string], 106 | set: (_, key, value) => result[key] = value, 107 | }) 108 | for (const key in contexts) { 109 | if (this.onContext && !this.onContext(key)) continue 110 | const patterns = this.traverse([contexts[key]]) 111 | if (!patterns.length) { 112 | continue 113 | } else if (patterns.length === 1) { 114 | result[key] = patterns[0] as Syntax.Rule 115 | } else { 116 | result[key] = { patterns } 117 | } 118 | } 119 | return result 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/scopes/document.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import * as tm from './textmate' 3 | import * as util from './utilities' 4 | 5 | export interface ScopeToken { 6 | range: vscode.Range 7 | text: string 8 | scopes: string[] 9 | } 10 | 11 | export default class DocumentWatcher implements vscode.Disposable { 12 | /** stores the state for each line */ 13 | private grammarState : tm.StackElement[] = [] 14 | private subscriptions : vscode.Disposable[] = [] 15 | 16 | public constructor(private document: vscode.TextDocument, private grammar: tm.IGrammar) { 17 | this.reparsePretties() 18 | this.subscriptions.push(vscode.workspace.onDidChangeTextDocument((e) => { 19 | if (e.document == this.document) this.applyChanges(e.contentChanges) 20 | })) 21 | } 22 | 23 | public dispose() { 24 | this.subscriptions.forEach(s => s.dispose()) 25 | } 26 | 27 | private refreshTokensOnLine(line: vscode.TextLine) : {tokens: tm.IToken[], invalidated: boolean} { 28 | if (!this.grammar) return {tokens: [], invalidated: false} 29 | const prevState = this.grammarState[line.lineNumber-1] || null 30 | const lineTokens = this.grammar.tokenizeLine(line.text, prevState) 31 | const invalidated = !this.grammarState[line.lineNumber] || !lineTokens.ruleStack.equals(this.grammarState[line.lineNumber]) 32 | this.grammarState[line.lineNumber] = lineTokens.ruleStack 33 | return {tokens: lineTokens.tokens, invalidated: invalidated} 34 | } 35 | 36 | public *getRangeByRegex(regex: RegExp): IterableIterator { 37 | let index = 0, text = this.document.getText() 38 | while (text) { 39 | const match = text.match(regex) 40 | if (!match) break 41 | const delta = match.index + match[0].length 42 | text = text.substring(delta) 43 | index += delta 44 | yield new vscode.Range( 45 | this.document.positionAt(index - match[0].length), 46 | this.document.positionAt(index), 47 | ) 48 | } 49 | } 50 | 51 | public getScopeAt(position: vscode.Position): ScopeToken { 52 | if (!this.grammar) return 53 | position = this.document.validatePosition(position) 54 | const state = this.grammarState[position.line - 1] || null 55 | const line = this.document.lineAt(position.line) 56 | const tokens = this.grammar.tokenizeLine(line.text, state) 57 | for (let token of tokens.tokens) { 58 | if (token.startIndex <= position.character && position.character < token.endIndex) { 59 | return { 60 | range: new vscode.Range(position.line, token.startIndex, position.line, token.endIndex), 61 | text: line.text.substring(token.startIndex, token.endIndex), 62 | scopes: token.scopes 63 | } 64 | } 65 | } 66 | } 67 | 68 | public *getRangeByScope(...scopes: string[]): IterableIterator { 69 | const scopeSet = new Set(scopes) 70 | for (let lineIndex = 0; lineIndex < this.document.lineCount; lineIndex += 1) { 71 | const lineText = this.document.lineAt(lineIndex).text 72 | const { tokens } = this.grammar.tokenizeLine(lineText, this.grammarState[lineIndex - 1]) 73 | for (const token of tokens) { 74 | for (const scope of token.scopes) { 75 | if (scopeSet.has(scope)) { 76 | yield new vscode.Range(lineIndex, token.startIndex, lineIndex, token.endIndex) 77 | break 78 | } 79 | } 80 | } 81 | } 82 | } 83 | 84 | private reparsePretties(range?: vscode.Range) : void { 85 | range = this.document.validateRange(range || new vscode.Range(0, 0, this.document.lineCount, 0)) 86 | 87 | let invalidatedTokenState = false 88 | const lineCount = this.document.lineCount 89 | for ( 90 | let lineIndex = range.start.line; 91 | lineIndex <= range.end.line || (invalidatedTokenState && lineIndex < lineCount); 92 | ++lineIndex 93 | ) { 94 | const line = this.document.lineAt(lineIndex) 95 | const { invalidated } = this.refreshTokensOnLine(line) 96 | invalidatedTokenState = invalidated 97 | } 98 | } 99 | 100 | private applyChanges(changes: vscode.TextDocumentContentChangeEvent[]) { 101 | changes = changes.sort((c1, c2) => c1.range.start.isAfter(c2.range.start) ? -1 : 1) 102 | for (const change of changes) { 103 | const delta = util.toRangeDelta(change.range, change.text) 104 | const editRange = util.rangeDeltaNewRange(delta) 105 | this.reparsePretties(editRange) 106 | } 107 | } 108 | 109 | public refresh() { 110 | this.grammarState = [] 111 | this.reparsePretties() 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vscode-wl", 3 | "icon": "logo.png", 4 | "displayName": "Wolfram Language", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/Shigma/vscode-wl.git" 8 | }, 9 | "description": "Wolfram Language support for Visual Studio Code.", 10 | "version": "1.7.3", 11 | "engines": { 12 | "vscode": "^1.29.0" 13 | }, 14 | "categories": [ 15 | "Programming Languages" 16 | ], 17 | "main": "out/index.js", 18 | "publisher": "shigma", 19 | "author": "shigma <1700011071@pku.edu.cn>", 20 | "scripts": { 21 | "build": "node build/syntax", 22 | "publish": "tsc -b & vsce publish", 23 | "compile": "tsc -p ./", 24 | "watch": "tsc -watch -p ./", 25 | "postinstall": "node ./node_modules/vscode/bin/install" 26 | }, 27 | "activationEvents": [ 28 | "*" 29 | ], 30 | "contributes": { 31 | "configuration": { 32 | "type": "object", 33 | "title": "Wolfram Language", 34 | "properties": { 35 | "wolfram.installationDirectory": { 36 | "type": "string", 37 | "default": null, 38 | "description": "The installation directory for Wolfram Mathematica." 39 | }, 40 | "wolfram.syntax.simplestMode": { 41 | "type": "boolean", 42 | "default": false, 43 | "description": "Use the simplest syntax definition for Wolfram Language. It is incompatible with plugins." 44 | }, 45 | "wolfram.syntax.xmlTemplate": { 46 | "type": "boolean", 47 | "default": false, 48 | "description": "Support XML template syntax in special functions (experimental)." 49 | }, 50 | "wolfram.syntax.typeInference": { 51 | "type": "boolean", 52 | "default": false, 53 | "description": "Support type inference in special functions. It uses recursive subpattern, which may cause regular expression denial of service, although it is not common." 54 | }, 55 | "wolfram.syntax.smartComments": { 56 | "type": "boolean", 57 | "default": true, 58 | "description": "Better coloring for comments, support paclet info, styled cells, and so on (experimental)." 59 | }, 60 | "wolfram.formatter.namedCharacters": { 61 | "type": "boolean", 62 | "default": true, 63 | "description": "Always use named characters to format files if possible." 64 | }, 65 | "wolfram.formatter.extendedAscii": { 66 | "type": "string", 67 | "enum": [ 68 | "original", 69 | "3-digit octal", 70 | "2-digit hexidecimal", 71 | "4-digit hexidecimal" 72 | ], 73 | "default": "3-digit octal", 74 | "description": "The default method to format extended ASCII characters." 75 | } 76 | } 77 | }, 78 | "configurationDefaults": { 79 | "[wolfram]": { 80 | "editor.wordWrap": "off", 81 | "editor.wordSeparators": "_~!@#%^&*()-=+[{]}\\|;:'\",.<>/?" 82 | } 83 | }, 84 | "commands": [ 85 | { 86 | "command": "wolfram.setInstallationDirectory", 87 | "title": "Set Installation Directory", 88 | "category": "Wolfram" 89 | }, 90 | { 91 | "command": "wolfram.generateSyntax", 92 | "title": "Generate Syntax File", 93 | "category": "Wolfram" 94 | }, 95 | { 96 | "command": "wolfram.formatWithUTF8", 97 | "title": "Format File With UTF-8 encoding", 98 | "category": "Wolfram" 99 | }, 100 | { 101 | "command": "wolfram.formatWithASCII", 102 | "title": "Format File With ASCII encoding", 103 | "category": "Wolfram" 104 | }, 105 | { 106 | "command": "wolfram.showThemes", 107 | "title": "Show All Themes", 108 | "category": "Wolfram" 109 | } 110 | ], 111 | "languages": [ 112 | { 113 | "id": "wolfram", 114 | "aliases": [ 115 | "Wolfram Language", 116 | "Mathematica" 117 | ], 118 | "extensions": [ 119 | ".wl", 120 | ".wlt", 121 | ".mt", 122 | ".m", 123 | ".nb", 124 | ".wls", 125 | ".nbp" 126 | ], 127 | "configuration": "./language.json" 128 | } 129 | ], 130 | "grammars": [ 131 | { 132 | "language": "wolfram", 133 | "scopeName": "source.wolfram", 134 | "path": "./out/syntax.json", 135 | "embeddedLanguages": { 136 | "meta.embedded.block.xml": "xml" 137 | } 138 | } 139 | ] 140 | }, 141 | "devDependencies": { 142 | "@types/node": "^10.12.9", 143 | "@types/strip-json-comments": "0.0.30", 144 | "commander": "^2.19.0", 145 | "js-yaml": "^3.12.0", 146 | "strip-json-comments": "^2.0.1", 147 | "typescript": "^3.1.6", 148 | "vscode": "^1.1.21", 149 | "vscode-textmate": "^4.0.1" 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /syntaxes/type-inference.yaml: -------------------------------------------------------------------------------- 1 | include: false 2 | 3 | embedding: 4 | string: all 5 | comments: all 6 | 7 | contexts: 8 | # var -> val 9 | # { var -> val } 10 | # { var } -> { val } 11 | limit-param: !next 12 | - patterns: !first 13 | - !parameter (?=\s*->) 14 | - !list 15 | - (->|[,\]]) 16 | - !foreach 17 | - !first 18 | - \}, 19 | - !parameter (?=\s*(->|[,}])) 20 | 21 | # var1, var2, ... 22 | scoped-param: 23 | - !foreach 24 | - !first 25 | - \}, 26 | - !parameter 27 | 28 | # var 29 | # { var1, var2, ... } 30 | solve-param: !first 31 | - \], 32 | - !parameter 33 | - !list 34 | - include: '#scoped-param' 35 | 36 | grad-param: !next 37 | - !first 38 | - include: '#solve-param' 39 | 40 | # { var, val1, val2, ... }, { var2, val1, val2, ... } 41 | plot-param: !next 42 | - !foreach 43 | - \] 44 | - !first 45 | - \], 46 | - !list 47 | - patterns: !first 48 | - \} 49 | - !parameter 50 | 51 | slot:functions: 52 | - !function 53 | type: scoping 54 | target: Block|Module|With|DynamicModule 55 | context: !first 56 | - !list 57 | - '[\],]' 58 | - !foreach 59 | - !first 60 | - \}, 61 | - !parameter \b 62 | - !function 63 | type: pure 64 | target: Function 65 | context: !first 66 | - !parameter (?=\s*,) 67 | - !list 68 | - ',' 69 | - include: '#scoped-param' 70 | - !function 71 | type: compilation 72 | target: Compile 73 | context: !first 74 | - !list 75 | - '[\],]' 76 | - !foreach 77 | - !first 78 | - \]}, 79 | - !parameter 80 | - !list 81 | - patterns: !first 82 | - \} 83 | - !parameter 84 | 85 | - !function 86 | type: limit-like 87 | target: '{{limit_functions}}' 88 | context: !push limit-param 89 | - !function 90 | type: asymptotic-like 91 | target: '{{asymptotic_functions}}' 92 | context: !next 93 | - include: '#limit-param' 94 | 95 | - !function 96 | type: solve-like 97 | target: '{{local_solve_var_at_2}}' 98 | context: !next 99 | - include: '#solve-param' 100 | - !function 101 | type: transform 102 | target: '{{local_solve_var_from_2}}' 103 | context: !next 104 | - !foreach 105 | - \] 106 | - !push solve-param 107 | - !function 108 | type: grad-like 109 | target: '{{local_grad_var_at_2}}' 110 | context: !first 111 | - begin: \s*({{identifier}})(?=\s*[,\]]) 112 | beginCaptures: !raw 113 | 1: '#function-identifier' 114 | patterns: !push grad-param 115 | - begin: (?=[^\]]) 116 | patterns: !push grad-param 117 | 118 | - !function 119 | type: plot-like 120 | target: '{{local_plot_var_from_2}}' 121 | context: !push plot-param 122 | - !function 123 | type: ndsolve-like 124 | target: '{{local_plot_var_from_3}}' 125 | context: !next 126 | - include: '#plot-param' 127 | - !function 128 | type: sum-like 129 | target: '{{local_sum_var_from_2}}' 130 | context: !first 131 | - begin: \s*({{identifier}})(?=\s*[,\]]) 132 | beginCaptures: !raw 133 | 1: '#function-identifier' 134 | patterns: !push plot-param 135 | - begin: (?=[^\]]) 136 | patterns: !push plot-param 137 | 138 | - !function 139 | type: manipulate 140 | target: '{{local_manipulate_var_from_2}}' 141 | context: !next 142 | - !foreach 143 | - \] 144 | - !first 145 | - \], 146 | - !list 147 | - patterns: !first 148 | - \} 149 | - !parameter 150 | - !list 151 | - patterns: !first 152 | - \} 153 | - !parameter 154 | 155 | - !function 156 | type: functional-first-param 157 | target: '{{functional_first_param}}' 158 | context: !first 159 | - begin: \s*({{identifier}})(?=\s*[,\]]) 160 | beginCaptures: !raw 161 | 1: '#function-identifier' 162 | - !function 163 | type: functional-last-param 164 | target: '{{functional_last_param}}' 165 | context: 166 | - match: \s*({{identifier}})(?=\s*\]) 167 | captures: !raw 168 | 1: '#function-identifier' 169 | - begin: (?=[^\]]) 170 | end: (?=\]) 171 | patterns: 172 | - match: (,)\s*({{identifier}})(?=\s*\]) 173 | captures: !raw 174 | 1: punctuation.separator.sequence.wolfram 175 | 2: '#function-identifier' 176 | - include: '#expressions' 177 | -------------------------------------------------------------------------------- /syntaxes/simplest.yaml: -------------------------------------------------------------------------------- 1 | name: Wolfram Language 2 | scopeName: source.wolfram 3 | 4 | variables: 5 | alnum: '[0-9a-zA-Z]' 6 | number: (?:\d+\.?|\.\d)\d* 7 | symbol: '[$a-zA-Z]+[$0-9a-zA-Z]*' 8 | identifier: '`?(?:{{symbol}}`)*{{symbol}}' 9 | pattern_ending: '[:-]>|:?=|\/[;.\/]|[\]\)\},;&]|>>|<<|[\n\r]' 10 | 11 | patterns: 12 | - match: \A(#!).*(?=$) 13 | name: comment.line.shebang.wolfram 14 | captures: !raw 15 | 1: punctuation.definition.comment.wolfram 16 | - include: '#expressions' 17 | 18 | contexts: 19 | expressions: 20 | - include: '#newline-escape' 21 | - include: '#comment-block' 22 | - include: '#literals' 23 | - include: '#shorthand' 24 | - include: '#operators' 25 | - include: '#patterns' 26 | - include: '#functions' 27 | - include: '#variables' 28 | - include: '#bracketing' 29 | 30 | newline-escape: 31 | - match: \\\r?\n 32 | name: constant.character.escape.wolfram 33 | 34 | named-characters: 35 | - match: \\\[({{alnum}}+)\] 36 | name: constant.character.built-in.wolfram 37 | 38 | comment-block: 39 | - begin: \(\* 40 | end: \*\) 41 | name: comment.block.wolfram 42 | patterns: !push comment-block 43 | 44 | literals: 45 | # numbers 46 | - match: |- 47 | (?x) 48 | (?: 49 | ([1-9]\d*\^\^) # base 50 | ((?:{{alnum}}+\.?|\.{{alnum}}){{alnum}}*) # value 51 | | 52 | ({{number}}) # value 53 | ) 54 | (?: 55 | (\`\`(?:{{number}})?) # accuracy 56 | | 57 | (\`(?:{{number}})?) # precision 58 | )? 59 | (\*\^[+-]?{{number}})? # exponent 60 | name: constant.numeric.wolfram 61 | captures: !raw 62 | 1: constant.numeric.base.wolfram 63 | 4: constant.numeric.accuracy.wolfram 64 | 5: constant.numeric.precision.wolfram 65 | 6: constant.numeric.exponent.wolfram 66 | 67 | # strings 68 | - begin: '"' 69 | end: '"' 70 | beginCaptures: !all punctuation.definition.string.begin.wolfram 71 | endCaptures: !all punctuation.definition.string.end.wolfram 72 | name: string.quoted.wolfram 73 | patterns: 74 | # named characters 75 | - include: '#named-characters' 76 | 77 | # escape characters 78 | - include: '#newline-escape' 79 | - match: \\[ !"%&()*+/@\\^_`bfnrt<>] 80 | name: constant.character.escape.wolfram 81 | 82 | # encoded characters 83 | - match: \\[0-7]{3}|\\\.[0-9A-Fa-f]{2}|\\:[0-9A-Fa-f]{4} 84 | name: constant.character.encoding.wolfram 85 | 86 | # invalid characters 87 | - match: |- 88 | (?x) 89 | \\[0-7]{1,2}(?=[^0-7])| 90 | \\\.[0-9A-Fa-f]?(?=[^0-9A-Fa-f])| 91 | \\:[0-9A-Fa-f]{0,3}(?=[^0-9A-Fa-f]) 92 | name: invalid.character.encoding.wolfram 93 | - match: \\[a-zA-Z\[] 94 | name: invalid.character.escape.wolfram 95 | 96 | shorthand: 97 | # Out 98 | - match: (%\d+|%+) 99 | name: storage.type.Out.wolfram 100 | 101 | # MessageName 102 | - match: (::)\s*({{alnum}}+) 103 | captures: !raw 104 | 1: keyword.operator.MessageName.wolfram 105 | 2: string.unquoted.wolfram 106 | 107 | # Get & Put & PutAppend 108 | - match: (<<|>>>?) *([a-zA-Z0-9`/.!_:$*~?\\-]+) *(?=[\)\]\},;]|$) 109 | captures: !raw 110 | 1: keyword.operator.flow.wolfram 111 | 2: string.unquoted.filename.wolfram 112 | 113 | # Infix 114 | - begin: '~' 115 | beginCaptures: !all keyword.operator.call.wolfram 116 | end: ({{identifier}})?\s*(~) 117 | endCaptures: !raw 118 | 1: '#function-identifier' 119 | 2: keyword.operator.call.wolfram 120 | name: meta.infix.wolfram 121 | patterns: !push expressions 122 | 123 | # Postfix & Composition & RightComposition 124 | - match: (//|[@/]\*)\s*({{identifier}}) 125 | captures: !raw 126 | 1: keyword.operator.call.wolfram 127 | 2: '#function-identifier' 128 | 129 | # At & Apply & Map & MapAll & Composition & RightComposition 130 | - match: ({{identifier}})\s*(?=@{1,3}|//?@|[/@]\*) 131 | captures: !raw 132 | 1: '#function-identifier' 133 | 134 | operators: 135 | # /. Replace 136 | # //. ReplaceAll 137 | - match: \/\/?\. 138 | name: keyword.operator.replace.wolfram 139 | 140 | # @ Prefix 141 | # @@ Apply 142 | # @@@ Apply 143 | # /@ Map 144 | # //@ MapAll 145 | # // Postfix 146 | # ~ Infix 147 | # @* Composition 148 | # /* RightComposition 149 | - match: //@?|[/@][@*]|@{1,3}|~ 150 | name: keyword.operator.call.wolfram 151 | 152 | # > Greater 153 | # < Less 154 | # >= GreaterEqual 155 | # <= LessEqual 156 | # == Equal 157 | # != Unequal 158 | # === SameQ 159 | # =!= UnsameQ 160 | - match: =?[=!]=|[<>]=|<(?!\|)|(? 161 | name: keyword.operator.comparison.wolfram 162 | 163 | # ! Not 164 | # || Or 165 | # && And 166 | - match: \|\||&&|! 167 | name: keyword.operator.logical.wolfram 168 | 169 | # = Set 170 | # := SetDelayed 171 | # ^= UpSet 172 | # ^:= UpSetDelayed 173 | # /: TagSet (TagUnset, TagSetDelayed) 174 | # =. Unset 175 | # += AddTo 176 | # -= SubtractFrom 177 | # *= TimesBy 178 | # /= DivideBy 179 | - match: \^?:?=|=.|/:|[+\-*/]= 180 | name: keyword.operator.assignment.wolfram 181 | 182 | # -> Rule 183 | # :> RuleDelayed 184 | # <-> TwoWayRule 185 | - match: <->|[-:]> 186 | name: keyword.operator.rule.wolfram 187 | 188 | # /; Condition 189 | - match: \/; 190 | name: keyword.operator.condition.wolfram 191 | 192 | # .. Repeated 193 | # ... RepeatedNull 194 | - match: \.\.\.? 195 | name: keyword.operator.repeat.wolfram 196 | 197 | # + Plus 198 | # - Minus, Subtract 199 | # * Multiply 200 | # / Devide 201 | # ^ Power 202 | # . Dot 203 | # ' Derivative 204 | # ** NonCommutativeMultiply 205 | # ++ Increment, PreIncrement 206 | # -- Decrement, PreDecrement 207 | - match: \+\+|--|\*\*|[+\-*/^.]|'+ 208 | name: keyword.operator.arithmetic.wolfram 209 | 210 | # << Get 211 | # >> Put 212 | # >>> PutAppend 213 | - match: <<|>>>? 214 | name: keyword.operator.flow.wolfram 215 | 216 | # <> StringJoin 217 | # ~~ StringExpression 218 | # | Alternatives 219 | - match: <>|~~|\| 220 | name: keyword.operator.string.wolfram 221 | 222 | # ;; Span 223 | - match: ;; 224 | name: keyword.operator.span.wolfram 225 | 226 | # ; CompoundExpression 227 | - match: ; 228 | name: keyword.operator.compound.wolfram 229 | 230 | # & Function 231 | - match: '&' 232 | name: keyword.operator.function.wolfram entity.name.function.wolfram 233 | 234 | # ? Definition 235 | # ?? FullDefinition 236 | - match: \?\?? 237 | name: keyword.operator.definition.wolfram 238 | 239 | - include: '#named-characters' 240 | 241 | patterns: 242 | # patterns 243 | - begin: ({{identifier}})\s*(:(?=[^:>=])) 244 | beginCaptures: !raw 245 | 1: variable.parameter.wolfram 246 | 2: keyword.operator.Pattern.wolfram 247 | end: (?={{pattern_ending}}) 248 | name: meta.pattern.wolfram 249 | patterns: !push post-pattern 250 | 251 | # parameters 252 | - begin: |- 253 | (?x) 254 | ({{identifier}})? 255 | (?: 256 | (_\.) # Default 257 | | 258 | (_{1,3}) # Blank, BlankSequence, BlankNullSequence 259 | ({{identifier}})? # Head 260 | ) 261 | beginCaptures: !raw 262 | 0: variable.parameter.wolfram 263 | 2: variable.parameter.default.wolfram 264 | 3: variable.parameter.blank.wolfram 265 | 4: variable.parameter.head.wolfram 266 | end: (?={{pattern_ending}}) 267 | contentName: meta.pattern.wolfram 268 | patterns: !push post-pattern 269 | 270 | post-pattern: 271 | # PatternTest 272 | - match: (\?)\s*({{identifier}}(?=\s*(:|{{pattern_ending}})))? 273 | captures: !raw 274 | 1: keyword.operator.PatternTest.wolfram 275 | 2: '#function-identifier' 276 | 277 | # Optional 278 | - match: :(?=[^:>=]) 279 | name: keyword.operator.Optional.wolfram 280 | 281 | - include: '#expressions' 282 | 283 | function-identifier: 284 | - match: (`?(?:{{symbol}}`)*){{symbol}} 285 | name: entity.name.function.wolfram 286 | captures: !raw 287 | 1: entity.name.function.context.wolfram 288 | 289 | functions: 290 | - begin: ({{identifier}})\s*(\[(?!\[)) 291 | beginCaptures: !raw 292 | 1: '#function-identifier' 293 | 2: meta.block.wolfram punctuation.section.brackets.begin.wolfram 294 | end: \] 295 | endCaptures: !all meta.block.wolfram punctuation.section.brackets.end.wolfram 296 | contentName: meta.block.wolfram 297 | patterns: !push expressions 298 | 299 | variables: 300 | # variables 301 | - match: (`?(?:{{symbol}}`)*){{symbol}} 302 | name: variable.other.wolfram 303 | captures: !raw 304 | 1: variable.other.context.wolfram 305 | 306 | # slots 307 | - match: '#[a-zA-Z]{{alnum}}*|##?\d*' 308 | name: variable.parameter.slot.wolfram 309 | 310 | bracketing: 311 | - match: ',' 312 | name: punctuation.separator.sequence.wolfram 313 | - !bracket parens 314 | - !bracket parts 315 | - !bracket brackets 316 | - !bracket braces 317 | - !bracket association 318 | 319 | # Box Forms 320 | - begin: (\\!)?\\\( 321 | beginCaptures: !all punctuation.section.box.begin.wolfram 322 | end: \\\) 323 | endCaptures: !all punctuation.section.box.end.wolfram 324 | name: meta.box.wolfram 325 | patterns: 326 | - match: \\[%&+_^] 327 | name: keyword.operator.x-scriptBox.wolfram 328 | - match: \\/ 329 | name: keyword.operator.FractionBox.wolfram 330 | - match: \\@ 331 | name: keyword.operator.SqrtBox.wolfram 332 | - match: \\` 333 | name: keyword.operator.FormBox.wolfram 334 | - match: \\\* 335 | name: keyword.operator.box-constructor.wolfram 336 | - include: '#expressions' 337 | -------------------------------------------------------------------------------- /docs/syntax-overview.md: -------------------------------------------------------------------------------- 1 | # Syntax Overview for Wolfram Language 2 | 3 | The page introduces the basic syntax of the wolfram language and explains the structure of the syntax files. [*src/syntaxes/simplest.yaml*](../src/syntaxes/simplest.yaml) is a direct implementation of this page. 4 | 5 | Note: the syntax definition uses some [YAML tags](https://yaml.org/spec/1.2/spec.html#id2761292) which can be found in [extended schema](./yaml-schema.md#basic-types). 6 | 7 | ## Glossary 8 | 9 | There are some basic concepts in this overview. These regular expressions are called *variables* and will be auto inserted into the syntax files through [Mustache](https://en.wikipedia.org/wiki/Mustache_(template_system)) in the building process. 10 | 11 | - *alnum:* `[0-9a-zA-Z]` 12 | - *number:* `(?:\d+\.?|\.\d)\d*` 13 | - *symbol:* `[$a-zA-Z]+[$0-9a-zA-Z]*` 14 | 15 | ## Basic Patterns 16 | 17 | A simplest syntax definition for Wolfram Language support the following syntax: 18 | 19 | - [Shebang](#Shebang) 20 | - [Numbers](#Numbers) 21 | - [Strings](#Strings) 22 | - [Operators](#Operators) 23 | - [Variables](#Variables) 24 | - [Functions](#Functions) 25 | - [Patterns](#Patterns) 26 | - [Bracketing](#Bracketing) 27 | - [Box Forms](#Box-forms) 28 | - [Comment blocks](#Comment-blocks) 29 | - [Shorthand expressions](#Shorthand-expressions) 30 | - [Escaping before newlines](#Escaping-before-newlines) 31 | 32 | ### Shebang 33 | 34 | See [here](https://en.wikipedia.org/wiki/Shebang_(Unix)) for the shebang definition. It's easy to support such a syntax: `\A(#!).*(?=$)`. 35 | 36 | ### Numbers 37 | 38 | In Wolfram Language, numbers can: 39 | 40 | - have base: `2^^10`, `11^^a.a` 41 | - have precision: ``2`10``, ``11` `` 42 | - have accuracy: ```2``10```, ```11`` ``` 43 | - in scientific form: `2*^10`, `2*^-1.1` 44 | 45 | So a complete syntax for number should be: 46 | 47 | ```regex 48 | (?x) 49 | (?: 50 | ([1-9]\d*\^\^) # base 51 | ((?:{{alnum}}+\.?|\.{{alnum}}){{alnum}}*) # value 52 | | 53 | ({{number}}) # value 54 | ) 55 | (?: 56 | (\`\`(?:{{number}})?) # accuracy 57 | | 58 | (\`(?:{{number}})?) # precision 59 | )? 60 | (\*\^[+-]?{{number}})? # exponent 61 | ``` 62 | 63 | Note: `^^`, `` ` ``, ``` `` ``` and `*^` should not be treated as operators. 64 | 65 | Reference: [Input Syntax](https://reference.wolfram.com/language/tutorial/InputSyntax.html). 66 | 67 | ### Strings 68 | 69 | A string in Wolfram Language must be quoted in a pair of `"` and can have the following special syntaxes: 70 | 71 | #### Named Characters 72 | 73 | Some special characters may have their names, and can be matched with `\\\[{{alnum}}+\]`. 74 | 75 | Note: not every `\\\[{{alnum}}+\]` is corrent grammar, but the simplest syntax definition does not provides a list of supported names. 76 | 77 | #### Escaped Characters 78 | 79 | In Wolfram Language, some charcters can be "escaped" while others cannot. Try the following code on Mathematica: 80 | 81 | ```mathematica 82 | Reap[ 83 | Scan[ 84 | Sow[#, Quiet @ Check[Length @ Characters @ ToExpression["\"\\" <> # <> "\""], -1]] &, 85 | CharacterRange[33, 126] 86 | ], 87 | _, 88 | #1 -> StringJoin[#2] & 89 | ] // Last 90 | ``` 91 | 92 | You can obtain the following result: 93 | 94 | - disappeared: `<>` 95 | - unchanged: `#$',-89;=?]{|}~` 96 | - escaped: ``!"%&()*+/@\^_`bfnrt`` 97 | - errored: other characters 98 | 99 | The first three kinds of characters can be placed after a `\` while characters from the last kind cannot. 100 | 101 | #### Encoded Characters 102 | 103 | The Wolfram Language also supports characters with encoding: 104 | 105 | - 3-digits octal: `\\[0-7]{3}` 106 | - 2-digits hexadecimal: `\\\.[0-9A-Fa-f]{2}` 107 | - 4-digits hexadecimal: `\\:[0-9A-Fa-f]{4}` 108 | 109 | Note: a string which begins with a `\`, `\.` or `\:` and followed by at least one number (or hexdecimal) character but don't matched with the syntax above is illegal. 110 | 111 | #### Embedded Box Forms 112 | 113 | A string can also include [box forms](#box-forms) which will be introduced later on. But in the simplest syntax, box forms in string will not be supported. 114 | 115 | References: 116 | - [Input Syntax](https://reference.wolfram.com/language/tutorial/InputSyntax.html) 117 | - [Special Characters](https://reference.wolfram.com/language/guide/SpecialCharacters.html) 118 | - [Listing of Named Characters](https://reference.wolfram.com/language/guide/ListingOfNamedCharacters.html) 119 | - [String Representation of Boxes](https://reference.wolfram.com/language/tutorial/StringRepresentationOfBoxes.html) 120 | 121 | ### Operators 122 | 123 | There are so many operators in Wolfram Language! But syntax definitions for them is easy to write. You only need to check them out and write them in a proper sequence. I divided them into 15 categories: 124 | 125 | ``` 126 | Replace: 127 | /. Replace 128 | //. ReplaceAll 129 | 130 | Call: 131 | @ Prefix 132 | @@ Apply 133 | @@@ Apply 134 | /@ Map 135 | //@ MapAll 136 | // Postfix 137 | ~ Infix 138 | @* Composition 139 | /* RightComposition 140 | 141 | Comparison: 142 | > Greater 143 | < Less 144 | >= GreaterEqual 145 | <= LessEqual 146 | == Equal 147 | != Unequal 148 | === SameQ 149 | =!= UnsameQ 150 | 151 | Logical: 152 | ! Not 153 | || Or 154 | && And 155 | 156 | Assignment: 157 | = Set 158 | := SetDelayed 159 | ^= UpSet 160 | ^:= UpSetDelayed 161 | /: TagSet (TagUnset, TagSetDelayed) 162 | =. Unset 163 | += AddTo 164 | -= SubtractFrom 165 | *= TimesBy 166 | /= DivideBy 167 | 168 | Rule: 169 | -> Rule 170 | :> RuleDelayed 171 | <-> TwoWayRule 172 | 173 | Condition: 174 | /; Condition 175 | 176 | Repeat: 177 | .. Repeated 178 | ... RepeatedNull 179 | 180 | Arithmetic: 181 | + Plus 182 | - Minus, Subtract 183 | * Multiply 184 | / Devide 185 | ^ Power 186 | . Dot 187 | ! Factorial 188 | !! Factorial2 189 | ' Derivative 190 | ** NonCommutativeMultiply 191 | ++ Increment, PreIncrement 192 | -- Decrement, PreDecrement 193 | 194 | Flow: 195 | << Get 196 | >> Put 197 | >>> PutAppend 198 | 199 | String: 200 | <> StringJoin 201 | ~~ StringExpression 202 | | Alternatives 203 | 204 | Span: 205 | ;; Span 206 | 207 | Compound: 208 | ; CompoundExpression 209 | 210 | Function: 211 | & Function 212 | 213 | Definition: 214 | ? Definition 215 | ?? FullDefinition 216 | ``` 217 | 218 | Note: Some operators may not be included in the list if they are declared in other scopes. 219 | 220 | Also, [named characters](#Named-Characters) can also be recognized as operators. 221 | 222 | Reference: [Operators](https://reference.wolfram.com/language/tutorial/Operators.html). 223 | 224 | ### Variables 225 | 226 | A general variable is some symbols joined with some `` ` `` (a symbol before a `` ` `` is called "context"). 227 | 228 | ```yaml 229 | match: (`?(?:{{symbol}}`)*){{symbol}} 230 | name: variable.other.wolfram 231 | captures: !raw 232 | 1: variable.other.context.wolfram 233 | ``` 234 | 235 | ### Functions 236 | 237 | Functions have no difference with variables in Wolfram Language. But we should color them more like functions in a syntax definition. Here are some basic way to identify a function: 238 | 239 | - an variable placed before `(@{1,3}|//?@|[/@]\*)` 240 | - an variable placed after `(//|[@/]\*)` 241 | - an variable placed on an even order in some expressions joined with some `~` 242 | - an variable placed after a PatternTest (which was introduced in the next part) 243 | 244 | ### Patterns 245 | 246 | Apart from functions, patterns have two forms: 247 | 248 | 1. in the shorthand form of pattern, that is a variable before `:(?=[^:>=])` 249 | 2. in the shorthand form of blank and default, that is a variable before 250 | 251 | ```regex 252 | (?x) 253 | (_\.) # Default 254 | | 255 | (_{1,3}) # Blank, BlankSequence, BlankNullSequence 256 | ({{identifier}})? # Head (here "identifier" means variable) 257 | ``` 258 | 259 | After a pattern, there may be some additional syntaxes other than expressions: 260 | 261 | - Optional: `:` 262 | - PatternTest: `?` 263 | 264 | However, how to color them properly is of great difficulty, and is not supposed to be discussed here. 265 | 266 | ### Bracketing 267 | 268 | There are many kinds of bracketing in the Wolfram Language. A general bracketing rule should be like this: 269 | 270 | ```yaml 271 | begin: \\( 272 | beginCaptures: !all punctuation.section.parens.begin.wolfram 273 | end: \\) 274 | endCaptures: !all punctuation.section.parens.end.wolfram 275 | name: meta.parens.wolfram 276 | patterns: !push expressions 277 | ``` 278 | 279 | In a simplest syntax declaration, we only need to support the following bracketing: 280 | 281 | - parens: `(` and `)` 282 | - braces: `{` and `}` 283 | - brackets: `[` and `]` 284 | - association: `<|` and `|>` 285 | - parts: `[[` and `]]` 286 | - box: `\(` and `\)` 287 | 288 | Reference: [The Four Kinds of Bracketing in the Wolfram Language](https://reference.wolfram.com/language/tutorial/TheFourKindsOfBracketingInTheWolframLanguage.html). 289 | 290 | ### Box Forms 291 | 292 | Box forms is a nested scope with all expression rules and some special syntaxes: 293 | 294 | - ``\\` ``: FormBox 295 | - `\\@`: SqrtBox 296 | - `\\/`: FractionBox 297 | - `\\[%&+_^]`: x-scriptBox (x can be Sub/Super/Over/Under/...) 298 | - `\\\*`: box constructors 299 | 300 | Reference: [String Representation of Boxes](https://reference.wolfram.com/language/tutorial/StringRepresentationOfBoxes.html). 301 | 302 | ### Comment blocks 303 | 304 | A comment block is wrapped in a pair of `(*` and `*)`: 305 | 306 | ```yaml 307 | begin: \(\* 308 | end: \*\) 309 | patterns: !push comment-block 310 | ``` 311 | 312 | Note: in the inner scope of a comment block, the rule itself must be included because the following syntax is legal in Wolfram Language and can be found in some *.wl* files: 313 | 314 | ```mathematica 315 | (* ::Input:: *) 316 | (*(* some *) 317 | (* comments *)*) 318 | ``` 319 | 320 | ### Shorthand expressions 321 | 322 | There are also some syntaxes which corresponds to a function but cannot be simply treated as operators. 323 | 324 | - [Out](https://reference.wolfram.com/language/ref/Out.html): `%(\d*|%*)` 325 | - [MessageName](https://reference.wolfram.com/language/ref/MessageName.html): `(::)\s*({{alnum}}+)` 326 | - [Slot](https://reference.wolfram.com/language/ref/Slot.html), [SlotSequence](https://reference.wolfram.com/language/ref/SlotSequence.html): `(#[a-zA-Z]{{alnum}}*|##?\d*)` 327 | - [Get](https://reference.wolfram.com/language/ref/Get.html), [Put](https://reference.wolfram.com/language/ref/Put.html), [PutAppend](https://reference.wolfram.com/language/ref/PutAppend.html): ``(<<|>>>?) *([a-zA-Z0-9`/.!_:$*~?\\-]+) *(?=[\)\]\},;]|$)`` 328 | 329 | Reference: [Wolfram Language Syntax](https://reference.wolfram.com/language/guide/Syntax.html). 330 | 331 | ### Escaping before newlines 332 | 333 | Finally, if a back-slash (`\\\r?\n`) is placed before a newline, it will eacape the newline. 334 | -------------------------------------------------------------------------------- /syntaxes/basic.yaml: -------------------------------------------------------------------------------- 1 | name: Wolfram Language 2 | scopeName: source.wolfram 3 | 4 | variables: 5 | alnum: '[0-9a-zA-Z]' 6 | number: (?:\d+\.?|\.\d)\d* 7 | symbol: '[$a-zA-Z]+[$0-9a-zA-Z]*' 8 | identifier: '`?(?:{{symbol}}`)*{{symbol}}' 9 | pattern_ending: '[:-]>|:?=|\/[;.\/]|[\]\)\},;&]|>>|<<|[\n\r]' 10 | 11 | escaped_character: \\[ !"%&()*+/@\\^_`bfnrt<>] 12 | encoded_character: \\[0-7]{3}|\\\.[0-9A-Fa-f]{2}|\\:[0-9A-Fa-f]{4} 13 | regexp_character: .|\\\\[\S]|{{escaped_character}}|{{encoded_character}} 14 | 15 | character_class: alnum|alpha|ascii|blank|cntrl|digit|graph|lower|print|punct|space|upper|word|xdigit 16 | numeric_constants: Catalan|Degree|E|EulerGamma|Glaisher|GoldenAngle|GoldenRatio|I|Khinchin|MachinePrecision|Pi 17 | attribute_names: Constant|Flat|HoldAll|HoldAllComplete|HoldFirst|HoldRest|Listable|Locked|NHoldAll|NHoldFirst|NHoldRest|NumericFunction|OneIdentity|Orderless|Protected|ReadProtected|SequenceHold|Stub|Temporary 18 | 19 | patterns: 20 | - match: \A(#!).*(?=$) 21 | name: comment.line.shebang.wolfram 22 | captures: !raw 23 | 1: punctuation.definition.comment.wolfram 24 | - include: '#expressions' 25 | 26 | embedding: 27 | string: 28 | - expressions 29 | - comments 30 | - comment-block 31 | - literals 32 | - box-form 33 | - shorthand 34 | - patterns 35 | - post-pattern 36 | - functions 37 | - bracketing 38 | 39 | comment: 40 | - expressions 41 | - comments 42 | - comment-block 43 | - literals 44 | - string 45 | - string-template 46 | - xml-template 47 | - regular-expression 48 | - box-form 49 | - shorthand 50 | - patterns 51 | - post-pattern 52 | - functions 53 | - bracketing 54 | 55 | contexts: 56 | expressions: 57 | - include: '#newline-escape' 58 | - include: '#comments' 59 | - include: '#literals' 60 | - include: '#shorthand' 61 | - include: '#operators' 62 | - include: '#patterns' 63 | - include: '#functions' 64 | - include: '#variables' 65 | - include: '#bracketing' 66 | 67 | newline-escape: 68 | - match: \\\r?\n 69 | name: constant.character.escape.wolfram 70 | 71 | comments: 72 | - include: '#slot:comments' 73 | - include: '#comment-block' 74 | 75 | comment-block: 76 | - begin: \(\* 77 | beginCaptures: !all punctualation.definition.comment.begin.wolfram 78 | end: \*\) 79 | endCaptures: !all punctualation.definition.comment.end.wolfram 80 | name: comment.block.wolfram 81 | patterns: !push comment-block 82 | 83 | named-characters: 84 | - match: \\\[({{named_characters}})\] 85 | name: constant.character.built-in.wolfram 86 | - match: \\\[\w+\] 87 | name: constant.character.built-in.wolfram invalid.character.built-in.wolfram 88 | 89 | literals: 90 | - include: '#literal-static' 91 | - begin: '"' 92 | end: '"' 93 | beginCaptures: !all punctuation.definition.string.begin.wolfram 94 | endCaptures: !all punctuation.definition.string.end.wolfram 95 | name: string.quoted.wolfram 96 | patterns: !push string 97 | 98 | literal-static: 99 | # numbers 100 | - match: |- 101 | (?x) 102 | (?: 103 | ([1-9]\d*\^\^) # base 104 | ((?:{{alnum}}+\.?|\.{{alnum}}){{alnum}}*) # value 105 | | 106 | ({{number}}) # value 107 | ) 108 | (?: 109 | (\`\`(?:{{number}})?) # accuracy 110 | | 111 | (\`(?:{{number}})?) # precision 112 | )? 113 | (\*\^[+-]?{{number}})? # exponent 114 | name: constant.numeric.wolfram 115 | captures: !raw 116 | 1: constant.numeric.base.wolfram 117 | 4: constant.numeric.accuracy.wolfram 118 | 5: constant.numeric.precision.wolfram 119 | 6: constant.numeric.exponent.wolfram 120 | 121 | # built-in symbols 122 | - !builtin attribute_names constant.language.attribute 123 | - !builtin numeric_constants constant.numeric 124 | - !builtin built_in_constants constant.language 125 | - !builtin built_in_options variable.parameter.option 126 | 127 | string: 128 | # box representation 129 | - begin: \\!\\\( 130 | end: \\\)|(?=") 131 | name: meta.embedded.string-box.wolfram 132 | captures: !all keyword.operator.string-box.wolfram 133 | patterns: !push box-form.in-string 134 | - include: '#string-basic' 135 | 136 | string-basic: 137 | # named characters 138 | - include: '#named-characters' 139 | 140 | # escape characters 141 | - include: '#newline-escape' 142 | - match: '{{escaped_character}}' 143 | name: constant.character.escape.wolfram 144 | - match: '{{encoded_character}}' 145 | name: constant.character.encoding.wolfram 146 | 147 | # invalid characters 148 | - match: |- 149 | (?x) 150 | \\[0-7]{1,2}(?=[^0-7])| 151 | \\\.[0-9A-Fa-f]?(?=[^0-9A-Fa-f])| 152 | \\:[0-9A-Fa-f]{0,3}(?=[^0-9A-Fa-f]) 153 | name: invalid.character.encoding.wolfram 154 | - match: \\[a-zA-Z\[] 155 | name: invalid.character.escape.wolfram 156 | 157 | string-template: 158 | - match: \`{{symbol}}\` 159 | name: variable.parameter.wolfram 160 | - begin: <\* 161 | end: \*>|(?=") 162 | name: meta.embedded.template-expression.wolfram 163 | captures: !all keyword.operator.template-expression.wolfram 164 | patterns: !push expressions.in-string 165 | - include: '#string' 166 | 167 | xml-template: 168 | - include: '#slot:xml-template' 169 | - include: '#string-template' 170 | 171 | regular-expression: 172 | - match: \\\\[dDsSwW] 173 | name: storage.type.character.regexp.wolfram 174 | - match: (\[\^?\[)(:(?:{{character_class}}):)(\]\]) 175 | captures: !raw 176 | 1: storage.class.character.regexp.wolfram 177 | 2: constant.other.class.regexp.wolfram 178 | 3: storage.class.character.regexp.wolfram 179 | - match: \\\\g?[1-9]+ 180 | name: storage.other.reference.regexp.wolfram 181 | - match: \(\?-?[ims]\) 182 | name: storage.modifier.mode.regexp.wolfram 183 | - match: \. 184 | name: keyword.other.any.regexp.wolfram 185 | - match: ([$^]|\\\\[bB]) 186 | name: keyword.control.anchors.regexp.wolfram 187 | - match: \| 188 | name: keyword.operator.alternation.regexp.wolfram 189 | - match: ([?+*]|{\d*,\d+}|{\d+,\d*}) 190 | name: keyword.other.qualifier.regexp.wolfram 191 | - begin: \[\^? 192 | end: \] 193 | captures: !all keyword.control.set.regexp.wolfram 194 | name: meta.set.regexp.wolfram 195 | patterns: 196 | - match: ({{regexp_character}})-({{regexp_character}}) 197 | name: constant.other.range.regexp.wolfram 198 | - include: '#string-basic' 199 | - begin: (\()(\?([:=!]|<[=!]))? 200 | beginCaptures: !raw 201 | 1: keyword.control.group.regexp.wolfram 202 | 2: constant.character.assertion.regexp.wolfram 203 | end: \) 204 | endCaptures: !all keyword.control.group.regexp.wolfram 205 | name: meta.group.regexp.wolfram 206 | patterns: !push regular-expression 207 | - match: \\\\[\S] 208 | name: constant.character.escape.regexp.wolfram 209 | - include: '#string-basic' 210 | 211 | box-form: 212 | - include: '#box-form-static' 213 | - include: '#expressions' 214 | 215 | box-form-static: 216 | - match: \\[%&+_^] 217 | name: keyword.operator.x-scriptBox.wolfram 218 | - match: \\/ 219 | name: keyword.operator.FractionBox.wolfram 220 | - match: \\@ 221 | name: keyword.operator.SqrtBox.wolfram 222 | - match: \\` 223 | name: keyword.operator.FormBox.wolfram 224 | - match: \\\* 225 | name: keyword.operator.box-constructor.wolfram 226 | 227 | shorthand: 228 | - begin: (::)\s*({{alnum}}+)\s*(:?=)\s*(") 229 | beginCaptures: !raw 230 | 1: keyword.operator.MessageName.wolfram 231 | 2: string.unquoted.wolfram 232 | 3: keyword.operator.assignment.wolfram 233 | 4: string.quoted.wolfram punctuation.definition.string.begin 234 | end: '"' 235 | endCaptures: !all string.quoted.wolfram punctuation.definition.string.end.wolfram 236 | contentName: string.quoted.wolfram 237 | patterns: !push string-template 238 | - include: '#shorthand-static' 239 | 240 | shorthand-static: 241 | - match: (%\d+|%+) 242 | name: storage.type.Out.wolfram 243 | - match: (::)\s*({{alnum}}+) 244 | captures: !raw 245 | 1: keyword.operator.MessageName.wolfram 246 | 2: string.unquoted.wolfram 247 | - match: (<<|>>>?) *([a-zA-Z0-9`/.!_:$*~?\\-]+) *(?=[\)\]\},;]|$) 248 | captures: !raw 249 | 1: keyword.operator.flow.wolfram 250 | 2: string.unquoted.filename.wolfram 251 | - begin: '~' 252 | beginCaptures: !all keyword.operator.call.wolfram 253 | end: ({{identifier}})?\s*(~) 254 | endCaptures: !raw 255 | 1: '#function-identifier' 256 | 2: keyword.operator.call.wolfram 257 | name: meta.infix.wolfram 258 | patterns: !push expressions 259 | - match: (//|[@/]\*)\s*({{identifier}}) 260 | captures: !raw 261 | 1: keyword.operator.call.wolfram 262 | 2: '#function-identifier' 263 | - match: ({{identifier}})\s*(?=@{1,3}|//?@|[/@]\*) 264 | captures: !raw 265 | 1: '#function-identifier' 266 | 267 | operators: 268 | - match: \/\/?\. 269 | name: keyword.operator.replace.wolfram 270 | - match: //@?|[/@][@*]|@{1,3}|~ 271 | name: keyword.operator.call.wolfram 272 | - match: =?[=!]=|[<>]=|<(?!\|)|(? 273 | name: keyword.operator.comparison.wolfram 274 | - match: \|\||&&|! 275 | name: keyword.operator.logical.wolfram 276 | - match: \^?:?=|=.|/:|[+\-*/]= 277 | name: keyword.operator.assignment.wolfram 278 | - match: <->|[-:]> 279 | name: keyword.operator.rule.wolfram 280 | - match: \/; 281 | name: keyword.operator.condition.wolfram 282 | - match: \.\.\.? 283 | name: keyword.operator.repeat.wolfram 284 | - match: \+\+|--|\*\*|[+\-*/^.]|'+ 285 | name: keyword.operator.arithmetic.wolfram 286 | - match: <<|>>>? 287 | name: keyword.operator.flow.wolfram 288 | - match: <>|~~|\| 289 | name: keyword.operator.string.wolfram 290 | - match: ;; 291 | name: keyword.operator.span.wolfram 292 | - match: ; 293 | name: keyword.operator.compound.wolfram 294 | - match: '&' 295 | name: keyword.operator.function.wolfram entity.name.function.wolfram 296 | - match: \?\?? 297 | name: keyword.operator.definition.wolfram 298 | - include: '#named-characters' 299 | 300 | patterns: 301 | - begin: ({{identifier}})\s*(:(?=[^:>=])) 302 | beginCaptures: !raw 303 | 1: variable.parameter.wolfram 304 | 2: keyword.operator.Pattern.wolfram 305 | end: (?={{pattern_ending}}) 306 | name: meta.pattern.wolfram 307 | patterns: !push post-pattern 308 | - begin: ({{identifier}})?(?:(_\.)|(_{1,3})({{identifier}})?) 309 | beginCaptures: !raw 310 | 0: variable.parameter.wolfram 311 | 2: variable.parameter.default.wolfram 312 | 3: variable.parameter.blank.wolfram 313 | 4: variable.parameter.head.wolfram 314 | end: (?={{pattern_ending}}) 315 | contentName: meta.pattern.wolfram 316 | patterns: !push post-pattern 317 | 318 | post-pattern: 319 | - include: '#post-pattern-static' 320 | - include: '#expressions' 321 | 322 | post-pattern-static: 323 | - match: (\?)\s*({{identifier}}(?=\s*(:|{{pattern_ending}})))? 324 | captures: !raw 325 | 1: keyword.operator.PatternTest.wolfram 326 | 2: '#function-identifier' 327 | - match: :(?=[^:>=]) 328 | name: keyword.operator.Optional.wolfram 329 | 330 | functions: 331 | - !string-function 332 | type: string-template 333 | target: StringTemplate|TemplateApply 334 | context: !push string-template 335 | - !string-function 336 | type: xml-template 337 | target: XMLTemplate 338 | context: !push xml-template 339 | - !string-function 340 | type: regular-expression 341 | target: RegularExpression 342 | context: !push regular-expression 343 | - include: '#slot:functions' 344 | - !function 345 | target: '{{identifier}}' 346 | captures: 347 | patterns: !push function-identifier 348 | 349 | variables: 350 | - !builtin undocumented_symbols support.undocumented 351 | - include: '#variable-function' 352 | - match: (`?(?:{{symbol}}`)*){{symbol}} 353 | name: variable.other.wolfram 354 | captures: !raw 355 | 1: variable.other.context.wolfram 356 | - match: '#[a-zA-Z]{{alnum}}*|##?\d*' 357 | name: variable.parameter.slot.wolfram 358 | 359 | variable-function: 360 | - !builtin built_in_functions support.function 361 | 362 | function-identifier: 363 | - !builtin undocumented_symbols support.function.undocumented 364 | - include: '#variable-function' 365 | - match: (`?(?:{{symbol}}`)*){{symbol}} 366 | name: entity.name.function.wolfram 367 | captures: !raw 368 | 1: entity.name.function.context.wolfram 369 | 370 | bracketing: 371 | - match: ',' 372 | name: punctuation.separator.sequence.wolfram 373 | - !bracket parens 374 | - !bracket parts 375 | - !bracket brackets 376 | - !bracket braces 377 | - !bracket association 378 | - begin: (\\!)?\\\( 379 | beginCaptures: !all punctuation.section.box.begin.wolfram 380 | end: \\\) 381 | endCaptures: !all punctuation.section.box.end.wolfram 382 | name: meta.box.wolfram 383 | patterns: !push box-form 384 | --------------------------------------------------------------------------------