Jump to content
IGNORED

Need help with Functions section of bB page


Random Terrain

Recommended Posts

I don't use functions, so I didn't really pay that much attention to the Functions section of the bB page. I don't understand this sentence:

 

To return a value, use the return keyword. Using return without a value will return an unpredictable value.

 

I don't see that in the examples.

 

 

Speaking of examples, can someone post an example of an asm function? I'd like to add that to the page.

 

 

Thanks.

Link to comment
Share on other sites

Ok here is a proper implementation of a bB function being used for both bB and asm:

//Variable for p0lives
  dim P0Lives = a
  dim Score   = b 
 
 //Lets add to the player lives based of the score player has
 //For every 16 points add a life to the player
 //Score will be passed to the function in temp1

//Declare a function
 function ScoreLife
   temp2 = Score / 16  //divide the score by 16 to find how many lives to add
   return temp2        //now return the value to be then added to p0lives
end                    //end function
 
//The function can be used to add lives to the player based on end of level score
P0Lives = P0Lives + ScoreLife(Score) //This then becomes in logic p0lives = p0lives + temp2 once the function is done

*

Now in ASM it's the same thing, but instead the function will hold ASM code:

//Variable for p0lives
  dim P0Lives = a
  dim Score   = b 
 
 //Lets add to the player lives based of the score player has
 //For every 16 points add a life to the player
 //Score will be passed to the function in temp1

//Declare a function
 function ASM_ScoreLife
 //temp2 = Score / 16  //divide the score by 16 to find how many lives to add
 asm                   //Replacing the above statement with asm
 lda temp1
 lsr //divide by 16
 lsr
 lsr
 lsr
 sta temp2
end                    //end asm
   return temp2        //now return the value to be then added to p0lives
end                    //end function
 
//The function can be used to add lives to the player based on end of level score
P0Lives = P0Lives + ASM_ScoreLife(Score) //This then becomes in logic p0lives = p0lives + temp2 once the function is done

Ok that should do it, forgive me if there are any more mistakes I am overlooking.

Edited by ScumSoft
Link to comment
Share on other sites

Thanks. I see return in the examples now. I was looking in the wrong place.

 

In the asm example, if a function ends with end and asm ends with end, why aren't two ends needed? Whatever the answer is, what would the call to the asm function look like?

Link to comment
Share on other sites

In the asm example, if a function ends with end and asm ends with end, why aren't two ends needed? Whatever the answer is, what would the call to the asm function look like?

 

Forgetting about the actual example that you posted, are any asm functions called in a similar way to what is already on the bB page?

Link to comment
Share on other sites

I think my example is more of mixed asm + bB rather than the pure asm implementation.

the differences being that you need to treat the asm as you would if you were coding in pure asm with dasm, and not bB.

 

You would define your function name in pure asm like so:

addlives ;function label
 ;a holds first value passed which is score
 ;y holds second value which is lives
 lsr
 lsr
 lsr
 lsr         ;//divided A cpu register a which currently holds the score by 16
 sta temp1   ;//Save result in temp1
 tya         ;//bring Lives in y over to a
 clc         ;//Clear carry flag
 adc temp1   ;//Add new lives from score together with existing lives
 sta lives   ;//Store final result in variable
 rts         ;//return for non-bank switched games
return lives ;//for bankswitched games, omit this line if you use rts above and vice versa (actually you would also need asm and end directives if used from bB, so this might be better ignored all together and just stick with rts)
 
//Call this pure asm routine like such
NewLives = addlives(score,lives)
Edited by ScumSoft
Link to comment
Share on other sites

A function passes the first two parameters in
the accumulator and the y register

The first thing a function does is store the
accumulator in temp1 and the y register in temp2
even if no parameters are passed.

Parameters beyond the first two get assigned to
the other temp variables in order third parameter
to temp3, fourth to temp4 etc. (if you pass 8
parameters bB will try to assign the eighth
parameter to temp8 so you can dim additional
variable named tempxx and pass more paramters
if you want)

