Sublime Forum

Complicated Syntax YAML Structure

#9

Yeah, what I generally do here is something like:

some_context_on_the_stack:
    - meta_scope: meta.declaration

more_specific context:
    - clear_scopes: 1
    - meta_scope: meta.declaration.name

I tried to come up with a live example, but I rely heavily on my macro system for stuff like this:

contexts:
  else-pop:
    - match: (?=\S)
      pop: true

  architectures:
    - match: !word architecture
      scope: storage.type.architecture.vhdl
      push:
        - !meta meta.block.arch-decl.vhdl
        - !expect [';', punctuation.terminator.vhdl]
        - - match: !word begin
            scope: keyword.declaration.vhdl
            set:
              - !meta_set meta.block.arch-decl.body.vhdl
              - !expect [ !word architecture, storage.type.architecture.vhdl ]
              - !expect [ !word end, keyword.declaration.vhdl ]
              - - !pop_on [ !word end ]
                - include: statements
          - include: else-pop
        - !expect ['{{identifier}}', entity.name.architecture.vhdl ]
        - !expect [ !word of, 'keyword.other.vhdl' ]
        - !expect ['{{identifier}}', entity.name.entity.vhdl]

The full expansion of this is not easy to read:

contexts:
  else-pop:
    - match: (?=\S)
      pop: true
  
  architectures:
    - match: '(?i)\b(?:architecture)\b'
      scope: storage.type.architecture.vhdl
      push:
        - - meta_scope: meta.block.arch-decl.vhdl
          - include: else-pop

        - - match: ';'
            scope: punctuation.terminator.vhdl
            pop: true
          - include: else-pop

        - - match: '(?i)\b(?:begin)\b'
            scope: keyword.declaration.vhdl
            set:
              - - clear_scopes: 1
                - meta_scope: meta.block.arch-decl.body.vhdl
                - include: else-pop

              - - match: '(?i)\b(?:architecture)\b'
                  scope: storage.type.architecture.vhdl
                  pop: true
                - include: else-pop

              - - match: '(?i)\b(?:end)\b'
                  scope: keyword.declaration.vhdl
                  pop: true
                - include: else-pop

              - - match: '(?=(?i)\b(?:end)\b)'
                  pop: true
                - include: statements

          - include: else-pop

        - - match: '{{identifier}}'
            scope: entity.name.architecture.vhdl
            pop: true
          - include: else-pop

        - - match: '(?i)\b(?:of)\b'
            scope: keyword.other.vhdl
            pop: true
          - include: else-pop

        - - match: '{{identifier}}'
            scope: entity.name.entity.vhdl
            pop: true
          - include: else-pop

If written by hand, this approach would probably call for quite a few more named contexts. This approach does have several advantages over the traditional method:

  • It seems more declarative, which helps me to reason about it.
  • It’s very concise (with macros).
  • It handles newlines very well without extra code (e.g.“architecture¬name¬of¬name¬is…”)
  • If any part is missing, the rest will be highlighted correctly.

But it does result in a lot of boilerplate if you have to write it all yourself.

1 Like

#10

Is your “macro system” what is handling those !meta, !expect, and a few of the others? I will have to study this and try to figure out what’s going on. Is this out there on package control or just your own creation for personal use?

It does look like I should 1) use named contexts here and 2) investigate how the clear command worked. I had forgotten that it didn’t clear everything and that you could provide a value for it to pop off. The name matching might go and I haven’t quite figured out the else-pop (I mean I can tell what it’s doing, but the why hasn’t struck me yet.)

Also, not entirely sure that wouldn’t match all the initial line elements out of order? It looks like it’s matching twice on my {{identifier}} variable, so wouldn’t the first one match before the other? Yeah I think this is definitely something I don’t understand about the deeper grouping.

I will say the basic syntax that I wrote (aside from the bit about ‘begin’) does seem to work out okay. It’ll identify all the way through to the end and seems to apply the context appropriately throughout. It’s just the matter of the declarative block and the statements block would be nice to have separate metas for. Clear might do it for me though… just need to look at the scope stack right before the begin and make sure that I’m going to erase the correct one.

0 Likes

#11

Yes; that is my macro system. The !whatever tokens are YAML tags, which are perfectly valid though somewhat obscure; they are used by various serializers and such. I have a build system that interprets them as macros; you supply your own python file with each macro defined as a method.

I have not yet made a package. I should probably do that.

