2

I am trying to design a custom syntax file that performs some basic error checking for an ascii input file. The input is broken up into sections, and there are input cards specific to each section. The input cards vary and can have either a single argument or multiple. A brief example of the cards with valid input is:

{FOO}
  card1  100.0       !%
  card2  559.0    A  ![ABC]
{BAR}

The goal is to have the following input cards have bad arguments, the syntax flips to highlight the errors for the first card1 argument which needs to be positive, and highlights the second card2 argument Z which needs to be either A, B, or C:

{FOO}
  card1  -100.0       !%
  card2  100.0    Z  ![ABC]
{BAR}

Here is what my current syntax file looks like for the above example:

syn match   testComment "\s*!.*$"
syn region testFOOBlockRegion start="^\{FOO\}" end="^\{"re=e-1,he=e-1,me=e-1 contains=testComment,testFOOCards,testFOOBlockName keepend
syn region testFOOBlockName start="^\{FOO" end="\}" contained oneline

syn match testChar "\s[ABC]\{1}" contained

" Floating point number with decimal no E or e
syn match testFloat "\s[-+]\=\d\+\.\=\d*"
syn match testFloat "/[^=]\+d\+\.\=\d*"
syn match testPosFloat "\s[+]\=\d\+\.\=\d*" contained
syn match testNegFloat "\s[-]\{1}\d\+\.\=\d*" contained

" Floating point like number with E and decimal point (+,-)
syn match testFloat "\s[-+]\=\d*\.\=\d*[eE][\-+]\=\d\+"
syn match testPosFloat "\s[+]\=\d*\.\=\d*[eE][\-+]\=\d\+" contained
syn match testNegFloat "\s[-]\{1}\d*\.\=\d*[eE][\-+]\=\d\+" contained

"Arbitrary regions for cards
syn region testSecondArgPosFloat  start="\s*\S\+\s\+"rs=e,ms=e,hs=e end="\S\+" contains=testPosFloat keepend contained
syn region testThirdArgChar  start="\s*\S\+\s\+\S\+\s\+"rs=e,ms=e,hs=e end="\S\+" contains=testChar keepend contained

"The semi-colon is for multiple cards on one line.
syn region testFOOCards  start="\s*card1" end=";\|$" contains=testComment,testSecondArgPosFloat  keepend contained
syn region testFOOCards  start="\s*card2" end=";\|$" contains=testComment,testSecondArgPosFloat,testThirdArgChar  keepend contained

"Assign syntax groups
hi def link testFloat                   Number
hi def link testPosFloat                Number
hi def link testNegFloat                Number
hi def link testChar                    Todo
hi def link testComment                 Comment
hi def link testFOOBlockName            Conditional
hi def link testFOOCards                Identifier
"Assign these groups to the Error group.  
"In this way, a correct match will cause the syntax to highlight the characters as defined above, 
"and a bad match will indicate an error.
"  If the third argument isn't a char from the list, error
hi def link testThirdArgChar            Error
  "If the second argument isn't a positive float, error
hi def link testSecondArgPosFloat       Error
  "This ensures that only valid cards are syntaxed correctly and other cards are highlighted as errors
hi def link testFOOBlockRegion          Error

Essentially, with the current syntax, I am able to get a card with a single argument behaving and highlighting properly. If card1 has a negative sign placed infront of it, the syntax highlighting changes to the error syntax. For card2, only the first argument's syntax works, the second does not. I believe it is an issue with the keepend option where the syntax reverts after the "second argument" is matched, regardless of it being correct or an error, to the syntax for testFOOCards.

I'm afraid that the only way to get this to work is to have multiple nested permutations of things like SecondArgFloatWithThirdArgChar regions and was wondering if anyone else had something more simple/elegant or knew where else I could be looking in the manual. Thanks in advance.

Update

Several tests using the nextgroup suggestion that were tried were:

  1. syn region testSecondArgPosFloat 
    start="\s*\S\+\s\+"rs=e,ms=e,hs=e end="\S\+"
    nextgroup=testThirdArgTempUnit contains=testPosFloat keepend
    contained
    
  2. syn region testFOOCards  start="\s*card2" end=";\|$" 
    nextgroup=testSecondArgPosFloat 
    contains=testComment,testSecondArgPosFloat,testThirdArgTempUnit 
    keepend contained
    
  3. syn region testSecondArgPosFloat 
    start="\s*\S\+\s\+"rs=e,ms=e,hs=e end="\S\+"
    nextgroup=testThirdArgTempUnit contains=testPosFloat
    contained
    
  4. syn region testFOOCards  start="\s*card2" end=";\|$" 
    nextgroup=testSecondArgPosFloat 
    contains=testComment,testSecondArgPosFloat,testThirdArgTempUnit 
    contained
    

It would seem that the nextgroup argument doesn't solve the problem. There was only a noticeable syntax change when the keepend option was toggled.

Since this current version uses regions to march forward across the line from left to right, I tried using syn match to match the third argument first, use nextgroup to match the second argument, and lastly a match for card2. That also did not work as I'd like.

I was also curious if defining the starting region in the examples above changed where subsequent matches would be found. I tested this by adding more floats between the first and second argument values in card2 to see if the highlighting would change to what I expected. That also did not work. I also tried using ^ in start match to anchor it to the beginning of the line, and repeated adding arguments. That didn't work either.

3
  • 2
    Welcome to Vi and Vim!
    – D. Ben Knoble
    Nov 14 '20 at 2:43
  • 1
    Do you think nextgroup= would help here? You could use it to specify which groups to use to match the following field... Right?
    – filbranden
    Nov 14 '20 at 3:18
  • 1
    @filbranden I added an update trying out your suggestion.
    – Vee
    Nov 16 '20 at 18:52
0

Ok, after playing around with all sorts of options starting with nextgroup=, I found a solution. The issue was a combination of using the rs=,ms= and needing to nest successive arguments in the contains block.

The original example:

"Arbitrary regions for cards
syn region testSecondArgPosFloat  start="\s*\S\+\s\+"rs=e,ms=e,hs=e end="\S\+" contains=testPosFloat keepend contained
syn region testThirdArgChar       start="\s*\S\+\s\+\S\+\s\+"rs=e,ms=e,hs=e end="\S\+" contains=testChar keepend contained

becomes:

"Arbitrary regions for cards
syn region testSecondArgPosFloat  start="\s*\S\+\s\+"hs=e end="\S\+" contains=testPosFloat keepend contained
syn region testThirdArgChar       start="\s*\S\+\s\+\S\+\s\+"hs=e end="\S\+" contains=testChar,testSecondArgPosFloat keepend contained

Also, there isn't a need to specify more contains= matches than necessary for the cards. Since the testThirdArgChar contains the second argument for a positive float, it doesn't need to be listed for the card.

The original card2:

syn region testFOOCards  start="\s*card2" end=";\|$" contains=testComment,testSecondArgPosFloat,testThirdArgTempUnit contained

becomes:

syn region testFOOCards  start="\s*card2" end=";\|$" contains=testComment,testThirdArgTempUnit contained

While this solution may not be the most efficient, it does behave how I want for 90% of the cases. I currently can't get the error highlighting to work if there are two cards on the same line when the ; is used, but that can be deferred for now.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.