6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Sun Nov 10, 2024 11:53 pm

All times are UTC




Post new topic Reply to topic  [ 9 posts ] 
Author Message
PostPosted: Fri Oct 20, 2017 3:57 am 
Offline
User avatar

Joined: Mon May 12, 2014 6:18 pm
Posts: 365
I can't figure out how the macros in the simulator work. I downloaded 1.2.12 and tried this code:
Code:
 .START Main   ;start execution at Main
 
;Console i/o address for simulator
io_clrscr .SET $E000
io_out .SET $E001
io_in   .SET $E004

;Simulator I/O macros
clrscr: .MACRO
   STA io_clrscr
   .ENDM
 
putc: .MACRO ...
   .IF %0==1
      LDA %1
      .IF .PARAMTYPE(%1)==1
          .ERROR "Type 1"
      .ENDIF
    .ENDIF
   STA io_out
   .ENDM
 
;Global variables
var1 .SET $0041
 
;Beginning of functions
   .ORG $8000
Main:
   LDA #'A'
   STA 'Y'
   LDA #'B'
    STA var1
   putc var1
   putc 'Y'
It stops assembling on line 17 where the .PARAMTYPE is saying "ERROR E030: Missing label. ROW 17, FILE" What is it talking about?

I commented those lines out and got it to print "CBA" like I expected. However, when I change the last line to putc #'Y', the simulator gives an "Encountered an improper argument" error message and stays stuck/crashed on the assembling screen. Any ideas?


Top
 Profile  
Reply with quote  
PostPosted: Fri Oct 20, 2017 6:16 am 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8481
Location: Midwestern USA
Druzyek wrote:
I can't figure out how the macros in the simulator work. I downloaded 1.2.12 and tried this code:
Code:
 .START Main   ;start execution at Main
 
;Console i/o address for simulator
io_clrscr .SET $E000
io_out .SET $E001
io_in   .SET $E004

;Simulator I/O macros
clrscr: .MACRO
   STA io_clrscr
   .ENDM
 
putc: .MACRO ...
   .IF %0==1
      LDA %1
      .IF .PARAMTYPE(%1)==1
          .ERROR "Type 1"
      .ENDIF
    .ENDIF
   STA io_out
   .ENDM
 
;Global variables
var1 .SET $0041
 
;Beginning of functions
   .ORG $8000
Main:
   LDA #'A'
nonsensical ——— > STA 'Y'  <——— nonsensical
   LDA #'B'
    STA var1
   putc var1
   putc 'Y'

It stops assembling on line 17 where the .PARAMTYPE is saying "ERROR E030: Missing label. ROW 17, FILE" What is it talking about?

I commented those lines out and got it to print "CBA" like I expected. However, when I change the last line to putc #'Y', the simulator gives an "Encountered an improper argument" error message and stays stuck/crashed on the assembling screen. Any ideas?

Let's start out by picking apart your code.

