[Resolved] Any volunteers to quick write a grammar far handlebars?

Hello, I’m just… not good at lezer. it’s not clicking, and… I’m stuck.

Handlebars is super simple .
(It’s not actually what I’m trying to write, but anything lisp-y should fit in here – which I am not aware of any prior existing S-Expression / Polish notation languages using CodeMirror / Lezer)

it’s a superset of HTML, but we can ignore the HTML parts for now.

The gist is:

  • ignore everything not in {{ }}
  • within {{ }} is where everything needs to happen, and it’s recursive
    what I’ve tried doesn’t matter, because it’s wrong.
    But here is how to read it (slight psuedo code):
    Value = number | string | identifier | SubExpression
    KeyValue = identifier "=" Value
    Positional = list<value>
    Named = list<KeyValue>
    CallExpression = identifier Positional Named
    SubExpression = "(" CallExpression ")"
    {{ Value | SubExpression | CallExpression }} 
    
    # blocks - a starting {{# must have a matching {{/
    {{ "#" identifier Value "as" "|" list<identifier> "|" }}
    
    {[ "/" identifier }}
    
    # comments, I have figured out so they are not a focus here
    
    ignoring white space / invisible characters within. They are insignificant, and can be multiples of a single space, new lines, etc.

This feels like the whole thing to me. Yet, with Lezer, I can’t :cry:

any help is super appreciated. I feel super stuck and alone.

I have a couple variants I’ve tried:

