A lot of users have asked how to extend an existing syntax with new functionality. Unfortunately, this has always required copying the entire syntax to modify it, which is a hassle and an impediment to maintenance.
In order to facilitate this common case, I’ve added to YAML Macros a macro library that makes extending an existing syntax a piece of cake. To demonstrate this, here is a complete implementation of JSX on top of the built-in JavaScript syntax*:
%YAML 1.2
%TAG ! tag:yaml-macros:YAMLMacros.lib.extend:
---
!extend
_base: JavaScript.sublime-syntax
name: JSX (YAML Macros example)
variables: !merge
jsx_identifier: '[_$[:alpha:]][-_$[:alnum:]]*'
contexts: !merge
expression-begin: !prepend
- include: jsx-tag
jsx-interpolation:
- match: '{'
scope: punctuation.section.braces.begin.js
push:
- - meta_scope: meta.jsx-interpolation.js
- match: '}'
scope: punctuation.section.braces.begin.js
pop: true
- expression
jsx-expect-tag-end:
- match: '>'
scope: punctuation.definition.tag.end.js
pop: true
- include: else-pop
jsx-tag:
- match: '<'
scope: punctuation.definition.tag.begin.js
set:
- meta_scope: meta.tag.js
- match: '/'
set:
- - meta_scope: invalid.illegal.unmatched-tag.js
- include: else-pop
- jsx-expect-tag-end
- jsx-tag-name
- match: (?=\S)
set:
- jsx-tag-attributes
- jsx-tag-name
jsx-tag-attributes:
- meta_scope: meta.tag.js
- match: '>'
scope: punctuation.definition.tag.end.js
set: jsx-body
- match: '/'
scope: punctuation.definition.tag.end.js
set: jsx-expect-tag-end
- include: jsx-interpolation
- match: '{{jsx_identifier}}'
scope: entity.other.attribute-name.js
- match: '='
scope: punctuation.separator.key-value.js
- match: (?=['"])
push: literal-string
jsx-tag-name:
- match: '{{jsx_identifier}}'
scope: entity.name.tag.js
set:
- match: '[:.]'
scope: punctuation.accessor.js
set: jsx-tag-name
- include: else-pop
- include: else-pop
jsx-body:
- match: '<'
scope: punctuation.definition.tag.begin.js
set:
- meta_scope: meta.tag.js
- match: '/'
scope: punctuation.definition.tag.begin.js
set:
- jsx-expect-tag-end
- jsx-tag-name
- match: (?=\S)
set:
- jsx-body
- jsx-tag-attributes
- jsx-tag-name
- match: ''
push:
- meta_include_prototype: false
- include: jsx-interpolation
- match: (?=<)
pop: true
Because this syntax is a simple extension of the default JavaScript syntax, it is fully compatible with tools designed for that package.
* This example extends the version pulled in #1009, which has not yet been merged.
Of course, when you extend someone else’s syntax, you will have to periodically copy the latest version and sometimes modify your extension to match, but this is still much less hassle than trying to patch upstream changes directly into a customized syntax. You could also use this system to provide multiple variants of a single new core syntax, and you wouldn’t have to worry about upstream changes at all.
Potential applications include:
- JavaScript Flow annotations.
- Integration of commenting or documentation systems.
- Easy custom syntaxes for XML, JSON, or YAML-based languages.
- A core SQL syntax with multiple vendor-specific extensions.
Some ideas for future extension:
- A script that takes in a simple set of tag names and scopes and outputs a brand-new custom XML-based syntax. (Likewise for YAML and JSON.)
- A Markdown extension that automatically writes syntax rules to highlight code in ```<language> … ``` blocks based on whatever syntaxes you have available.
- Configurable syntaxes – specify a set of options and the system would auto-build a customized syntax for you. Hypothetical options for a JavaScript syntax:
- Use JSX.
- Use Flow annotations.
- Use TypeScript extensions.
- Highlight bare object keys as strings.
- Mark non-strict code as
invalid.deprecated
. - Highlight/don’t highlight DOM/Node constants.
Questions/comments/complaints/suggestions welcomed.