The general root of your problem is that only the rules from the context
currently at the top of the context stack are used.
In your example here, the context that contains - match: [
pushes the preprocessor
context onto the top of the stack. Once that happens the only rules that can match are the \.
and the ]
to leave.
So, given the input [a [b] c]
, once the first [
is seen, you’re inside preprocessor
where there’s no rule that says that a [
should enter a new nested level; instead that character is consumed by the \.
rule, the ]
is seen to pop
back to the prior context, and now the remainder of the input c]
is just highlighted by whatever rules exist in the outer context.
Try something like this:
contexts:
main:
- include: enter-preprocessor
enter-preprocessor:
- match: '\['
push: preprocessor
preprocessor:
- meta_scope: storage.type.qpscl
- include: enter-preprocessor
- match: ']'
pop: true
Now from the main level if there’s a [
seen, it pushes into the preprocessor
context, which also contains the rule that says that a [
should push into a new instance of preprocessor
, allowing the nested parsing you want.
If you check the scopes, you can see that the first [
scopes as source.qpscl storage.type.qpscl
whereas the second [
scopes as source.qpscl storage.type.qpscl storage.type.qpscl
(i.e. there are two storage scopes now because it’s nesting on itself).
You may be tempted to put that include
directly inside of preprocessor
and skip the extra context, but that will generate an error. When syntaxes are compiled, the include
statements inject the content of the given context
directly into the current one as new instances of the same rule. A context
that includes itself will trigger an error about infinite recursion since it doesn’t know when to stop.