BenjaminSchaaf / sbnf

A BNF-style language for writing sublime-syntax files
MIT License
58 stars 6 forks source link

Unexpected result with recursive rules? #23

Closed nuchi closed 3 years ago

nuchi commented 3 years ago

Using sbnf version 0.4.0. EDIT: same on master

main = expr;
expr = primitive (`+` primitive)*;
primitive = num | `(` expr `)`;
num = '\d+'{constant.numeric};

Sample highlighting: image

Expected result: the first ( should be invalid.illegal. Actual result: ( matches in main.

compiled output ```yaml %YAML 1.2 --- # http://www.sublimetext.com/docs/syntax.html version: 2 name: test file_extensions: - test scope: source.test contexts: # Rule: expr expr|0: - match: '\+' push: expr|1 - match: '(?=\S)' pop: true # Rule: expr expr|1: - match: '\d+' scope: constant.numeric.test pop: true - match: '\(' set: primitive|0 - match: '\S' scope: invalid.illegal.test pop: true # Rule: main main: - match: '\d+' scope: constant.numeric.test set: expr|0 - match: '\(' set: [expr|0, primitive|0] - match: '\S' scope: invalid.illegal.test # Rule: primitive primitive|0: - match: '\d+' scope: constant.numeric.test set: [primitive|1, expr|0] - match: '\(' set: [primitive|1, expr|0, primitive|0] - match: '\S' scope: invalid.illegal.test pop: true # Rule: primitive primitive|1: - match: '\)' pop: true - match: '\S' scope: invalid.illegal.test pop: true ```

The first 1 matches in main, and then expr|0 is set; the ( matches '(?=\S)' in expr|0 which pops the stack (which only contains expr|0), resetting back to main.

I'm only mostly sure that there's a bug here; it's possible I have a misunderstanding somewhere. I'll also note that what I wanted to write was actually something like:

main = expr;
expr = expr `+` expr
     | primitive
     ;
primitive = num | `(` expr `)`;
num = '\d+'{constant.numeric};

That's because I'm trying to directly translate a yacc source file which contains things like the latter. sbnf overflows the stack if I try it, but compiles my workaround. I hope there's some workaround I can use!

Thanks for taking the report!

nuchi commented 3 years ago

The following workaround seems to get me what I want:

- main = expr;
+ main = expr '\s';
  expr = primitive (`+` primitive)*;
  primitive = num | `(` expr `)`;
  num = '\d+'{constant.numeric};
mitranim commented 3 years ago

Sounds like a misunderstanding. The first SBNF snippet, when interpreted in my head, behaves exactly like the screenshot. Not sure why you expect it to illegalize the first paren.

BenjaminSchaaf commented 3 years ago

With the way sublime-syntax works, main and prototype are special contexts that can never pop. Thus SNBF makes main = expr equivalent to main = expr*. I'm not sure if there's something in the language design I can do to make it clearer.

nuchi commented 3 years ago

Thus SNBF makes main = expr equivalent to main = expr*.

Aha! That's what I was missing. Thanks for the clarification — I was expecting the whole document to match a single expr.