The .SET directive should not be used to define constants such as the number of seconds in a minute or the address of a function in ROM (ROM doesn't change as the program is running and there have been 60 seconds in a minute for many centuries :D ). For example, io_clrscr .SET $E000 should be written as io_clrscr = $E000. Use .SET (or its equivalent, which is .=) only to define variables that are to be reassigned later on. .SET is mostly of value in macros, but can be used anywhere a declaration must be redefined as assembly progresses.

At the start of any program to be assembled by the Kowalski assembler a setup directive should declare the target processor and whether or not your source code is case-sensitive. I use the following in my source code:

Code:
   .opt proc65c02,caseinsensitive

The above declares the (Rockwell) 65C02 as the target MPU, and that mnemonics, labels and symbols are not case-sensitive (recommended).

Labels and symbols must always start in the first column. Appending a colon to a label is optional; I don't do it, as it serves no purpose in the way in which I structure my source code.

Mnemonics must start in at least the second column, which is also true with assembler directives (pseudo-ops). Due to the way in which the assembler parses source code lines, it thinks anything that starts at the first column is a label or a symbol and generates a diagnostic if a mnemonic or pseudo-op is in the first column. Use tabs or spaces to align your fields as you type in code.

STA 'Y' is ambiguous at best. As assembly progresses, 'Y' will be converted to $59. So STA 'Y' is the same as writing STA $59.

I recommend that you place an .END pseudo-op on the last line of your source file. If you do so you can type in notes after .END without having to mark them as comments, which is handy for things such as revision tables or discussion of an algorithm.

The diagnostic you are getting is telling you that when the assembler attempted to expand the putc #'Y' macro invocation, it couldn't figure out what you meant with #'Y'. #'Y' appears to be a single argument to putc, but # is seen as an addressing mode symbol and is not a legal argument to a macro unless surrounded by single quotes. What could work is putc '#','Y', which appears to the macro as two single character arguments. Your macro would have to be defined with two parameters, not one. I'll come back to this in a minute

Arguments to macro calls must be symbols, labels or quoted literal characters. Here's a simple macro example:

Code:
char     ='A'                  ;assigns ASCII value of "A" to char, which is $41
;
;
;   macro definition...
;
putchr   .macro .c             ;.c is the parameter
         lda #.c               ;load arg &...
         jsr putch             ;call console display function
         .endm                 ;end of macro definition
;
;
;   macro call...
;
         putchr char           ;outputs "A" to console
         putchr 'Z'            ;outputs "Z" to console

The label putchr becomes the macro's name and is used to invoke the macro. Note that since the macro definition is declared with one parameter (.c) one and only one argument must be passed with the macro invocation or else the assembler will complain and halt. It is not necessary to check the argument count in such a case, as the assembler will enforce it for you.

In the above example, .c is a local variable known only to the macro. Each invocation of the macro will create .c on the fly and assign the invocation argument to it. At the completion of macro expansion .c will cease to exist until the next invocation of the macro.

Macros may defined in any of three ways:

  1. No parameters. No parameters are defined in the macro and the macro must be invoked without arguments.
  2. Fixed parameters. The macro is defined with a fixed number of parameters. The correct number of arguments must be passed when the macro is invoked.
  3. Variable parameters. The macro is defined with .macro ..., where the ellipsis indicates that a variable number of parameters are expected. The body of the macro uses runtime variables that are prefixed with % to process the parameter list according to whatever rules you formulate.

Assembler internal variables that exist while a macro is being expanded are:

Code:
%0  — number of arguments passed with macro invocation
%1  — first argument
%2  — second argument
%N  — Nth argument
%0$ — name of the macro

You can also refer to an argument within a loop. For example, if the local variable .ct has been defined (for example, .ct .= 1) within the body of the macro, %.ct will refer to a specific argument. .ct could be incremented (.ct .= .ct + 1) or decremented (.ct .= .ct - 1) with each pass of the loop.

If the argument passed to the macro is a character string, you can determine that with the .paramtype pseudo-op. For example:

Code:
         .if .paramtype (%1) == 2

which would evaluate true if the first argument to the macro is a literal character string, e.g., prints "Testing" (not a recommended usage, incidentally). A .paramtype of 1 would result with something like putchr 'A'.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Fri Oct 20, 2017 12:32 pm 
Offline
User avatar

Joined: Mon May 12, 2014 6:18 pm
Posts: 365
Hi BDD,
Thank you for your help.
Quote:
STA 'Y' is ambiguous at best. As assembly progresses, 'Y' will be converted to $59. So STA 'Y' is the same as writing STA $59.
Right. I should have mentioned that I was playing with it to see if it could tell the difference between a memory address and a constant without crashing the simulator. I wouldn't do that in a real program.

Quote:
Arguments to macro calls must be symbols, labels or quoted literal characters. Here's a simple macro example:
That makes sense. The problem I'm having is making a macro that can take no arguments and use whatever value is in A, take a label as a memory address, or take a constant. It seems like you are stuck with only constants or only addresses with this method. Here is what I was hoping for:

Code:
LDA #'B'
STA variable_address

LDA #'A'
putchar
putchar variable_address
putchar #'C'


Top
 Profile  
Reply with quote  
PostPosted: Fri Oct 20, 2017 6:18 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8481
Location: Midwestern USA
Druzyek wrote:
Quote:
Arguments to macro calls must be symbols, labels or quoted literal characters. Here's a simple macro example:

That makes sense. The problem I'm having is making a macro that can take no arguments and use whatever value is in A, take a label as a memory address, or take a constant. It seems like you are stuck with only constants or only addresses with this method. Here is what I was hoping for:

Code:
LDA #'B'
STA variable_address

LDA #'A'
putchar
putchar variable_address
putchar #'C'

putchar #'C' is impossible; it's syntactically unacceptable to the assembler. I'm going to come up with an example for you and post later on. I'm real busy right now.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Fri Oct 20, 2017 6:33 pm 
Online
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8539
Location: Southern California
Druzyek wrote:
BigDumbDinosaur wrote:
Arguments to macro calls must be symbols, labels or quoted literal characters. Here's a simple macro example:
That makes sense. The problem I'm having is making a macro that can take no arguments and use whatever value is in A, take a label as a memory address, or take a constant. It seems like you are stuck with only constants or only addresses with this method. Here is what I was hoping for:

Code:
LDA #'B'
STA variable_address

LDA #'A'
putchar
putchar variable_address
putchar #'C'

I'm not familiar with that assembler; but from what BDD is saying, it sounds like you should be able to use %0 to get the number of arguments and test it, and then have conditional assembly that omits the LDA altogether if the number of arguments is 0. Assuming there's one argument, the only limitation left is that the variable address cannot be in the part of ZP whose addresses look like characters you might want to use. (For example, if you'll never use characters over $7F, ZP variables' addresses could be above that.) All non-ZP addresses would be interpreted as addresses. If the argument is over $FF, LDA <abs> is automatically used.

If you really need variables that could be in any part of ZP, how about putting another argument in the invocation, like:
Code:
        putchar  var, foobar
"var" could be a constant; but you wouldn't even have to use it, only see that the number of arguments is two, so you take foobar to be an address and never an actual character, even if it's a ZP address that does look like a valid character.

It looks quite doable.

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
PostPosted: Fri Oct 20, 2017 9:30 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8481
Location: Midwestern USA
GARTHWILSON wrote:
I'm not familiar with that assembler; but from what BDD is saying, it sounds like you should be able to use %0 to get the number of arguments and test it, and then have conditional assembly that omits the LDA altogether if the number of arguments is 0...

Correct. See next.

Code:
   .opt proc65c02,caseinsensitive
;
;MACRO EXAMPLE
;
;   This example shows how to create a macro that may be invoked as follows:
;
;       putchar               ;uses whatever is in the accumulator
;       putchar '#','A'       ;outputs literal character "A", note syntax
;       putchar addr          ;outputs character at address addr
;
putchar  .macro ...            ;putchar accepts 0 or more arguments
         .if %0                ;if macro was invoked with argument(s)...
             .if %0 == 1       ;if single argument, assume it's an address
                 lda %1        ;get char at that address & fall through
             .else
                 .if %0 > 2    ;if more than 2 args in invocation...
                     .error ""+%0$+": syntax error: too many arguments"
                 .endif
                 .if %1 != '#'
                     .error ""+%0$+": syntax error: arg 1 must be #"
                 .endif
                 lda #%2       ;load accumulator with arg 2
             .endif
         .endif
         jsr chrout            ;call output function
         .endm                 ;end of marco
;
;
;   test code...
;
var      =$80                  ;local storage
chrout   =$ffd2                ;output char to current device
;
         *=$2000               ;start here
;
         lda #'B'              ;test value
         sta var               ;store it
         putchar '#','A'       ;outputs "A"
         putchar var           ;outputs "B"
         lda #'C'
         putchar               ;outputs "C"
         putchar '#','A','B'   ;emits a "to many arguments" diagnostic
;
         .end

The above is mostly self-explanatory. Note the use of the .ERROR pseudo-op in the macro body to flag invocation errors, such as the putchar '#','A','B' statement in the test code section.

Next is an example of how to loop on a variable number of arguments.

Code:
;   ————————————————————————————————————————————————————————————————————————
;   getkey TIMOUT [,KEY1[,KEY2[,...[,KEYn]]]]
;   ...or...
;   getkey TIMOUT
;
;   0 < TIMOUT < $8000 returns after TIMOUT seconds if no keystroke has been
;   detected.  If TIMOUT is 0 an immediate return occurs with a keystroke if
;   available & in the optional keystroke list.  If TIMOUT is -1 no input
;   time-out will occur.
;
;   [,KEY1[,KEY2[,...[,KEYn]]]] is an optional list of ASCII keystrokes that
;   may be accepted.  If list is omitted, any keystroke will be accepted.
;
;   See the GETKEY library function header for more info.
;   ————————————————————————————————————————————————————————————————————————
;
getkey   .macro ...            ;variable number of arguments accepted
         .if %0 == 0
             .error ""+%0$+": syntax: getkey TIMOUT [,KEY1,KEY2,...,KEYn]"
         .endif
         .if %0 > 129
             .error ""+%0$+": too many arguments: syntax: getkey TIMOUT [,KEY1,KEY2,...,KEYn]"
         .endif
;
;   ————————————————————————————————————————————————————————————————————————
;   At least one arg, TIMOUT, must be present.  The GETKEY library function
;   can process up to 128 separate key values, so macro expansion will halt
;   with one of the above errors if no args are present or more than 128 key
;   args are invoked.
;   ————————————————————————————————————————————————————————————————————————
;
.nk      .= -1                 ;count of number of keys to detect
.np      .= %0                 ;argument index, starts w/last argument
         .rept %0              ;loop to process all args
             pea %.np          ;push current argument
.nk          .= .nk+1          ;increment number of keys
.np          .= .np-1          ;decrement argument index
         .endr                 ;end of loop
         pea .nk               ;push number of keys to detect...
;
;   ————————————————————————————————————————————————————————————
;   If only TIMOUT was invoked with macro call .nk will be zero.
;   ————————————————————————————————————————————————————————————
;
         jsr getkey            ;call GETKEY library function
         .endm

The above is a macro from my input processing library. It is a wrapper for the GETKEY library function, which accepts a keystroke according to defined rules and returns it. GETKEY can be configured to time out if a keystroke is not detected within 32,767 seconds. Here's the header from the GETKEY function so you can see how the above macro ties into it:

Code:
;getkey: GET A KEYPRESS: getkey TIMOUT [,KEY1[,KEY2[,...[,KEYn]]]]
;
;   ————————————————————————————————————————————————————————————————————————
;   Calling syntax: PEA #KEYn             ;optional ASCII value of nth key
;                   ---
;                   PEA #KEY2             ;optional ASCII value of 2nd key
;                   PEA #KEY1             ;optional ASCII value of 1st key
;                   - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;                   PEA #TIMOUT           ;no-input time-out period (3,4)
;                   PEA #NKEY             ;number of keys to detect (5)
;                   JSR GETKEY
;
;   Exit registers: .C: key index or keystroke (1,2,4)
;                   .X: entry value
;                   .Y: entry value
;                   DB: entry value
;                   DP: entry value
;                   PB: entry value
;                   SR: NVmxDIZC
;                       ||||||||
;                       |||||||+———> 0: valid keystroke detected
;                       |||||||      1: input timed out
;                       +++++++————> entry value
;
;   Notes: 1) The key index value is determined by the order of the key val-
;             ues passed in the parameter stack frame.  KEY1 will return 0,
;             KEY2 will return 1 & KEYn will return N-1.  See 1st example
;             below.
;
;          2) Entry value if input times out.
;
;          3) TIMOUT is in seconds.  Behavior is as follows:
;
;             § $0001 to $7FFF: input will time out TIMOUT seconds after no
;               keystroke has been detected.
;
;             § $8000 to $FFFF: input will never time out.  If using the
;               GETKEY macro TIMOUT can be passed as -1, which is assembled
;               as $FFFF.
;
;             § $0000: an immediate return with timed-out status will occur
;               if no keystrokes have been buffered.  If a keystroke is buf-
;               fered & is valid, the exit status will be set to keystroke-
;               detected.
;
;          4) The timer is reset each time a keystroke is detected, whether
;             valid or not.
;
;          5) If no KEY values are passed then any keystroke is valid & the
;             keystroke will be returned in .C with the MSB set to $00.
;
;          6) A maximum of 128 key values can be processed.  It is possible
;             to cause deadlock if TIMOUT > $7FFFF by passing only key val-
;             ues that do not correspond to any key on a terminal keyboard.
;
;          7) A mismatch between the key count (NKEY) & the actual number of
;             key values in the stack frame will cause the stack to become
;             unbalanced.
;
;          8) This function makes calls to the GETCHA & GETSUTIM functions
;             in the BIOS.
;
;          9) The actual time-out period will be approximately equal to the
;             value passed in TIMOUT.
;
;   Example:  PEA #A_ESC            ;KEY3 is escape
;             PEA #A_CR             ;KEY2 is carriage return
;             PEA #A_BS             ;KEY1 is backspace
;             PEA #600              ;input time-out in 600 seconds
;             PEA #3                ;3 KEY parameters on stack
;             JSR GETKEY            ;get <BS>, <CR> or <ESC> keystroke
;             BCS NOINPUT           ;timed out
;             ASL A
;             TAX
;             JMP (KEYTAB,X)        ;execute according to keypress
;
;   The above example waits for <BS>, <CR> or <ESC> & jumps to the appropr-
;   iate function if one of these keystrokes is detected within 10 minutes.
;
;   Example:  PEA #5*60             ;times out in 5 minutes
;             PEA #0                ;no KEY parameters
;             JSR GETKEY            ;get any keystroke
;             BCS NOINPUT           ;timed out w/no keystroke
;             SHORTA                ;8 bit accumulator
;             STA KEYSTROK          ;save keystroke
;
;   The above example waits for a maximum of 5 minutes for any keystroke &
;   stores the returned keystroke in memory for later processing.
;
;   Example:  PEA #$FF              ;KEY is undetectable
;             PEA #$FFFF            ;will never time out
;             PEA #1                ;1 KEY parameter on stack
;             JSR GETKEY            ;get input
;
;   The above example will cause deadlock, as the KEY parameter $FF cannot
;   be typed on a terminal keyboard & input time-out has been disabled.
;   ————————————————————————————————————————————————————————————————————————

