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

Higher-order macros and macro definitions with explicit delimiters #90

Merged
merged 42 commits into from
Aug 22, 2024

Conversation

fpottier
Copy link
Collaborator

Hello,

This PR proposes:

  • Higher-order macros. This does not break any existing code: the formal parameters of a higher-order macro must be annotated with their types, and there is a new syntax for this purpose.

  • Macro definitions with explicit delimiters, #def and #enddef. This allows multi-line macros (without backslashes) and nested macros. This is unlikely to break any existing code, but it does introduce two new directives.

  • (minor) #define on the last line of a file, without a newline, is now accepted. (It used to be an error. Accepting it allows me to produce a reliable error message when #def does not have a matching #enddef.)

  • (minor) Better locations for some syntax error messages, by systematically using long_loc instead of loc.

The new features are documented in README.md.

Some examples of the use of higher-order macros appear in the example file test/higher_order_macros.cppo. Similar examples, which also take advantage of #def ... #enddef, appear in test/def.cppo.

Here are some more ideas, about which I would welcome the opinion of the cppo maintainers. I believe that each of these ideas would be easy to implement:

  • I am tempted to introduce #scope and #endscope delimiters, so as to allow limiting the scope of a macro definition. I believe that this would often remove the need to use #undef, which is quite painful.

  • The use of higher-order macros is currently somewhat heavy, because the actual argument must be a named macro, so, at the call site, it must be locally defined (and possibly undefined). I am tempted to introduce a syntax for macro-abstractions, which would be allowed to appear as actual arguments to higher-order macros. The syntax would be #lambda(formals) ... #endlambda. Perhaps, for greater conciseness, it would be necessary to allow #lambda and #endlambda to be recognized as directives even if they are not placed at the beginning of a line.

  • In order to improve hygiene in higher-order macros, I would like to introduce a new directive #fresh x y, which behaves like #define x <some fresh name based on y>. A globally fresh name would be obtained by combining the prefix y with a numeric suffix obtained by reading and incrementing a global counter.

  • Because #undef is heavy, I have been tempted to add a command-line switch --allow-shadowing, which allows a new macro definition to shadow an existing definition. That said, perhaps #scope ... #endscope would lessen the need for this feature.

The lexer rule [formals] is new.
The token [DEFUN] disappears.
The non-terminal symbols [def_args1] and [arg_blank] disappear.

The locations of a few error messages change slightly.
The expected test output is adjusted in a separate commit.
The expected test output is adjusted in a separate commit.
The new way is more verbose but should be more modular (extensible).
For the moment, this shape information is carried around, but unused.
This actually enables support for higher-order macros.
This is slightly less redundant and allows extracting a location out
of an actual argument even if this is argument is empty. This improves
the location of the error message in [test/expect_ident_empty].

The expected test output is updated in the next commit.
This is not a deep change; this way seems slightly preferable.
Also, isolate the auxiliary functions [int_expansion_error]
and [int_expansion].
In particular, explain when parentheses must be used and must not be used.
Give examples.
This clarifies the fact that [line] is the entry point of the lexer.
@pmetzger
Copy link
Member

A minor comment:

(minor) #define on the last line of a file, without a newline, is now accepted. (It used to be an error. Accepting it allows me to produce a reliable error message when #def does not have a matching #enddef.)

Not that C has much to do with this, but I'll note that in C, a translation unit (aka a file) without a terminating newline is documented as UB.

@pmetzger
Copy link
Member

Also, not that Lisp is a precedent we need to care about...

In order to improve hygiene in higher-order macros, I would like to introduce a new directive #fresh x y, which behaves like #define x . A globally fresh name would be obtained by combining the prefix y with a numeric suffix obtained by reading and incrementing a global counter.

In Lisp, this would be a gensym.

@pmetzger
Copy link
Member

So a lot of this looks neat. I am obligated to ask, of course, what the anticipated use case is. I've traditionally viewed cppo as a sometimes needed hack that one generally wants to avoid. Also, since I started doing a lot of work on refactoring C code I've come to hate cpp in that context because it makes it harder to automate refactoring. However, some of this seems like a very cool experiment.

@fpottier
Copy link
Collaborator Author

So a lot of this looks neat. I am obligated to ask, of course, what the anticipated use case is.

Good question! I don't really have a clear use case at this point. Lately, for performance reasons, I have been playing with unrolling loops and specializing higher-order functions (which the OCaml compiler does not do), so cppo came in handy. This led me to think about the potential uses and current limitations of cppo, and I thought that it could be useful to remove some of these limitations.

@fpottier
Copy link
Collaborator Author

In Lisp, this would be a gensym.

Sure. I would be happy to use whatever syntax you think is most convenient or elegant.

@pmetzger
Copy link
Member

Lately, for performance reasons, I have been playing with unrolling loops and specializing higher-order functions (which the OCaml compiler does not do), so cppo came in handy. This led me to think about the potential uses and current limitations of cppo, and I thought that it could be useful to remove some of these limitations.

I have never used metaocaml, but I understand it is also useful for such things. Would this serve a distinct need?

Also, I have no particular brief for gensym, I just am noting it's an analogous piece of functionality.

@pmetzger
Copy link
Member

What do we need to do next to move this pull request forward?

src/cppo_parser.mly Outdated Show resolved Hide resolved
Copy link
Member

@liyishuai liyishuai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Propose merging if no concerns arise by Tuesday 20 Aug AoE.

@pmetzger
Copy link
Member

Seems fine by me.

@fpottier
Copy link
Collaborator Author

Is there something I can/should do?

In particular, I haven't written an entry in Changes.md.

@liyishuai liyishuai merged commit d7c3426 into ocaml-community:master Aug 22, 2024
73 checks passed
@liyishuai
Copy link
Member

Given the time length for re-running the CI, maybe worth editting the changelog in a separate commit.

@fpottier
Copy link
Collaborator Author

I have pushed two commits to fix a couple typos and add an entry in Changes.md. If the CI passes then I believe it is ready to release as 1.7.0.

@fpottier fpottier deleted the def branch August 22, 2024 06:59
@fpottier
Copy link
Collaborator Author

I believe that the CI has succeeded. Is it OK if I release by running make publish1.7.0? Or should I wait for a maintainer to do it?

@liyishuai
Copy link
Member

Please fire at will. You might need to create a tag for v1.7.0 before running the Makefile script.

@pmetzger
Copy link
Member

@fpottier Just FYI, you're as much a maintainer now as the rest of us. :)

@fpottier
Copy link
Collaborator Author

It's an honor :-)

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

Successfully merging this pull request may close these issues.

3 participants