You don't need an end statement you do need a return
The return can be the return statement or rts in asm

The return value is either a variable or a constant
not an expression.

The function returns whatever is in the accumulator
if a return value is specified it gets passed through
the y register

function f
p = temp1 + temp2
return

q = f(2 3)

will assign 5 (2 + 3) to q because that's what's in the
accumulator, left over from the assignment to p
(the return statements in Scumsoft's examples are
redundant)


(as near as I can tell) A function can only be used
in a simple assignment

variable = function

bB goofs if the first parameter is indexed and
there's more than one indexed parameter.
The first parameter gets the last index.

function(a c[d] e[f])

gets a[f] passed as the first parameter

Link to comment
Share on other sites

So does that mean that the example on the bB page is wrong?

  function max
  if temp1>temp2 then temp1bigger else temp2bigger 
temp1bigger if temp1>temp3 then return temp1 else return temp3 
temp2bigger if temp2>temp3 then return temp2 else return temp3
end

It should have return instead of end?

Link to comment
Share on other sites

It already has returns for every possible condition, so it's fine.

 

The 2600basic compiler sets the internal state variable "doingfunction" when you start a bB function, and clears it when it encounters the "end". It doesn't seem to do much more with that variable then tracking if you have extra "end"s, but perhaps batari has more plans.

 

I'd say the bB example function should have the "end", though I don't think its going to break anything currently if its missed.

  • Like 1
Link to comment
Share on other sites

If you guys feel like editing that section and adding more examples, it sure would help because it's more confusing to me than helpful:

 

http://www.randomterrain.com/atari-2600-memories-batari-basic-commands.html#function

 

 

Adding an actual asm function example in the style of what is in that section already would also be very helpful.

Link to comment
Share on other sites

I think the "return" serves two purposes-- to tell the 2600 to return back to the calling statement (just like returning from a subroutine), and to tell batari Basic what value to return in the accumulator.

 

If you want to write a pure-assembly user function, you would need to use "RTS" instead of "return," but you can't include a value with the "RTS," so you'd have to load the accumulator with the return value before the "RTS."

Link to comment
Share on other sites

ScumSoft, thanks for bringing up bankswitching. If you call a user-defined function from a different bank, you have to call it the same way you call a subroutine in a different bank, which means you can't use the standard format of calling the function with the parameters in quotes. Instead, you need to use assembly code to load the parameters into the appropriate registers or variables-- accumulator, X register, Y register, followed if necessary by (IIRC) the temp variables(?)-- and then use "gosub" to call the subroutine (function name) in the other bank. Then when you're ready to return from the function you need to use assembly code to load the return value into the accumulator, and use "return otherbank" to return to the calling statement. At least, that's my recollection.

Link to comment
Share on other sites

ScumSoft, thanks for bringing up bankswitching. If you call a user-defined function from a different bank, you have to call it the same way you call a subroutine in a different bank, which means you can't use the standard format of calling the function with the parameters in quotes. Instead, you need to use assembly code to load the parameters into the appropriate registers or variables-- accumulator, X register, Y register, followed if necessary by (IIRC) the temp variables(?)-- and then use "gosub" to call the subroutine (function name) in the other bank. Then when you're ready to return from the function you need to use assembly code to load the return value into the accumulator, and use "return otherbank" to return to the calling statement. At least, that's my recollection.

Can't say I've ever tryed calling a function

in another bank, but my reading of the bank switch

code is that it preserves the accumulator and the

x register on the stack and doesn't touch the temp

variables so calling a function in another bank

should be possible.

 

I should clarify my statements about return

and the return value.

 

Perhaps I made it sound like a return

statement and an rts are inter changeable.

Presumably you'd need a return statement

to return to a different bank.

But you don't necessarily have to explicitly

specify a return value.

 

 

I don't understand why if you specify a

return value it gets passed through the

