new text compression and tokeniser - faster, about 100 bytes saved

This commit is contained in:
FeralChild64 committed 2020-06-21 18:16:38 +02:00
1 parent 51e45e9983
commit 94170f156c
24 files changed
+1848 -872

No files matched your search

@@ -3,6 +3,7 @@
// #LAYOUT# X16 BASIC_0 #TAKE
// #LAYOUT# * * #IGNORE
packed_keywords:
put_packed_keywords()
packed_as_1n:
put_packed_as_1n()
@@ -3,6 +3,7 @@
// #LAYOUT# X16 BASIC_0 #TAKE
// #LAYOUT# * * #IGNORE
packed_message_chars:
put_packed_chars()
packed_as_3n:
put_packed_as_3n()
@@ -3,6 +3,7 @@
// #LAYOUT# X16 BASIC_0 #TAKE
// #LAYOUT# * * #IGNORE
packed_message_tokens:
put_packed_tokens()
packed_str_errors:
put_freq_packed_errors()
@@ -0,0 +1,9 @@
// #LAYOUT# STD * #TAKE
// #LAYOUT# M65 BASIC_1 #TAKE
// #LAYOUT# X16 BASIC_0 #TAKE
// #LAYOUT# * * #IGNORE
packed_str_keywords_V2:
put_freq_packed_keywords_V2()
@@ -3,6 +3,7 @@
// #LAYOUT# X16 BASIC_0 #TAKE
// #LAYOUT# * * #IGNORE
packed_message_words:
put_packed_words()
packed_str_misc:
put_freq_packed_misc()
+4 -4
View File
@@ -4,8 +4,8 @@
cmd_stop:
ldx #30 // "BREAK"
jsr print_packed_message
ldx #IDX__EV2_1E // "BREAK"
jsr print_packed_error
// Are we in direct mode
lda CURLIN+1
@@ -13,8 +13,8 @@ cmd_stop:
beq !+
// Not direct mode
ldx #31
jsr print_packed_message
ldx #IDX__STR_IN
jsr print_packed_misc_str
lda CURLIN+1
ldx CURLIN+0
+2 -2
View File
@@ -6,8 +6,8 @@
basic_main_loop:
// Tell user we are ready
ldx #30
jsr print_packed_message
ldx #IDX__STR_READY
jsr print_packed_misc_str
// Enable Kernal messages
lda #$80
@@ -11,13 +11,15 @@
// Label definitions
.label VB1__INITMSG = $4000 + 2 * 0
.label VB1__LINKPRG = $4000 + 2 * 1
.label VB1__tokenise_line = $4000 + 2 * 2
.label VB1__list_single_line = $4000 + 2 * 3
.label VB1__print_packed_message = $4000 + 2 * 4
.label VB1__basic_do_clr = $4000 + 2 * 5
.label VB1__basic_do_new = $4000 + 2 * 6
.label VB1__INITMSG = $4000 + 2 * 0
.label VB1__LINKPRG = $4000 + 2 * 1
.label VB1__tokenise_line = $4000 + 2 * 2
.label VB1__list_single_line = $4000 + 2 * 3
.label VB1__print_packed_error = $4000 + 2 * 4
.label VB1__print_packed_misc_str = $4000 + 2 * 5
.label VB1__print_packed_keyword_V2 = $4000 + 2 * 6
.label VB1__basic_do_clr = $4000 + 2 * 7
.label VB1__basic_do_new = $4000 + 2 * 8
#else
@@ -27,7 +29,9 @@
.word LINKPRG
.word tokenise_line
.word list_single_line
.word print_packed_message
.word print_packed_error
.word print_packed_misc_str
.word print_packed_keyword_V2
.word basic_do_clr
.word basic_do_new
+5 -5
View File
@@ -151,10 +151,10 @@ do_basic_error: // error code in .X
// Error message text + " ERROR"
jsr print_packed_message
jsr print_packed_error
ldx #33
jsr print_packed_message
ldx #IDX__STR_ERROR
jsr print_packed_misc_str
// Check if direct mode
@@ -164,8 +164,8 @@ do_basic_error: // error code in .X
// We were in a program, so show IN <line>
ldx #31
jsr print_packed_message
ldx #IDX__STR_IN
jsr print_packed_misc_str
lda CURLIN+1
ldx CURLIN+0
+2 -2
View File
@@ -19,8 +19,8 @@ basic_warm_start_real:
ora CMP0+1
beq !+
ldx #32
jsr print_packed_message
ldx #IDX__STR_BRK_AT
jsr print_packed_misc_str
lda CMP0+1
jsr print_hex_byte
+2 -2
View File
@@ -21,5 +21,5 @@ initmsg_bytes_free:
jsr print_integer
// Print rest of the start up message
ldx #34
jmp print_packed_message
ldx #IDX__STR_BYTES
jmp print_packed_misc_str
-260
View File
@@ -1,260 +0,0 @@
// #LAYOUT# STD * #TAKE
// #LAYOUT# M65 BASIC_1 #TAKE
// #LAYOUT# X16 BASIC_0 #TAKE
// #LAYOUT# * * #IGNORE
// Pack a word using the simple compression scheme.
// This has to be the same implementation as in compress_text.c
// because we compress input lines of BASIC to do the tokenisation.
// This allows us to search through the compressed tokens directly
// using the compressed fragment of BASIC, and thus work out the
// token for a given piece of string.
// This also helps ensure that we have an implementation that is totally
// different to that of the original C64 BASIC, as well as likely
// saving a bit of space in the ROM, especially once we start implementing
// larger keyword vocabs for BASIC 7 or BASIC 10 compatibility, since the
// compression and decompression machinery is of fixed size.
pack_word:
// Inputs:
// X = offset of string to pack. Address is $0200 + X
// __tokenise_work1 = number of bytes to pack
// __tokenise_work2 = packed length of string
// Outputs:
// Output is written to $0100 (bottom of stack).
// Memory modified:
// __tokenise_work4 = temporary storage
// __tokenise_work3 = flag for whether we have a nybl prepared or not.
// (This can also be checked on exit, to know whether the last byte
// is incomplete, which is important for searching for abbreviated
// key words.)
// Make sure we have a sane request
lda __tokenise_work1
bne !+
sec
rts
!:
// Initialise internal variables
lda #$00
sta __tokenise_work2
sta __tokenise_work3
pack_char_loop:
// Get the next char
lda $0200,x
// Work out what type of character we have, and thus
// how we need to pack it.
jsr get_packed_char_index
cmp #$00
bne not_exception_char
// Char is an exception char.
// We need to either finish the previous byte with $xF,
// Or if there is no existing nybl, then we need to output
// either $FE or $FF, depending on whether this is the last
// byte or not
lda __tokenise_work3
bne have_nybl_before_exception_char
// No nybl exists before the exception char, so prefix with $FE or $FF
// based on whether we are at the end of the input string or not
inx
lda __tokenise_work1
cmp #$01
beq at_end
lda #$FF
.byte $2C
at_end:
lda #$FE
dex
jsr write_unpacked_char
// Fall through
output_exception_byte:
lda $0200,x
jsr write_unpacked_char
jmp consider_next_char
have_nybl_before_exception_char:
// Or existing byte with #$0F, and clear nybl flag
ldy __tokenise_work2
lda $0100,y
ora #$0f
sta $0100,y
inc __tokenise_work2
// FALL THROUGH
write_literal_and_terminate_if_required:
// Clear have nybl flag
lda #$00
sta __tokenise_work3
lda $0200,x
jsr write_unpacked_char
jmp end_string_if_required
end_string_if_required:
// Check if X points to last char of string to be packed
inx
lda __tokenise_work1
cmp #$01
beq !+
dex
jmp consider_next_char
!:
dex
// Fall through
end_string:
// Write end of string sequence if required
lda #$00
jsr write_unpacked_char
rts
write_unpacked_char:
ldy __tokenise_work2
sta $0100,y
iny
sty __tokenise_work2
rts
not_exception_char:
// It wasn't an exception char, so write the symbol out
cmp #$10
bcs whole_byte_symbol
// Nybl encoded value: Put in upper half or lower half of a byte?
ldy __tokenise_work3
bne store_low_nybl
// Store in high nybl -- so shift it into the hi nybl
asl
asl
asl
asl
ldy __tokenise_work2
sta $0100,y
bne stored_nybl // Must be taken, as A cannot be $00
store_low_nybl:
// Low nybl gets added to high nybl already stored in the byte
ldy __tokenise_work2
ora $0100,y
sta $0100,y
stored_nybl:
// Now toggle the nybl flag
lda __tokenise_work3
eor #$ff
sta __tokenise_work3
// And advance the offset if we filled this byte up
bne consider_next_char // Taken if byte has a nybl free, i.e., don't advance pointer while half byte remains free
// Count the filled up byte
inc __tokenise_work2
// Add termination byte if required
// (This re-writes the same byte again, which we don't care about)
jmp end_string_if_required
whole_byte_symbol:
// It's a $Fx symbol.
// But if we have a nybl to flush, then we handle it like
// a full exception character
ldy __tokenise_work3
beq nothing_to_flush
// Set low nybl to $F to mark next byte as literal
ldy __tokenise_work2
lda $0100,y
ora #$0f
sta $0100,y
inc __tokenise_work2
jmp write_literal_and_terminate_if_required
nothing_to_flush:
// Write the symbol as it is, or the literal
// if it has been passed to us via fall-through
jsr write_unpacked_char
jmp end_string_if_required
consider_next_char:
// Pack next char
inx
dec __tokenise_work1
beq !+
jmp pack_char_loop
!:
// Add 1 to length if nybl waiting to be flushed
// (but leave flag set so the caller knows if the
// bottom nybl might be different if the string were
// longer).
lda __tokenise_work3
beq !+
inc __tokenise_work2
rts
!:
// Last byte was full, so we need at $00 on the end
// (unless a $FE token was written 2 bytes ago)
ldy __tokenise_work2
iny
lda #$00
sta $0100,y
inc __tokenise_work2
rts
get_packed_char_index:
// Find char in A in the packed char list, and return
// $1 - $E, $F1 - $FD if the charactar is abbreviable,
// or else return $00 if the character has to be stored literally.
// Y register and A are modified, as are processor flags.
ldy #$00
find_char_loop:
cmp packed_message_chars,y
bne not_a_match
enumerate:
// Is it a nybl abbreviable char?
cpy #14
bcs not_nybl_char
// Yes, so add 1 and return it
iny
tya
rts
not_nybl_char:
cpy #27
bcs not_a_match
// It's a $Fx character.
// Since the lowest index is $E, and it should be encoded as $F1
// we need to add $E3
tya
clc
adc #$E3
rts
not_a_match:
iny
// Loop until we find it, or we discover it isn't
// a character with a special encoding
cpy #27
bne find_char_loop
// Not a char that we can pack, so return 0
lda #$00
rts
+4 -10
View File
@@ -86,22 +86,16 @@ list_not_quote:
pha
phy_trash_a
// Get pointer to compressed keyword list
lda #<packed_keywords
sta FRESPC+0
lda #>packed_keywords
sta FRESPC+1
// Subtract $80 from token to get offset in word
// list
// Subtract $80 from token to get offset in token list
txa
and #$7f
tax
// Now ask for it to be printed
ldy #$ff
jsr packed_word_search
jsr print_packed_keyword_V2
// Restore registers
ply_trash_a
pla
-244
View File
@@ -1,244 +0,0 @@
// #LAYOUT# STD * #TAKE
// #LAYOUT# M65 * #TAKE
// #LAYOUT# X16 BASIC_0 #TAKE
// #LAYOUT# * * #IGNORE
// This routine prints messages that have been packed
// using the make_error_tables program.
// The general idea is to save space in the ROM.
// The output of make_error_tables is written to packed_messages.s, and
// contains the following:
//
// packed_message_words - List of nybl packed words.
// packed_message_tokens - List of tokens that get joined to make messages
// packed_message_chars - List of characters in messages sorted by frequency.
print_packed_message:
// .X = message number
#if (ROM_LAYOUT_M65 && SEGMENT_BASIC_0)
jsr map_BASIC_1
jsr_ind VB1__print_packed_message
jmp map_NORMAL
#else
lda #$ff
ldy #$ff
packed_message_search:
iny
cpx #$00
beq found_message_in_token_stream
lda packed_message_tokens,y
cmp #$ff
bne !+
dex
!:
cpy #$ff
bne packed_message_search
// Could not find message
sec
rts
found_message_in_token_stream: // XXX size-optimize this
// Now print each word in the message
phy_trash_a
lda packed_message_tokens,y
cmp #$FF
beq found_message_in_token_stream_done
jsr print_packed_word
ply_trash_a
iny
beq found_message_in_token_stream_done
!:
phy_trash_a
lda packed_message_tokens,y
cmp #$FF
beq found_message_in_token_stream_done
pha
jsr print_space
pla
jsr print_packed_word
ply_trash_a
iny
bne !-
found_message_in_token_stream_done:
pla
rts
print_packed_word:
// A = the word to print
tax
// Setup pointer to packed words
lda #<packed_message_words
sta FRESPC+0
lda #>packed_message_words
sta FRESPC+1
// Find the word in the list
ldy #$ff
packed_word_search:
iny
cpx #$00
beq found_packed_word
// $x0 is an end of work marker
lda (FRESPC),y
and #$0f
cmp #$00
bne !+
dex
!:
// $FE $xx is an end of work marker
lda (FRESPC),y
cmp #$fe
bne !+
dex
iny // skip next byte
!:
cmp #$ff
bne !+
iny // skip next byte
!:
// Advance to next page if required
cpy #$ff
bne packed_word_search
inc FRESPC+1
jmp packed_word_search
found_packed_word:
// Get pointer to start of packed word
// Make sure we do not wrap on a page boundary
tya
clc
adc FRESPC+0
sta FRESPC+0
lda FRESPC+1
adc #$00
sta FRESPC+1
print_packed_string:
ldy #$ff
next_packed_word_char:
// .Y = offset into packed word data - 1
iny
// Check for end of word
lda (FRESPC),y
cmp #$00
beq end_of_packed_word
cmp #$fe
beq last_literal
cmp #$ff
beq next_is_literal_char
and #$f0
beq end_of_packed_word
// Check if it is an uncommon char
lda (FRESPC),y
and #$f0
cmp #$f0
beq is_uncommon_char
// Is not an uncommon char, so get char of upper nybl
lda (FRESPC),y
// Put high nybl into low bits of X
lsr
lsr
lsr
lsr
tax
// X=1-14 = first 14 chars, so subtract one
lda packed_message_chars-1,x
jsr JCHROUT // preserves .Y
// See if there is a char in the low nybl to print
lda (FRESPC),y
and #$0f
beq end_of_packed_word
cmp #$0f
beq no_lo_nybl_char
has_lo_nybl_char:
// We need to print the low nybl char as well
tax
phy_trash_a
// Skip lower nybls of $F or $0 in nybl packed
// bytes, as these do not encode characters
cpx #$f
beq !+
has_nybl:
cpx #0
beq !+
// X=1-14 = first 14 chars, so subtract one
lda packed_message_chars-1,x
jsr JCHROUT
!:
ply_trash_a
// FALLTHROUGH
no_lo_nybl_char:
// If current byte has $0F in low nybl, then the next
// byte is a whole byte.
lda (FRESPC),y
and #$0f
cmp #$0f
beq next_is_literal_char
ready_for_next_char:
cpy #$ff
bne next_packed_word_char
end_of_packed_word:
// Hit end of packed data
rts
last_literal:
jsr unpack_literal_char
rts
next_is_literal_char:
jsr unpack_literal_char
jmp ready_for_next_char
unpack_literal_char:
// Output the literal char, and ready for next char
iny
phy_trash_a
lda (FRESPC),y
jsr JCHROUT
pla
tay
rts
is_uncommon_char:
// Lower nybl has offset into low-frequency part of the table
lda (FRESPC),y
and #$0f
clc
adc #14
// We need to print the low nybl char as well
tax
phy_trash_a
jmp has_nybl
#endif // ROM layout
+182
View File
@@ -0,0 +1,182 @@
// #LAYOUT# STD * #TAKE
// #LAYOUT# M65 * #TAKE
// #LAYOUT# X16 BASIC_0 #TAKE
// #LAYOUT# * * #IGNORE
// This routine prints strings that have been packed using the 'generate_trings' tool.
// The general idea is to save space in the ROM.
#if (ROM_LAYOUT_M65 && SEGMENT_BASIC_0)
print_packed_error:
jsr map_BASIC_1
jsr_ind VB1__print_packed_error
jmp map_NORMAL
print_packed_misc_str:
jsr map_BASIC_1
jsr_ind VB1__print_packed_misc_str
jmp map_NORMAL
print_packed_keyword_V2:
jsr map_BASIC_1
jsr_ind VB1__print_packed_keyword_V2
jmp map_NORMAL
#else
print_packed_error: // .X - error string index
lda #<packed_str_errors
ldy #>packed_str_errors
bne print_freq_packed_string // branch always
print_packed_misc_str: // .X - misc string index
lda #<packed_str_misc
ldy #>packed_str_misc
bne print_freq_packed_string // branch always
print_packed_keyword_V2: // .X - token number
lda #<packed_str_keywords_V2
ldy #>packed_str_keywords_V2
// FALLTROUGH
print_freq_packed_string: // not to be used directly XXX rename to print_packed_string
// First store the initial address of the packed strings list
sta FRESPC+0
sty FRESPC+1
// Now find the address of the string to print out
// The implementation assumes no packed string is longer than 255 bytes, including trailing zero
!:
cpx #$00
beq print_freq_packed_string_found // branch if no need to advance anymore
dex
ldy #$00
!:
lda (FRESPC), y
iny
cmp #$00
bne !- // branch if not the end of packed string
clc // advance FREESPC pointer
tya
adc FRESPC+0
sta FRESPC+0
lda #$00
adc FRESPC+1
sta FRESPC+1
jmp !--
print_freq_packed_string_found:
// At this point FRESPC contains a pointer to the string to display
// and we should start from the lower nibble
ldy #$00
// FALLTROUGH
print_freq_packed_string_nibble_lo:
// Get the low nibble and check for the end of packed string
lda (FRESPC), y
and #$0F
beq print_freq_packed_string_end
// Not the end - check if this is a common character, encoded by a single nibble
cmp #$0F
beq print_freq_packed_string_3n_split
// Character is encoded by a single nibble - fetch it and display
tax
lda packed_as_1n-1, x
jsr JCHROUT // preserves .Y
// FALLTROUGH
print_freq_packed_string_nibble_hi:
// Get the high nibble and check for the end of packed string
lda (FRESPC), y
and #$F0
beq print_freq_packed_string_end
// Not the end - check if this is a common character, encoded by a single nibble
cmp #$F0
beq print_freq_packed_string_3n_single
// Character is encoded by a single nibble - fetch it and display
lsr
lsr
lsr
lsr
tax
lda packed_as_1n-1, x
jmp !+ // XXX should be bra on some CPUs
print_freq_packed_string_3n_single:
// Character is encoded using 3 nibbles (1st is a mark, already interpreted)
iny
lda (FRESPC), y
tax
lda packed_as_3n-1, x
!:
jsr JCHROUT // preserves .Y
// Advance to next nibble
iny
bne print_freq_packed_string_nibble_lo // branch always
print_freq_packed_string_3n_split:
// Character is encoded using 3 nibbles (1st is a mark, already interpreted),
// but the 2 remaining nibbles are in two neighbour bytes; use a stack to combine them
lda (FRESPC), y
and #$F0
tsx
pha
iny
lda (FRESPC), y
and #$0F
clc
adc STACK, x
txs // one cycle faster than PLA
tax
lda packed_as_3n-1, x
jsr JCHROUT // preserves .Y
jmp print_freq_packed_string_nibble_hi
print_freq_packed_string_end:
rts
#endif // ROM layout
-306
View File
@@ -1,306 +0,0 @@
// #LAYOUT# STD * #TAKE
// #LAYOUT# M65 * #TAKE
// #LAYOUT# X16 BASIC_0 #TAKE
// #LAYOUT# * * #IGNORE
// Tokenise a line of BASIC
// Stored at $0200
// Length in __tokenise_work1
tokenise_line:
#if (ROM_LAYOUT_M65 && SEGMENT_BASIC_0)
jsr map_BASIC_1
jsr_ind VB1__tokenise_line
jmp map_NORMAL
#else
// We need to consider each offset in the string to see if it matches
// a token. No BASIC keyword is > 7 bytes, so we need to start with that
// length (or remaining length of string) and count our way back down.
// This means we need separate start and end points to keep track of.
// $0110 = offset into string
// $0111 = number of bytes to pack
// $0112 = offset into tokenised output
// $0113 = length of raw string
lda #$00
sta $0110
sta $0112
sta QTSW
lda __tokenise_work1
sta $0113
tokenise_char:
// Check if we have reached end of the line?
lda $0110
cmp $0113
bne !+
// End of line reached
// Finished tokenising line
// Update __tokenise_work1 and terminate with null
ldx $0112
lda #$00
sta $0200,x
stx __tokenise_work1
// Clear quote mode flag that is also used by KERNAL for screen display
lda #$00
sta QTSW
rts
!:
// More to do
// Maximum keyword length
lda #7
sta $0111
tokenise_char_loop:
// Pack the word from the start (start = X, raw len = __tokenise_work1)
// Returns packed length in __tokenise_work2 and writes the packed
// word at $0100+
ldx $0110
lda $0200,x
cmp #$22
beq tk_quote
cmp #$DE
beq tk_pi
cmp #$3F
bne tk_not_special
// FALLTROUGH
tk_question_mark:
lda #$99
skip_2_bytes_trash_nvz
// FALLTROUGH
tk_pi:
lda #$FF
ldx $0110
sta $0200,x
jmp tk_literal_char
tk_quote:
// Quote
lda QTSW
eor #$ff
sta QTSW
jmp tk_literal_char
tk_not_special:
// Check for literal characters in various ranges
// (This just speeds the tokeniser up, by avoiding the packed token search
// for such characters).
// SPACE and punctuation before +,- and *
cmp #$2a
bcc tk_literal_char
// Don't try to tokenise digits
cmp #$30
bcc tk_might_be_keyword
cmp #$3a
bcs tk_might_be_keyword
jmp tk_literal_char
tk_might_be_keyword:
// Don't tokenise in quote mode
lda QTSW
bne tk_literal_char
// Pack string
ldx $0110
lda $0111
sta __tokenise_work1
// XXX - We should implement an optimisation where we
// trim the last symbol of successively, instead of re-packing the word
// again with one less char each time.
// XXX - We should limit the word length to the length of interesting chars
// that are available.
jsr pack_word
lda __tokenise_work2
cmp #$10
bcc !+
// Packed string too long
jmp do_STRING_TOO_LONG_error
!:
// Search for it in keywords
jsr keyword_search
// Did we find a keyword?
bcc found_token_in_line
// Nope, so try shorter
dec $0111
bne tokenise_char_loop
tk_literal_char:
// Is not a token, so copy to output verbatim
ldx $0110
ldy $0112
lda $0200,x
sta $0200,y
inc $0112
inc $0110
// Tokenise next char
jmp tokenise_char
found_token_in_line:
// Got a token, so write it out
ldx $0112
sta $0200,x
inx
stx $0112
// If it is REM, then lock us in quote mode to the end of the line
cmp #$8f
bne not_rem
sta QTSW
not_rem:
// Now skip over the length of the token
lda $0110
clc
adc $0111
sta $0110
// See if there is more of the line left to process
jmp tokenise_char
// Search for word in keyword list
// The packed form is canonical, so we can search for the keyword while
// it remains compressed. This also saves a little time.
// Algorithm is simply to find matching first bytes, and then look for
// the rest being a match.
keyword_search:
// Search for the compressed keyword stored at $0100 in the list
// of compressed keywords
// __tokenise_work1 = offset in line of input
// __tokenise_work2 = length of token we are matching
// __tokenise_work3 = offset in token list
// __tokenise_work5 = temporary work space
// X and Y registers used as temporary space.
// returns C=1 if no matching token, else C=0 and A=token
lda #$00
sta __tokenise_work1
sta __tokenise_work3
next_kw_offset:
// Advance offset in compressed keyword list, to see if a match
// BEGINS here
// Get count of bytes to compare
lda __tokenise_work2
sta __tokenise_work5
// Load current position
ldy __tokenise_work3
ldx __tokenise_work1
// Advance offset in compressed token list, and stop at the end
inc __tokenise_work3
lda __tokenise_work3
cmp #packed_keyword_table_len
bne next_in_match
jmp done_searching_for_token
next_in_match:
lda $0100,x
cmp packed_keywords,y
bne next_kw_offset
// Advance to next chars in source and target
inx
iny
dec __tokenise_work5 // Have we compared all bytes yet?
bne next_in_match
// Keyword matches!
// Now make sure we started on a keyword boundary
// (we know we finished on one, because of the structure of the compressed
// keyword data).
// Offset 0
// previous byte is $x0
// two byte prior is $FE
// Get offset of first byte of this token
ldx __tokenise_work3
dex // pointer points one late, so rewind it
// At start of list?
cpx #$00
beq word_boundary
dex
// Previous byte is $x0 end token?
lda packed_keywords,x
and #$0f
beq word_boundary
dex
// Uber-previouss byte is $FE final literal token?
lda packed_keywords,x
cmp #$fe
beq word_boundary
// Not a word boundary, so ignore
jmp next_kw_offset
word_boundary:
// Found a token whose offset is in __tokenise_work3
// Now trace back to the start of the compressed keyword list
// to work out what keyword number we are, so that we can return
// the token number.
ldy #$80
token_count_loop:
lda packed_keywords,x
cmp #$fe
bne !+
iny
!:
and #$0f
cmp #$00
bne !+
iny
!:
cpx #$00
beq !+
dex
jmp token_count_loop
!:
tya
clc
rts
done_searching_for_token:
// No token found
sec
lda #$00
rts
#endif // ROM layout
+228
View File
@@ -0,0 +1,228 @@
// #LAYOUT# STD * #TAKE
// #LAYOUT# M65 BASIC_1 #TAKE
// #LAYOUT# X16 BASIC_0 #TAKE
// #LAYOUT# * * #IGNORE
//
// Packs a keyword candidate using a simple compression scheme,
// same as in 'generate_strings.cc'.
//
// This allows us to search through the compressed tokens directly
// using the compressed fragment of BASIC, and thus work out the
// token for a given piece of string.
//
// This also helps ensure that we have an implementation that is totally
// different to that of the original C64 BASIC, as well as likely
// saving a bit of space in the ROM, especially once we start implementing
// larger keyword vocabs for extended BASIC, since the compression and
// decompression machinery is of fixed size.
//
// Input:
// - .X = offset of string to pack, address is BUF ($0200) + .X
// Output:
// - see labels in 'tk_pack'
//
tk_pack:
// Reuse the CPU stack - addresses below $102 are used by 'tokenise_line.s'
.label tk__len_unpacked = $103 // length of unpacked data; it could replace 'tk__nibble_flag', but at the cost of code size/performance
.label tk__shorten_bits = $104 // for quick shortening of packed candidate
.label tk__nibble_flag = $105 // $00 = start from new byte, $FF = start from high nibble
.label tk__byte_offset = $106 // offset of the current byte (to place new data) in tk__packed
.label tk__packed = $107 // packed candidate, 13 bytes is enough for worst case - 8 byte keyword
// Initialize variables
lda #$00
ldy #(tk__packed - tk__len_unpacked + 13) // XXX maybe do not clear that much, just put 0 at the end while compressing
!:
sta tk__len_unpacked, y
dey
bpl !-
// FALLTROUGH
tk_pack_loop:
// Get the next character
lda BUF, x
// If character cannot be a part of a keyword - do not pack anything more
cmp #$23 // '#' - first sane character for a keyword
bcc tk_pack_done // branch if below '#'
cmp #$7B // 'Z' + 1
bcs tk_pack_done // branch if above 'Z'
// Keywords should not contain digits, colons, or semicolons
cmp #$30
bcc !+ // branch if below '0'
cmp #$3C
bcc tk_pack_done // branch if below '<'
!:
// Try to find a nibble to encode the character
ldy #$0E
!:
cmp packed_as_1n-1, y
beq tk_pack_1n
dey
bne !-
// Try to find a 3-nibble sequence to encode the character (1st nibble is always 0xF in this case)
ldy #TK__PACKED_AS_3N
!:
cmp packed_as_3n-1, y
beq tk_pack_3n
dey
bne !-
// If it is not on the list, it cannot be a part of token - quit
// FALLTROUGH
tk_pack_done:
rts
tk_pack_1n:
// .Y contains our nibble; check whether we should start a new encoded byte
bit tk__nibble_flag
bpl tk_pack_1n_new_byte
// Store in the high nibble of the current byte
tya
asl
asl
asl
asl
ldy tk__byte_offset
ora tk__packed, y
sta tk__packed, y
// Adjust remaining counters / flags
inc tk__byte_offset
inc tk__nibble_flag // $FF -> $00
// FALLTROUGH
tk_pack_1n_done:
// Put the bit mark - to indicate 1-nibble encoding
clc
ror tk__shorten_bits
// FALLTROUGH
tk_pack_loop_next:
// Prepare for the next loop iteration - increment counters
inx
inc tk__len_unpacked
// Check length, if it is OK to pack one more byte; quit if not
lda tk__len_unpacked
cmp #TK__MAX_KEYWORD_LEN
bne tk_pack_loop
rts
tk_pack_1n_new_byte:
// Store in the low nibble of the next byte
tya
ldy tk__byte_offset
sta tk__packed, y
// Adjust remaining counters / flags
dec tk__nibble_flag // $00 -> $FF
bmi tk_pack_1n_done
tk_pack_3n:
// .Y contains 2 nibbles to encode; check whether we should start a new encoded byte
bit tk__nibble_flag
bpl tk_pack_3n_new_byte
// Store 1st nibble (alwys 0xF) in the high nibble of the current byte
phy_trash_a
lda #$F0
ldy tk__byte_offset
ora tk__packed, y
sta tk__packed, y
// Store byte in the next byte
iny
pla
sta tk__packed, y
// Adjust remaining counters / flags
iny
sty tk__byte_offset
inc tk__nibble_flag // $FF -> $00
// FALLTROUGH
tk_pack_3n_done:
// Put the bit mark - to indicate 3-nibble encoding
sec
ror tk__shorten_bits
bcc tk_pack_loop_next // branch always
tk_pack_3n_new_byte:
// First store everything in the current byte, we will set '0xF' mark later
tya
ldy tk__byte_offset
sta tk__packed, y
// Now store just the low nibble in the next byte
iny
and #$0F
sta tk__packed, y
// Go back for a moment and set the '0xF' mark in the low nibble
dey
lda tk__packed, y
ora #$0F
sta tk__packed, y
iny
// Adjust remaining counters / flags
iny
sty tk__byte_offset
dec tk__nibble_flag // $00 -> $FF
bmi tk_pack_3n_done // branch always
+93
View File
@@ -0,0 +1,93 @@
// #LAYOUT# STD * #TAKE
// #LAYOUT# M65 BASIC_1 #TAKE
// #LAYOUT# X16 BASIC_0 #TAKE
// #LAYOUT# * * #IGNORE
//
// Search for a token on the list pointed by FRESPC
//
// Input:
// - list address in FRESPC, has to be refreshed after routine call
// - uses same variables as 'tk_pack'
// Output:
// - Carry set = not found
// - .X = index of token found
//
tk_search:
ldx #$00 // initial token index
// FALLTROUGH
tk_search_loop:
// Check if end of the list
ldy #$00
lda (FRESPC), y
cmp #$FF
bne tk_search_compare
iny
lda (FRESPC), y
dey
cmp #$FF
bne tk_search_compare
// End of the list
sec
rts
tk_search_compare:
// Compare the packed candidate with the current keyword list entry
lda (FRESPC), y
cmp tk__packed, y
bne tk_search_not_matching
cmp #$00
beq tk_search_found
// For now everything matches, try the next byte
iny
bne tk_search_compare // branch always
tk_search_not_matching:
inx // increment token index
// We need to find the end of the keyword on the list
// and proceed one byte further
cmp #$00
beq tk_search_next
!:
iny
lda (FRESPC), y
bne !-
// FALLTROUGH
tk_search_next:
// Update FRESPC - advance by .Y
iny
tya
clc
adc FRESPC+0
sta FRESPC+0
lda FRESPC+1
adc #$00
sta FRESPC+1
bcc tk_search_loop // branch always
tk_search_found:
clc
rts
+112
View File
@@ -0,0 +1,112 @@
// #LAYOUT# STD * #TAKE
// #LAYOUT# M65 BASIC_1 #TAKE
// #LAYOUT# X16 BASIC_0 #TAKE
// #LAYOUT# * * #IGNORE
//
// Shortens a keyword candidate previously packed by tk_pack by 1 character
//
// Input:
// - uses same variables as 'tk_pack'
//
tk_shorten:
// First make sure we actually can shorten anything
dec tk__len_unpacked
beq tk_shorten_end
// Now check if the last character was encoded as 1 nibble or 3 nibbles
asl tk__nibble_flag
bcs tk_shorten_3n
// FALLTROUGH
tk_shorten_1n:
ldy tk__byte_offset // will be needed in both cases
// Now we need to check which nibble to cut away
bit tk__nibble_flag
bcs tk_shorten_1n_lo
// FALLTROUGH
tk_shorten_1n_hi: // tk__nibble_flag = $00, both nibbles free
// Go back one byte and clear the high nibble only
dey
sty tk__byte_offset
lda tk__packed, y
and #$F0
sta tk__packed, y
// Adjust remaining counters / flags, and quit
dec tk__nibble_flag // $00 -> $FF
// FALLTROUGH
tk_shorten_end:
rts
tk_shorten_1n_lo: // tk__nibble_flag = $FF, only the high nibble is free
// Clear the whole byte, it will be faster
lda #$00
sta tk__packed, y
// Adjust remaining counters / flags, and quit
inc tk__nibble_flag // $FF -> $00
rts
tk_shorten_3n:
ldy tk__byte_offset // will be needed in both cases
lda #$00
// Now we need to check which nibble to cut away
bit tk__nibble_flag
bcs tk_shorten_3n_2bytes
// FALLTROUGH
tk_shorten_3n_byte_nibble: // tk__nibble_flag = $00, both nibbles free
// Go back one byte, clear it, go back and clear the high nibble only
dey
sta tk__packed, y
// Reuse 'tk_shorten_1n_hi'
sty tk__byte_offset
#if HAS_OPCODES_65C02
bra tk_shorten_1n_hi
#else
jmp tk_shorten_1n_hi
#endif
tk_shorten_3n_2bytes: // tk__nibble_flag = $FF, only the high nibble is free
// Clear the byte, go back, clear one more byte
sta tk__packed, y
dey
sta tk__packed, y
// Adjust remaining counters / flags, and quit
sty tk__byte_offset
inc tk__nibble_flag // $FF -> $00
rts
+176
View File
@@ -0,0 +1,176 @@
// #LAYOUT# STD * #TAKE
// #LAYOUT# M65 * #TAKE
// #LAYOUT# X16 BASIC_0 #TAKE
// #LAYOUT# * * #IGNORE
//
// Tokenise a line of BASIC stored at $0200
//
// Input:
// - length in __tokenise_work1
//
tokenise_line:
#if (ROM_LAYOUT_M65 && SEGMENT_BASIC_0)
jsr map_BASIC_1
jsr_ind VB1__tokenise_line
jmp map_NORMAL
#else
// Reuse the CPU stack - addresses above $101 are used by 'tk_pack.s'
.label tk__offset = $100 // offset into string
.label tk__length = $101 // length of the raw string XXX reuse __tokenise_work1?
// Initialize variables
lda #$00
sta tk__offset
ldx __tokenise_work1
stx tk__length
// Terminate input string with $00
// XXX do we need this?
sta BUF, x
// FALLTROUGH
tokenise_line_loop:
// Check if we have reached end of the line
lda tk__offset
cmp tk__length
beq tokenise_line_done
// Check for some special characters
ldx tk__offset
lda BUF, x
cmp #$22
beq tokenise_line_quote
cmp #$DE
beq tokenise_line_pi
cmp #$3F
beq tokenise_line_question_mark // shortcut for PRINT command
// Try to tokenise
jsr tk_pack
!:
lda tk__len_unpacked
beq tokenise_line_char // branch if attempt to tokenise failed
lda #<packed_str_keywords_V2
sta FRESPC+0
lda #>packed_str_keywords_V2
sta FRESPC+1
jsr tk_search
bcc tokenise_line_keyword_V2 // branch if keyword identified
// XXX add support for additional keyword lists here
// Shorten packed keyword candidate and try again
jsr tk_shorten
#if HAS_OPCODES_65C02
bra !-
#else
jmp !-
#endif
tokenise_line_keyword_V2:
// .X contains a token ID, starting from 0
txa
pha // store the token on the stack, we will need it for REM support
// Store the token
ldx tk__offset
clc
adc #$80
sta BUF, x
// Cut away unnecessary bytes
inc tk__offset
ldx tk__offset
dec tk__len_unpacked // keyword length, afterwards number of bytes to cut away
lda tk__len_unpacked
clc
adc tk__offset
tay
!:
lda BUF, y
sta BUF, x
beq !+
inx
iny
bne !-
!:
// Special handling for REM command - after this one nothing more should be tokenised
pla
cmp #$0F // REM token index
bne tokenise_line_loop
// FALLTROUGH
tokenise_line_done:
// Update line length and quit
lda tk__length
sta __tokenise_work1
rts
tokenise_line_quote:
// For the quote, we advance without tokenizing, until the next quote is found
inc tk__offset
ldx tk__offset
lda BUF, x
beq tokenise_line_done
cmp #$22
beq tokenise_line_char
bne tokenise_line_quote // branch always
tokenise_line_pi:
lda #$FF // token for PI
skip_2_bytes_trash_nvz
// FALLTROUGH
tokenise_line_question_mark:
lda #$99 // token for PRINT
sta BUF, x
// FALLTROUGH
tokenise_line_char:
inc tk__offset
jmp tokenise_line_loop
#endif // ROM layout