|
Zugg MASTER
Joined: 25 Sep 2000 Posts: 23379 Location: Colorado, USA
|
Posted: Wed Sep 27, 2006 1:51 am
New feature for #IF and #WHILE commands |
In C you can do fancy stuff in IF statements such as variable assignment. I have always found this really hard to read and support over time. Doing:
always makes me think of boolean testing (from Basic, Pascal, and most other languages), rather than variable *assignment*. So from a code readability and support issue, I still don't plan to allow variable assignment in the #IF command.
However, this syntax can be really useful sometimes. Look at this zMUD example:
Code: |
#IF (%ismember(%1,@List)) {...} |
OK, this tests to see if %1 is in the string list @List. But %ismember actually returns the position of the item in the list. The #IF statement will take any non-zero value as true. So what if we really need the return value of %ismember?
In C it would be easy:
Code: |
if (pos = ismember(var,list)) {...} |
because then the variable "pos" is assigned to the return result of ismember and "pos" could then be used within the body of the if statement.
In zMUD this is a pain. Typically you need to use code like this:
Code: |
pos = %ismember(%1,@List)
#IF (@pos) {...} |
So, while I was working in the parser today, I thought of an idea...what if you could access the value of the expression in the #IF test? How would you access it? Well, the %i,%j,%k... variables of course. Just like being inside of a loop and accessing the loop variable.
This is implemented in CMUD 1.08. In CMUD, you can do something like this:
Code: |
#IF (%ismember(%1,@List)) {#DELNITEM List %i} |
Here, we are checking to see if %1 is in the @List string list, and if it is, then we delete that item using %i as the position that the item was found in the list.
This is a somewhat contrived example, but it should be useful in some scripts.
Of course, if you are within a LOOP or nested IF statements, just remember to start with %i for the outer loop and then %j, %k, etc for the inner loops. CMUD supports up to %n for nested loop variables.
I hope some advanced programmers find this useful. It's not as flexible as allowing variable assignment within the expression, but it's better than nothing. And it was a nice little feature to add that was easy and doesn't effect any existing scripts. |
|
|
|
MattLofton GURU
Joined: 23 Dec 2000 Posts: 4834 Location: USA
|
Posted: Wed Sep 27, 2006 2:58 am |
This would make things a lot simpler. One thing that might prove to be a minor issue is mixing #IF and #WHILE on the same nest level:
#while (stuff) {
#if (other stuff) {#if (yet more stuff) {}} {unrelated stuff}
#forall (still more stuff) {}
}
Would the "other stuff" #IF have the same variable as the #FORALL (%j), or would they increment (%j for the #IF, maybe %k for the second #IF, and %L for the #FORALL)? |
|
_________________ EDIT: I didn't like my old signature |
|
|
|
nexela Wizard
Joined: 15 Jan 2002 Posts: 1644 Location: USA
|
Posted: Wed Sep 27, 2006 3:01 am |
*drool* I think you just made my day!
f I understand correctly I should also be able to do something like this?
Code: |
#IF (%ismember(%1,@List) AND %ismember(%1,@List2)) {#DELNITEM List %i;#DELNITEM List2 %j} |
|
|
|
|
Namsar Beginner
Joined: 14 Jun 2006 Posts: 29 Location: Sydney - Australia
|
Posted: Wed Sep 27, 2006 3:04 am |
Hmm I'd say no... but thats from the description he has given so far.
The %i relates only to the result of the bracketed #IF statement... so for that one you just posted it would only come back as true... since the actual result is from the AND.
You Could do...
Code: |
#IF (%ismember(%1,@List)) {#IF (%ismember(%1,@List2)) {#DELNITEM List %i;#DELNITEM List2 %j}} |
I think though ? |
|
|
|
Zugg MASTER
Joined: 25 Sep 2000 Posts: 23379 Location: Colorado, USA
|
Posted: Wed Sep 27, 2006 6:16 am |
Namsar is correct. And that's a nice trick for it. But yeah, CMUD can't get stuff from "subexpressions", only from the full IF expression.
Matt: mixing IF, WHILE and loops is no problem. Just start at the outer-most block and use %i, then move to inner blocks and increment to %j, %k, etc. They act like local variables within the scope of their respective statements.
Actually, Matt's comment just made me realize that this actually might break some stuff. When a loop is nested within a IF like this:
Code: |
#IF (1=1) {
#LOOP 100 {#show %i %j}
} |
Now, in zMUD, %i was always the loop counter, and %j would be undefined. But with this change in CMUD, %i now refers to the IF statement in the outer block, and %j refers to the LOOP counter value.
Hmm, I'm not sure I like that. It might break some scripts. And it makes this "feature" into a "wierdness" that is going to confuse people. Everyone would normally expect %i to always refer to the #LOOP value.
Let me give this some more thought. |
|
|
|
Vijilante SubAdmin
Joined: 18 Nov 2001 Posts: 5182
|
Posted: Wed Sep 27, 2006 10:17 am |
How about using ^i..^n. Right now the caret isn't used for anything that I can think of.
|
|
_________________ The only good questions are the ones we have never answered before.
Search the Forums |
|
|
|
Seb Wizard
Joined: 14 Aug 2004 Posts: 1269
|
Posted: Wed Sep 27, 2006 4:10 pm |
Using carets just for this might seem like another wierdness.
How about having something like %exprresult(1), %exprresult(2), ... , %exprresult(n) ?
Or, if it should be a variable and not a function:
%exprresult1, %exprresult2, ... , %exprresult99 |
|
|
|
Zugg MASTER
Joined: 25 Sep 2000 Posts: 23379 Location: Colorado, USA
|
Posted: Wed Sep 27, 2006 4:59 pm |
I'm leaning towards something like Seb's suggestion. I definitely don't want to add any other special characters. I'd like something that fits into the current language scheme.
However, I also think there might be a way to capture multiple expressions into variables. Like the post that Nexela made. I want to add some code to optimize expression evaluation so that when you do X AND Y then it never evaluates Y if X is already false, and X OR Y where it doesnt evaluate Y if X is already true.
When adding this kind of optimization, it is possible to capture the individual subexpressions (X, Y). So in this case, something like %expresult(1) would refer to X and %expresult(2) would refer to Y, with just %expresult referring to the entire expression.
The problem with this is that you can access the %expresult of any outer statements. However, I think this might actually be OK. After all, if you want to access the value of an outer statement, you can always assign %expresult to a local variable of your choice, and that probably results in a more readable script. |
|
|
|
Vitae Enchanter
Joined: 17 Jun 2005 Posts: 673 Location: New York
|
Posted: Wed Sep 27, 2006 6:43 pm |
Vijilante,
Wouldn't using ^ break the anchors for triggers then? |
|
|
|
MattLofton GURU
Joined: 23 Dec 2000 Posts: 4834 Location: USA
|
Posted: Wed Sep 27, 2006 10:12 pm |
I think that's a different parsing engine, Vitae. Although maybe it might generate a syntax error, using ^ doesn't similarly interfere with trigger anchors.
|
|
_________________ EDIT: I didn't like my old signature |
|
|
|
Vijilante SubAdmin
Joined: 18 Nov 2001 Posts: 5182
|
Posted: Wed Sep 27, 2006 10:25 pm |
I posted during my first cup of coffee in the morning, and reflecting on it I have to say it is much better not to add another special character. I like the idea of treating it as a function or other new naming. It is rather easily documented and very understandable.
Zugg, if you do manage to implement it in such a way that portions of the full expression are available to scripts I would suggest assigning those portions based on the use of parenthesis. Thereby a user that does:
#IF (a=1 and b=2) {
would only have 1 expression result for the entire thing
#IF (a=1 and (b=2)) {
would have results 1:a=1 and (b=2);2:b=2
#IF ((a=1) and (b=2)) {
would have results 1:(a=1) and (b=2);2a=1;3:b=2
This should match the REGEX method for determining returned values from parenthesis. |
|
_________________ The only good questions are the ones we have never answered before.
Search the Forums |
|
|
|
Zugg MASTER
Joined: 25 Sep 2000 Posts: 23379 Location: Colorado, USA
|
Posted: Fri Sep 29, 2006 4:02 am |
I forgot to remove this "feature" in the 1.08 release. So unfortunately, if you have a Loop within an IF or WHILE statement, then %i is going to return the IF expression rather than the Loop variable. You can temporarily change %i to %j to get this working. I'll fix this new feature in the next version. Sorry I forgot about this :(
|
|
|
|
asira Beginner
Joined: 24 Jun 2002 Posts: 24 Location: United Kingdom
|
Posted: Fri Sep 29, 2006 8:28 pm |
There appears to be a somewhat interesting side effect of this, the %i variables take account of all if and loop statements involved above it, which can potentially cause problems. To demonstrate consider the following (utterly contrived) code:
Code: |
#alias one {
#if (1) {
two
}
#if (2) {
#loop 5,5 {
two
}
}
}
#alias two {
#if (3) {
#say %i - %j - %k - %l
}
} |
The actual output of this is:
Code: |
1 - 3 - -
2 - 5 - 3 - |
This makes it impossible to use a loop or the new feature in an alias, when it can be called from different depths of ifs and loops. |
|
|
|
Vijilante SubAdmin
Joined: 18 Nov 2001 Posts: 5182
|
Posted: Fri Sep 29, 2006 10:12 pm |
This seems to indicate a problem with properly removing the references from the script stack, in the current incomplete implementation. I think from the discussion in this thread Zugg will be seperating them and further improving the functionality of this new feature.
|
|
_________________ The only good questions are the ones we have never answered before.
Search the Forums |
|
|
|
edb6377 Magician
Joined: 29 Nov 2005 Posts: 482
|
Posted: Sat Sep 30, 2006 12:06 pm |
didnt zugg create the switch command instead of using if after if after if or nested ifs?
|
|
_________________ Confucious say "Bugs in Programs need Hammer"
Last edited by edb6377 on Tue Oct 10, 2006 3:45 am; edited 1 time in total |
|
|
|
MattLofton GURU
Joined: 23 Dec 2000 Posts: 4834 Location: USA
|
Posted: Sat Sep 30, 2006 9:49 pm |
Only for cases where the same expression was being used. If each #IF had a different expression, then just recently he created the #SWITCH command.
Still, there are always cases where nested or multiple #IFs will have to be used either because the logic is short enough to do so or because there's simply no better way. |
|
_________________ EDIT: I didn't like my old signature |
|
|
|
Zugg MASTER
Joined: 25 Sep 2000 Posts: 23379 Location: Colorado, USA
|
Posted: Tue Oct 03, 2006 12:24 am |
OK, I have this implemented and working properly in v1.09 now.
Here is what I did: I implemented a new function called %exp. The first argument is the subexpression index that you want to return. Subexpressions start at 0 and get incremented for each parenthesis, much like with () in patterns. The reason is starts at zero is so the outermost main expression is always subexpression 0. An optional second argument specifies which "stack level" to return the expression from. If omitted, the current stack is used (same as specifying zero). A one will access the parent-stack, 2 the parent of the parent, etc.
Here is an example:
Code: |
a=123
b=456
c=999
#if ((@a) AND (@b)) {
#show %exp(0) %exp(1) %exp(2)
#if (@c) {
#show %exp(0) %exp(2,1)
}
}
1 123 456
999 456 |
Within the first #IF statement, %exp(0) accesses the subexpression from the first paren, which is the result of ((@a) or (@b)) which has the value of 1. The %exp(1) returns the subexpression from the second paren, which is (@a) or 123. %exp(2) returns the subexpression from the third paren, which is 456.
Within the nested #IF statement, %exp(0) returns the subexpression of the first paren within the current stack (the current IF statement), which is (@c) or 999. Then we have %exp(2,1) which returns the third subexpression of the *previous* stack (the outermost IF statement from above). This is (@b) or 456.
If you omit the arguments, zeros are assumed. So just using %exp will return the full expression of the current stack.
These subexpression values are set by *any* expression evaluation. So, for example, you can do:
Code: |
a=123
#if (%max(888,999,(@a+1))) {#show %exp(0) %exp(1)}
999 124 |
The %exp(0) still returns the outer-most expression (the #IF expression). But here we have added extra () parens to one of the arguments of the %max command. The normal parenthesis for %max don't count because the first paren after %max just indicates a list of arguments and doesn't actually indicate the start of an expression. But by adding the extra parens around @a+1 we have "captured" that value to %exp(1).
This is definitely an advanced feature, but hopefully it will come in handy. |
|
Last edited by Zugg on Tue Oct 03, 2006 1:50 am; edited 1 time in total |
|
|
|
Guinn Wizard
Joined: 03 Mar 2001 Posts: 1127 Location: London
|
Posted: Tue Oct 03, 2006 1:35 am |
Nifty, look forward to playing with it
|
|
_________________ CMUD Pro, Windows Vista x64
Core2 Q6600, 4GB RAM, GeForce 8800GT
Because you need it for text... ;) |
|
|
|
Zugg MASTER
Joined: 25 Sep 2000 Posts: 23379 Location: Colorado, USA
|
Posted: Tue Oct 03, 2006 1:50 am |
Btw, I also implemented the boolean expression optimization. In most modern programming languages, the compiler will optimize your AND/OR boolean tests and avoid executing any code that isn't needed. For example:
Code: |
a=0
b=1
#IF ((@a > 0) AND (@b > 0)) {true} {false} |
In this case, since @a=0, the first subexpression is already false. So there is no need to test the @b>0 expression.
So, when executing a series of AND expressions, as soon as any subexpression is FALSE, then CMUD skips past the rest of the tests and returns FALSE for the expression. Similarly with OR, as soon as any subexpression is TRUE, CMUD skips past the rest of the tests and returns TRUE for the expression.
This kind of optimization is very important when dealing with variables that might not be initialized, especially COM variables. For example, now you can do this:
Code: |
#IF (not(%null(@ComVar)) AND (@ComVar.Property = Value)) {...} |
This prevents you from trying to access the Property of a Null COM variable. In zMUD it would still try to execute the @ComVar.Property expression even though @ComVar was null.
Keep this in mind when using the above %exp syntax. Here is an example:
Code: |
a=123
b=456
#if ((@a) OR (@b)) {
#show %exp(0) %exp(1) %exp(2)
}
123 123 // @b wasn't evaluated so %exp(2) was null and %exp(0) is the same as %exp(1)
#if ((@a) AND (@b)) {
#show %exp(0) %exp(1) %exp(2)
}
1 123 456 // @b was evaluated |
|
|
|
|
|
|