Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add syntax files to simplify porting re2c to new languages. #450

Open
skvadrik opened this issue Jun 21, 2023 · 48 comments
Open

Add syntax files to simplify porting re2c to new languages. #450

skvadrik opened this issue Jun 21, 2023 · 48 comments

Comments

@skvadrik
Copy link
Owner

Syntax files should be config files that describe a language backend via a set of configurations. When generating code, re2c would map various codegen concepts to the descriptions provided by the syntax file. This way a new language can be added easily by supplying a syntax file (by the user or by re2c developers --- existing backends should be described via syntax files, distributed with the re2c source code and as part of a re2c installation).

The man difficulty is to decide on a minimal set of configurations that are orthogonal and capable of describing different languages, so that we don't have to add new ad-hoc configurations for each new language. Once this is decided, codegen subsystem should be modified to support syntax files, and exising backends should be rewritten using syntax files (before adding new ones).

Related bugs/commits:

@pmetzger
Copy link
Contributor

FWIW, this would be an amazingly cool feature.

@pmetzger
Copy link
Contributor

pmetzger commented Oct 9, 2023

Just wanted to say, again, this would be an amazingly cool feature.

@skvadrik
Copy link
Owner Author

skvadrik commented Oct 9, 2023

I started some experimental work on this. I'm constrained on time at the moment, so it's not moving fast, but it's my next most important goal for re2c.

@skvadrik
Copy link
Owner Author

An update. I've been doing some experimental work on syntax-file branch, and I'm now at the point when all three existing language backends (C/C++, Go, Rust) can be expressed via config files: https://github.com/skvadrik/re2c/tree/syntax-files/include/syntax. There language option has been removed from re2c, meaning that the codebase is now clear from any language-specific checks: 80bb7db (any language-specific behaviour is based on syntax configs). For the three existing backends, config files are built into re2c so that there's not need to pass any syntax configs (the --lang option works as before, as well as re2go and re2rust binaries), so this is all backwards compatible.

@skvadrik
Copy link
Owner Author

skvadrik commented Dec 19, 2023

Next thing for me is to write a syntax config for D (very close to C/C++) and resurrect test cases from #431.

Feedback on the current DSL I used in syntax file is welcome. Although it's not documented yet, but it shouldn't be very hard to understand. There are different groups of configurations:

  • list configurations
  • single-word configuration
  • code configurations (those starting with code:)

The first two groups are simple, and the last group is basically templates for language constructs that are used in different parts of codegen. The DSL allows conditionals (condition ? it-yes-part : if-no-part), list generators that loop for a specific list variable [var: loop-body], strings and simple variables. All variables are in essence callbacks to re2c that get substituted with code fragments. Configurations in syntax files respect re2c options and configurations inside of the re2c blocks in the input file.

@skvadrik
Copy link
Owner Author

This is all experimental work, all configurations are subject to change while they are on syntax-files branch.

@pmetzger
Copy link
Contributor

Handling a couple of more diverse languages (OCaml, Python) might be an interesting test here. I'm not sure I entirely understand how the init file works btw.

@skvadrik

This comment was marked as outdated.

@skvadrik
Copy link
Owner Author

Dlang support was added in d492026.

