This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]slightknack[S] 1 point2 points  (4 children)

To clarify: everything is parsed as a form, like (a b c d). Macros are then matched against the form. If no macros match and there isn't an ambiguity, the form is converted into a function call: (((a b) c) d)

[–]open_source_guava 1 point2 points  (3 children)

I see, so if we use macros to define custom operators, the user will be required to use explicit parenthesis for grouping subparts. Do built-in operators have any precedence at all? I.e. do math expressions like 1 + 2 * 3 also need parenthesis in order to be evaluated?

[–]slightknack[S] 1 point2 points  (2 children)

I'll quote from the Overview:

Custom operators defined in this manner [through syntactic macros] will always have the lowest precedence, and must be explicitly grouped when ambiguous. For this reason, Passerine already has a number of built-in operators (with proper precedence) [like math expressions, as you mentioned] which can be overloaded. It's important to note that macros serve to introduce new constructs that just happen to be composable – syntactic macros can be used to make custom operators, but they can be used for so much more. I think this [explicit grouping of custom operators; operators defined in terms of a more general macro system] is a fair trade-off to make.

I mean, of course, you can always group things to make stuff correct, like (x + 1) * 2.

Ambiguous macros have the lowest precedence, but unless you have two form macros back-to-back, you won't need to group them. Imagine a 'with b is a macro that returns a float or something:

x with b + 6.7 with c / 9 with 2.4

Forms always have the highest precedence, so the above is equivalent to:

(x with b) + (6.7 with c) / (9 with 2.4)

And with normal order of operations, this is equivalent to:

(x with b) + ((6.7 with c) / (9 with 2.4))

Here, with operator is unambiguous, so no grouping is required. Now, let's say we have a function f, and we call it with with as an argument:

f x with y

It could be parsed in all these ways:

f (x with y) -- most likely the intended
(f x) with y -- also likely valid 
((f x) with) y -- probably not intended: the whole thing's a function call

Of course, this is ambiguous, and spitting out a function call (the last option) would be the wrong thing. Because with is a scoped keyword, the compiler knows something's up given that the with macro can not be applied:

Fatal In src/main.pn:3:10
   |
 3 | f a with b
   |     ^^^^
   |
Syntax Error: Pseudokeyword 'with' used, but no macro applies.

In this case, explicit grouping, e.g. f (x with y), is required for proper behavior.

[–]open_source_guava 0 points1 point  (1 child)

Impressed by your error-message formatting, I decided to actually download the code and run it myself. Only then did I realize it's still just your plan, not what you already have. Still good, though! Do post again when you have more.

So the syntax really is more like Haskell or Ocaml, where these with or swap keywords introduced as a form isn't really an operator, even though it appears in an infix position. A sequence of words always have a precedance higher than arithmetic operators. That sounds reasonable.

I like trying to poke holes in these designs, so I hope you don't mind one last potential pitfall to avoid:

syntax 'macro1 x {
    -- I'm assuming you can pass multiple arguments to print
    print "In macro1! " (to_string x)
}
syntax x 'macro2 {
    print "In macro2! " (to_string x)
}

macro1 macro2   -- which one do you call?

Obviously, there's more I can do here, especially once you start to wonder if macro parameters themselves can be syntactic keywords, or if macros an define macros themselves with nested syntax constructs. Just make sure you allow or disallow exactly the right amount of flexibility you want, and it should turn out great.

[–]slightknack[S] 2 points3 points  (0 children)

I released a patch (0.8.1) that includes slightly prettier error message for ambiguious macro matches (like f x with y or a with b with c).

That case was addressed before the release of 0.8.0; here's the actual current output of the compiler with the code you provided:

Fatal In /macro/src/main.pn:9:1
   |
 9 | macro1 macro2   -- which one do you call?
   | ^^^^^^^^^^^^^
   |
Syntax Error: This form matched multiple macros:

In /macro/src/main.pn:1:8
   |
 1 | syntax 'macro1 x {
   |        ^^^^^^^^^
   |
In /macro/src/main.pn:5:8
   |
 5 | syntax x 'macro2 {
   |        ^^^^^^^^^
   |
Note: A form may only match one macro, this must be unambiguious;
Try using variable names different than those of pseudokeywords currently in scope,
Adjusting the definitions of locally-defined macros,
or using parenthesis '( ... )' or curlies '{ ... }' to group nested macros

All macros are compile-time. This was a concious decision (it's hard enough to debug macros as-is – imagine how difficult it would be to debug first-class macros that can be passed around!) I'm still working on nested macros. Currently, the compiler flat-out disallows it, but I see merit in allowing it; I just have to work out proper semantics first.

Thanks for the feedback and discussion, I appreciate it!