SkillAgentSearch skills...

Sbnf

A BNF-style language for writing sublime-syntax files

Install / Use

/learn @BenjaminSchaaf/Sbnf
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

SBNF

Build Status Crate

A BNF-style language for writing sublime-syntax files.

Try it out now on the Live Playground!

SBNF is currently used for SWI-Prolog.

Motivation & Goals

Writing syntax definitions is error prone and the result is hard to maintain. The addition of branch_point, while a great feature, dramatically increases complexity and duplication when used.

SBNF attempts do the following:

  • Provide a maintainable, declarative language for writing sublime syntax definitions
  • Compile quickly for fast iteration
  • Compile to an efficient syntax, comparable to hand-made ones

Installation

Note that in order to use the generated syntax you'll need at minimum Sublime Text build 4077 with support for version 2 of Sublime Syntax.

Sublime Package

Navigate to the [Releases page] (https://github.com/BenjaminSchaaf/sbnf/releases) and download the SBNF.sublime-package file. This is a zip file. You'll need to extract the contents of this zip file into the Packages/SBNF folder within the [Sublime Text data directory] (https://www.sublimetext.com/docs/side_by_side.html).

Cargo

With rust installed you can download, build and install the latest released version of SBNF using:

$ cargo install sbnfc

Or if you want the latest features, clone this repository, then build and install using:

$ cargo install --path cli

Sublime Syntax

The syntax definition for SBNF is found in sbnf/sbnf.sbnf. To compile it simply run sbnf sbnf/sbnf.sbnf, you can then symlink or copy the sbnf/ directory to your user packages.

Example

The following is a sbnf grammar for a cut-down version of C. It only allows global/local variable declarations, function definitions and simple function calls. Even this cut down version is extremely difficult to parse correctly with the required meta.function and meta.function-call scopes, as both function definitions and function calls require branch points.

NAME = `simplec`

prototype : ( ~comment )* ;

comment : '(//+).*\n?'{comment.line, 1: punctuation.definition.comment} ;

main : ( variable-declaration | function-definition )* ;

IDENTIFIER = '\b[A-Za-z_]+\b'

function-definition{meta.function}
: type
  IDENTIFIER{entity.name.function}
  `(`
  `)`
  block
;

block{meta.block} : '{' statement* '}' ;

statement : variable-declaration
          | value ';'
          | block
          ;

variable-declaration : type IDENTIFIER{variable} ( '=' value )? ';' ;

type : IDENTIFIER{storage.type} ;

value : '[0-9]+'{constant.numeric}
      | function-call
      ;

# Function calls don't have arguments :)
function-call{meta.function-call}
: IDENTIFIER{variable.function meta.path} `(` `)` ;

The above grammar compiles to the following:

%YAML 1.2
---
# http://www.sublimetext.com/docs/syntax.html
version: 2
name: simplec
scope: source.simplec
contexts:
  # Rule: block
  block|0:
    - meta_content_scope: meta.block.simplec
    - match: '{'
      scope: meta.block.simplec
      set: block|1
    - match: '\S'
      scope: invalid.illegal.simplec
      pop: true
  # Rule: block
  block|1:
    - meta_content_scope: meta.block.simplec
    - include: include!block@1
    - match: '[0-9]+'
      scope: meta.block.simplec constant.numeric.simplec
      push: [block|meta, statement|0]
    - match: '{'
      scope: meta.block.simplec meta.block.simplec
      push: [block|meta, block|1]
    - match: '}'
      scope: meta.block.simplec
      pop: true
    - match: '\S'
      scope: invalid.illegal.simplec
      pop: true
  # Rule: block
  #  For branch point 'block@1'
  block|2|block@1:
    - match: '\b[A-Za-z_]+\b'
      scope: meta.block.simplec variable.simplec
      set: [block|meta, variable-declaration|2]
    - match: '\S'
      fail: block@1
  # Rule: block
  #  For branch point 'block@1'
  block|3|block@1:
    - match: '\('
      scope: meta.block.simplec meta.function-call.simplec
      set: [block|meta, statement|0, function-call|1]
    - match: '\S'
      scope: invalid.illegal.simplec
      pop: true
  # Meta scope context for block
  block|meta:
    - meta_content_scope: meta.block.simplec
    - match: ''
      pop: true
  # Rule: function-call
  function-call|0:
    - meta_content_scope: meta.function-call.simplec
    - match: '\('
      scope: meta.function-call.simplec
      set: function-call|1
    - match: '\S'
      scope: invalid.illegal.simplec
      pop: true
  # Rule: function-call
  function-call|1:
    - meta_content_scope: meta.function-call.simplec
    - match: '\)'
      scope: meta.function-call.simplec
      pop: true
    - match: '\S'
      scope: invalid.illegal.simplec
      pop: true
  function-call|2|block@1:
    - meta_include_prototype: false
    - match: '\b[A-Za-z_]+\b'
      scope: meta.function-call.simplec variable.function.simplec meta.path.simplec
      push: block|3|block@1
      pop: true
  # Rule: function-definition
  function-definition|0:
    - meta_content_scope: meta.function.simplec
    - match: '\)'
      scope: meta.function.simplec
      set: [function-definition|meta, block|0]
    - match: '\S'
      scope: invalid.illegal.simplec
      pop: true
  # Meta scope context for function-definition
  function-definition|meta:
    - meta_content_scope: meta.function.simplec
    - match: ''
      pop: true
  # Include context for branch point block@1
  include!block@1:
    - match: '(?=\b[A-Za-z_]+\b)'
      branch_point: block@1
      branch:
        - type|2|block@1
        - function-call|2|block@1
  # Include context for branch point main@1
  include!main@1:
    - match: '(?=\b[A-Za-z_]+\b)'
      branch_point: main@1
      branch:
        - type|0|main@1
        - type|1|main@1
  # Rule: main
  main:
    - include: include!main@1
    - match: '\S'
      scope: invalid.illegal.simplec
  # Rule: main
  #  For branch point 'main@1'
  main|0|main@1:
    - match: '\b[A-Za-z_]+\b'
      scope: variable.simplec
      push: main|2|main@1
      pop: true
    - match: '\S'
      fail: main@1
  # Rule: main
  #  For branch point 'main@1'
  main|1|main@1:
    - match: '\b[A-Za-z_]+\b'
      scope: meta.function.simplec entity.name.function.simplec
      push: main|3|main@1
      pop: true
    - match: '\S'
      scope: invalid.illegal.simplec
      pop: true
  # Rule: main
  #  For branch point 'main@1'
  main|2|main@1:
    - match: '='
      set: variable-declaration|0
    - match: ';'
      pop: true
    - match: '\S'
      fail: main@1
  # Rule: main
  #  For branch point 'main@1'
  main|3|main@1:
    - match: '\('
      scope: meta.function.simplec
      set: function-definition|0
    - match: '\S'
      scope: invalid.illegal.simplec
      pop: true
  # Rule: prototype
  prototype:
    - match: '(//+).*\n?'
      scope: comment.line.simplec
      captures:
        1: punctuation.definition.comment.simplec
  # Rule: statement
  statement|0:
    - match: ';'
      pop: true
    - match: '\S'
      scope: invalid.illegal.simplec
      pop: true
  type|0|main@1:
    - meta_include_prototype: false
    - match: '\b[A-Za-z_]+\b'
      scope: storage.type.simplec
      push: main|0|main@1
      pop: true
  type|1|main@1:
    - meta_include_prototype: false
    - match: '\b[A-Za-z_]+\b'
      scope: meta.function.simplec storage.type.simplec
      push: main|1|main@1
      pop: true
  type|2|block@1:
    - meta_include_prototype: false
    - match: '\b[A-Za-z_]+\b'
      scope: storage.type.simplec
      push: block|2|block@1
      pop: true
  # Rule: variable-declaration
  variable-declaration|0:
    - match: '[0-9]+'
      scope: constant.numeric.simplec
      set: variable-declaration|1
    - match: '\b[A-Za-z_]+\b'
      scope: meta.function-call.simplec variable.function.simplec meta.path.simplec
      set: [variable-declaration|1, function-call|0]
    - match: '\S'
      scope: invalid.illegal.simplec
      pop: true
  # Rule: variable-declaration
  variable-declaration|1:
    - match: ';'
      pop: true
    - match: '\S'
      scope: invalid.illegal.simplec
      pop: true
  # Rule: variable-declaration
  variable-declaration|2:
    - match: '='
      set: variable-declaration|0
    - match: ';'
      pop: true
    - match: '\S'
      scope: invalid.illegal.simplec
      pop: true

Usage

A SBNF file contains two types of elements: clauses and rules. Clauses provide meta-data for the syntax such as the file extensions, as well as some meta-programming. Rules are the bnf-style rules that define the parsing and scoping of the grammar.

Comments in SBNF start with a # and end at the next newline.

See sbnf.sbnf for a full example grammar.

Clauses

Clauses are in the form <name> <parameters> = <value>. The name must follow SCREAMING_SNAKE_CASE. The following names are reserved for meta-data:

  • NAME: The name of the syntax. This defaults to the base-name of the sbnf file.
  • EXTENSIONS: A space-separated list of file extensions. Equivalent to file_extensions in sublime-syntax.
  • FIRST_LINE: A regex for matching the first line of a file. Equivalent to first_line_match in sublime-syntax.
  • SCOPE: The default scope for the grammar. This defaults to source. followed by the lowercased name of the syntax.
  • SCOPE_POSTFIX: A postfix appended to all scopes in the grammar (excluding the SCOPE clause). This defaults to the name lowercased. Can be left empty to leave out the postfix.
  • HIDDEN: Whether the syntax will be shown in the menu in Sublime Text.

Example:

NAME = `SBNF`
EXTENSIONS = `sbnf`
# Don't need this, as this is already
View on GitHub
GitHub Stars62
CategoryContent
Updated25d ago
Forks6

Languages

Rust

Security Score

95/100

Audited on Mar 15, 2026

No findings