Register to post in forums, or Log in to your existing account
 

Play RetroMUD
Post new topic  Reply to topic     Home » Forums » CMUD Beta Forum
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: 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:
Code:
if (a = 1) {...}

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.
Reply with quote
MattLofton
GURU


Joined: 23 Dec 2000
Posts: 4834
Location: USA

PostPosted: 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
Reply with quote
nexela
Wizard


Joined: 15 Jan 2002
Posts: 1644
Location: USA

PostPosted: 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}
_________________
Zmud Support Library
Zmud Knowledge Base
Reply with quote
Namsar
Beginner


Joined: 14 Jun 2006
Posts: 29
Location: Sydney - Australia

PostPosted: 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 ?
Reply with quote
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: 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.
Reply with quote
Vijilante
SubAdmin


Joined: 18 Nov 2001
Posts: 5182

PostPosted: 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
Reply with quote
Seb
Wizard


Joined: 14 Aug 2004
Posts: 1269

PostPosted: 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
Reply with quote
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: 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.
Reply with quote
Vitae
Enchanter


Joined: 17 Jun 2005
Posts: 673
Location: New York

PostPosted: Wed Sep 27, 2006 6:43 pm   
 
Vijilante,
Wouldn't using ^ break the anchors for triggers then?
_________________
http://www.Aardwolf.com
Reply with quote
MattLofton
GURU


Joined: 23 Dec 2000
Posts: 4834
Location: USA

PostPosted: 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
Reply with quote
Vijilante
SubAdmin


Joined: 18 Nov 2001
Posts: 5182

PostPosted: 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
Reply with quote
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: 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 :(
Reply with quote
asira
Beginner


Joined: 24 Jun 2002
Posts: 24
Location: United Kingdom

PostPosted: 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.
Reply with quote
Vijilante
SubAdmin


Joined: 18 Nov 2001
Posts: 5182

PostPosted: 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
Reply with quote
edb6377
Magician


Joined: 29 Nov 2005
Posts: 482

PostPosted: 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
Reply with quote
MattLofton
GURU


Joined: 23 Dec 2000
Posts: 4834
Location: USA

PostPosted: 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
Reply with quote
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: 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
Reply with quote
Guinn
Wizard


Joined: 03 Mar 2001
Posts: 1127
Location: London

PostPosted: 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... ;)
Reply with quote
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: 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
Reply with quote
Display posts from previous:   
Post new topic   Reply to topic     Home » Forums » CMUD Beta Forum All times are GMT
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum

© 2009 Zugg Software. Hosted by Wolfpaw.net