607 lines
13 KiB
C
607 lines
13 KiB
C
|
|
/* $Id: input.c,v 1.3 2000/07/05 15:20:34 jholder Exp $
|
|
* --------------------------------------------------------------------
|
|
* see doc/License.txt for License Information
|
|
* --------------------------------------------------------------------
|
|
*
|
|
* File name: $Id: input.c,v 1.3 2000/07/05 15:20:34 jholder Exp $
|
|
*
|
|
* Description:
|
|
*
|
|
* Modification history:
|
|
* $Log: input.c,v $
|
|
* Revision 1.3 2000/07/05 15:20:34 jholder
|
|
* Updated code to remove warnings.
|
|
*
|
|
* Revision 1.2 2000/05/25 22:28:56 jholder
|
|
* changes routine names to reflect zmachine opcode names per spec 1.0
|
|
*
|
|
* Revision 1.1.1.1 2000/05/10 14:21:34 jholder
|
|
*
|
|
* imported
|
|
*
|
|
*
|
|
* --------------------------------------------------------------------
|
|
*/
|
|
|
|
/*
|
|
* input.c
|
|
*
|
|
* Input routines
|
|
*
|
|
*/
|
|
|
|
#include "ztypes.h"
|
|
|
|
/* Statically defined word separator list */
|
|
|
|
static const char *separators = " \t\n\f.,?";
|
|
static zword_t dictionary_offset = 0;
|
|
static ZINT16 dictionary_size = 0;
|
|
static unsigned int entry_size = 0;
|
|
|
|
static void tokenise_line( zword_t, zword_t, zword_t, zword_t );
|
|
static const char *next_token( const char *, const char *, const char **, int *, const char * );
|
|
static zword_t find_word( int, const char *, long );
|
|
|
|
/*
|
|
* z_read_char
|
|
*
|
|
* Read one character with optional timeout
|
|
*
|
|
* argv[0] = input device (must be 1)
|
|
* argv[1] = timeout value in tenths of a second (optional)
|
|
* argv[2] = timeout action routine (optional)
|
|
*
|
|
*/
|
|
|
|
void z_read_char( int argc, zword_t * argv )
|
|
{
|
|
int c;
|
|
zword_t arg_list[2];
|
|
|
|
/* Supply default parameters */
|
|
|
|
if ( argc < 3 )
|
|
argv[2] = 0;
|
|
if ( argc < 2 )
|
|
argv[1] = 0;
|
|
|
|
/* Flush any buffered output before read */
|
|
|
|
flush_buffer( FALSE );
|
|
|
|
/* Reset line count */
|
|
|
|
lines_written = 0;
|
|
|
|
/* If more than one characters was asked for then fail the call */
|
|
|
|
if ( argv[0] != 1 )
|
|
|
|
c = 0;
|
|
|
|
else
|
|
{
|
|
|
|
if ( ( c = playback_key( ) ) == -1 )
|
|
{
|
|
|
|
/* Setup the timeout routine argument list */
|
|
|
|
arg_list[0] = argv[2];
|
|
arg_list[1] = 0; /* as per spec 1.0 */
|
|
/* was: arg_list[1] = argv[1]/10; */
|
|
|
|
/* Read a character with a timeout. If the input timed out then
|
|
* call the timeout action routine. If the return status from the
|
|
* timeout routine was 0 then try to read a character again */
|
|
|
|
do
|
|
{
|
|
flush_buffer( FALSE );
|
|
c = input_character( ( int ) argv[1] );
|
|
}
|
|
while ( c == -1 && z_call( 1, arg_list, ASYNC ) == 0 );
|
|
|
|
/* Fail call if input timed out */
|
|
|
|
if ( c == -1 )
|
|
c = 0;
|
|
else
|
|
record_key( c );
|
|
}
|
|
}
|
|
|
|
store_operand( (zword_t)c );
|
|
|
|
} /* z_read_char */
|
|
|
|
/*
|
|
* z_sread_aread
|
|
*
|
|
* Read a line of input with optional timeout.
|
|
*
|
|
* argv[0] = character buffer address
|
|
* argv[1] = token buffer address
|
|
* argv[2] = timeout value in seconds (optional)
|
|
* argv[3] = timeout action routine (optional)
|
|
*
|
|
*/
|
|
|
|
void z_sread_aread( int argc, zword_t * argv )
|
|
{
|
|
int i, in_size, out_size, terminator;
|
|
char *cbuf, *buffer;
|
|
|
|
/* Supply default parameters */
|
|
|
|
if ( argc < 4 )
|
|
argv[3] = 0;
|
|
if ( argc < 3 )
|
|
argv[2] = 0;
|
|
if ( argc < 2 )
|
|
argv[1] = 0;
|
|
|
|
/* Refresh status line */
|
|
|
|
if ( h_type < V4 )
|
|
z_show_status( );
|
|
|
|
/* Flush any buffered output before read */
|
|
|
|
flush_buffer( TRUE );
|
|
|
|
/* Reset line count */
|
|
|
|
lines_written = 0;
|
|
|
|
/* Initialise character pointer and initial read size */
|
|
|
|
cbuf = ( char * ) &datap[argv[0]];
|
|
in_size = ( h_type > V4 ) ? cbuf[1] : 0;
|
|
|
|
/* Read the line then script and record it */
|
|
|
|
terminator = get_line( cbuf, argv[2], argv[3] );
|
|
script_line( ( h_type > V4 ) ? &cbuf[2] : &cbuf[1] );
|
|
record_line( ( h_type > V4 ) ? &cbuf[2] : &cbuf[1] );
|
|
|
|
/* Convert new text in line to lowercase */
|
|
|
|
if ( h_type > V4 )
|
|
{
|
|
buffer = &cbuf[2];
|
|
out_size = cbuf[1];
|
|
}
|
|
else
|
|
{
|
|
buffer = &cbuf[1];
|
|
out_size = (int)strlen( buffer );
|
|
}
|
|
|
|
if ( out_size > in_size )
|
|
for ( i = in_size; i < out_size; i++ )
|
|
buffer[i] = ( char ) tolower( buffer[i] );
|
|
|
|
/* Tokenise the line, if a token buffer is present */
|
|
|
|
if ( argv[1] )
|
|
tokenise_line( argv[0], argv[1], h_words_offset, 0 );
|
|
|
|
/* Return the line terminator */
|
|
|
|
if ( h_type > V4 )
|
|
store_operand( ( zword_t ) terminator );
|
|
|
|
} /* z_sread_aread */
|
|
|
|
/*
|
|
* get_line
|
|
*
|
|
* Read a line of input and lower case it.
|
|
*
|
|
*/
|
|
|
|
int get_line( char *cbuf, zword_t timeout, zword_t action_routine )
|
|
{
|
|
char *buffer;
|
|
int buflen, read_size, status, c;
|
|
zword_t arg_list[2];
|
|
|
|
/* Set maximum buffer size to width of screen minus any
|
|
* right margin and 1 character for a terminating NULL */
|
|
|
|
buflen = ( screen_cols > 127 ) ? 127 : screen_cols;
|
|
buflen -= right_margin + 1;
|
|
if ( ( int ) cbuf[0] <= buflen )
|
|
buflen = cbuf[0];
|
|
|
|
/* Set read size and start of read buffer. The buffer may already be
|
|
* primed with some text in V5 games. The Z-code will have already
|
|
* displayed the text so we don't have to do that */
|
|
|
|
if ( h_type > V4 )
|
|
{
|
|
read_size = cbuf[1];
|
|
buffer = &cbuf[2];
|
|
}
|
|
else
|
|
{
|
|
read_size = 0;
|
|
buffer = &cbuf[1];
|
|
}
|
|
|
|
/* Try to read input from command file */
|
|
|
|
c = playback_line( buflen, buffer, &read_size );
|
|
|
|
if ( c == -1 )
|
|
{
|
|
|
|
/* Setup the timeout routine argument list */
|
|
|
|
arg_list[0] = action_routine;
|
|
arg_list[1] = 0; /* as per spec.1.0 */
|
|
/* arg_list[1] = timeout/10; */
|
|
|
|
/* Read a line with a timeout. If the input timed out then
|
|
* call the timeout action routine. If the return status from the
|
|
* timeout routine was 0 then try to read the line again */
|
|
|
|
do
|
|
{
|
|
c = input_line( buflen, buffer, timeout, &read_size );
|
|
status = 0;
|
|
}
|
|
while ( c == -1 && ( status = z_call( 1, arg_list, ASYNC ) ) == 0 );
|
|
|
|
/* Throw away any input if timeout returns success */
|
|
|
|
if ( status )
|
|
read_size = 0;
|
|
|
|
|
|
}
|
|
|
|
/* Zero terminate line */
|
|
|
|
if ( h_type > V4 )
|
|
{
|
|
cbuf[1] = ( char ) read_size;
|
|
}
|
|
else
|
|
{
|
|
/* Zero terminate line (V1-4 only) */
|
|
buffer[read_size] = '\0';
|
|
}
|
|
|
|
return ( c );
|
|
|
|
} /* get_line */
|
|
|
|
/*
|
|
* tokenise_line
|
|
*
|
|
* Convert a typed input line into tokens. The token buffer needs some
|
|
* additional explanation. The first byte is the maximum number of tokens
|
|
* allowed. The second byte is set to the actual number of token read. Each
|
|
* token is composed of 3 fields. The first (word) field contains the word
|
|
* offset in the dictionary, the second (byte) field contains the token length,
|
|
* and the third (byte) field contains the start offset of the token in the
|
|
* character buffer.
|
|
*
|
|
*/
|
|
|
|
static void tokenise_line( zword_t char_buf, zword_t token_buf, zword_t dictionary, zword_t flag )
|
|
{
|
|
int i, count, words, token_length;
|
|
long word_index, chop = 0;
|
|
int slen;
|
|
char *str_end;
|
|
char *cbuf, *tbuf, *tp;
|
|
const char *cp, *token;
|
|
char punctuation[16];
|
|
zword_t word;
|
|
|
|
/* Initialise character and token buffer pointers */
|
|
|
|
cbuf = ( char * ) &datap[char_buf];
|
|
tbuf = ( char * ) &datap[token_buf];
|
|
|
|
/* Find the string length */
|
|
|
|
if ( h_type > V4 )
|
|
{
|
|
slen = ( unsigned char ) ( cbuf[1] );
|
|
str_end = cbuf + 2 + slen;
|
|
}
|
|
else
|
|
{
|
|
slen = (int)strlen( cbuf + 1 );
|
|
str_end = cbuf + 1 + slen;
|
|
}
|
|
|
|
/* Initialise word count and pointers */
|
|
|
|
words = 0;
|
|
cp = ( h_type > V4 ) ? cbuf + 2 : cbuf + 1;
|
|
tp = tbuf + 2;
|
|
|
|
/* Initialise dictionary */
|
|
|
|
count = get_byte( dictionary++ );
|
|
for ( i = 0; i < count; i++ )
|
|
punctuation[i] = (char)get_byte( dictionary++ );
|
|
punctuation[i] = '\0';
|
|
entry_size = get_byte( dictionary++ );
|
|
dictionary_size = ( ZINT16 ) get_word( dictionary );
|
|
dictionary_offset = dictionary + 2;
|
|
|
|
/* Calculate the binary chop start position */
|
|
|
|
if ( dictionary_size > 0 )
|
|
{
|
|
word_index = dictionary_size / 2;
|
|
chop = 1;
|
|
do
|
|
chop *= 2;
|
|
while ( word_index /= 2 );
|
|
}
|
|
|
|
/* Tokenise the line */
|
|
|
|
do
|
|
{
|
|
|
|
/* Skip to next token */
|
|
|
|
cp = next_token( cp, str_end, &token, &token_length, punctuation );
|
|
if ( token_length ) {
|
|
|
|
/* If still space in token buffer then store word */
|
|
|
|
if ( words <= tbuf[0] )
|
|
{
|
|
|
|
/* Get the word offset from the dictionary */
|
|
|
|
word = find_word( token_length, token, chop );
|
|
|
|
/* Store the dictionary offset, token length and offset */
|
|
|
|
if ( word || flag == 0 )
|
|
{
|
|
tp[0] = ( char ) ( word >> 8 );
|
|
tp[1] = ( char ) ( word & 0xff );
|
|
}
|
|
tp[2] = ( char ) token_length;
|
|
tp[3] = ( char ) ( token - cbuf );
|
|
|
|
/* Step to next token position and count the word */
|
|
|
|
tp += 4;
|
|
words++;
|
|
}
|
|
else
|
|
{
|
|
|
|
/* Moan if token buffer space exhausted */
|
|
|
|
output_string( "Too many words typed, discarding: " );
|
|
output_line( token );
|
|
}
|
|
}
|
|
}
|
|
while ( token_length );
|
|
|
|
/* Store word count */
|
|
|
|
tbuf[1] = ( char ) words;
|
|
|
|
} /* tokenise_line */
|
|
|
|
/*
|
|
* next_token
|
|
*
|
|
* Find next token in a string. The token (word) is delimited by a statically
|
|
* defined and a game specific set of word separators. The game specific set
|
|
* of separators look like real word separators, but the parser wants to know
|
|
* about them. An example would be: 'grue, take the axe. go north'. The
|
|
* parser wants to know about the comma and the period so that it can correctly
|
|
* parse the line. The 'interesting' word separators normally appear at the
|
|
* start of the dictionary, and are also put in a separate list in the game
|
|
* file.
|
|
*
|
|
*/
|
|
|
|
static const char *next_token( const char *s, const char *str_end, const char **token, int *length,
|
|
const char *punctuation )
|
|
{
|
|
int i;
|
|
|
|
/* Set the token length to zero */
|
|
|
|
*length = 0;
|
|
|
|
/* Step through the string looking for separators */
|
|
|
|
for ( ; s < str_end; s++ )
|
|
{
|
|
|
|
/* Look for game specific word separators first */
|
|
|
|
for ( i = 0; punctuation[i] && *s != punctuation[i]; i++ )
|
|
;
|
|
|
|
/* If a separator is found then return the information */
|
|
|
|
if ( punctuation[i] )
|
|
{
|
|
|
|
/* If length has been set then just return the word position */
|
|
|
|
if ( *length )
|
|
return ( s );
|
|
else
|
|
{
|
|
|
|
/* End of word, so set length, token pointer and return string */
|
|
|
|
( *length )++;
|
|
*token = s;
|
|
return ( ++s );
|
|
}
|
|
}
|
|
|
|
/* Look for statically defined separators last */
|
|
|
|
for ( i = 0; separators[i] && *s != separators[i]; i++ )
|
|
;
|
|
|
|
/* If a separator is found then return the information */
|
|
|
|
if ( separators[i] )
|
|
{
|
|
|
|
/* If length has been set then just return the word position */
|
|
|
|
if ( *length )
|
|
return ( ++s );
|
|
}
|
|
else
|
|
{
|
|
|
|
/* If first token character then remember its position */
|
|
|
|
if ( *length == 0 )
|
|
*token = s;
|
|
( *length )++;
|
|
}
|
|
}
|
|
|
|
return ( s );
|
|
|
|
} /* next_token */
|
|
|
|
/*
|
|
* find_word
|
|
*
|
|
* Search the dictionary for a word. Just encode the word and binary chop the
|
|
* dictionary looking for it.
|
|
*
|
|
*/
|
|
|
|
static zword_t find_word( int len, const char *cp, long chop )
|
|
{
|
|
ZINT16 word[3];
|
|
long word_index, offset, status;
|
|
|
|
/* Don't look up the word if there are no dictionary entries */
|
|
|
|
if ( dictionary_size == 0 )
|
|
return ( 0 );
|
|
|
|
/* Encode target word */
|
|
|
|
encode_text( len, cp, word );
|
|
|
|
/* Do a binary chop search on the main dictionary, otherwise do
|
|
* a linear search */
|
|
|
|
word_index = chop - 1;
|
|
|
|
if ( dictionary_size > 0 )
|
|
{
|
|
|
|
/* Binary chop until the word is found */
|
|
|
|
while ( chop )
|
|
{
|
|
|
|
chop /= 2;
|
|
|
|
/* Calculate dictionary offset */
|
|
|
|
if ( word_index > ( dictionary_size - 1 ) )
|
|
word_index = dictionary_size - 1;
|
|
|
|
offset = dictionary_offset + ( word_index * entry_size );
|
|
|
|
/* If word matches then return dictionary offset */
|
|
|
|
if ( ( status = word[0] - ( ZINT16 ) get_word( offset + 0 ) ) == 0 &&
|
|
( status = word[1] - ( ZINT16 ) get_word( offset + 2 ) ) == 0 &&
|
|
( h_type < V4 || ( status = word[2] - ( ZINT16 ) get_word( offset + 4 ) ) == 0 ) )
|
|
return ( ( zword_t ) offset );
|
|
|
|
/* Set next position depending on direction of overshoot */
|
|
|
|
if ( status > 0 )
|
|
{
|
|
word_index += chop;
|
|
|
|
/* Deal with end of dictionary case */
|
|
|
|
if ( word_index >= ( int ) dictionary_size )
|
|
word_index = dictionary_size - 1;
|
|
}
|
|
else
|
|
{
|
|
word_index -= chop;
|
|
|
|
/* Deal with start of dictionary case */
|
|
|
|
if ( word_index < 0 )
|
|
word_index = 0;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
for ( word_index = 0; word_index < -dictionary_size; word_index++ )
|
|
{
|
|
|
|
/* Calculate dictionary offset */
|
|
|
|
offset = dictionary_offset + ( word_index * entry_size );
|
|
|
|
/* If word matches then return dictionary offset */
|
|
|
|
if ( ( status = word[0] - ( ZINT16 ) get_word( offset + 0 ) ) == 0 &&
|
|
( status = word[1] - ( ZINT16 ) get_word( offset + 2 ) ) == 0 &&
|
|
( h_type < V4 || ( status = word[2] - ( ZINT16 ) get_word( offset + 4 ) ) == 0 ) )
|
|
return ( ( zword_t ) offset );
|
|
}
|
|
}
|
|
|
|
return ( 0 );
|
|
|
|
} /* find_word */
|
|
|
|
/*
|
|
* z_tokenise
|
|
*
|
|
* argv[0] = character buffer address
|
|
* argv[1] = token buffer address
|
|
* argv[2] = alternate vocabulary table
|
|
* argv[3] = ignore unknown words flag
|
|
*
|
|
*/
|
|
|
|
void z_tokenise( int argc, zword_t * argv )
|
|
{
|
|
|
|
/* Supply default parameters */
|
|
|
|
if ( argc < 4 )
|
|
argv[3] = 0;
|
|
if ( argc < 3 )
|
|
argv[2] = h_words_offset;
|
|
|
|
/* Convert the line to tokens */
|
|
|
|
tokenise_line( argv[0], argv[1], argv[2], argv[3] );
|
|
|
|
} /* z_tokenise */
|