├── .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 |
--------------------------------------------------------------------------------