Sublime Forum

YAML Macros 2.0: Extend existing syntaxes with new functionality

#1

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.

13 Likes

Syntax Creation
Creating a custom color scheme?
Extending attributes in HTML for sublime-syntax files
Syntax hilight in comment
2 Syntaxes on same file with SublimeText 3
Inheritance for sql syntax variants?
How do I add syntax highlighting to a theme
#2

So, if I need to add the followint to the c++ syntax and making so a new one what I must do?

`- match: \b(HIGH|LOW|INPUT|OUTPUT|INPUT_PULLUP|LED_BUILTIN)\b
      scope: constant.language.arduino
    - match: \b(boolean|word|String|string|array)\b
      scope: storage.type.arduino
    - match: PROGRAM
      scope: storage.modifier.arduino
    - match: \b(Serial|Stream|Keyboard|Mouse)\b
      scope: entity.name.class.arduino
    - match: \b(pinMode|digitalWrite|digitalRead|analogReference|analogRead|analogWrite|analogReadResolution|analogWriteResolution|tone|noTone|shiftOut|shiftIn|pulseIn|millis|micros|delay|delayMicroseconds|min|max|constrain|map|pow|sqrt|sin|cos|tan|isAlphaNumeric|isAlpha|isAscii|isWhitespace|isControl|isDigit|isGraph|isLowerCase|isPrintable|isPunct|isSpace|isUpperCase|isHexadecimalDigit|randomSeed|random|lowByte|highByte|bitRead|bitWrite|bitSet|bitClear|bit|attachInterrupt|detachInterrupt|interrupts|noInterrupts)\b
      scope: entity.name.function.arduino`
0 Likes

#3

Looking at the existing C++ syntax definition, you’d want something like this:

%YAML 1.2
%TAG ! tag:yaml-macros:YAMLMacros.lib.extend:
---
!extend
_base: C++.sublime-syntax
name: C++ (with Arduino extensions)

contexts: !merge
  unique-constants: !prepend
    - match: \b(HIGH|LOW|INPUT|OUTPUT|INPUT_PULLUP|LED_BUILTIN)\b
      scope: constant.language.arduino.c++
    - match: \b(Serial|Stream|Keyboard|Mouse)\b
      scope: support.class.arduino.c++

  unique-modifiers: !prepend
    - match: PROGRAM
      scope: storage.modifier.arduino.c++

  unique-types: !prepend
    - match: \b(boolean|word|String|string|array)\b
      scope: storage.type.arduino.c++

The functions are trickier. The C++ syntax already extends the C syntax in an odd way, and it’s the C syntax that handles builtin functions. What you’d probably have to do is also extend the C syntax and change the extended C++ syntax to reference the extended C syntax instead of the original.

0 Likes

#4

Thanks!
I copy the file that you give in the user folder, renaming the name from c++ (with Arduino extensions) to Arduino but it does not appear in list of available language

0 Likes

#5

Did you name the file <something>.sublime-syntax.yaml-macros and build it with the YAML Macros build system? Were there any build errors or console errors?

Also, I glossed over a step; _base: C++.sublime-syntax will look for a file named C++.sublime-syntax in the same directory as your syntax extension file. When extending an existing syntax not under your direct control, the easiest thing to do is probably to put a copy of that syntax definition in the same directory as your syntax extension. (There’s definitely room for improvement in the user experience here.)

0 Likes