y register. Mind you it doesn't get passed

back in the y register it just goes through

y on it's way to the accumulator and gets

passed back in the accumulator.

 

Edited by bogax
Link to comment
Share on other sites

IIRC, the only reason I even tried to call a function from a different bank was because Random Terrain or someone else had asked if it could be done. :)

 

I need to correct and expand on what I said earlier. I hope the following is correct, but I can't guarantee that it is, or that it's complete-- for example, I haven't tried using indexed variables in the parameters.

 

When you call a function, the way that batari Basic stores the parameters depends on how many parameters are being passed:

- If there's only 1 parameter, it gets stored in the accumulator.

- If there are 2 or more parameters, they get stored in reverse order (i.e., last parameter first, and first parameter last).

- If there are 2 parameters, the last parameter gets stored in the Y register, and the first parameter gets stored in the accumulator.

- If there are 3 or more parameters, one or more temp variables will be used in addition to the Y register and accumulator, but the X register will not be used. (I presume this is so the X register is left available for use with any indexed variables in the parameters list.)

- The temp variables used will depend on the number of parameters, as each parameter after the second parameter will be stored in the temp variable of the same number.

- For example, if there are 5 parameters, they will get stored (in reverse order) in temp5, temp4, temp3, the Y register, and the accumulator.

- There are only 7 temp variables, which means a function can have up to 7 parameters-- if you try to define a function with 8 or more parameters, you'll get a compile error.

- However, the parameters in a function are global, not local, so you can actually use more than 7 parameters in a function if you store the "extra" parameters yourself in whatever variables are available, and then reference those variables inside your function-- you just can't include them inside the parentheses when calling the function.

 

Since temp7 is used in bankswitching, you need to be cautious about calling a function with 7 parameters (i.e., inside the parentheses) if you're using bankswitching in your game, as follows:

- You can call the 7-parameter function from within the same bank, but not from a different bank (see below about calling a function in a different bank).

- If you call a subroutine in a different bank, you mustn't call the 7-parameter function until after you've returned to the original bank, or it will interfere with the bankswitching process for returning to the original bank.

- For the same reason-- the need to preserver the temp7 variable's value-- you mustn't call a subroutine in a different bank, then from there call another subroutine in yet another bank.

 

When you define a function, the first two assembly statements in the compiled function will be as follows:

- STA temp1

- STY temp2

This is true even if only 1 parameter will be passed to the function-- the intention being that the parameters will be available to the function in temp1, temp2, temp3, etc., up to temp7. This means that calling a function will always destroy anything you've stored in temp1 and temp2, even if you're passing only 1 parameter to the function. Thus, if you're using any of the temp variables to temporarily store values, you must be careful about calling functions (or using other commands or operations that use the temp variables) before you need to use the values you'd stored in the temp variables.

 

A function doesn't need to return a value-- it will return whatever happens to be in the accumulator, but you can define a function that performs some task without returning a meaningful value (i.e., it's understood that the returned value is meaningless). But if you do want to return a value, the safest (and intended) way is to specify the return value at the end of the "return" statement (e.g., "return 3" or "return 162" or "return g"). You can return a variable or a numeric literal, but not an expression (e.g., "return g+4*k" will actually return the value that's in g, not the value of the expression "g+4*k"; and "return 4*g" will actually return 4). If you want to return different values (e.g., either 0 or 1 for "false" or "true") without using a variable, or return different variables (e.g., either a or b), the code in the function will need to include multiple "return" statements-- one for each possible return value-- with appropriate "if" logic so the desired "return" statement gets used to return from the function.

 

As bogax noted, when a value is being returned, it gets temporarily stored in the Y register, and then gets transferred to the accumulator just before the "RTS" instruction in the compiled assembly code. This appears to be a sort of safety precaution in case bankswitching is being used, because if bankswitching is being used then the "return" statement will be compiled into assembly code that uses the accumulator an the X register, but not the Y register-- so the return value gets stored in the Y register, some bankswitching return logic is performed (destroying whatever happened to be in the accumulator), and then the Y register gets transferred to the accumulator just before the "RTS" instruction.

 

The user-defined functions weren't really designed to be used with bankswitching-- I think user-defined functions were added to batari Basic before bankswitching was, and the compile logic probably had to be changed a bit when bankswitching was added (i.e., it may be that originally the Y register wasn't used to temporarily hold the return value, as it is now). It may seem odd that batari Basic compiles the "return" statement(s) of a function as though it's a bankswitched return, but apparently the compiler doesn't have a way (e.g., a flag or variable) to distinguish a function "return" from a subroutine "return"-- and after all, a function is really just a type of subroutine. So if you're using bankswitching, the bankswitching logic automatically gets included when the "return" statement is compiled-- even if you use "thisbank" (e.g., "return 17 thisbank").

 