I haven’t quite figured out the else-pop (I mean I can tell what it’s doing, but the why hasn’t struck me yet.)

The idea is that you have a context that does exactly one thing — for example, consumes a semicolon. If that is your top context, then anything other than a semicolon is unexpected and you should break out of that context. In this architecture, you often have large stacks of single-purpose contexts — you push them all at once in reverse order, and they are popped one by one in forward order. This can be a bit confusing — the context at the end of the push list will be the first one whose match will be checked. I once wrote a !sequence macro that reversed this so that the contexts were written in sequence order, but that became confusing in complex cases.

The context produced by !meta doesn’t match anything at all. It’s just there so that the meta_scope is used as long as the context is on the stack. When everything above it pops off, it means that the construct you’re parsing is done, so the !meta context will pop immediately.

I readily admit that this is not the typical approach; I don’t recall seeing any other syntaxes written in this style. For me, the heavily stack-based approach makes sense, whereas long chains of sets confused me, and I sometimes found it difficult to maintain the correct meta_scope and to handle errors with grace.

In particular, I was working on a custom Oracle SQL syntax. I ran into several major difficulties when testing it on my company’s large legacy code base:

  • Multi-word keywords and operators might be split across several lines, with many optional parts.

    select
    distinct
    …
    group
    by
    rollup
    …
    
  • Identifiers can always be double-quoted.

    select "colA" as "Column A" …;
    create table "myTable" …;
    
  • The as keyword is often optional, making expression parsing tricky:

    select
        "colA" as "Column A",
        "colB" "Column B"
    --  ^^^^^^ variable.other.column
    --          ^^^^^^^^ entity.name.alias
    ;
    

The heavily stack-based architecture helped with the first and last problems, and the macros cleaned up the code dramatically while solving the second problem. Example:

contexts:
  select-clause:
    - match: !word select
      scope: keyword.other.select
      push:
        - !meta select-clause
        - select-list
        - !expect [ !word "distinct|unique|all", keyword.other ]
        - expect-hint

  select-list:
    - match: (?=\S)
      set:
        - select-list-rest
        - select-list-item

  select-list-rest:
    - match: ','
      scope: punctuation.separator.comma
      push: select-list-item
    - include: else-pop

  select-list-item:
    - match: '\*'
      scope: keyword.other.star
      pop: true
    - match: (?=\S)
      set:
        - !meta_set meta.select-clause.item
        - !expect_identifier entity.name.alias
        - !expect [ !word as, keyword.other ]
        - expression // not pictured

The macros:

def word(expr):
  return r'(?i:\b(?:%s)\b)' % expr

def meta(scope):
  return [
    { "meta_scope": scope, }
    { "include": "else-pop", }
  ]

def meta_set(scope):
  return [
    { "clear_scopes": 1, }
    { "meta_scope": scope, }
    { "include": "else-pop", }
  ]

def expect(expr, scope):
  return [
    {
      "match": expr,
      "scope": scope,
      "pop": True,
    },
    { "include": "else-pop", }
  ]