Hope this information is helpful.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Sat Oct 21, 2017 12:48 am 
Offline
User avatar

Joined: Mon May 12, 2014 6:18 pm
Posts: 365
Thanks for sharing your macros, BDD. That is really fancy.

Quote:
If you really need variables that could be in any part of ZP, how about putting another argument in the invocation, like:
Code:
        putchar  var, foobar
I thought about that too or a slightly different macro name. I think it is a clunky way to do it and I'm really surprised that any assembler anywhere has this limitation (but thanks Garth for your workaround).

I looked at some code I wrote with ca65 three years ago and its macros handle the # with no problem for single byte arguments. For 16 bit values I found this one (which looks incomplete):
Code:
  .macro MOV16 src, dest
    .if (.match(.left(1,{src}),#))
      LDA #<(.right(.tcount({src})-1,{src}))
      STA dest
      LDA #>(.right(.tcount({src})-1,{src}))
      STA dest+1
    .else
      LDA src
      STA dest
      LDA src+1
      STA dest+1
    .endif
  .endmacro
It is also nice that you can put as many spaces as you want before labels when you are nesting loops. I tried assembling some old code with ca65 and loading the binary into Kowalski's simulator, which works pretty well. I can scroll through the ca65 listing while single stepping the simulator, which is not ideal but works. Is there a different software combo you guys think would work better?


Top
 Profile  
Reply with quote  
PostPosted: Thu Nov 09, 2017 10:57 pm 
Offline
User avatar

Joined: Mon May 12, 2014 6:18 pm
Posts: 365
I'm trying to use macros to assign numbers to variable names, but I can't get the simulator macros to do it. I want to use X indexing for a pseudo stack in zero page. Here is what I am trying to do:
Code:
;BEFORE
;=====
Function:
var_count .= 0
   byte foo
   byte bar

   LDA #5
   STA bar, X

;AFTER
;=====
Function:
var_count .= 0
   foo = var_count
var_count .= var_count+1
   bar = var_count
var_count .= var_count+1

   LDA #5
   STA 1, X

I couldn't get either of these to work:
Code:
byte_: .MACRO ...
%1 .= var_bytecount
var_bytecount .= var_bytecount+1
   .ENDM
   
byte: .MACRO var
var .= var_bytecount
var_bytecount .= var_bytecount+1
   .ENDM

Any ideas?


Top
 Profile  
Reply with quote  
PostPosted: Fri Nov 10, 2017 2:11 am 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8481
Location: Midwestern USA
Druzyek wrote:
I'm trying to use macros to assign numbers to variable names...I couldn't get either of these to work:
Code:
byte_: .MACRO ...
%1 .= var_bytecount
var_bytecount .= var_bytecount+1
   .ENDM
   
byte: .MACRO var
var .= var_bytecount
var_bytecount .= var_bytecount+1
   .ENDM

Any ideas?

I've never been able to get what you are trying to do to work. The %1 .= assignment always fails, probably because the %1 parameter is internal to the macro parser and thus not changeable in code.

On the other hand, if the macro parameters were passed as strings, it's possible the desired substitution could be made to occur. I have not explored that.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 9 posts ] 

All times are UTC


Who is online

Users browsing this forum: No registered users and 1 guest


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 post attachments in this forum

Search for:
Jump to: