Sublime Forum

Is it possible to fail initial match if following condition never matches?

#1

Syntax Definitions gives this example for bracket balancing:

name: C
scope: source.c

contexts:
  main:
    - match: \(
      push: brackets
    - match: \)
      scope: invalid.illegal.stray-bracket-end

  brackets:
    - match: \)
      pop: true
    - include: main

This will highlight extra closing brackets, but will not highlight opened brackets that were not closed:

())) <- last two highlighted
(( <- both not highlighted, even though it’s a mistake as well

I’m trying to write a rule for Clojure that will match (comment if and only if if ( is followed by comment. Branch works for this, but because I have to try for comment first, simple unfinished ( is also considered a comment (it never gets to the rule that might fail the branch).

Any ideas?

0 Likes

#2

Simple capture groups are enough, if comment follows on same line.

  comments:
    - match: (\()\s*(comment\b)
      captures:
        1: punctuation.definition.comment.begin
        2: keyword
      push: comment-body

  comment-body:
    - meta_scope: comment.block
    - match: \)
      scope: punctuation.definition.comment.end
      pop: 1
0 Likes

#3

Yes, but this will mark (comment (unfinished) as comment. I am looking for a way to get around that

0 Likes

#4

If comment is a keyword, what’s the problem with highlighting incomplete comments?

It is very likely to become one, no?


The only way to check for a condition on subsequent lines to decide for a code block’s scope is branching.

It however requires reliable termination conditions, for both: success and failure.

It pushes branches on stack which need to be popped or rewinded. The latter triggers trying the next available branch. If only a single one of them is not popped or rewinded, it can cause context stack to overflow.

Termination conditions must follow within 128 lines after the branch point matches. Otherwise a branch will never fail or pop.

So, what’s the “failure” condition? When to give up trying to look for a closing parenthesis?

Note,

  1. it should hit early. Otherwise you’d probably tank your syntax’s performance.
  2. it likely increases complexity as ( may introduce many different kinds of grpups in Clojure, which would all need to be handled in this branch point construct.

Therefore, I’d probably avoid the following theoretical solution to your question.

  comments:
    - match: (?=\()
      branch_point: parens
      branch:
        - maybe-nothing
        - maybe-comment

  maybe-nothing:
    - match: \(
      set: maybe-comment-keyword

  maybe-comment-keyword:
    # We see a "comment" keyword right after opening parenthesis,
    # so this plain text branch may fail at next closing parenthesis.
    - match: comment\b
      set: maybe-comment-body
    # We found anyhting else of no interest, so pop this branch
    # and take opening parenthesis as plain text.
    - match: (?=\S)
      pop: 1

  maybe-comment-body:
    # we saw a comment before and now a closing paren
    # looks like a comment, so fail this plain text branch
    - match: (?=\))
      fail: parens
    # a pattern, which indicates for sure, no valid closing paren
    # will follow. So pop this context
    - match: (?=\()
      pop: 1

  maybe-comment:
    - match: \(
      scope: punctuation.definition.comment.begin
      set:
        - comment-body
        - comment-keyword

  comment-keyword:
    - match: comment\b
      scope: keyword
      pop: 1
    - match: (?=\S)
      pop: 1

  comment-body:
    - meta_scope: comment.block
    - match: \)
      scope: punctuation.definition.comment.end
      pop: 1
0 Likes

#5

Thank you! That’s what I ended up doing, too.

  1. A lookahead before branch, so that actual opening paren is captured by list or by comment
  2. Do normal list first but fail and go to comment if comment symbol is encountered. Before, I was starting with comment first, that what caused a single paren to be treated as comment
0 Likes