big thing that I forked from codemirror-language-svelte (I'm still removing stuff from this one slowly)
@top Document { (entity | DoctypeDecl)* }

entity[@isGroup=Entity] {
  Comment | 
  IfBlock |
  UnlessBlock |
  EachBlock |
  LetBlock |
  KeyBlock |
  RawHTMLBlock |
  DebugBlock |
  ConstBlock |
  Interpolation |
  UnknownBlock |
  Text |
  EntityReference |
  CharacterReference |
  InvalidEntity |
  Element |
  ProcessingInst |
  IncompleteCloseTag |
  MismatchedCloseTag |
  NoMatchCloseTag
}

Comment { ShortComment | LongComment | HTMLComment }
ShortComment { shortCommentStart shortCommentContent* shortCommentEnd } 
LongComment { longCommentStart longCommentContent* longCommentEnd }
HTMLComment { htmlCommentStart htmlCommentContent* htmlCommentEnd }

IfBlock[group=Block] {
  IfBlockOpen (AmbiguousElseBlock | ElseBlock | entity)* IfBlockClose
}

UnlessBlock[group=Block] {
  UnlessBlockOpen (AmbiguousElseBlock | ElseBlock | entity)* UnlessBlockClose
}

LetBlock[group=Block] {
  LetBlockOpen (entity)* LetBlockClose
}


EachBlock[group=Block] {
  EachBlockOpen (ElseBlock | entity)* EachBlockClose
}

KeyBlock[group=Block] {
  KeyBlockOpen entity* KeyBlockClose
}

Element {
  OpenScriptTag ScriptText (CloseScriptTag | missingCloseTag) |
  OpenStyleTag StyleText (CloseStyleTag | missingCloseTag) |
  OpenTextareaTag TextareaText (CloseTextareaTag | missingCloseTag) |
  OpenTag entity* (CloseTag | missingCloseTag) |
  SelfClosingTag
}

ScriptText[group="TextContent Entity"] { scriptText* }

StyleText[group="TextContent Entity"] { styleText* }

TextareaText[group="TextContent Entity"] { textareaText* }


@skip { space } {
  OpenTag[closedBy=CloseTag] {
    StartTag elementName attr* EndTag
  }

  SelfClosingTag {
    StartSelfClosingTag TagName attr* EndTag |
    StartTag elementName attr* SelfClosingEndTag
  }

  MismatchedCloseTag {
    MismatchedStartCloseTag elementName EndTag
  }

  NoMatchCloseTag[@name=CloseTag] {
    NoMatchStartCloseTag elementName EndTag
  }

  CloseTag[openedBy=OpenTag] {
    StartCloseTag elementName EndTag
  }

  OpenScriptTag[@name=OpenTag,closedBy=CloseTag] {
    StartScriptTag TagName attr* EndTag
  }

  CloseScriptTag[@name=CloseTag,openedBy=OpenTag] {
    StartCloseScriptTag TagName EndTag
  }

  OpenStyleTag[@name=OpenTag,closedBy=CloseTag] {
    StartStyleTag TagName attr* EndTag
  }

  CloseStyleTag[@name=CloseTag,openedBy=OpenTag] {
    StartCloseStyleTag TagName EndTag
  }

  OpenTextareaTag[@name=OpenTag,closedBy=CloseTag] {
    StartTextareaTag TagName attr* EndTag
  }

  CloseTextareaTag[@name=CloseTag,openedBy=OpenTag] {
    StartCloseTextareaTag TagName EndTag
  }

  attr {
    Modifier |
    StyleAttribute |
    Attribute
  }

  Modifier {
    "{{" (ModifierValue { identifier })? "}}"
  }

  StyleAttribute {
    StyleAttributeName (Is (AttributeValue | UnquotedAttributeValue))?
  }

  Attribute {
    AttributeName
    ("|" Modifier)*
    (Is (AttributeValue | UnquotedAttributeValue))?
  }

  UnlessBlockOpen[group=BlockOpen,closedBy=UnlessBlockClose] {
    "{{" pfx<"#"> blk<"unless"> Expression "}}"
  }
  
  UnlessBlockClose[group=BlockClose,openedBy=UnlessBlockOpen] {
    "{{" pfx<"/"> blk<"unless"> "}}"
  }
  
  IfBlockOpen[group=BlockOpen,closedBy=IfBlockClose] {
    "{{" pfx<"#"> blk<"if"> Expression "}}"
  }
  
  IfBlockClose[group=BlockClose,openedBy=IfBlockOpen] {
    "{{" pfx<"/"> blk<"if"> "}}"
  }
  
  ElseBlock[group=BlockInline] {
    "{{" pfx<":"> blk<"else"> (kw<"if"> Expression)? "}}"
  }

  EachBlockOpen[group=BlockOpen,closedBy=EachBlockClose] {
    "{{" pfx<"#"> blk<"each">
    Expression
    kw<"as">
    "}}"
  }
  
  EachBlockClose[group=BlockClose,openedBy=EachBlockOpen] {
    "{{" pfx<"/"> blk<"each"> "}}"
  }

  LetBlockOpen[group=BlockOpen,closedBy=LetBlockClose] {
    "{{" pfx<"#"> blk<"let">
    Expression
    kw<"as">
    "|" (Variable)* "|"
    "}}"
  }
  
  LetBlockClose[group=BlockClose,openedBy=LetBlockOpen] {
    "{{" pfx<"/"> blk<"let"> "}}"
  }

  KeyBlockOpen[group=BlockOpen,closedBy=KeyBlockClose] {
    "{{" pfx<"#"> blk<"key"> Expression "}}"
  }
  
  KeyBlockClose[group=BlockClose,openedBy=KeyBlockOpen] {
    "{{" pfx<"/"> blk<"key"> "}}"
  }

  RawHTMLBlock[group=BlockInline] {
    "{{" pfx<"@"> blk<"html"> Expression "}}"
  }

  DebugBlock[group=BlockInline] {
    "{{" pfx<"@"> blk<"debug"> Variable ("," Variable)* "}}"
  }

  ConstBlock[group=BlockInline] {
    "{{" pfx<"@"> blk<"const"> Expression "}}"
  }

  UnknownBlock[group=BlockInline] {
    "{{"
    BlockPrefix
    BlockType
    UnknownBlockContent?
    "}}"
  }
  
  Interpolation {
    "{{" Value "}}"
  }

  Value {
    ThisExpression 
    | MemberExpression 
    | CallExpression
    | identifier 
    | String 
  }

  CallExpression {
    (identifier | Builtin) list<Value>
  }

  MemberExpression {
    identifier ("." identifier)?
  }

  ThisExpression {
    kw<"this"> "." MemberExpression*
  }

  SubExpression { 
   "(" Value ")"
  }

  SExpression {
    (Value | SubExpression)*
  }


  AmbiguousElseBlock[group=BlockInline] {
    "{{" blk<"else"> (kw<"if"> Expression)? "}}"
  }
}

elementName {
  TagName |
  ComponentName
}

Builtin {
  kw<"on"> |
  kw<"let"> |
  kw<"debugger"> |
  kw<"log"> |
  kw<"each"> |
  kw<"if"> |
  kw<"unless"> |
  kw<"fn"> |
  kw<"modifier"> |
  kw<"helper"> |
  kw<"component"> |
  kw<"yield"> |
  kw<"outlet">
}

String {
  "\"" attributeValueContentDouble? "\"" |
  "\'" attributeValueContentSingle? "\'"
}

AttributeValue {
  Interpolation | String
}

attributeValueContentDouble[@name=AttributeValueContent] {
  (attributeValueContentCharDouble | attributeValueContentEntity)+
}

attributeValueContentSingle[@name=AttributeValueContent] {
  (attributeValueContentCharSingle | attributeValueContentEntity)+
}

attributeValueContentEntity {
  EntityReference |
  CharacterReference |
  Interpolation |
  InvalidEntity
}

@context elementContext from "./html-tokens.js"

@external tokens scriptTokens from "./html-tokens.js" {
  scriptText
  StartCloseScriptTag[@name=StartCloseTag,closedBy=EndTag]
}

@external tokens styleTokens from "./html-tokens.js" {
  styleText
  StartCloseStyleTag[@name=StartCloseTag,closedBy=EndTag]
}

@external tokens textareaTokens from "./html-tokens.js" {
  textareaText
  StartCloseTextareaTag[@name=StartCloseTag,closedBy=EndTag]
}

@external tokens tagStart from "./html-tokens.js" {
  StartTag[closedBy="EndTag SelfClosingEndTag"],
  StartScriptTag[@name=StartTag,closedBy=EndTag],
  StartStyleTag[@name=StartTag,closedBy=EndTag],
  StartTextareaTag[@name=StartTag,closedBy=EndTag],
  StartSelfClosingTag[@name=StartTag,closedBy=EndTag],
  StartCloseTag[closedBy=EndTag],
  NoMatchStartCloseTag[@name=StartCloseTag,closedBy=EndTag]
  MismatchedStartCloseTag[@name=StartCloseTag,closedBy=EndTag],
  missingCloseTag,
  IncompleteCloseTag
}

@external tokens shortCommentContent from "./tokens.js" {
  shortCommentContent
}
@external tokens longCommentContent from "./tokens.js" {
  longCommentContent
}
@external tokens htmlCommentContent from "./tokens.js" {
  htmlCommentContent
}

@external tokens expression from "./tokens.js" {
  Expression
}

list<item> { item (item)* }
kw<term> { @specialize[@name={term}]<identifier, term> }

pfx<type> { @extend[@name=BlockPrefix]<BlockPrefix, type> }

blk<type> { @specialize[@name=BlockType]<BlockType, type> }

@tokens {
  "\""[@name=DoubleQuote, openedBy="\"", closedBy="\""]
  "\'"[@name=SingleQuote, openedBy="\'", closedBy="\'"]
  
  "{{"[closedBy="}}"]
  "}}"[openedBy="{{"]
  "("[closedBy="("]
  ")"[openedBy=")"]
  "["[closedBy="]"]
  "]"[openedBy="["]
  
  "," "|" "..."
  
  space { (" " | "\t" | "\r" | "\n")+ }
  
  identifierChar { @asciiLetter | $[_$\u{a1}-\u{10ffff}] }
  word { identifierChar (identifierChar | @digit)* }
  identifier { word }
  
  Variable { identifierChar (identifierChar | @digit | ".")* }

  BlockPrefix { "#" | "@" | "/" | ":" }
  BlockType { identifier }

  UnknownBlockContent { ![}]+ }

  @precedence { UnknownBlockContent space }

  htmlCommentStart { "<!--" }
  htmlCommentEnd { "-->" }

  shortCommentStart { "{{!" }
  shortCommentEnd { "}}" }

  longCommentStart { "{{!--" }
  longCommentEnd { "--}}" }

  @precedence { htmlCommentStart, longCommentStart, shortCommentStart, "{{" }


  EndTag[openedBy="StartTag StartCloseTag"] { "/"? ">" }
  SelfClosingEndTag[openedBy=StartTag] { "/>" }
  @precedence { SelfClosingEndTag, EndTag }

  tagNameStart {
    "_" |
    @asciiLowercase |
    $[\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D] |
    $[\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u{10000}-\u{EFFFF}]
  }

  componentNameStart {
    @asciiUppercase
  }

  nameChar {
    "_" |
    "-" |
    "." |
    @asciiLetter |
    @digit |
    $[\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D] |
    $[\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u{10000}-\u{EFFFF}] |
    $[\u00B7\u0300-\u036F\u203F-\u2040]
  }

  TagName { tagNameStart nameChar* }

  ComponentName { componentNameStart nameChar* }

  attributeChar { ![\u0000-\u0020\u007F-\u009F"'>/=|:{\uFDD0-\uFDEF\uFFFE\uFFFF] }

  AttributeName { attributeChar+ }
  StyleAttributeName { "--" attributeChar+ }

  @precedence { StyleAttributeName AttributeName }

  UnquotedAttributeValue { ![ \t\n\r\u000C=<>"'`{] ![ \t\n\r\u000C=<>"'`]* }

  attributeValueContentCharDouble { !["&{] }

  attributeValueContentCharSingle { !['&{] }

  Is { "=" }

  EntityReference { "&" ![#; ]+ ";" }

  CharacterReference { "&#" ![; ]+ ";" }

  InvalidEntity { "&" }

  @precedence { CharacterReference, EntityReference, InvalidEntity }

  Text[group=TextContent] { ![<&{]+ }

  ProcessingInst { "<?" piContent }

  piContent { ![?] piContent | "?" piQuestion }
  piQuestion { ![>] piContent | ">" }

  DoctypeDecl { "<!" ("doctype" | "DOCTYPE") ![>]* ">" }

  @precedence { htmlCommentStart, ProcessingInst, DoctypeDecl }

}

@external propSource svelteHighlighting from "./highlight"

previous iteration where I tried writing from scratch
@detectDelim
@top Glimmer { ( Expression | BlockComment )* }
@skip { invisibles }

String { string }

ShortComment { StartShortComment Text* EndStache }
LongComment { StartLongComment Text* EndLongComment }
BlockComment { LongComment | ShortComment }


Expression {
  StartStache SubExpression EndStache
  | Block
}

Block {
  StartBlock
  Expression*
  EndBlock
}
As { kw<"as"> "|" list<name> "|" }

StartBlock[closedBy=EndBlock] {
  StartOpenBlockStache Function SubExpression? As? EndStache
}
EndBlock[openedBy=StartBlock] {
  StartCloseBlockStache Function EndStache
}

Function { if | let | each | else | name }

SubExpression {
  list<Value>
  NamedArgs?
}


Value {
  boolean
  | null
  | undefined
  | Function
  | String
  | Number
  | Invocation
  | Property
  | PropertyPath
}

Invocation { SubExpStart SubExpression SubExpEnd }

Pair { string "=" Value }
NamedArgs { list<Pair> }

@precedence {
  invisibles @left,
  Invocation @left,
  Value @left
}

Argument { "@" identifier }
Property { this | Argument | identifier }
PropertyPath { Property ("." identifier)+ }

boolean {
  @specialize[@name=BooleanLiteral]<identifier, "true" | "false">
}

let { kw<"let"> }
each { kw<"each"> }
if { kw<"if"> }
else { kw<"else"> }

this { kw<"this"> }
null { kw<"null"> }
undefined { kw<"undefined"> }


Number { number }



@tokens {
  invisibles { @whitespace }
  number { @digit+ | (@digit+ ("." @digit*))}

  identifierChar { @asciiLetter | $[_$\u{a1}-\u{10ffff}] }
  word { identifierChar (identifierChar | @digit)* }
  name { word }
  identifier { word }


  Text { ![{] Text? | "{" (@eof | ![{] Text?) }

  string {
   "\"" !["]* "\"" |
    "\'" ![']* "\'"
  }

  StartOpenBlockStache[closedBy="EndStache"] { "{{#" }
  StartCloseBlockStache[closedBy="EndStache"] { "{{/" }
  StartStache[closedBy="EndStache"] { "{{" }
  EndStache[openedBy="StartStache | StartShortComment | StartOpenBlockStache | StartCloseBlockStache"] { "}}" }

  StartShortComment[closedBy="EndStache"] { "{{!" }
  StartLongComment[closedBy="EndLongComment"] { "{{!--" }
  EndLongComment[openedBy="StartLongComment"] { "--}}" }

  SubExpStart[closedBy="SubExpEnd"] { "(" }
  SubExpEnd[openedBy="SubExpStart"] { ")" }


  @precedence {
    identifier,
    name,
    ".", "=",
    "|", "@",
    SubExpStart, SubExpEnd,
    StartLongComment, EndLongComment,
    StartShortComment, StartOpenBlockStache, StartCloseBlockStache,
    StartStache, EndStache,
    invisibles,
    string,
    number,
    Text
  }
}


// Helper And Special Things
list<item> { item (invisibles+ item)* }
kw<term> { @specialize[@name={term}]<identifier, term> }


@external propSource glimmerHighlighting from './highlight'
The change from the big one from svelte that makes it compile, but doesn't do anything that I want with `{{ }}`
  Interpolation {
+    "{{" Expression "}}"
-    "{{" Value "}}"
  }

Did you see the mixed parsing example which contains an example grammar for most of Twig, which is very similar to Handlebars?

I’ll see how far I can get with those examples, thanks!

However, I have a question around this:

  Text { ![{] Text? | "{" (@eof | ![%{] Text?) }

Is this saying that Text can’t start with {? does this work for double {{?

I’m not sure how to read this whole line, actually :thinking:

I made this stackblitz REPL to show what I’ve come up with based on that example: Lezer: Polish Notation Grammar - StackBlitz

something weird is happening though – both @eof and @whitespace give “unexpected token”
(deps were not up to date in the blitz)

This is using this watch script:

const { watch } = require('@codemirror/buildhelper');
const { resolve } = require('path');

let args = process.argv.slice(2);

console.log(args);

if (args.length != 1) {
  console.log('Usage: cm-buildhelper src/index.ts');
  process.exit(1);
}

watch([resolve(args[0])]);

aliased as

    "dev:cm": "node ./dev/watch.cjs src/index.ts",

and ran as

pnpm dev:cm

However, I’m back to … errors :sweat_smile:

 Inconsistent skip sets after "{{" identifier

what is inconsistent and why?

In this fork of the above blitz, I remove everything dealing with comments, and strings: Lezer: Polish Notation Grammar (no comments) - StackBlitz

I believe this is as minimal as I can make it without changing the spirit of the language – about 50 lines

Same thing as before. Something like MemberExpression, which is defined in the global (empty) skip context, has a * repeat at its end, meaning that there are parse states which may or may not be the end of the rule depending on what token comes next, but is used (via Expression and SExpression) in a context with the space skip set.

How do you mean? MemberExpression has nothing to do with {{ though – it’s just identifiers, which even exclude the { character

I don’t see any overlap with what can have {{. :thinking:

@top Template { entity* }

entity { Moustache | Text }

Moustache { Expression }

Value {
  ThisExpression 
  | MemberExpression 
  | CallExpression
  | identifier
}

MemberExpression { identifier ("." identifier)* }
ThisExpression { kw<"this"> ("." MemberExpression)? }

NamedArg { identifier "=" Value }


@skip { space } {
  Expression { "{{" SExpression? "}}" }
  Positional { Value* }
  Named { NamedArg* }

  CallExpression { "(" identifier Positional? Named? ")" }
  SExpression { Value* }
}

list<item> { item (item)* }
kw<term> { @specialize[@name={term}]<identifier, term> }

@tokens {
  //Text { ![<&{]+ }
  Text { ![{] Text? | "{" (@eof | ![%{] Text?) }
  space { @whitespace+ }
  
  "{{"[closedBy="}}"]
  "}}"[openedBy="{{"]
  "("[closedBy="("]
  ")"[openedBy=")"]
  "["[closedBy="]"]
  "]"[openedBy="["]
  
  identifierChar { @asciiLetter | $[_$\u{a1}-\u{10ffff}] }
  word { identifierChar (identifierChar | @digit)* }
  identifier { word }
}

@external propSource svelteHighlighting from "./highlight"

or maybe you mean {{ is just the first stable reference before the conflict?

the first thing that could be to the right of {{ is a Value, which includes identifier, MemberExpression (which also includes identifier) and ThisExpression which could have overlap with MemberExpression, idk.

I thought removing identifier from Value would solve this, as MemberExpression technically allows no .property chain, but no dice

Here, I commented out everything from Value except CallExpression – and I’m starting to think Lezer can’t support Polish notation.

@top Template { entity* }

// entity { Moustache | Text }
entity { Expression | Text }

// Moustache { Expression }

Value {
  // ThisExpression 
  // | MemberExpression 
  CallExpression
}

@skip {} {
  MemberExpression { identifier ("." identifier)* }
  ThisExpression { kw<"this"> ("." MemberExpression)? }

  NamedArg { identifier "=" Value }
}

@skip { space } {
  Expression { "{{" SExpression* "}}" }
  Positional { Value+ }
  Named { NamedArg+ }

  CallExpression { "(" identifier Positional? Named? ")" }
  SExpression { Value* }
}

kw<term> { @specialize[@name={term}]<identifier, term> }

@tokens {
  //Text { ![<&{]+ }
  Text { ![{] Text? | "{" (@eof | ![%{] Text?) }
  space { @whitespace+ }
  
  "{{"[closedBy="}}"]
  "}}"[openedBy="{{"]
  "("[closedBy="("]
  ")"[openedBy=")"]
  "["[closedBy="]"]
  "]"[openedBy="["]
  
  identifierChar { @asciiLetter | $[_$\u{a1}-\u{10ffff}] }
  word { identifierChar (identifierChar | @digit)* }
  identifier { word }
}

@external propSource svelteHighlighting from "./highlight"

produces the error:

rror: Could not load /home/projects/node-rwuxbu/src/syntax.grammar (imported by src/index.js): shift/reduce conflict between
  CallExpression -> · "(" identifier ")"
and
  SExpression -> 
With input:
  "{{" · "(" …
Shared origin: SExpression+ -> · SExpression
  via SExpression -> · Value+
    via Value+ -> · Value
      via Value -> · CallExpression
        CallExpression -> · "(" identifier ")"

maybe precedence could fix this? idk yet

NextJournal wrote a lezer spec for Clojure. I think repository is called clojure-mode.

That helped a little bit, but only in getting a compile – I’m still a long ways from correct.

Here is my current project: Lezer: Polish Notation Grammar (no comments) - StackBlitz

@top Template { entity* }

@skip { whitespace }

entity { 
  Moustache 
  | Comment
}

Moustache { Expression }

Positional { Value+ }
Named { NamedArg+ }

CallExpression { "(" identifier Positional? Named? ")" }

SExpression { 
  Value
}
@external tokens shortCommentContent from "./tokens.js" { shortCommentContent }
@external tokens longCommentContent from "./tokens.js" { longCommentContent }
@external tokens htmlCommentContent from "./tokens.js" { htmlCommentContent }
Comment { shortComment | longComment | htmlComment }
shortComment { shortCommentStart shortCommentContent* shortCommentEnd } 
longComment { longCommentStart longCommentContent* longCommentEnd }
htmlComment { htmlCommentStart htmlCommentContent* htmlCommentEnd }



@skip {} {
  Expression { "{{" SExpression* "}}" }

  Value {
    ThisExpression 
    | MemberExpression 
    //| CallExpression
  }

  MemberExpression { identifier ("." identifier)* }
  ThisExpression { kw<"this"> ("." MemberExpression)? }

  NamedArg { identifier "=" Value }
}

kw<term> { @specialize[@name={term}]<identifier, term> }

@tokens {
  //Text { ![<&{]+ }
  Text { ![{] Text? | "{" (@eof | ![%{] Text?) }
  space { @whitespace+ }
  whitespace { (std.whitespace | ",")+ }

  
  "{{"[closedBy="}}"]
  "}}"[openedBy="{{"]
  "("[closedBy="("]
  ")"[openedBy=")"]
  
  // identifierChar { @asciiLetter | $[_$\u{a1}-\u{10ffff}] }
  identifierChar { @asciiLetter }
  word { identifierChar (identifierChar | @digit)* }
  identifier { word }

  htmlCommentStart { "<!--" }
  htmlCommentEnd { "-->" }

  shortCommentStart { "{{!" }
  shortCommentEnd { "}}" }

  longCommentStart { "{{!--" }
  longCommentEnd { "--}}" }

}

@external propSource svelteHighlighting from "./highlight"

and the debug tree renderer is working:

except it seems comments are unterminated, not sure why, or how to debug :sweat_smile:

Also, when I uncomment CallExpression, I get this error:

Error: Could not load /home/projects/node-rwuxbu/src/syntax.grammar (imported by src/index.js): Inconsistent skip sets after "{{" "(" identifier identifier

{{ ( identifier identifier is legal syntax :thinking: (assuming it ends with ) }}

How do I debug this? :thinking:
I would like to be self-sufficient, but atm, I don’t know how.
So far, I feel like I’m just guessing when I try to resolve problems.

I’ve made progress by ignoring structure/correctness in favor of just highlighting anything. Gonna mark this post as finished, and I’ll create new posts with questions related to this approach.

Did you end up with a satisfying solution for handlebars? If so, would you mind sharing it? I also don’t need the HTML part, just the templating.

potentially – i need to publish this project: glimdown/packages/codemirror/glimmer at main · NullVoxPopuli/glimdown · GitHub

and actually get it implemented in my REPL, here: limber.glimdown.com/ to see how it turned out :sweat_smile:

Just published them here:

And I will continue this PR: Use glimdown deps by NullVoxPopuli · Pull Request #911 · NullVoxPopuli/limber · GitHub (previously, I was pnpm link-ing)

I have this so far:

which feels close – but the way I’m overlaying HTML isn’t quite right, and the different types of comments cause cascading issues later in the document when inserted