Despite the fact that functions weren't designed to be used with bankswitching, they can be. The problem is that there's no provision in batari Basic for specifying the target bank when calling the function, so you can't just use a simple function call such as "a=myfunction(b) bank2." Instead, you'll need to store the parameters yourself, preferably using the same logic that batari Basic would. If you're using more than 2 parameters, you can store everything after the second parameter using regular batari Basic statements, in any order you want (e.g., "temp3=parameter3:temp4=parameter4:temp5=parameter5"), but you'll need to use inline assembly to store the first two parameters in the accumulator (first parameter) and Y register (second parameter). Once you've stored the parameters as needed, you can call the function from a different bank by using "gosub" (e.g., "gosub myfunction bank2").

 

However, this raises a second issue-- when batari Basic compiles the "gosub" statement, it adds a period in front of the subroutine name (e.g., "gosub myfunction" will become "JSR .myfunction"). That means you'll need to define the function with a period in front of its name (e.g., "function .myfunction"), otherwise you'll get a compile error on the "gosub" statement. That means if your program calls the same function from within the same bank, you'll need to include the period in the function's name when you call it normally (e.g., "a=.myfunction(b)"). So you if define a function with a period in front of its name, you'll be able to call it from either the same bank or from a different bank, since the "return" statement will automatically handle the necessary bankswitching to return to the correct bank.

  • Like 1
Link to comment
Share on other sites

 

 

Since temp7 is used in bankswitching, you need to be cautious about calling a function with 7 parameters (i.e., inside the parentheses) if you're using bankswitching in your game, as follows:

- You can call the 7-parameter function from within the same bank, but not from a different bank (see below about calling a function in a different bank).

- If you call a subroutine in a different bank, you mustn't call the 7-parameter function until after you've returned to the original bank, or it will interfere with the bankswitching process for returning to the original bank.

- For the same reason-- the need to preserver the temp7 variable's value-- you mustn't call a subroutine in a different bank, then from there call another subroutine in yet another bank.


Near as I can tell, temp7 is only used to
save the accumulator while it's being used
to stuff the stack.
So a function using temp7 won't mess up
bank switching but bank switching will
mess up the function's use of temp7.

 

Despite the fact that functions weren't designed to be used with bankswitching, they can be. The problem is that there's no provision in batari Basic for specifying the target bank when calling the function, so you can't just use a simple function call such as "a=myfunction(b) bank2." Instead, you'll need to store the parameters yourself, preferably using the same logic that batari Basic would. If you're using more than 2 parameters, you can store everything after the second parameter using regular batari Basic statements, in any order you want (e.g., "temp3=parameter3:temp4=parameter4:temp5=parameter5"), but you'll need to use inline assembly to store the first two parameters in the accumulator (first parameter) and Y register (second parameter). Once you've stored the parameters as needed, you can call the function from a different bank by using "gosub" (e.g., "gosub myfunction bank2").


(I think) You could define functions in
each bank that simply jumped via a goto
to the code for the body of the function
in some other bank.
I can't imagine a situation where it would
be worth it.

 