OCaml support was added in c1ccefa (see discussion in #449).

Now, as suggested by @pmetzger I started looking at python. Basic example (with a custom syntax file, not shared here):

/*!re2c
    re2c:define:YYFN = ["lex;", "str;", "cur;"];
    re2c:define:YYPEEK = "str[cur]";
    re2c:define:YYSKIP = "cur += 1";
    re2c:yyfill:enable = 0;

    number = [1-9][0-9]*;

    number { return True }
    *      { return False }
*/

def main():
    str = "1234\x00"
    if not lex(str, 0):
         raise "error"

if __name__ == "__main__":
    main()

The generated code looks like this:

# Generated by re2c

def yy0(str, cur):
        yych = str[cur]
        cur += 1
        if yych <= '0':
                return yy1(str, cur)
        elif yych <= '9':
                return yy2(str, cur)
        else:
                return yy1(str, cur)


def yy1(str, cur):
        return False

def yy2(str, cur):
        yych = str[cur]
        if yych <= '/':
                return yy3(str, cur)
        elif yych <= '9':
                cur += 1
                return yy2(str, cur)
        else:
                return yy3(str, cur)


def yy3(str, cur):
        return True

def lex(str, cur):
        return yy0(str, cur)



def main():
    str = "1234\x00"
    if not lex(str, 0):
         raise "error"

if __name__ == "__main__":
    main()

Does it look reasonable? I plan to use recursive functions code model by default, but loop/switch model should work as well. Which one is preferable? Do function calls add much overhead in python? I'll do some benchmarks myself later, but I'm curios to hear what others think.

@pmetzger
Copy link
Contributor

pmetzger commented Mar 22, 2024

Python is interpreted, and doesn't have much of an optimizer. I suspect loop switch will be faster, but I don't know for sure. Benchmarks will be needed.

Another thing about python: it has a // operator, which potentially might be mistaken for a comment. Generally, I think that it might be good if the comment character for a particular language could be defined rather than using the default.

Oh, and lastly: python has optional type annotations. Those might be helpful in the generated code for those using mypy.

@skvadrik
Copy link
Owner Author

Huh, I got RecursionError: maximum recursion depth exceeded on one example (not even a big one). In compiled languages I enforced tail recursion (ether in the form of annotation, or optimization level), but in python I think nothing can be done, the only way is to go with loop/switch. Am I missing something?

@pmetzger
Copy link
Contributor

@skvadrik Oh! Python does not have tail recursion. I had not noticed how you were doing it, if you want to use recursion for this in Python you need a trampoline function so that you don't infinitely recurse. I guess using match (the equivalent of switch) is pretty much what you would need to use if you don't use a trampoline.

@skvadrik
Copy link
Owner Author

For reference, https://github.com/0x65/trampoline describes how to do trampolines with python.

@skvadrik
Copy link
Owner Author

skvadrik commented Apr 3, 2024

Python support was added in 95b916d (based on loop/switch mode).

Update: commit changed after force-push: 63c775a

@pmetzger
Copy link
Contributor

pmetzger commented Apr 3, 2024

Just looked at the python example, it seems pretty reasonable.

@skvadrik
Copy link
Owner Author

Vlang support was added in 73853c5.

@pmetzger
Copy link
Contributor

So I find myself wanting to use the Python support. I'm an adult and understand that all the syntax etc. for syntax files may change in the future. Could a suitable version of re2c get tagged (perhaps not officially released) for people who want to experiment with real code?

@skvadrik
Copy link
Owner Author

So I find myself wanting to use the Python support. I'm an adult and understand that all the syntax etc. for syntax files may change in the future. Could a suitable version of re2c get tagged (perhaps not officially released) for people who want to experiment with real code?

Use this: https://github.com/skvadrik/re2c/releases/tag/python-experimental. I previously rebased git history so that all python-specific work goes before it, and I shouldn't break git history up to this commit with my future changes.

@skvadrik
Copy link
Owner Author

@pmetzger It will be very helpful if you try it out and report any issues. :)

@skvadrik
Copy link
Owner Author

