cerus / edina

Edina - A simple stack-oriented compiled programming language.
MIT License
13 stars 0 forks source link

Odd choice for ifs #2

Closed aanastasiou closed 2 years ago

aanastasiou commented 2 years ago

Hi, just a note really: I found having different flavours for if test conditions a bit of an odd choice, instead of offering eq, lt, lte, gt, gte binary functions that respond with true, false and then having a single if, which would lead to leaner code. This is also a bit of a contradictory decision to the way while works. while checks by default for a nonzero condition. Which means that "while a number is positive" has to involve an if. With a gt, lt, gte, ... all you have to do is workout an expression and then let if, while "branch" depending on the result of that expression.

cerus commented 2 years ago

Great feedback! This change would require a few changes to the standard library, but fortunately it's still small so that shouldn't be a problem. I have to admit that I didn't really think about the practicality of multiple ifs when I added them.

I'll add this to my list of things to work on today and I'll keep the issue open until this has been implemented.

aanastasiou commented 2 years ago

@cerus Hi, thanks for such a quick response.

For this one, I think it's the other way around, for while you can leave the condition expression on the stack but not for if which results in the different flavours.

All the best :)

cerus commented 2 years ago

I was planning on implementing it like this:

eq / neq / gt / gte / lt / lte will peek the top two items of the stack, perform a comparison and push the result (0 or 1). Peeking instead of popping would reduce the amount of required commands if the user wants to use these values after the comparison.

if will pop the top item and only fire if the value is > 0.

while and until will remain the way they are - they will continue to peek.

What do you think about this?

cerus commented 2 years ago

Implemented in 3aa12f949425895bcd83f4efc94b36aad3b62059. Thanks again for the great feedback!

aanastasiou commented 2 years ago

I was planning on implementing it like this:

eq / neq / gt / gte / lt / lte will peek the top two items of the stack, perform a comparison and push the result (0 or 1). Peeking instead of popping would reduce the amount of required commands if the user wants to use these values after the comparison. [...] What do you think about this?

Hi there. Very quick once again. Making these comparison functions peek rather than pop and push might be premature optimisation. After all, these functions can be very useful when evaluating other expressions anyway. So, I would say keep them "standard".

If you are interested in the concatenative paradigm, I would suggest that you maintain a core subset of "words" as close as possible to the original Forth and expand that with features that make your language (e.g. Edina) more special. This will give you a baseline language that programmers that are familiar with concatenative languages would not spend too much time bending their head around the basics.

You might find the following interesting, if you have not come across them already:

All the best

cerus commented 2 years ago

Hey,

thank you for providing me with these great resources. Edina currently has routines, which is kind of the same as words from Forth, but much more verbose. Your comment is making me think about implementing words as well. I think they could coexist together nicely because they both have matching tradeoffs:

Routines Words
Global & Internal Always global
Very verbose Very minimalistic
Must be explicitly called (:import.routine) Automatically imported into the script with the import command, can be used directly

Routines trade off flexibility for simplicity whereas with words it's the other way around.

# Declaration would be the same for both:
rt pop2
  pop pop
end

word pop2
  pop pop
end

.pop2   # Routines need the '.' in front to signal a routine call
pop2    # Words can be used directly

# --------------------------------------------------------

import "my_pop2_script.edina"

:my_pop2_script.pop2  # We need to specify the import name if we want to call a foreign routine - very verbose
pop2                  # Words are automatically imported into this scope, we can use them directly

# ---------------------------------------------------------

# Routines can be internal which prevents other scripts that import this one from accessing the routine:
rt _my_internal_routine
  "Secret code"
end

# Words can not be internal, they can always be used by scripts that import it

Again, I can not thank you enough for providing me with feedback and resources. It's always nice to see when someone takes the time to engage with your project. :)

aanastasiou commented 2 years ago

No worries.

Re: routines Vs "words", it would probably be better to unify the two. Provide one interface that makes the distinction between valid data type that can go on the stack and a symbol. When you encounter a symbol you call it with the current stack state. How it gets resolved or referenced is a different matter (e.g, the built-ins could be imported at the same scope as the entry point and then preserved throughout).

All the best