From 72b956400c716a8e4f57ce52642d1f6ca59dca3a Mon Sep 17 00:00:00 2001 From: jonnosan Date: Sat, 28 Aug 2010 07:31:23 +0000 Subject: [PATCH] git-svn-id: http://svn.code.sf.net/p/netboot65/code@270 93682198-c243-4bdb-bd91-e943c89aac3b --- client/basic/Makefile | 2 +- client/basic/bails.s | 1007 ++++++++++++++++++++++++++++++++++++ client/basic/kipperbas.d64 | Bin 174848 -> 174848 bytes 3 files changed, 1008 insertions(+), 1 deletion(-) create mode 100644 client/basic/bails.s diff --git a/client/basic/Makefile b/client/basic/Makefile index 98f4497..136c8c2 100644 --- a/client/basic/Makefile +++ b/client/basic/Makefile @@ -17,7 +17,7 @@ IP65LIB=../ip65/ip65_tcp.lib C64PROGLIB=../drivers/c64prog.lib -all: kipperbas.d64 +all: kipperbas.d64 bails.prg %.o: %.s $(INCFILES) $(AS) $(AFLAGS) $< diff --git a/client/basic/bails.s b/client/basic/bails.s new file mode 100644 index 0000000..3bacab0 --- /dev/null +++ b/client/basic/bails.s @@ -0,0 +1,1007 @@ + +.include "../inc/common.i" +.ifndef KPR_API_VERSION_NUMBER + .define EQU = + .include "../inc/kipper_constants.i" +.endif + +VARTAB = $2D ;BASIC variable table storage +ARYTAB = $2F ;BASIC array table storage +FREETOP = $33 ;bottom of string text storage area +MEMSIZ = $37 ;highest address used by BASIC +VARNAM = $45 ;current BASIC variable name +VARPNT = $47 ; pointer to current BASIC variable value + +SETNAM = $FFBD +SETLFS = $FFBA +OPEN = $FFC0 +CHKIN = $FFC6 +READST = $FFB7 ; read status byte +CHRIN = $FFCF ; get a byte from file +CLOSE = $FFC3 +MEMTOP = $FE25 +TXTPTR = $7A ;BASIC text pointer +IERROR = $0300 +ICRUNCH = $0304 ;Crunch ASCII into token +IQPLOP = $0306 ;List +IGONE = $0308 ;Execute next BASIC token + +CHRGET = $73 +CHRGOT = $79 +CHROUT = $FFD2 +GETBYT = $B79E ;BASIC routine +GETPAR = $B7EB ;Get a 16,8 pair of numbers +CHKCOM = $AEFD +NEW = $A642 +CLR = $A65E +NEWSTT = $A7AE +GETVAR = $B0E7 ;find or create a variable +FRMEVL = $AD9E ;evaluate expression +FRESTR = $B6A3 ;free temporary string +FRMNUM = $AD8A ;get a number +GETADR = $B7F7 ;convert number to 16 bit integer +INLIN = $A560 ; read a line from keyboard + +VALTYP=$0D ;00=number, $FF=string + +LINNUM = $14 ;Number returned by GETPAR + +crunched_line = $0200 ;Input buffer + +.import copymem +.importzp copy_src +.importzp copy_dest +.import dhcp_init +.import ip65_init +.import cfg_get_configuration_ptr +.import ip65_process +.import ip65_error +.import cfg_ip +.import cfg_dns +.import cfg_gateway +.import cfg_netmask +.import icmp_ping +.import icmp_echo_ip +.import dns_set_hostname +.import dns_resolve +.import dns_ip + +.import print_a +.import print_cr +.import dhcp_server +.import cfg_mac +.import cs_driver_name +.import get_key_if_available +.import timer_read +.import native_to_ascii +.import ascii_to_native +.zeropage +temp: .res 2 +temp2: .res 2 +pptr=temp +.segment "STARTUP" ;this is what gets put at the start of the file on the C64 +.word basicstub ; load address +basicstub: + .word @nextline + .word 2003 ;line number + .byte $9e ;SYS + .byte <(((relocate / 1000) .mod 10) + $30) + .byte <(((relocate / 100 ) .mod 10) + $30) + .byte <(((relocate / 10 ) .mod 10) + $30) + .byte <(((relocate ) .mod 10) + $30) + .byte 0 +@nextline: + .word 0 +relocate: + ldax #end_of_loader + stax copy_src + ldax #main_start + stax copy_dest + stax MEMSIZ +FS=$8000-main_start + ldax #FS + +;copy memory + sta end + ldy #0 + + cpx #0 + beq @tail + +: lda (copy_src),y + sta (copy_dest),y + iny + bne :- + inc copy_src+1 ;next page + inc copy_dest+1 ;next page + dex + bne :- + +@tail: + lda end + beq @done + +: lda (copy_src),y + sta (copy_dest),y + iny + cpy end + bne :- + +@done: + ldax #welcome_banner + jsr print + + ldx #5 ;Copy CURRENT vectors +@copy_old_vectors_loop: + lda ICRUNCH,x + sta oldcrunch,x + dex + bpl @copy_old_vectors_loop + + ldx #5 ;Copy CURRENT vectors +install_new_vectors_loop: + lda vectors,x + sta ICRUNCH,x + dex + bpl install_new_vectors_loop + + ;BASIC keywords installed, now bring up the ip65 stack + + jsr ip65_init +@init_failed: + + jsr $A644 ;do a "NEW" + jmp $A474 ;"READY" prompt + +welcome_banner: +.byte "### BASIC ON BAILS ###",13 +.byte 0 +end: .res 1 +end_of_loader: + +.segment "MAINSTART" +main_start: + +safe_getvar: ;if GETVAR doesn't find the desired variable name in the VARTABLE, a routine at $B11D will create it + ;however that routine checks if the low byte of the return address of the caller is $2A. if it is, + ;it assumes the caller is the routine at $AF28 which just wants to get the variable value, and + ;returns a pointer to a dummy 'zero' pointer. + ;so if user code that is calling GETVAR happens to be compiled to an address $xx28, it will + ;trigger this check, and not create a new variable, which (from painful experience) will create + ;a really nasty condition to debug! + ;so vector to GETVAR via here, so the return address seen by $B11D is here, and never $xx28 + jsr GETVAR + rts +.code + + +; CRUNCH -- If this is one of our keywords, then tokenize it +; +crunch: + jsr jmp_crunch ;First crunch line normally + ldy #05 ;Offset for KERNAL + ;Y will contain line length+5 +@loop: + sty temp + jsr isword ;Are we at a keyword? + bcs @gotcha +@next: + jsr nextchar + bne @loop ;Null byte marks end + sta crunched_line-3,Y ;00 line number + lda #$FF ;'tis what A should be + rts ;Buh-bye + +; Insert token and crunch line +@gotcha: + ldx temp ;If so, A contains opcode + sta crunched_line-5,X +@move: + inx + lda crunched_line-5,Y + sta crunched_line-5,X ;Move text backwards + beq @next + iny + bpl @move + + +; ISWORD -- Checks to see if word is +; in table. If a word is found, then +; C is set, Y is one past the last char +; and A contains opcode. Otherwise, +; carry is clear. +; +; On entry, TEMP must contain current +; character position. +; +isword: + ldx #00 +@loop: + ldy temp +@loop2: + lda keywords,x + beq @notmine + cmp #$E0 + bcs @done ;Tokens are >=$E0 + cmp crunched_line-5,Y + bne @next + iny ;Success! Go to next char + inx + bne @loop2 +@next: + inx + lda keywords,x ;Find next keyword + cmp #$E0 + bcc @next + inx + bne @loop ;And check again +@notmine: + clc +@done: + rts + + +; NEXTCHAR finds the next char +; in the buffer, skipping +; spaces and quotes. On +; entry, TEMP contains the +; position of the last spot +; read. On exit, Y contains +; the index to the next char, +; A contains that char, and Z is set if at end of line. + +nextchar: + ldy temp +@loop: + iny + lda crunched_line-5,Y + beq @done + cmp #$8F ;REM + bne @cont + lda #00 +@skip: + sta temp2 ;Find matching character +@loop2: + iny + lda crunched_line-5,Y + beq @done + cmp temp2 + bne @loop2 ;Skip to end of line + beq @loop +@cont: + cmp #$20 ;space + beq @loop + cmp #$22 ;quote + beq @skip +@done: + rts + + +; +; LIST -- patches the LIST routine +; to list our new tokens correctly. +; + +list: + cmp #$E0 + bcc @notmine ;Not my token + cmp #HITOKEN + bcs @notmine + bit $0F ;Check for quote mode + bmi @notmine + sec + sbc #$DF ;Find the corresponding text + tax + sty $49 + ldy #00 +@loop: + dex + beq @done +@loop2: + iny + lda keywords,y + cmp #$E0 + bcc @loop2 + iny + bne @loop +@done: + lda keywords,y + cmp #$91 ;is it "ON"? + bne @not_on + lda #'O' + jsr CHROUT + lda #'N' + bne @skip + +@not_on: + cmp #$9B ;is it "LIST"? + + bne @not_list + lda #'L' + jsr CHROUT + lda #'I' + jsr CHROUT + lda #'S' + jsr CHROUT + lda #'T' + bne @skip +@not_list: + lda keywords,y + bmi @out ;is it >=$80? +@skip: + jsr CHROUT + iny + bne @done +@out: + cmp #$E0 ;It might be BASIC token + bcs @cont + ldy $49 +@notmine: + and #$FF + jmp (oldlist) +@cont: + ldy $49 + jmp $A700 ;Normal exit + + + +; +; EXECUTE -- if this is one of our new +; tokens, then execute it. +execute: + jsr CHRGET +execute_a: + php + cmp #':' ;is it a colon? + beq execute;if so, skip over and go to next token + cmp #$8B ;is it 'IF'? + bne @not_if + lda #$E0 ;our dummy IF token +@not_if: + cmp #$E0 + bcc @notmine + cmp #HITOKEN + bcs @notmine + plp + jsr @disp + jmp NEWSTT +@disp: + eor #$E0 + asl ;multiply by 2 + tax + lda token_routines+1,x + pha + lda token_routines,x + pha + jmp CHRGET ;exit to routine (via RTS) +@notmine: + plp + cmp #0 ;reset flags + jmp $A7E7 + +;the standard BASIC IF routine calls the BASIC EXECUTE routine directly, +;without going through the vector. That means an extended keyword following THEN +;will lead to a syntax error. So we have to reimpliment IF here +;this is taken from TransBASIC - The Transactor, vol 5, Issue 04 (March 1985) page 34 +if_keyword: + jsr FRMEVL ;evaluate expression + jsr CHRGOT + cmp #$89 ;is next token GOTO? + beq @ok + lda #$A7 ;is next token THEN + jsr $AEFF ;will generate SYNTAX ERROR if not +@ok: + jsr CHRGOT + ldx $61 ;result of expression : 0 means false + bne @expression_was_true + jmp $A93B ;go to REM implementation - skips rest of line +@expression_was_true: + bcs @not_numeric;CHRGOT clears carry flag if current char is a number + jmp $A8A0 ;do a GOTO +@not_numeric: + pla + pla ;pop the return address off the stack + jsr CHRGOT + jmp execute_a ;execute current token + + + +find_var: + sta VARNAM + stx VARNAM+1 + jsr safe_getvar + ldy #0 + rts + +;emit the 4 bytes pointed at by AX as dotted decimals +emit_dotted_quad: + sta pptr + stx pptr + 1 + ldy #0 + lda (pptr),y + jsr emit_decimal + lda #'.' + jsr emit_a + + ldy #1 + lda (pptr),y + jsr emit_decimal + lda #'.' + jsr emit_a + + ldy #2 + lda (pptr),y + jsr emit_decimal + lda #'.' + jsr emit_a + + ldy #3 + lda (pptr),y + jsr emit_decimal + + rts + +emit_decimal: ;emit byte in A as a decimal number + pha + sta temp_bin ;save + sed ; Switch to decimal mode + lda #0 ; Ensure the result is clear + sta temp_bcd + sta temp_bcd+1 + ldx #8 ; The number of source bits + : + asl temp_bin+0 ; Shift out one bit + lda temp_bcd+0 ; And add into result + adc temp_bcd+0 + sta temp_bcd+0 + lda temp_bcd+1 ; propagating any carry + adc temp_bcd+1 + sta temp_bcd+1 + dex ; And repeat for next bit + bne :- + + cld ;back to binary + + pla ;get back the original passed in number + bmi @emit_hundreds ; if N is set, the number is >=128 so emit all 3 digits + cmp #10 + bmi @emit_units + cmp #100 + bmi @emit_tens +@emit_hundreds: + lda temp_bcd+1 ;get the most significant digit + and #$0f + clc + adc #'0' + jsr emit_a + +@emit_tens: + lda temp_bcd + lsr + lsr + lsr + lsr + clc + adc #'0' + jsr emit_a +@emit_units: + lda temp_bcd + and #$0f + clc + adc #'0' + jsr emit_a + + rts + +print: + sta pptr + stx pptr + 1 + +@print_loop: + ldy #0 + lda (pptr),y + beq @done_print + jsr print_a + inc pptr + bne @print_loop + inc pptr+1 + bne @print_loop ;if we ever get to $ffff, we've probably gone far enough ;-) +@done_print: + rts + + +extract_string: + jsr FRMEVL + jsr FRESTR ;if not string, will create type mismatch error + sta param_length + tay + lda #0 + sta transfer_buffer,y + dey +@loop: + lda ($22),y + sta transfer_buffer,y + dey + bpl @loop + jmp FRESTR ;free up the temp string created by FRMEVL + +;get a string value from BASIC command, turn into a 32 bit IP address,save it in the 4 bytes pointed at by AX +get_ip_parameter: + stax temp2 + jsr extract_string + ldax #transfer_buffer + jsr dns_set_hostname + + bcs @error + jsr dns_resolve + bcc @ok +@error: + ldax #address_resolution + jmp print_error + +@ok: + ldax #dns_ip + ldx #4 +@copy_dns_ip: + lda dns_ip,y + sta (temp2),y + iny + dex + bne @copy_dns_ip + rts + + +reset_string: + ldy #string_buffer + sty current_output_ptr+1 + rts + +print_dotted_quad: + jsr reset_string + jsr emit_dotted_quad +make_null_terminated_and_print: + lda #0 + jsr emit_a + ldax #string_buffer + jmp print + +print_mac: + jsr reset_string + jsr emit_mac + jmp make_null_terminated_and_print + +;print 6 bytes printed at by AX as a MAC address +emit_mac: + stax pptr + ldy #0 +@one_mac_digit: + tya ;just to set the Z flag + pha + beq @dont_print_colon + lda #':' + jsr emit_a +@dont_print_colon: + pla + tay + lda (pptr),y + jsr emit_hex + iny + cpy #06 + bne @one_mac_digit + rts + +emit_hex: + pha + pha + lsr + lsr + lsr + lsr + tax + lda hexdigits,x + jsr emit_a + pla + and #$0F + tax + lda hexdigits,x + jsr emit_a + pla + rts + +print_hex: + jsr reset_string + jsr emit_hex + jmp make_null_terminated_and_print + +print_error: + jsr print + ldax #error + jsr print + lda ip65_error + jsr print_hex + jsr print_cr + sec + rts + +get_optional_byte: + jsr CHRGOT + beq @no_param ;leave X as it was + jsr CHKCOM ;make sure next char is a comma (and skip it) + jsr CHRGOT + beq @eol + jsr GETBYT +@no_param: + rts +@eol: + jmp $AF08 ;SYNTAX ERROR + +ipcfg_keyword: + + ldax #interface_type + jsr print + + ldax #cs_driver_name + jsr print + jsr print_cr + + ldax #mac_address_msg + jsr print + ldax #cfg_mac + jsr print_mac + jsr print_cr + + ldax #ip_address_msg + jsr print + ldax #cfg_ip + jsr print_dotted_quad + jsr print_cr + + ldax #netmask_msg + jsr print + ldax #cfg_netmask + jsr print_dotted_quad + jsr print_cr + + ldax #gateway_msg + jsr print + ldax #cfg_gateway + jsr print_dotted_quad + jsr print_cr + + ldax #dns_server_msg + jsr print + ldax #cfg_dns + jsr print_dotted_quad + jsr print_cr + + + ldax #dhcp_server_msg + jsr print + ldax #dhcp_server + jsr print_dotted_quad + jsr print_cr + rts + + +dhcp_keyword: + jsr dhcp_init + bcc @init_ok + jsr ip65_init ;if DHCP failed, then reinit the IP stack (which will reset IP address etc that DHCP messed with to default values) + +@init_failed: + ldax #dhcp + jmp print_error +@init_ok: + rts + +ping_keyword: + ldax #icmp_echo_ip + jsr get_ip_parameter + bcc @no_error + rts +@no_error: + + ;is there an optional parameter? + ldx #3 + jsr get_optional_byte + stx ping_counter + + ldax #pinging + jsr print + ldax #dns_ip + jsr print_dotted_quad + jsr print_cr + +@ping_loop: + jsr icmp_ping + bcs @error + lda #'.' +@print_and_loop: + jsr print_a + lda $cb ;current key pressed + cmp #$3F ;RUN/STOP? + beq @done + dec ping_counter + bne @ping_loop +@done: + jmp print_cr +@error: + lda #'!' + jmp @print_and_loop + + +myip_keyword: + ldax #cfg_ip + jmp get_ip_parameter + +dns_keyword: + ldax #cfg_dns + jmp get_ip_parameter + +gateway_keyword: + ldax #cfg_gateway + jmp get_ip_parameter + +netmask_keyword: + ldax #cfg_netmask + jmp get_ip_parameter + + + +skip_comma_get_integer: + jsr CHRGOT + jsr CHKCOM ;make sure next char is a comma (and skip it) +get_integer: + jsr CHRGOT + beq @eol + jsr FRMNUM + jsr GETADR + ldax LINNUM +@no_param: + rts +@eol: + jmp $AF08 ;SYNTAX ERROR + + +hook_keyword: + jsr extract_string + ldax #transfer_buffer + jsr skip_comma_get_integer + stax handler_address + jsr find_hook + bcc @existing_entry + + lda hooks + cmp #MAX_HOOKS + bmi @got_space + ldx #$10 ;OUT OF MEMORY + jmp $A437 ;print error +@got_space: + clc + lda #0 + adc hooks + adc hooks + adc hooks + adc hooks + adc hooks + adc hooks + tay + inc hooks + @existing_entry: + ;y now points to free slot in hook table + lda transfer_buffer + sta hook_table,y + lda transfer_buffer+1 + sta hook_table+1,y + lda param_length + sta hook_table+2,y + lda hash + sta hook_table+3,y + lda handler_address + sta hook_table+4,y + lda handler_address+1 + sta hook_table+5,y + rts + +grok_keyword: + jsr extract_string + jsr find_hook + bcc @got_hook + ldx #$11 ;UNDEFED FUNCTION + jmp $A437 ;print error + @got_hook: + lda hook_table+4,y + ldx hook_table+5,y + jmp goto + +goto: + sta $14 + stx $15 + jmp $a8a3 ;GOTO keyword + + +find_hook: + jsr calc_hash + ldy #0 + ldx hooks + beq @done +@compare_one_entry: + lda transfer_buffer + cmp hook_table,y + bne @nope + lda transfer_buffer+1 + cmp hook_table+1,y + bne @nope + lda param_length + cmp hook_table+2,y + bne @nope + lda hash + cmp hook_table+3,y + bne @nope +;found it! + clc + rts +@nope: + dex + beq @done + iny + iny + iny + iny + iny + iny + + bne @compare_one_entry +@done: + sec + rts + +calc_hash: + clc + lda #0 + ldy param_length + beq @done +@loop: + adc transfer_buffer,y + dey + bne @loop +@done: + sta hash + rts + + +yield_keyword: + rts +gosub: + + +.rodata +vectors: + .word crunch + .word list + .word execute + +; Keyword list +; Keywords are stored as normal text, +; followed by the token number. +; All tokens are >$80, +; so they easily mark the end of the keyword +hexdigits: +.byte "0123456789ABCDEF" + +pinging: +.byte"PINGING ",0 +interface_type: +.byte "INTERFACE : ",0 + +mac_address_msg: +.byte "MAC ADDRESS : ", 0 + +ip_address_msg: +.byte "IP ADDRESS : ", 0 + +netmask_msg: +.byte "NETMASK : ", 0 + +gateway_msg: +.byte "GATEWAY : ", 0 + +dns_server_msg: +.byte "DNS SERVER : ", 0 + +dhcp_server_msg: +.byte "DHCP SERVER : ", 0 + + +address_resolution: +.byte "ADDRESS RESOLUTION",0 + +tftp: +.byte "TFTP",0 +dhcp: +.byte "DHCP",0 + +connect: +.byte "CONNECT",0 + +error: +.byte " ERROR $",0 + +disconnected: +.byte 13,"DIS" +connected_msg: +.byte "CONNECTED",13,0 + +keywords: + .byte "IF",$E0 ;our dummy 'IF' entry takes $E0 + .byte "IPCFG",$E1 + .byte "DHCP",$E2 + .byte "PING",$E3 + .byte "MYIP",$E4 + .byte "NETMASK",$E5 + .byte "GATEWAY",$E6 + .byte "DNS",$E7 + .byte "HOOK",$E8 + .byte "YIELD",$E9 + .byte "GROK",$EA + .byte $00 ;end of list +HITOKEN=$EB + +; +; Table of token locations-1 +; +token_routines: +E0: .word if_keyword-1 +E1: .word ipcfg_keyword-1 +E2: .word dhcp_keyword-1 +E3: .word ping_keyword-1 +E4: .word myip_keyword-1 +E5: .word netmask_keyword-1 +E6: .word gateway_keyword-1 +E7: .word dns_keyword-1 +E8: .word hook_keyword-1 +E9: .word yield_keyword-1 +EA: .word grok_keyword-1 + +.segment "SELF_MODIFIED_CODE" + + +jmp_crunch: .byte $4C ;JMP +oldcrunch: .res 2 ;Old CRUNCH vector +oldlist: .res 2 +oldexec: .res 2 + +emit_a: +current_output_ptr=emit_a+1 + sta $ffff + inc string_length + inc current_output_ptr + bne :+ + inc current_output_ptr+1 +: + rts + +MAX_HOOKS=4 + +hook_table: +.res MAX_HOOKS*6 +;format is: +; $00/$01 first 2 chars of hook name +; $02 length of name +; $03 hash of name +; $04/$05 line number that hook handler starts at + +hooks: .byte 0 + +.bss +string_length: .res 1 +param_length: .res 1 +temp_bin: .res 1 +temp_bcd: .res 2 +ping_counter: .res 1 +string_buffer: .res 128 +transfer_buffer: .res 256 +handler_address: .res 2 +hash: .res 1 \ No newline at end of file diff --git a/client/basic/kipperbas.d64 b/client/basic/kipperbas.d64 index a885ab0ade452e8bbcd05d3e5742c0b82d443ba3..3e926a0580f26817accf5efa94eac91e9ea67c80 100644 GIT binary patch delta 7759 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 delta 7719 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