def expect_identifier(scope):
  return [
    {
      "match": "{{identifier}}",
      "scope": scope,
      "pop": True,
    },
    {
      "match": '"',
      "scope": "punctuation.definition.string.begin",
      "set": [
        { "meta_scope": "meta.string.identifier" },
        { "meta_content_scope": scope },
        {
          "match": '"',
          "scope": "punctuation.definition.string.end",
          "pop": True,
        },
        {
          "match": "\n",
          "scope": "invalid.illegal.newline",
          "pop": True,
        }
      ],
    },
    { "include": "else-pop", }

This example covers a core set of macros that are useful throughout a SQL syntax. Having these available made it easy to “do the right thing” when it came to quoted identifiers, newlines, case-sensitivity, and so forth, without filling the syntax with hundreds of lines of boilerplate. The macros also helped to provide a much richer set of meta scopes for custom tooling than is available for most syntaxes.

Finally, a key design requirement of the syntax was gracefully handling invalid syntax. We have a lot of legacy code that manually builds up very large and complex queries, and it was very important that the syntax could correctly handle a partial code fragment out of context and deal with “missing” constructs that are interpolated from the enclosing syntax using with_prototype. The built-in SQL syntax handles this moderately well because it doesn’t try to interpret much structure, but it lacked support for Oracle features we use. Existing Oracle syntaxes tried to be smarter, but this made them brittle.

Eventually, I tried to prototype my own syntax to see if there was any way to modify an existing syntax to correctly handle alias names without the as keyword, but that proved impossible without a dramatic restructuring of large parts of the syntax, so I wrote my own instead. (This restructuring, less the macros, is essentially what I’ve been working on to fix a variety of issues in the JavaScript syntax.)

4 Likes

#12

@ThomSmith you magnificent bastard :smiley: meta-programming syntax files (!) thanks for giving this exposition.

0 Likes

#13

Wow. That is seriously impressive. I’ll have to give it some thought and see if a strategy like that would be suitable for this language. I have been progressing down a more traditional route and it’s worked out fairly well so far, but I’ll think about it and see if it might be a good idea. This language (originally with Ada roots) is pretty great, but also very verbose so having a tool aid keeping track of things is pretty nice.

0 Likes

#14

Yeah clear_scopes has achieved one thing at the loss of another. If I do it as follows I can properly scope the declarative portion of the structure and the concurrent statements portion, but I lose the ability to check the identifier as the knowledge of the regexp field from the original match seems to be lost once I push down again.

  architectures:
    - match: '(?i)^\s*(architecture)\s+({{identifier}})\s+(of)\s+({{identifier}})\s+(is)'
      captures:
        1: storage.type.architecture.vhdl
        2: entity.name.architecture.vhdl
        3: keyword.other.vhdl
        4: entity.name.entity.vhdl
        5: keyword.declaration.vhdl
      push:
        - meta_scope: meta.block.arch-declarations.vhdl
        - include: block-declarative-items
        - match: '(?i)\b(begin)\b'
          captures:
            1: keyword.declaration.vhdl
          push: 
            - clear_scopes: 1
            - meta_scope: meta.block.arch-statements.vhdl
            - match: '(?i)^\s*(end)\s+(architecture)?\s+({{identifier}})?\s*(;)'
              captures:
                1: keyword.declaration.vhdl
                2: storage.type.architecture.vhdl
                3: entity.name.architecture.vhdl
                4: punctuation.terminator.vhdl
              pop: true

This may not be a great loss as you typically don’t have a lot of these blocks running around (it’s pretty much the ‘main’ of a VHDL code block) so if you screw up the identifier match at the end, all it’ll do is give you a compiler error and it’s a simple quick fix. What I CAN do this way though is have two different includes for the two different meta scopes. Only partially shown here because I don’t have everything in the second set done, but you have some things that are the declaratives and others that are the statements, so I could separate that out a bit and that could be useful for parsing.

Where it’s more problematic is there are other variations on this theme (intro lexical statement / block / begin / another block / end lexical statement) that are a lot more frequent. It would be nice to have a way to have some sort of consistent captured store but I can pick and choose when it’s more important to support well defined includes and when it’s more useful to support matching identifiers.

0 Likes

#15

Well I spoke too soon. clear_scope is not doing what I want it to do.

So, it looked like it was doing what it was supposed to do, however it’s not actually clearing as far as I can tell. Observing the scope just after the architecture statement, it is correctly

source.vhdl meta.block.arch-declarations.vhdl

Observing the scope just after the begin statement, it appears to be:

source.vhdl meta.block.arch-statements.vhdl

Then I go to the end, it correctly identifies the ending clause, and then I sniff again afterwards and it’s back to

source.vhdl meta.block.arch-declarations.vhdl

So… as far as I can tell, clear scopes didn’t actually clear it… just made it invisible for a little bit? I may just have to punt and go back to not marking internal portions, or doing it with several named contexts.

EDIT: So I translated this into a 3 named context variation and the scopes are still not working correctly. Here’s what I have thus far:

  architecture-begin:
    - match: '(?i)^\s*(architecture)\s+({{identifier}})\s+(of)\s+({{identifier}})\s+(is)'
      captures:
        1: storage.type.architecture.vhdl
        2: entity.name.architecture.vhdl
        3: keyword.other.vhdl
        4: entity.name.entity.vhdl
        5: keyword.declaration.vhdl
      push: architecture-declarations

  architecture-declarations:
    - meta_scope: meta.block.arch-declarations.vhdl
    - include: block-declarative-items
    - match: '(?i)\b(begin)\b'
      captures:
        1: keyword.declaration.vhdl
      push: architecture-statements

  architecture-statements:
    - clear_scopes: 1
    - meta_scope: meta.block.arch-statements.vhdl
    - include: concurrent-statements
    - match: '(?i)^\s*(end)\s+(architecture)?\s+({{identifier}})?\s*(;)'
      captures:
        1: keyword.declaration.vhdl
        2: storage.type.architecture.vhdl
        3: entity.name.architecture.vhdl
        4: punctuation.terminator.vhdl
      pop: true

If I’m examining the code I get the following. I’ll have to inject the scope similar to the testing.

...
   ^-- source.vhdl
architecture rtl of thing is
   ^-- correctly identified with contexts
...
   ^-- source.vhdl meta.block.arch-declarations.vhdl
begin
...
   ^-- source.vhdl meta.block.arch-statements.vhdl
end architecture rtl;
   ^-- correctly identified marks
...
   ^-- source.vhdl meta.block.arch-declarations.vhdl

Why in the world is arch-declarations coming back?

0 Likes

#16

Well I have a method that is working. I think (but do not know for sure) that what is happening is that my first context identifies the starter and pushes the basic large body structure notion onto the stack, more specifically it pushed the declaration point onto the stack. When I found the statements portion the push there works in some ways, but honestly it was the problem because now I’m 2 deep into a context that really should be one deep. When I pop, I went back to the declaration portion and so the meta_scope actually reapplied itself.

So trying set seems to work, as long as I get rid of the clear_scopes construct (because that was destroying my source.vhdl line as well.

I think what is getting to me is that there’s a context stack and there’s a scope stack and there are commands for manipulating both. It would have been nice to handle this with anonymous contexts, but this is okay with three and it’s definitely self documenting. And I can clearly have my includes for each block.

I decided as cool as the preprocessor macro system was for the syntax files, it doesn’t suit itself well to this situation because there are so many variations on these verbose structures that even that would probably get mangled.

pop taking an integer argument a bit like clear_scopes might have also been a potential solution if that worked. I suppose I might suggest that as a feature.

0 Likes

#17

I’m not entirely following you, but if you have the problem that a meta_scope somewhere down the stack is prematurely scoping things that should not yet be scoped with that particular meta_scope, you could use something like this:

architecture-statements:
  # the dummy, only here to match anything and consume nothing
  - match: ""
    set:
      # this is the real context
      - meta_scope: meta.block.arch-statements.vhdl
      - ... etc ... pop as usual
0 Likes

#18

Why in the world is arch-declarations coming back?

I can’t see where you’re ever popping architecture-declarations off the stack.

It looks like once you see the begin and you put architecture-statements onto the stack, you never want to see the scope meta.block.arch-declarations.vhdl again. If that is so, then in architecture-declarations, you should set architecture-statements instead of pushing it. Then, you also don’t need clear_scopes.

If you do want the meta.block.arch-declarations.vhdl to continue for a time after the statements end, then your implementation is nearly correct, but you’ll have to define a rule that will pop architecture-declarations when you’re done with it.

1 Like

#19

Yes, I apologize. I end up with a question and I realize there’s a lag time between when I am working on the problem, and when anyone reads it. However at the same time I’m continuing work on it, and thus I end up editing the post and trying to add in additional information that I find out, but to someone coming in somewhat fresh it’s confusing.

I believe when I was using two push statements, I originally had problems because whether anonymous or named, when I hit the ending construct, I was two contexts deep onto the stack. However I can’t actually see the context stack. All I can see is my trail of meta_scopes. (Seeing the current context stack would be kind of useful for debugging for sure.) I was using clear_scopes: 1 so that a 2nd push didn’t end up with both scopes on the scope stack, however pop doesn’t work on scopes, it works on contexts so since I can’t pop twice, when I popped I actually went back to my 2nd tier context.

To elaborate:

Context Stack                             Scope Stack
orig_stack                                source.vhdl
orig_stack arch-decl                      source.vhdl meta.block.arch-decl    
orig_stack arch-decl arch-stmt            source.vhdl (meta.block.arch-decl deleted) meta.block.arch-stmts
orig_stack arch-decl                      source.vhdl meta.block.arch-decl  <-- REAPPLIED because I'm 
                                                                                NOT where I thought I was.

Point being, I was trying to take care of the stack I could see (scopes) but had forgotten that I am dealing with a context stack too and pop works on that.

@ThomSmith Thank you, yes, I wasn’t. And honestly there is no way to do that really with the language. It’s a 3 stage statement. There’s a lexical beginning, a lexical separator, and a lexical ending. Thus, using set seems to work out okay because it pops and pushes and I’m only ever 1 context deep. There’s no real ‘end’ to the first section because that’s automatically the beginning of the second.

I’m seriously half tempted to do a writeup on the documentation for these commands because I think there’s a lot of nuance to each one that is not immediately obvious – either that or I’m just especially dense, I don’t discount that possibility! :smiley:

0 Likes

#20

I think I may be misunderstanding you. How does the following differ from the behavior you want?

contexts:
  architecture-begin:
    - match: '(?i)^\s*(architecture)\s+({{identifier}})\s+(of)\s+({{identifier}})\s+(is)'
      captures:
        1: storage.type.architecture.vhdl
        2: entity.name.architecture.vhdl
        3: keyword.other.vhdl
        4: entity.name.entity.vhdl
        5: keyword.declaration.vhdl
      push: architecture-declarations

  architecture-declarations:
    - meta_scope: meta.block.arch-declarations.vhdl
    - include: block-declarative-items
    - match: '(?i)\b(begin)\b'
      captures:
        1: keyword.declaration.vhdl
      set: architecture-statements

  architecture-statements:
    - meta_scope: meta.block.arch-statements.vhdl
    - include: concurrent-statements
    - match: '(?i)^\s*(end)\s+(architecture)?\s+({{identifier}})?\s*(;)'
      captures:
        1: keyword.declaration.vhdl
        2: storage.type.architecture.vhdl
        3: entity.name.architecture.vhdl
        4: punctuation.terminator.vhdl
      pop: true

0 Likes

#21

That is what I’ve done, actually. There was ambiguity in what I said in the bit you quoted. When I said there was no way to do that in the language, what I meant if I used two pushes, there’s no way to find a construct that will end the declaration stage. There’s only the one lexical construct that ends the whole block. Thus if I push after begin then I’ve got no way to come back. The end statement ends everything.

Sorry for the confusion. Using set does a pop and push so I never go above 1 context on the stack.

0 Likes

#22

Note that due how the meta declarations work, your begin token will receive the meta scopes of both contexts. Unfortunately there is no easy way around that and you’ll probably need an additional empty context like @rwols said with an empty match , which will then “eat” the meta scope of the second context it pushes.

0 Likes

#23

Well, it’s working okay with the 1 push at the outset and then a set at the trasitional point mid structure. Only thing I wish I could do is properly check for the closing identifier validity, but without a way to create a persistent capture, I’ve given up on that goal.

It’s been a good effort because the exact same structure happens with processes (a small block of sequential behavior in a larger block of concurrent behavior) and then a more complicated variation with if/then/else/elsif/end it and there I’ve managed to identify the conditional and statements sections of the blocks along with nested behavior so I think it’s a decent model.

0 Likes

#24

That structure looks very similar to Ada which I am just starting to attempt. See my new post:
https://forum.sublimetext.com/t/is-is-possible-to-create-syntax-definitions-where-contexts-can-refer-up-the-context-stack/32065

I initially did the multiple keyword match in one regexp, but don’t you loose the ability to put those keywords on separate lines because the match only matches single lines at a time. I assume that single parser tokens can be separated by any white-space including new-lines allowing a valid syntax expressed by a single parser token on its own line.

Have you come up with an elegant solution for this kind of structure? I’m also like to match the identifier after the ‘end’ keyword with the one in the definition - arch-identifier in your case.

1 Like

#25

Yeah, VHDL was based upon Ada, so they’re going to share very similar code structures.

Ultimately, no, I did not want to try to do single token at a time matches. It would be extremely tedious and the number of branching paths due to optional structures would turn it into a nightmare.

I opted for trying to stay with ‘expected usage’ which does require someone to stick to a coding style to some degree, but it’s one they should probably ALREADY be following, so I didn’t feel like it was too onerous a task. If someone wants to create hideous code, they can do so without my help :wink:

If you want to see what I’ve done, you can take a peek at https://github.com/Remillard/VHDL-Mode

Sorry it’s late and I keep picking up various things in your post. In several instances where I wished to have a declaration and body scope, I lose the capability to detect an mismatched end clause identifier. Without a way to temporarily save a capture, after you push the declaration context, and then set the body context, you lose the capture. I haven’t tried it because it’s theoretically “bad” practice to stack scopes for a structure, but I have to wonder if there was an overall meta.block context for a structure, and the subcontexts for declaration and body, if it might still be possible to capture the end . Plus this makes expand selection to scope work a little better. But according to scope naming practice, you don’t keep layering scopes for this reason. (Of course, you can do whatever you like).

Anyhow, I am reasonably satisfied with the lexing results I was able to achieve. It’s not as robust as a compiler per se where I’d be doing full tokenizing, but this is for an editor and like I said, we care about creating maintainable expressive code.

0 Likes

#26

If someone wants to create hideous code, they can do so without my help.

haha

Is it bad practice to have many scope levels? Does it slow the editor down? I was really hoping to follow the Ada BNF production rules and I would expect that would result in A LOT of scope levels.

After learning a bit more about this YAML stuff, it still sounds like it’s not going to do what I hoped it would. It doesn’t sound like it’s going to let me identify syntax problems without A LOT of effort and planning and then the result, I feel, would not be maintainable for when new features are added to the language. I created an Ada syntax highlighter for Notepad++ in about 15 minutes. I’m not sure spending 200 hours on this is going to get me anything better without new features like the ones I proposed. It sounds like you discovered the same thing.

I’ll take a look at your VHDL-Mode syntax. Thanks for sharing it.

0 Likes

#27

Well, the YAML is just a slightly different format (and less cumbersome) than the TextMate XML file. The basic lexical searching is the same; same context and scope stacks and that sort of thing. I don’t know about Notepad++ but we’re not just coloring keywords here, we’re actually putting in lexical structure in the background that (theoretically) can be leveraged to do other neat things.

Just sort of depends on what neat things you want to do I guess.

You could do just a keyword coloration in probably about 15 minutes or less because it’s quite easy to put in a list of those and just have it scope them as keyword.other.ada and bam, you’re done. And maybe that’s enough!

As for scoping, it’s not really a matter of speed or number of scopes, just I was going by the Scope Naming best practices in Sublime’s documentation. When you get way down in a structure, there are a lot of layered scopes.

So, just as a for instance, given this VHDL code (which should look a lot like Ada)

entity foobar is
			
end entity foobar;

architecture rtl of foobar is

begin
	
	MY_PROCESS : process (clk, reset)
	begin
		if (reset = '1') then
			a <= b;
			c <= d;
		elsif rising_edge(clk) then
			if (ce = '1') then
				e <= f;
			end if;
		end if;
	end process MY_PROCESS;
end architecture rtl;

If I put the cursor down at the e <= f; line I have the following scope:

vhdl-mode: source.vhdl meta.block.architecture.body.vhdl meta.block.process.body.vhdl meta.block.if.body.vhdl meta.block.if.body.vhdl meta.statement.assignment.signal.vhdl keyword.operator.assignment.vhdl 

That’s pretty fine grained, and that’s what I wanted to have. I think the nesting scope naming rules are to keep a single lexical structure from generating too many scopes. Where you see meta.block.architecture.body.vhdl honestly I could have probably gone with meta.block.architecture.vhdl meta.group.architecture.body.vhdl and subscoped the architecture structure. Same with process there because it too has a declaration portion and a body portion (even though this example didn’t use it. The relevant documentation that explains this is in the scope naming document (https://www.sublimetext.com/docs/3/scope_naming.html)

The entire scope of a function should be covered by one of the following scopes. Each variant should be applied to a specific part, and not stacked. For example, meta.function.php meta.function.parameters.php should never occur, but instead the scopes should alternate between meta.function.php then meta.function.parameters.php and back to meta.function.php.

I took this to read that I shouldn’t layer like I said. However, there is an issue with expand selection to scope where it starts popping scopes off the stack for selection and doing that, it’ll never get the entire meta.block though I think Thom has a fix for that.

Hope that helps! I do think you can do quite a lot with the syntax and it’s possible to make it simple, or really elaborate, as you will.

For what it’s worth, I do have an Ada book here. I don’t use it, but I have it mainly because it tickled me to have it because it was the starting baseline for VHDL. However point being, I can probably help out a bit if you still want to take on the syntax file task.

0 Likes

#28

That’s not the whole thing though. I mean yes, the old TextMate syntaxes also operate on a stack, but all contexts must be nested cleanly. You cannot replace the current context on the stack with another one. You cannot push multiple contexts at once. You cannot have more than a single pop pattern. You cannot have that pop pattern at any position other than either the first or the last pattern of a context.

Nothing more to say about this. You don’t need to use more than a single context (main) if you just want to highlight keywords, operators, numeric literals, strings et al. Highlighting will be very inaccurate, but the syntax will be really easy to look at, understand and modify.

0 Likes