tsoding / porth

It's like Forth but in Python
628 stars 50 forks source link

Partial Block Macros Not Possible #72

Open IHateYourCode opened 2 years ago

IHateYourCode commented 2 years ago
macro ifSmaller
< if
end

'Half' macros like this sadly don't work currently

As far as I can see, the lexer goes through the macro content and actually evaluates the if end sequence instead of ignoring it until all macros are expanded.

Correct me if I'm wrong.

drocha87 commented 2 years ago

What are the benefits this approach brings to the language? ifSmaller is bigger than and harder to read than < if.

Other thing is how macro should guess that this end is to close the macro itself and not the if? Should we introduce a end_macro keyword?

IMO introducing this kind of syntactic sugar to the language at this point will just make a lot harder to implement the language in itself in the future.

IHateYourCode commented 2 years ago

It allows for macros that can extend the head of any block statement, which opens quite a lot of possibilities.

Yes ifSmaller is longer than < if and I wouldn't call it that if I were to use it, that misses the point though. The point is it abbreviates an expression and makes it more readable, again I 'ifSmaller' was only and example.

No, I of course wouldn't suggest a separate keyword for that, it's not needed for something like that, there are multiple ways to handle this.

Going by 'macros can only be implemented in top lvl scope' (not sure if nested macros were allowed), you could go by identation, something like:

'Macros can be written as one liners, in which case they end at the current end of the line or if stretched over multiple lines have to be closed with the keyword end and their content has to be indented' this would force indentation, which isn't nice, tho on the other hand if you write a macro over multiple lines, not identing isn't something you should do anyways.

A second approach could be the one C takes, similar one-liner policy but multiline proof through backslashes at the end of each line that should be continued.

Macros aren't anything new, neither is this problem, other languages offer existing, working solutions.

Personally I'd go for the second solutions since it's quite clean if one alines the backslashes.

'at this point will just make a lot harder to implement the language in itself in the future.' For a compiler, integral changes should be made ealier than later, considering how hard it may later become.

cg-jl commented 2 years ago

I think that this is not really necessary. I don't see myself in a scenario where this would be useful.

Aside from that, I wanted to point out a couple things:

Going by 'macros can only be implemented in top lvl scope' (not sure if nested macros were allowed), you could go by identation

The language isn't indentation based, this isn't python. The language is meant to identify blocks by having a keyword that starts the block (macro, if, while) and having an end keyword that signifies 'end of the current block'. It's meant to be a simple language, straightforward to lex, parse, and compile. Scope-based macros would be a nice thing if... there were actual reasons to scope them (such as modules inside modules, functions). Macros inside macros aren't possible because the contents of the macro is meant to be a list of tokens.

As for the C approach, I don't have anything to say, except that it breaks the consistency of having block_start ... end define a comprised list of tokens that the language seems to be following.

IHateYourCode commented 2 years ago

As for the C approach, I don't have anything to say, except that it breaks the consistency of having block_start ... end define a comprised list of tokens that the language seems to be following.

Not quite, if you go with the following example code

macro ifTrue \
    dup 1 =   \
end

You can see that any line that is not supposed to be lexed, within the macro is followed by a \. This doesn't remove the end keyword, it just helps the compiler know where not to search for it. So it starts by seeing the macro keyword, uses the next token as the macro name, and following that it checks if the current line ends with a backshlash. For as long as each next line ends that way, the compiler ignores the lines (just takes note of them) and continues. Once a lines without backshlash appears it starts lexing again, finding the end keyword like normal and closing the macro. Now, in the expansion of the macro, the backslahes are simply removed, allowing for the content to be lexed.

IHateYourCode commented 2 years ago

Other examples:

macro repeat         \
    while dup 0 < do \
        1 -          \
end
5 repeat
    "test\n" 1 1 syscall3 drop
end

Domkeykong commented 2 years ago

allowing if or other blocks inside a macro allows to create min and max macros:

macro max 2dup > if drop else swap drop end end

macro min 2dup < if drop else swap drop end end
0dminnimda commented 2 years ago

I think that this is not really necessary. I don't see myself in a scenario where this would be useful.

That's as I see right now mostly for ease of the writing code. For example you wanna implementation python-like range and be able to do such thing

1 10 2 for_range
  dup print
end drop drop drop
// 1 3 5 7 9

You can do this with partial while in the for_range

// in: start end step
macro for_range
  // end step (start - step)
  rot over -
  // end step num
  while
    // end step (num + step)
    over +
    // step (num + step) end
    rot
    // end step (num + step) ((num + step) < end)
    if 2dup < do
      rot rot true
    else
      rot rot false
    end
  do
  // no `end` for `while`
end

So obviously your code gonna become nightmare if you will just expand this macro by hand every time you want this behaviour (image two nested ranges), and all that is because of impossibility to use partial macros.


Yeah, that not complete implementation of python range, because it doesn't work with negative step, but you got the idea.