if you try to define a function with 8 or more parameters, you'll get a compile error.


Not if you've dimmed temp8 etc

Link to comment
Share on other sites

I think functions in general just make code look nicer, but they don't have any inherent properties that cannot be achieved through normal methods.

I have not used them yet in bB as I prefer more finite control over everything done, but I like the cleaner code.

 function .Multiply
 Result = temp1 * temp2
 return Result //Result not needed as value is kept in Acc?
end
 
P0velocity = .Multiply(Vector,Speed)
 
//This looks far better than say
 
.Multiply //Label
 Result = temp1 * temp2 //Save result of multiplication
 return thisbank //or otherbank
 
temp1 = Vector
temp2 = Speed
gosub .Multiply
P0velocity = Result
Edited by ScumSoft
Link to comment
Share on other sites

bogax, I never thought of dimming temp8, temp9, etc. If that works (I haven't tried it), it's a great idea!

 

As for temp7 and bankswitching, for some reason I thought it actually retained its value to help with switching back to the original bank. I'll have to take another look at the includes.

Link to comment
Share on other sites

As for temp7 and bankswitching, for some reason I thought it actually retained its value to help with switching back to the original bank. I'll have to take another look at the includes.

 

Hmm

 

Since I didn't look at the source(s) for the

bank switching code, just the resulting .lst

and bB.asm files for some simple test code,

there may be more possiblities than I'm seeing.

 

Or maybe I'm just missing something.

 

I didn't try it to see if it actually worked.

 

Link to comment
Share on other sites

bogax, you are correct-- temp7 is used only temporarily to briefly save the value in the accumulator until it can be pushed onto the stack-- so calling a function with 7 or more parameters will not interfere with bankswitching, but bankswitching will destroy any value that you may have saved in temp7.

 

I think I got the idea that temp7 did more than that from back when I first added bankswitching to batari Basic. At that time (version 0.3 or 0.35, IIRC) there was no bankswitching in batari Basic yet. I customized some of the include files to allow M-Network bankswitching. There was no temp7 at that time, just up to temp6-- so I added temp7 to save the bank number so I could switch to a different bank and later switch back (I wanted to add a new variable rather than interfere with any existing ones, and IIRC it just so happened that a byte was still free). After Fred saw what I'd done, he worked out how to add Atari bankswitching to batari Basic. His method works differently than what I was doing, but he did keep the new temp7 variable. So I've been confusing the way I was using temp7 in my original method with the way batari uses it in his method. I apologize for misleading everyone about that-- mea culpa!

Link to comment
Share on other sites

It's true the *task* a function does can also be done through other means. But a function doesn't just make code look cleaner, it also makes programming easier or more convenient-- like adding a new statement to the language. It reduces the amount of code you'd otherwise have to type to manually set the parameters and call the subroutine, as well as the extra statement that would be required to manually move the returned value (if a value is returned) from a generic variable name into the desired variable.

 

As far as what I said before about a function not needing to return a meaningful value, I'd like to add that it's probably good practice to code a function such that a status code is returned to indicate whether an error occurred. bB returns the function's value in the accumulator and stores it in the indicated variable-- but since calling a function destroys whatever was in temp1 and temp2 (and possibly in other temp variables), the status could be returned in temp1, as follows:

 

 

   a = myfunction(b c d)
   if temp1 <> 0 then goto oops_an_error_occurred

 

The non-zero values of the status could indicate the actual nature of the error, such as

if temp1 = 0 then no errors occurred

if temp1 = 1 then the first parameter contained an "illegal" value

if temp1 = 2 then the second parameter contained an "illegal" value

etc.

 

I was going to suggest an error code for "too few parameters" and "too many parameters," but the function can't tell how many parameters were passed; it assumes that anything in temp1, temp2, etc. was passed to the function by the calling statement-- so the best a function could do is check the value of each expected parameter and return a status code indicating if a parameter contained a value the function wasn't programmed to handle.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...