Haskell support was added in 4e78ef8. The configurations have to be a bit more verbose, as even simple operations have to update lexer state and propagate it further down the program (see https://github.com/skvadrik/re2c/tree/syntax-files/examples/haskell). I'm thinking that this can benefit from language-specific default API (so far it only exists for the C/C++ backend, but the definitions are now all in syntax files, so each syntax file may provide its own default API). There are monadic and pure styles for Haskell.

@skvadrik
Copy link
Owner Author

skvadrik commented Jul 3, 2024

Java support was added in e2facbf. Update: rebased as 2dd0de3.

Unlike other languages, there is no good default implementation for YYPEEK in Java as it has very different syntax for strings and arrays. Therefore YYPEEK is left for the user to define even in default and record APIs.

@pmetzger
Copy link
Contributor

pmetzger commented Jul 5, 2024

Java support was added in e2facbf.

🔥

@skvadrik
Copy link
Owner Author

skvadrik commented Jul 8, 2024

JS support was added in 74ace08.

@skvadrik
Copy link
Owner Author

Zig support was added in 5cd48a8.

@skvadrik
Copy link
Owner Author

My further plan is to focus on polishing syntax file API (and who knows - maybe eventually even releasing it :D). If you have other interesting languages in mind, please mention them in this thread - the API is not frozen yet and it's possible to change it. That said, for the last three languages (Java, JS, Zig) no changes were needed, which means it should be expressive enough (at least for C-like languages).

@pmetzger
Copy link
Contributor

My main issue remains the "comment syntax" for the re2c blocks, but I will confess I haven't dived in deeply enough to things like the API. Maybe I should.

One option is to do a release soon but make the support for languages using the syntax files "experimental" to get more widespread feedback.

@skvadrik
Copy link
Owner Author

@pmetzger I rebased syntax-files branch and pulled it into master. Sorry if I broke your workflow. From now on just use master - I will keep merging syntax-files into it.

@skvadrik
Copy link
Owner Author

My main issue remains the "comment syntax" for the re2c blocks, but I will confess I haven't dived in deeply enough to things like the API. Maybe I should.

I will give it more thought.

One option is to do a release soon but make the support for languages using the syntax files "experimental" to get more widespread feedback.

It's not the re2c way to break backward compatibility, if possible to avoid it - I don't think we have a big enough community to get timely feedback.

@pmetzger
Copy link
Contributor

So on the comments: there are going to end up being languages where // or /* is valid syntax. (For example, in Python, // is the integer division operator.) It feels safer to be able to use comments that make sense in the context of a given language.

@skvadrik
Copy link
Owner Author

So on the comments: there are going to end up being languages where // or /* is valid syntax. (For example, in Python, // is the integer division operator.) It feels safer to be able to use comments that make sense in the context of a given language.

That's a good point about syntax clash: I don't think it's a problem for the opening comment /*!re2c, as it is too specific, but the closing comment */ may be a problem.

Language-specific lexer will be hard to implement. At the moment lexer is written in re2c, and I'd like to keep it this way both for dogfooding and performance reasons.

Also, not all languages have multiline comments.

Instead of trying to use language-specific syntax, we can do what lex and bison do: use syntax that fits equally bad into any language, namely %{ and %}. These are already partially supported by re2c, so it will be natural to extend them, it will be familiar for the users (at least to some extent) and it shouldn't be that hard to implement. What do you think?

What I'm more worried about are single quotes (some languages allow them as parts of identifiers, labels, etc.). Syntax files already have some configurations that tell re2c whether to expect single quotes, backtick-quoted strings, etc.

@pmetzger
Copy link
Contributor

pmetzger commented Jul 16, 2024

Instead of trying to use language-specific syntax, we can do what lex and bison do: use syntax that fits equally bad into any language, namely %{ and %}. These are already partially supported by re2c, so it will be natural to extend them, it will be familiar for the users (at least to some extent) and it shouldn't be that hard to implement. What do you think?

I think that's certainly an option, especially if that can be shifted to an alternative in the unlikely event that a specific language is using that specific bracket pair for real syntax.

What I'm more worried about are single quotes (some languages allow them as parts of identifiers, labels, etc.).

ML descended languages use them to identify type variables. Lisp uses them to identify unevaluated forms.

@pmetzger
Copy link
Contributor

It occurs to me that, with very high likelihood, nothing is ever going to use %RE2C{ and %RE2C}

@skvadrik
Copy link
Owner Author

skvadrik commented Jul 16, 2024

I think that's certainly an option, especially if that can be shifted to an alternative in the unlikely event that a specific language is using that specific bracket pair for real syntax.

Exactly, that's the way it already works. We just need to extend %{ and %} to cover directives like /*!stags:re2c and prefixes like /*!local:re2c. Also at the moment it requires -F/--flex-syntax option - we'll need to drop that requirement.

ML descended languages use them to identify type variables. Lisp uses them to identify unevaluated forms.

Good, let's keep a list of all such cases and gradually add support for them in the lexer (it already knows about some). So far there's one boolean-valued configuration standalone_single_quotes in syntax files that turns on a bit of lexer logic that tries to parse what comes after the single quote as a label (that can be extended to look for an identifier, etc.).

@pmetzger
Copy link
Contributor

Lisp will do both things like '(a b c) and 'a (both values that are treated as unevaluated constants), while ML will do both 'a (type variable) and sometimes 'a' (character constant.)

@skvadrik
Copy link
Owner Author

I added flex-style start/end markers %{, %{rules, %{stags, etc. in 2d37b92.

See python and haskell examples, and I will port OCaml next (maybe Zig as well, as it has no C-style multiline comments /* ... */ - the rest of the languages are fine).

@pmetzger
Copy link
Contributor

Nice! I'm curious why you're allowing arbitrary text before the %{?

@skvadrik
Copy link
Owner Author

Nice! I'm curious why you're allowing arbitrary text before the %{?

I think it's useful (it saves space) to allow staring a block in the middle of a line, e.g.:

    while True: %{
        // ... re2c code
    %}

@pmetzger
Copy link
Contributor

Makes sense. I also see (given that this is using an re2c regex) why it would be hard to have several different flavors of braces etc. I almost wonder if adding one more character (something like %!{ or whatever) might be good to make a later conflict with a given programming language even more unlikely.

@skvadrik
Copy link
Owner Author

I almost wonder if adding one more character (something like %!{ or whatever) might be good to make a later conflict with a given programming language even more unlikely.

I think &{ should be fine, given that there is another option (/*!re2c).

@skvadrik
Copy link
Owner Author

As I was updating docs for the next release, I noticed one more thing to make the syntax more consistent for new languages: block start markers have the word re2c in them (/*!re2c, /*!max:re2c, etc.) and configurations also start with re2c.

For block start markers the easy and logical solution is to use /*!re2go, /*!re2ocaml, etc. (same as the name of the binary) and to allow /*re2c for backwards compatibility. Of course we have language-independent %{, but I find comment syntax nice for languages that have C-style multiline comments.

For configurations, I always felt that re2c prefix is a waste of space, as they are obviously inside of a re2c block, so there's no need to type it again. Also, in configurations like re2c:define:YYCTYPE the only part carrying useful information is YYCTYPE, so define: can be dropped as well. This leaves just :YYCTYPE or e.g. :yyfill:enable - the leading colon is useful, as it allows the lexer immediately know it's a configuration.

The change is on syntax-files branch: cb247ad and examples are updated in later commits, e.g. the commit for C/C++ is 48ac0c2. Of course, old syntax is still supported, but all the examples use the new simplified syntax.

@pmetzger @helly25 @trofi @sergeyklay (as our most active participants, anyone else welcome) what do you think? Any problems with the new syntax?

@helly25
Copy link
Collaborator

helly25 commented Oct 19, 2024 via email

@skvadrik
Copy link
Owner Author

skvadrik commented Oct 19, 2024 via email

@skvadrik
Copy link
Owner Author

@trofi suggested (offline) to rebrand "re2c" from "regular expressions to C" to "regular expression to compiler" and use it for all language backends. It may be a good idea (although at the moment I'm perhaps irrationally opposed to it after a few weeks of work on writing scripts to auto-fix all the docs to use the right tool name).

The question is, should we still install individual tools like re2go, re2ocaml, etc.? We already have re2go and re2rust, so taking them away will break people's workflows. But it seems illogical to have re2ocaml but re2c: prefix in configurations.

@helly25
Copy link
Collaborator

helly25 commented Oct 20, 2024 via email

@skvadrik
Copy link
Owner Author

skvadrik commented Oct 20, 2024

Alright.

I spoke to some long-time active re2c users that are not on github, and they also like the prefix and want it to be re2c for all languages. They suggested that it should mean "Regular Expressions to Code". :)

So all of the opinions I've heard are unanimous and I'll go by re2c for all languages.

I still want to allow dropping define: and variable: parts, though. There're useful hierarchical parts like cond:, state:, tags:, yyfill: that really do make sense and help to scope configurations. But define: and variable: ones I find confusing. It's hardly possible to use these prefixes consistently: e.g. we have re2c:define:YYCURSOR but re2c:variable:yych (why one should be a define and the other a variable?) and then re2c:tags:expression (which has to be scoped under tags, but it's also a definition) and a number of other string configurations, some with and some without define/variable part.

@pmetzger
Copy link
Contributor

+1 for "regular expressions to code".

@sergeyklay
Copy link
Collaborator

... @sergeyklay (as our most active participants, anyone else welcome) what do you think? Any problems with the new syntax?

I must admit, I'm not the best person to provide advice on the conciseness of syntax, as I tend to shy away from abbreviations in my work, whenever a longer version exists. Given the vast array of contexts I work with daily, I've developed a habit of using descriptive lexemes everywhere — even down to the parameters in command-line options. This helps me avoid having to recall what a shorthand might mean, which could be a time sink in my case.

That being said, I have no issues with the clarity or convenience of the proposed simplifications. I agree that within the re2c blocks, these prefixes may indeed be unnecessary, as the context is already clear. Simplifying the syntax will undoubtedly make configuration files more readable and easier to write, which is always a good thing.

And if I've understood the thread correctly — the old syntax will remain supported, so folks like me who prefer more verbose prefixes, or already have systems set up using them, won’t be affected.

In summary, the proposed simplifications seem like a reasonable step towards improving usability. I’d support these changes, particularly if they maintain backward compatibility and uphold the principles of structured configuration where it matters.

@skvadrik
Copy link
Owner Author

Thanks for a detailed answer @sergeyklay !

And if I've understood the thread correctly — the old syntax will remain supported, so folks like me who prefer more verbose prefixes, or already have systems set up using them, won’t be affected.

Sure, backward compatibility is a strict policy, we definitely won't break it for something like adding new configuration syntax. If we want any backwards compatible changes though, now is a good time, as the upcoming release will add support for 8 new languages.

In summary, the proposed simplifications seem like a reasonable step towards improving usability. I’d support these changes, particularly if they maintain backward compatibility and uphold the principles of structured configuration where it matters.

Thanks! Given that every single re2c user or contributor I asked has either a personal, or a general preference for re2c: prefix, I think it's a clear signal that it should remain. I intend to go ahead with the smaller simplification (allowing one to drop define:/variable: parts) unless @helly25 or others talk me out of it. But it will be backwards compatible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants