Functions with Two Arguments

Basic and Machine Language

Moderator: Moderators

Post Reply
User avatar
pitcalco
just pitcalco
Posts: 1272
Joined: Wed Dec 28, 2005 4:13 pm
Location: Berlin

Functions with Two Arguments

Post by pitcalco »

Of course, it would be easiest just to try myself, but I might look for some advice first.

I was wondering if it were possible to use functions in VIC Basic with two arguments in the form of FNA(X,Y).

In particular, I am trying to develop functions equivalent to DIV and MOD as found in Pascal or QBasic. I really am missing those operations whenever I go back to the VIC.

So, say I want to get the remainder of 19 divided by 3 (which is 1). I usually go through the messy stuff of,

Code: Select all

X=19: Y=3
R=(X/Y-(INT(X/Y))*Y

Is it possible to use for instance:

Code: Select all

DEF FNDV(X,Y)=(X/Y-(INT(X/Y))*Y
and from then on just use,

Code: Select all

R=FNDV(19,3)
to get the correct result?

If so, just how many arguments can functions have in VIC Basic?
There are only three kinds of people in the world: those who can count and those who can't.

Paul Lambert
Berlin
Federal Republic of Germany
Richard James
Vic 20 Devotee
Posts: 269
Joined: Mon Feb 04, 2008 6:06 am

Re: Functions with Two Arguments

Post by Richard James »

pitcalco wrote:If so, just how many arguments can functions have in VIC Basic?
I just tried this in VICE and all I can do is one argument. However look at the following program

Code: Select all

10 DEF FNDV(X)=(X/Y-(INT(X/Y)))*Y
15 Y=3
20 R=FNDV(19)
30 PRINT R
That will print 1, it is up to you to decide if this is useful.
Change is inevitable except from a vending machine.
User avatar
Mike
Herr VC
Posts: 4841
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Post by Mike »

Pitcalco,

You didn't say which of the two possible integer divide/remainder functions you want. ;)

That said beforehand, you can set aside four variables, say N (nominator), D (denominator), Q (quotient), and R (remainder). Then you provide two subroutines, in the aforementioned two variants, as follows:

Code: Select all

990 REM Remainder same sign as denominator, or zero
1000 Q=INT(N/D):RETURN:REM DIV
1010 GOSUB 1000:R=N-Q*D:RETURN:REM MOD

or

990 REM Remainder same sign as numerator, or zero
1000 Q=N/D:Q=INT(ABS(Q))*SGN(Q):RETURN:REM DIV
1010 GOSUB 1000:R=N-Q*D:RETURN:REM MOD
DEF FN functions accept only one argument. If you want DIV and MOD with two parameters like in other BASIC dialects, you must write them as BASIC extensions in machine language.

Greetings,

Michael
Last edited by Mike on Tue Apr 08, 2008 12:50 am, edited 1 time in total.
User avatar
pitcalco
just pitcalco
Posts: 1272
Joined: Wed Dec 28, 2005 4:13 pm
Location: Berlin

Post by pitcalco »

Thanks to both Mike and Richard. Those were some clever solutions for the problem. Alas, they were both rather more elaborate than the original problem. But you have answered my question - only one argument allowed.

I find myself being pushed by necessity more and more toward machine language.
There are only three kinds of people in the world: those who can count and those who can't.

Paul Lambert
Berlin
Federal Republic of Germany
carlsson
Class of '6502
Posts: 5516
Joined: Wed Mar 10, 2004 1:41 am

Post by carlsson »

Let's make this thread a lesson in how to write the ML subroutine and Basic extension. Mike has previously went through the fundamentals.

Besides, I'm unsure if any Microsoft or other Basic dialect allows more than one argument to FN statements. BBC Basic perhaps?
Anders Carlsson

Image Image Image Image Image
User avatar
Mike
Herr VC
Posts: 4841
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

open Kick-Off meeting

Post by Mike »

This lesson naturally divides into two tasks:

* Tokenizing and Listing:

Internally, all BASIC keywords are stored as tokens of one byte. 'PRINT' for example is stored as $99. The BASIC extension must provide new tokens for DIV, and MOD. And it must be able to LIST them.

- This requires hooking into the vectors $0304 (tokenize), and $0306 (list)


* Performing the actual operation of DIV and MOD:

This amounts to converting either of the two subroutines I gave above into ML. The proposed syntax is 'op(numerator,denominator)'. That means, after the token of DIV or MOD has been identified, one must check for '(', read in the numerator, check for ',', read in the denominator, and finally check for ')'. And then perform the desired action. DIV, and MOD should operate correctly in case of recursive calls.

- This requires hooking into the vector $030A (eval fn)


I would volunteer programming the function evaluation. Anders, would you take care about the first part?

Michael
carlsson
Class of '6502
Posts: 5516
Joined: Wed Mar 10, 2004 1:41 am

Post by carlsson »

Well, if I get to experiment a bit myself.. actually I can't remember have written any true Basic extensions before. Or did I? Perhaps I can have a go at it tonight.
Anders Carlsson

Image Image Image Image Image
User avatar
Mike
Herr VC
Posts: 4841
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Hooking into eval fn

Post by Mike »

In http://codebase64.org/doku.php?id=magazines:discovery3 Steven Judd describes a BASIC extension with an own tokenizer. This should easily be adapted to your need.

Anyway, here we go:

The vector eval fn ($030a) points to $CE86 in the BASIC ROM.

At this time within formula evaluation, BASIC expects a single operand. This can be a number, a variable (floating or integer), a function result, or an expression in parenthesis. BASIC has not yet looked at the first byte of the operand.

The begin of the routine at $ce86 reads thus:

Code: Select all

.C:ce86   A9 00      LDA #$00
.C:ce88   85 0D      STA $0D    ; assume numeric value
.C:ce8a   20 73 00   JSR $0073  ; CHRGET first byte of operand,
                                ; could also be a function token
The operand shall be returned in FAC#1. Furthermore, the TXTPTR has to be advanced behind the operand. This can be deduced from the following code continuation, where we assume, that {PI} was the token returned from CHRGET:

Code: Select all

.C:ce8d   B0 03      BCS $CE92  ; branch taken
.C:ce8f   4C F3 DC   JMP $DCF3
.C:ce92   20 13 D1   JSR $D113  ; Is the token an alphabetic character?
.C:ce95   90 03      BCC $CE9A  ; No. branch taken
.C:ce97   4C 28 CF   JMP $CF28
.C:ce9a   C9 FF      CMP #$FF   ; Is it {PI}?
.C:ce9c   D0 0F      BNE $CEAD  ; Yes. branch not taken
.C:ce9e   A9 A8      LDA #$A8
.C:cea0   A0 CE      LDY #$CE
.C:cea2   20 A2 DB   JSR $DBA2  ; load FAC#1 from memory
.C:cea5   4C 73 00   JMP $0073  ; CHRGET behind {PI}, and exit.
>C:cea8   82 49 0f da a1        ; value of {PI}
The replacement routine copies the start of the original ROM routines, checks for the DIV, or MOD tokens, and re-enters the ROM routine at $ce8d, if neither of them is there:

Code: Select all

.EvalV
 LDA #$00
 STA $0D
 JSR $0073
 PHP               ; store flags on stack
 CMP #{DIV token}
 BEQ EvalV_00
 CMP #{MOD token}
 BEQ EvalV_01
 PLP               ; restore flags
 JMP &CE8D         ; re-enter ROM eval fn
.EvalV_00
 PLP               ; clean up stack
 JMP EvalDIV       ; jumps to custom DIV routine
.EvalV_01
 PLP               ; clean up stack
 JMP EvalMOD       ; jumps to custom MOD routine
If more functions are implemented, a jump table is more efficient, just like BASIC does it.

Both the DIV, and MOD functions take two arguments in parenthesis, separated by comma. So it makes sense to provide a common subroutine. First we set aside two 5 byte buffers for numerator, and denominator:

Code: Select all

.Numerator
 EQUB 0:EQUB 0:EQUB 0:EQUB 0:EQUB 0
.Denominator
 EQUB 0:EQUB 0:EQUB 0:EQUB 0:EQUB 0
BASIC provides three calls to check for '(', ')', and ','. If the correct char is not there, these calls end our program with '?SYNTAX ERROR'. Otherwise TXTPTR is advanced. We're still on the function token, so the first action we take is to advance TXTPTR to (hopefully) the opening parenthesis of the function:

Code: Select all

.GetArgs
 JSR $0073
 JSR $CEFA         ; check for '(', and advance
 JSR $CD8A         ; get first operand, and store in FAC#1
 LDX #<Numerator
 LDY #>Numerator
 JSR $DBD4         ; Store FAC#1 to memory at address (Y/X)
 JSR $CEFD         ; check for ',', and advance
 ...
There now already has been a recursive formula evaluation, which resulted in the Numerator. If we now read in the denominator, we must push the Numerator value on the stack, as a recursive call of DIV, or MOD at this stage would otherwise destroy the stored value!

Code: Select all

 ...
 LDX #0
.GetArgs_00
 LDA Numerator,X
 PHA               ; Push the 5 bytes of the Numerator to the stack
 INX
 CPX #5
 BNE GetArgs_00
 JSR $CD8A         ; get second operand, and store in FAC#1
 LDX #<Denominator
 LDY #>Denominator
 JSR $DBD4         ; Store FAC#1 to memory at address (Y/X)
 LDX #4
.GetArgs_01
 PLA               ; Restore the 5 Bytes of the Numerator from stack
 STA Numerator,X
 DEX
 CPX #$FF
 BNE GetArgs_01
 JMP $CEF7         ; Check for ')', advance TXTPTR, and exit.
Both EvalDIV, and EvalMOD now start their business with a JSR GetArgs to fetch their arguments. And TXTPTR then is already behind the whole function call, as required.

Code: Select all

.EvalDIV
 JSR GetArgs
; ** commented out, since the Denominator is already in FAC#1 after JSR GetArgs
;LDA #<Denominator
;LDY #>Denominator
;JSR $DBA2         ; Load Denominator in FAC#1
 LDA #<Numerator
 LDY #>Numerator
 JSR $DB0F         ; Divide value at (Y/A) by FAC#1, result in FAC#1
                   ; Denominator=0 results in a '?DIVISION BY ZERO' error
 JMP $DCCC         ; Perform INT() on FAC#1, result in FAC#1:=INT(N/D)
Since Q=INT(N/D) is shared by both DIV, and MOD, EvalMOD simply calls EvalDIV first:

Code: Select all

.EvalMOD
 JSR EvalDIV       ; return FAC#1:=INT(N/D) (=Q)
 LDA #<Denominator
 LDY #>Denominator
 JSR $DA28         ; Multiply FAC#1 by value at (Y/A), result in FAC#1:=Q*D
 LDA #<Numerator
 LDY #>Numerator
 JMP $D850         ; Subtract FAC#1 from value at (Y/A), result in FAC#1:=N-Q*D (=R!)
User avatar
pitcalco
just pitcalco
Posts: 1272
Joined: Wed Dec 28, 2005 4:13 pm
Location: Berlin

Post by pitcalco »

My goodness! :shock:

You sure don't mess around, do you Mike.

Great stuff!
There are only three kinds of people in the world: those who can count and those who can't.

Paul Lambert
Berlin
Federal Republic of Germany
carlsson
Class of '6502
Posts: 5516
Joined: Wed Mar 10, 2004 1:41 am

Post by carlsson »

Wow! Now only the LIST part remains? I was busy with other matters yesterday so I had no chance to react before Mike presented his solution. :-)
Anders Carlsson

Image Image Image Image Image
User avatar
Mike
Herr VC
Posts: 4841
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Post by Mike »

@Pitcalco:

If the arguments are integer, and within a limited range, you can actually use an array (i.e. a table) to appear like a function with multiple arguments.

A particularily nice example I remember is this one:

Code: Select all

1 DIMI(1,1,1,1,1,1,1,1)
2 FORA=0TO1:FORB=0TO1:FORC=0TO1:FORD=0TO1:FORE=0TO1:FORF=0TO1:FORG=0TO1:FORH=0TO1
3 I(A,B,C,D,E,F,G,H)=128*A+64*B+32*C+16*D+8*E+4*F+2*G+H
4 NEXT:NEXT:NEXT:NEXT:NEXT:NEXT:NEXT:NEXT
5 :
6 REM EXAMPLE
7 PRINT "VIC ";I(0,0,0,1,0,1,0,0)
@Anders:

Any news?
Last edited by Mike on Thu Apr 17, 2008 5:31 am, edited 1 time in total.
User avatar
pitcalco
just pitcalco
Posts: 1272
Joined: Wed Dec 28, 2005 4:13 pm
Location: Berlin

Post by pitcalco »

Mike wrote:@Pitcalco:

If the arguments are integer, and within a limited range, you can actually use an array (i.e. a table) to appear like a function with multiple arguments.

A particularily nice example I remember is this one:

Code: Select all

1 DIMI(1,1,1,1,1,1,1,1)
2 FORA=0TO1:FORB=0TO1:FORC=0TO1:FORD=0TO1:FORE=0TO1:FORF=0TO1:FORG=0TO1:FORH=0TO1
3 I%(A,B,C,D,E,F,G,H)=128*A+64*B+32*C+16*D+8*E+4*F+2*G+H
4 NEXT:NEXT:NEXT:NEXT:NEXT:NEXT:NEXT:NEXT
5 :
6 REM EXAMPLE
7 PRINT "VIC ";I(0,0,0,1,0,1,0,0)

I see! I could use that or certain modifications of that in specific instances. Thanks.

@Anders:

Any news?
:D
There are only three kinds of people in the world: those who can count and those who can't.

Paul Lambert
Berlin
Federal Republic of Germany
carlsson
Class of '6502
Posts: 5516
Joined: Wed Mar 10, 2004 1:41 am

Post by carlsson »

What is the word.. repressed. Yes! That's it, I have repressed that I was suppose to contribute to the solution.
Anders Carlsson

Image Image Image Image Image
User avatar
Mike
Herr VC
Posts: 4841
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Post by Mike »

At least you made the suggestion, to make the thread into a live development example. After all this is a spare-time activity, right? :)

From what I now believe, the tokenization, and token decode part is big enough to be split itself into two sub-tasks. So either I would take one of these, or we ask a third one to join the club.

The example of Steven Judd I posted has some problems: It directly uses the normal tokenizer, and then makes another pass with its own keywords. That means a keyword like 'ORIGIN' is tokenized twice, first to {OR}IGIN, then to {{OR}IGIN}. The keyword table therefore looks rather strange, as it contains the token of {OR}. And for this very reason it can't be used again to list the tokens... so ugly special-casing is necessary.

It might be more sensible to dissect, for example, the Super Expander code.

Greetings,

Michael
Post Reply