Skip to content

Grammar

Fresh

The official MD-LD grammar is published in two forms: a TextMate grammar for syntax highlighting, and an EBNF for formal specification.

Files

  • mdld.tmLanguage.json — The TextMate grammar (published standard)
  • mdld.ebnf — ISO 14977 EBNF grammar (reference specification)

Philosophy

The TextMate grammar is the core published artifact for MD-LD syntax, replacing the need for formal parser generators (Nearley, BNF, etc.). This approach:

  1. Portability — Works with VS Code, Sublime Text, GitHub, Shiki, and any TextMate-compatible tool
  2. Practicality — Syntax highlighting is the primary use case for grammar definitions
  3. Simplicity — Single, well-maintained grammar instead of multiple format variants
  4. Verification — The handmade parser can validate against this grammar

Grammar Structure

1. Context Declarations

json
{
  "match": "^\\s*(\\[)(@vocab|[A-Za-z][A-Za-z0-9_-]*)(\\])\\s+(<)([^>]+)(>)",
  "captures": {
    "2": { "name": "entity.name.tag.prefix.mdld" },
    "5": { "name": "string.unquoted.iri.mdld" }
  }
}

2. Attribute Blocks

The heart of MD-LD — attribute blocks {...} contain semantic annotations:

TokenPatternExample
Subject={=ex:subject}
Fragment=#{=#fragment}
Object+{+ex:object}
Type.{.foaf:Person}
Predicate(bare){predicate}
Object Predicate?{?predicate}
Reverse Predicate!{!predicate}
Datatype^^{^^xsd:integer}
Language@{@en}
Remove- prefix{-predicate}

3. Value Carriers

Annotations attach to Markdown constructs:

  • Headings: # Title {=subject .Type}
  • List items: - Item {predicate}
  • Blockquotes: > Quote {=subject}
  • Inline spans: [text] {+object}
  • Code fences: ```code``` {=subject}
  • Standalone: {=subject}

Usage with Shiki

javascript
import { getHighlighter } from 'shiki';
import mdldGrammar from './mdld.tmLanguage.json';

const highlighter = await getHighlighter({
  themes: ['github-light'],
  langs: [
    {
      id: 'mdld',
      ...mdldGrammar
    }
  ]
});

const html = highlighter.codeToHtml(mdldSource, {
  lang: 'mdld',
  theme: 'github-light'
});

Scope Names

ConstructScope
Prefixentity.name.tag.prefix.mdld
IRIstring.unquoted.iri.mdld
Subjectentity.name.subject.mdld
Objectentity.name.object.mdld
Typeentity.name.type.mdld
Predicatevariable.other.predicate.mdld
Datatypeentity.name.type.datatype.mdld
Languageentity.name.language.mdld
Remove modkeyword.operator.remove.mdld
Attribute blockmeta.attrsBlock.mdld

EBNF Grammar

ISO 14977 EBNF formal grammar (version 1.1, 2026-05-04):

ebnf
(* MD-LD EBNF Grammar *)
(* Defines syntax of MD-LD annotations embedded in CommonMark Markdown *)

(* 1. Lexical elements *)
letter        = "A".."Z" | "a".."z" ;
digit         = "0".."9" ;
whitespace    = " " | "\t" ;
newline       = "\n" | "\r\n" ;
vchar         = ? any visible non-whitespace character ? ;
text          = { vchar | whitespace } ;

(* 2. Context declarations *)
contextDecl   = "[" , contextKey , "]" , whitespace , "<" , contextIri , ">" ;
contextKey    = "@vocab" | prefixName ;
prefixName    = letter , { letter | digit | "-" | "_" } ;
contextIri    = iri | curie ;

(* 3. Attribute block *)
attrsBlock    = "{" , whitespace* , attrsTokens? , whitespace* , "}" ;
attrsTokens   = attrsToken , { whitespace+ , attrsToken } ;

(* 4. Attribute tokens with optional remove polarity *)
removeMod     = "-" ;

modifiedSubjectDecl      = [ removeMod ] , subjectDecl ;
modifiedObjectDecl       = [ removeMod ] , objectDecl ;
modifiedTypeDecl         = [ removeMod ] , typeDecl ;
modifiedPredicate        = [ removeMod ] , predicate ;
modifiedObjectPredicate  = [ removeMod ] , objectPredicate ;
modifiedReversePredicate = [ removeMod ] , reversePredicate ;
modifiedDatatype         = [ removeMod ] , datatype ;
modifiedLanguage         = [ removeMod ] , language ;

(* 5. Subject and object declarations *)
subjectDecl   = "=" , [ iriRef | "#" , fragment ] ;
fragmentDecl  = "=#" , fragment ;
objectDecl    = "+" , iriRef ;
objectFragmentDecl = "+#" , fragment ;

(* 6. Predicates *)
predicate          = iriRef ;               (* literal predicate *)
objectPredicate    = "?" , iriRef ;         (* object predicate *)
reversePredicate   = "!" , iriRef ;         (* reverse predicate *)

(* 7. Type declaration *)
typeDecl      = "." , iriRef ;

(* 8. Literal modifiers *)
datatype      = "^^" , iriRef ;
language      = "@" , langTag ;
langTag       = letter , { letter | digit | "-" } ;

(* 9. CURIEs and IRIs *)
iriRef        = curie | iri | fragment ;
curie         = prefixName , ":" , reference ;
reference     = ( letter | digit ) , { letter | digit | "-" | "_" | "." } ;
fragment      = reference ;

(* 10. Value carriers *)
inlineSpan    = emphasisSpan | strongSpan | codeSpan | linkSpan ;
heading       = "#" , { "#" } , whitespace , text ;
blockquote    = ">" , whitespace , text ;

(* 11. Grammar guarantees (normative) *)
(* Attribute token order is semantically unordered *)
(* Lists are pure Markdown structure with no semantic scope *)
(* No blank nodes *)
(* No implicit inheritance across list levels *)
(* Deterministic, streaming-friendly parsing *)
(* Round-trip safe *)
(* No semantic inference at syntax level *)

(* 12. Forbidden constructs *)
(* - Blank nodes *)
(* - Key-value pairs *)
(* - Nested attribute blocks *)
(* - Implicit subjects or predicates *)
(* - Structural inference *)
(* - Semantic list propagation *)

Origin Integration

The "flat" quad-based architecture works seamlessly with TextMate highlighting:

  1. Parser emits Quads + Origin {start, end, type}
  2. Shiki generates HTML spans based on TextMate grammar
  3. Glue layer uses Origin data to overlay interactive behavior

Because MD-LD is flat (not tree-based), the mapping between text ranges and semantic data is straightforward. Each character range maps to exactly zero or one quads.