rs@uunet.UU.NET (Rich Salz) (06/19/87)
Submitted by: Robert W. Baldwin <BALDWIN@XX.LCS.MIT.EDU>
Mod.sources: Volume 10, Issue 8
Archive-name: cbw/Part08
#! /bin/sh
# This is a shell archive. Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file". To overwrite existing
# files, type "sh file -c". You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g.. If this archive is complete, you
# will see the following message at the end:
# "End of archive 8 (of 11)."
# Contents: stats.c terminal.c
# Wrapped by rs@uunet on Wed Jun 17 18:17:26 1987
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f stats.c -a "${1}" != "-c" ; then
echo shar: Will not over-write existing file \"stats.c\"
else
echo shar: Extracting \"stats.c\" \(18796 characters\)
sed "s/^X//" >stats.c <<'END_OF_stats.c'
X/*
X * Statistics routines.
X *
X * Robert W. Baldwin, January 1985.
X * Scoring based on letter pairs added by Bob Baldwin May 1985.
X */
X
X
X#include <stdio.h>
X#include <math.h>
X#include "window.h"
X#include "specs.h"
X#include "cipher.h"
X
X
X#define STANDALONE FALSE
X
X
X
X/* Globals */
Xint stats1loaded = FALSE; /* True if letter stats loaded. */
Xchar *letterstats; /* Filename to find single letter counts. */
Xint stats2loaded = FALSE; /* True if letter pair stats loaded. */
Xchar *bigramstats; /* Filename to find letter pair counts. */
X
X
X/* This array contains the single letter frequencies.
X * That is, prob[i] is the probability that a randomly selected
X * plaintext character has ascii value i.
X */
Xfloat prob[MAXCHAR+1];
Xfloat pmean, pvar; /* Mean and variance of above. */
X
X
X/* This array contains the base ten logarithms of the single
X * letter frequencies (probabilities) of ASCII characters.
X * The frequencies are between 0 and 1, so the enteries in the
X * table are between minus infinity and 0.
X * The log of a vanishingly small frequency is represented as
X * zero rather than some large negative number.
X * The expected value of logprob[c] for a randomly selected
X * character, c, is given by logmean. The variance of logprob[c]
X * is given by logvar. The standard deviation is in logsd.
X */
Xfloat logprob[MAXCHAR+1];
Xfloat logmean, logvar, logsd;
X
X
X/* This array contains the bigram (letter pair) frequencies.
X * That is, biprob[i][j] is the probability that a randomly selected
X * pair of adjacent plaintext characters is Ai, Aj. Where Ai
X * is the ith letter of the alphabet (0 = 'A' or 'a', and
X * 26 = space or other non-alphabetic).
X * Eventually this will be generalized to include an arbitrary
X * character translation table to handle punctuation and to allow
X * groups of characters such as (, <, {, and [ to be treated the same.
X *
X * The array slbiprob is the single letter frequencies taken from the
X * same sources as biprob[][].
X */
Xfloat biprob[MXBIINDEX][MXBIINDEX];
Xfloat slbiprob[MXBIINDEX];
X
X
X/* The array is used to map from 7-bit ascii to indices in the biprob
X * and related arrays. The variable nbichars is set to the next index
X * to use in the biprob array.
X */
Xint char_bimap[MAXCHAR+1];
Xint nbichars;
X
X
X/* This array contains the base ten logarithms of the letter pair
X * frequencies (biprob[][]).
X * The frequencies are between 0 and 1, so the enteries in the
X * table are between minus infinity and 0.
X * The log of a vanishingly small frequency is represented as
X * zero rather than some large negative number.
X * The expected value of bilogprob[c] for a randomly selected
X * character, c, is given by bilogmean. The variance of bilogprob[c]
X * is given by bilogvar. The standard deviation is in bilogsd.
X */
Xfloat bilogprob[MXBIINDEX][MXBIINDEX];
X
X
X/* This vector contains the base ten logarithms of the single letter
X * frequencies which are derived from biprob[][].
X * They are used to compute the log of the conditional probabilities
X * of letter pairs, given a known value for either the first or
X * second character.
X * Specifically: log( prob(XY given X=Ai) ) equals
X * log( prob(XY) / prob(Ai) ) which equals
X * bilogprob[X][Y] - sllogprob[Ai].
X */
Xfloat sllogprob[MXBIINDEX];
X
X
X/* The scoring function that uses letter pair frequencies is based
X * on a statistic that has a computable mean and variance (and
X * standard deviation). The are stored in the following variables.
X */
Xfloat score2_mean, score2_var, score2_sd, score2_scale;
Xfloat score1_mean, score1_var, score1_sd, score1_scale;
X
X
X#if STANDALONE
X#define filename "/usr/baldwin/Ecrypt/mss-bigram.stats"
Xmain()
X{
X FILE *inp;
X
X load_2stats_from(filename);
X print_2stats(stdout);
X}
X#endif
X
X
X
X/* Score the given plaintext block. Returns a floating point number.
X * For now a stud.
X */
Xfloat score(pblock)
Xint pblock[];
X{
X int pchar;
X int i;
X float score;
X
X score = 0.0;
X for (i = 0 ; i < BLOCKSIZE ; i++) {
X pchar = pblock[i];
X if (pchar == -1) continue;
X if (pchar == ' ') {score += 0.1;}
X else if (lletter(pchar)) {score += 0.1;}
X else if (uletter(pchar)) {score += 0.05;}
X else if (printable(pchar)) {score += 0.02;}
X else if (pchar == '\t' || pchar == '\n' || pchar == '\f')
X {score += 0.05;}
X else if ('0' <= pchar && pchar <= '9')
X {score += 0.05;}
X else {score -= 0.4;}
X }
X return(score);
X}
X
X
X/* Score a vector of integers that represent characters.
X * The vector is terminated by a value of NONE.
X * The returned score is the number of standard deviations
X * that the observed statistics differs from its expected value.
X * Scores are positive with low scores being better.
X * A negative score indicates an impossible plaintext value.
X */
Xfloat pvec_1score(pvec)
Xint *pvec;
X{
X int i;
X int c;
X float tmp, sum, count, score;
X
X if (!stats1loaded) {
X load_1stats_from(letterstats);
X }
X
X count = 0.0;
X sum = 0.0;
X while (*pvec != NONE) {
X count += 1.0;
X c = *pvec++;
X if (c != c & CHARMASK) return(-1.0);
X tmp = logprob[c & CHARMASK];
X if (tmp == 0.0) return(-1.0);
X sum += tmp;
X }
X
X if (count == 0.0) return(-1.0);
X tmp = (sum / count) - logmean;
X tmp = tmp > 0 ? tmp : 0.0 - tmp;
X score = tmp / (logsd / sqrt(count));
X/* printf(" dividing by logsd yields %g", score);
X tmp = tmp * tmp;
X tmp = (tmp * count) / logvar;
X tmp = exp(-0.5 * tmp);
X printf("\nThe exponential yields %g", tmp);
X printf("\n");
X score = sqrt(count) * exp((0 - tmp)/2.0);
X*/
X return(score);
X}
X
X
X/* Score a vector of integers that represent characters.
X * The vector is terminated by a value of NONE.
X * Scoring is based on ratio of observed and expected variance.
X */
Xfloat var_1score(pvec)
Xint *pvec;
X{
X int i;
X int c;
X float tmp, sum, count, score;
X
X if (!stats1loaded) {
X load_1stats_from(letterstats);
X }
X
X count = 0.0;
X sum = 0.0;
X while (*pvec != NONE) {
X count += 1.0;
X c = *pvec++;
X if (c != c & CHARMASK) return(0.0);
X tmp = logprob[c & CHARMASK];
X if (tmp == 0.0) return(0.0);
X tmp = tmp - logmean;
X tmp = tmp * tmp;
X sum += tmp;
X }
X
X if (count == 0.0) return(0.0);
X score = sum / (count * logvar);
X return(score);
X}
X
X
X/* Score a vector of integers that represent characters.
X * The vector is terminated by a value of NONE.
X * Score is the probability that the given characters
X * were drawn from english.
X * NOTE: doesn't correctly handle repeated letters.
X */
Xfloat prob_1score(pvec)
Xint *pvec;
X{
X int i;
X int c;
X float tmp, product, count, score;
X
X if (!stats1loaded) {
X load_1stats_from(letterstats);
X }
X
X count = 0.0;
X product = 1.0;
X while (*pvec != NONE) {
X count += 1.0;
X c = *pvec++;
X if (c != c & CHARMASK) return(0.0);
X product *= prob[c] * count;
X }
X
X if (count == 0.0) return(0.0);
X score = product;
X return(score);
X}
X
X
X/* Score a guess based on letter pair frequencies.
X * The returned score is the number of standard deviations
X * that the observed statistics differs from its expected value.
X * Scores are positive with low scores being better.
X * A negative score indicates an impossible plaintext value.
X */
Xfloat gsi_2score(gsi)
Xreg gsinfo *gsi;
X{
X float score;
X float total;
X float tmp;
X int nchars;
X int i;
Xreg int pos;
Xreg int c;
Xreg int center_letter;
X int left_letter, right_letter;
X float pair_score;
X
X if (!stats2loaded) {
X load_2stats_from(bigramstats);
X }
X
X nchars = 0;
X total = 0.0;
X for (i = 0 ; (pos = gsi->cpos[i]) != NONE ; i++) {
X nchars++;
X c = (gsi->cguessed)[pos];
X center_letter = char_bimap[c & CHARMASK];
X if (sllogprob[center_letter] == 0.0)
X return(-1.0);
X
X if (pos == 0) {
X total += sllogprob[center_letter];
X }
X else {
X c = (gsi->cknown)[pos - 1];
X if (c == NONE) {
X c = (gsi->cguessed)[pos - 1];
X }
X if (c == NONE) {
X total += sllogprob[center_letter];
X }
X else {
X left_letter = char_bimap[c & CHARMASK];
X pair_score = bilogprob[left_letter][center_letter];
X if (pair_score == 0.0)
X return(-1.0);
X total += pair_score - sllogprob[center_letter];
X }
X }
X
X if (pos == (BLOCKSIZE - 1)) {
X total += sllogprob[center_letter];
X }
X else {
X c = (gsi->cknown)[pos + 1];
X if (c == NONE) {
X c = (gsi->cguessed)[pos + 1];
X }
X if (c == NONE) {
X total += sllogprob[center_letter];
X }
X else {
X right_letter = char_bimap[c & CHARMASK];
X pair_score = bilogprob[center_letter][right_letter];
X if (pair_score == 0.0)
X return(-1.0);
X total += pair_score - sllogprob[center_letter];
X }
X }
X }
X
X if (nchars == 0)
X return(-1.0);
X tmp = (total / nchars) - score2_mean;
X tmp = tmp > 0.0 ? tmp : 0.0 - tmp;
X score = tmp / (score2_sd / isqrt[nchars]);
X return(score);
X}
X
X
X/* Score a guess based on single letter frequencies.
X * The returned score is the number of standard deviations
X * that the observed statistics differs from its expected value.
X * Scores are positive with low scores being better.
X * A negative score indicates an impossible plaintext value.
X */
Xfloat gsi_1score(gsi)
Xreg gsinfo *gsi;
X{
Xreg int pos;
X int i;
X int c;
X int nchars;
X float sum, score;
Xreg float tmp;
X
X if (!stats1loaded) {
X load_1stats_from(letterstats);
X }
X
X nchars = 0;
X sum = 0.0;
X for (i = 0 ; (pos = gsi->cpos[i]) != NONE ; i++) {
X nchars++;
X c = (gsi->cguessed)[pos];
X tmp = logprob[c & CHARMASK];
X if (tmp == 0.0)
X return(-1.0);
X sum += tmp;
X }
X
X if (nchars == 0)
X return(-1.0);
X tmp = (sum / nchars) - logmean;
X tmp = tmp > 0 ? tmp : 0.0 - tmp;
X score = tmp / (logsd / isqrt[nchars]);
X
X return(score);
X}
X
X
X/* Compute expected value of a scoring function given
X * a vector of probabilities for values and a vector of
X * scores for each value.
X */
Xfloat vec_mean(probvec, scorevec, maxindex)
Xfloat *probvec;
Xfloat *scorevec;
Xint maxindex;
X{
X int i;
X float mean;
X
X mean = 0.0;
X for (i = 0 ; i <= maxindex ; i++) {
X mean += (*probvec++) * (*scorevec++);
X }
X return(mean);
X}
X
X
X/* Compute variance of a scoring function given
X * a vector of probabilities for values and a vector of
X * scores for each value.
X */
Xfloat vec_variance(probvec, scorevec, maxindex)
Xfloat *probvec;
Xfloat *scorevec;
Xint maxindex;
X{
X int i;
X float var, mean;
X float delta;
X
X mean = vec_mean(probvec, scorevec, maxindex);
X var = 0.0;
X for (i = 0 ; i <= maxindex ; i++) {
X delta = (*scorevec++) - mean;
X var += (*probvec++) * (delta * delta);
X }
X return(var);
X}
X
X
X/* Read from given stream to set up logprob table and constants
X * logmean and logvar.
X *
X * The table format is:
X * <Total count>
X * <Blankline>
X * <Count><space><One or more slashified characters to share that count>
X * ...
X * <Count><space><One or more slashified characters to share that count>
X * <Blankline>
X * <EOF>
X */
Xload_1stats(inp)
XFILE *inp;
X{
X int i,n;
X int tmp;
X int c;
X float v, lv, fv;
X float etotal, ctotal;
X
X stats1loaded = TRUE;
X
X for (i = 0 ; i <= MAXCHAR ; i++) logprob[i] = 0.0;
X
X if (fscanf(inp, "%d", &tmp) != 1) {
X printf("Error while getting total");
X return;
X }
X etotal = tmp;
X ctotal = 0.0;
X
X if (fscanf(inp, "\n") != 0) {
X printf("Error while skipping blank line");
X return;
X }
X
X while (TRUE) {
X if ((n = fscanf(inp, "%d", &tmp)) != 1) {
X if (n == 0) break;
X if (n == EOF) break;
X printf("Error while getting character count");
X return;
X }
X v = tmp;
X ctotal += v;
X fv = v/etotal;
X if (fv != 0.0) {lv = log10(fv);}
X else {lv = 0.0;}
X
X c = read_char(inp); /* Skip the space. */
X while (TRUE) {
X c = read_char(inp);
X if (c == EOL) break;
X prob[c&CHARMASK] = fv;
X logprob[c&CHARMASK] = lv;
X }
X }
X
X if (etotal != ctotal) {
X printf("Expected total is %f. Actual total is %f.\n",etotal,ctotal);
X }
X
X logmean = vec_mean(prob, logprob, MAXCHAR);
X logvar = vec_variance(prob, logprob, MAXCHAR);
X logsd = sqrt(logvar);
X score1_mean = logmean;
X score1_var = logvar;
X score1_sd = logsd;
X score1_scale = sqrt(2 * PI * score1_var);
X pmean = vec_mean(prob, prob, MAXCHAR);
X pvar = vec_variance(prob, prob, MAXCHAR);
X}
X
X
X/* Load the letter pair statistics from the given file name.
X */
Xload_2stats_from(statfname)
Xchar *statfname; /* Full path name of file with statistics. */
X{
X FILE *inp;
X
X if ((inp = fopen(statfname, "r")) == NULL) {
X printf("\nCan't open %s to read letter statistics\n", statfname);
X exit(0);
X }
X load_2stats(inp);
X fclose(inp);
X}
X
X
X/* Read from given stream to set up bilogprob table and constants
X * bilogmean, bilogsd, and bilogvar.
X *
X * The format of the statistics file is: [This should be more general.]
X * <Total count>
X * <Blankline>
X * <single letter counts>
X * <line with the chars '***'>
X * <double letter counts>
X * <line with the chars '***'>
X * <mean of matrix>
X * <variance of matrix>
X * <standard deviation of matrix>
X * <Blankline>
X * <EOF>
X *
X * Where single letter counts also define the mapping from ascii chars to
X * distinguished letters (i.e., all open brackets are treated the same).
X * The single letter format is:
X * <Count><space><One or more slashified characters to share that count>
X * ...
X * <Count><space><One or more slashified characters to share that count>
X * NOTE: the first entry should be for a low probability letter because the
X * default mapping for unknown chars is zero. See code for details.
X *
X * The double letter format is:
X * <Count><space><Representative of first letter group><Rep of second letter>
X * ...
X * <Count><space><Representative of first letter group><Rep of second letter>
X *
X * For example if 'T' and 't' are treated the same, a double letter entry
X * might look like: "1247 TT" and count for Tt, tT, tt, and TT.
X */
Xload_2stats(inp)
XFILE *inp;
X{
Xregister int i,j;
X int n;
X int tmp;
X int c;
X int left_index, right_index;
X float v, lv, fv;
X float etotal, ctotal;
X char linebuf[300];
X
X stats2loaded = TRUE;
X nbichars = 0;
X
X for (i = 0 ; i < MXBIINDEX ; i++) {
X sllogprob[i] = 0.0;
X slbiprob[i] = 0.0;
X for (j = 0 ; j < MXBIINDEX ; j++) {
X bilogprob[i][j] = 0.0;
X biprob[i][j] = 0.0;
X }
X }
X
X for (i = 0 ; i < MAXCHAR+1 ; i++)
X char_bimap[i] = 0; /* Default index if char unknown. */
X
X if (fscanf(inp, "%d", &tmp) != 1) {
X printf("Error while getting total");
X exit(0);
X }
X etotal = tmp;
X
X if (fscanf(inp, "\n") != 0) {
X printf("Error while skipping blank line before single letters");
X exit(0);
X }
X
X ctotal = 0.0;
X while (TRUE) {
X if ((n = fscanf(inp, "%d", &tmp)) != 1) {
X if (n == 0) break;
X if (n == EOF) break;
X printf("Error while getting character count (singles)");
X exit(0);
X }
X v = tmp;
X ctotal += v;
X fv = v/etotal;
X if (fv == 0.0)
X lv = 0.0;
X else
X lv = log10(fv);
X
X c = read_char(inp); /* Skip the space. */
X while (TRUE) {
X c = read_char(inp);
X if (c == EOL) break;
X char_bimap[c & CHARMASK] = nbichars;
X slbiprob[nbichars] = fv;
X sllogprob[nbichars] = lv;
X }
X nbichars++;
X }
X
X if (etotal != ctotal) {
X printf("Expected total is %f. Actual total is %f for singles.\n",
X etotal, ctotal);
X exit(0);
X }
X
X
X if (fscanf(inp, "***\n") != 0) {
X printf("Error on delimiter before letter pairs");
X exit(0);
X }
X
X ctotal = 0.0;
X while (TRUE) {
X if ((n = fscanf(inp, "%d", &tmp)) != 1) {
X if (n == 0) break;
X if (n == EOF) break;
X printf("Error while getting character count (pairs)");
X exit(0);
X }
X v = tmp;
X ctotal += v;
X fv = v/etotal;
X if (fv == 0.0)
X lv = 0.0;
X else
X lv = log10(fv);
X
X c = read_char(inp); /* Skip the space. */
X c = read_char(inp); /* First letter. */
X if (c == EOL) {
X printf("Line ends before letter pair");
X exit(0);
X }
X left_index = char_bimap[c & CHARMASK];
X c = read_char(inp); /* Second letter. */
X if (c == EOL) {
X printf("Line ends in middle of letter pair");
X exit(0);
X }
X right_index = char_bimap[c & CHARMASK];
X
X biprob[left_index][right_index] = fv;
X bilogprob[left_index][right_index] = lv;
X }
X
X if (etotal != ctotal) {
X printf("Expected total is %f. Actual total is %f for pairs.\n",
X etotal, ctotal);
X exit(0);
X }
X
X if (fscanf(inp, "***\n") == 0) {
X if (fscanf(inp, "%f", &score2_mean) != 1) {
X printf("Error reading mean.");
X exit(0);
X }
X if (fscanf(inp, "%f", &score2_var) != 1) {
X printf("Error reading variance.");
X exit(0);
X }
X if (fscanf(inp, "%f", &score2_sd) != 1) {
X printf("Error reading standard deviations.");
X exit(0);
X }
X score2_scale = sqrt(2 * PI * score2_var);
X approx_init();
X return;
X }
X
X stats2();
X printf("Mean: %f, Var: %f, SD: %f\n", score2_mean, score2_var, score2_sd);
X}
X
X
X/* Compute scoring statistics for the letter pair frequencies.
X * Uses the globals: biprob[][], sllogbiprob[], and bilogprob[][].
X * Sets gobals: score2_mean, score2_var, score2_sd.
X */
Xstats2()
X{
Xregister int i,j,k;
X float mean, var;
X float weight, score;
X
X mean = 0.0;
X var = 0.0;
X for (i = 0 ; i < nbichars ; i++)
X for (j = 0 ; j < nbichars ; j++) {
X if (slbiprob[j] == 0.0)
X continue;
X for (k = 0 ; k < nbichars ; k++) {
X weight = biprob[i][j] * biprob[j][k] / slbiprob[j];
X score = bilogprob[i][j] + bilogprob[j][k] - 2 * sllogprob[j];
X mean += weight * score;
X var += weight * score * score;
X }
X }
X var -= mean * mean;
X
X score2_mean = mean;
X score2_var = var;
X score2_sd = sqrt(score2_var);
X score2_scale = sqrt(2 * PI * score2_var);
X
X approx_init();
X}
X
X
X/* Print the bigram statistics.
X */
Xprint_2stats(out)
XFILE *out;
X{
X float sllog_mean;
X float sllog_var;
X float lev_mean, lev_var;
X float rev_mean, rev_var;
X
X fprintf(out, "\t\tBigram Statistics\n");
X fprintf(out, "Score2_mean is %f", score2_mean);
X fprintf(out, ", score2_var is %f", score2_var);
X fprintf(out, ", score2_sd is %f", score2_sd);
X fprintf(out, "\nnbichars is %d", nbichars);
X fprintf(out, "\n");
X
X sllog_mean = vec_mean(slbiprob, sllogprob, nbichars);
X sllog_var = vec_variance(slbiprob, sllogprob, nbichars);
X fprintf(out, "sllog_mean is %f", sllog_mean);
X fprintf(out, ", sllog_var is %f", sllog_var);
X fprintf(out, "\n");
X}
X
X
X
X/* Print the first order log statistics on a stream.
X */
Xprint_1stats(out)
XFILE *out;
X{
X
X fprintf(out, "Single letter frequencies\n");
X fprintf(out, "\nExpected value of prob is %f. Variance is %f.\n",
X pmean, pvar);
X print_stat_tab(out, prob, MAXCHAR);
X
X fprintf(out, "\nExpected value of logprob is %f. Variance is %f.\n",
X logmean, logvar);
X fprintf(out, "Log of single letter frequencies\n");
X print_stat_tab(out, logprob, MAXCHAR);
X}
X
X
X/* Dumpa statistics table on to a stream.
X */
Xprint_stat_tab(out, table, maxindex)
XFILE *out;
Xfloat table[];
Xint maxindex;
X{
X int i;
X
X for (i = 0 ; i <= maxindex ; i++) {
X if (i % 8 == 0) fprintf(out, "\n");
X fprintf(out, "%7.4f ", table[i]);
X }
X fprintf(out, "\n");
X}
X
X
X/* Load the first order statistics from the given file name.
X */
Xload_1stats_from(statfname)
Xchar *statfname;
X{
X FILE *inp;
X
X if ((inp = fopen(statfname, "r")) == NULL) {
X printf("\nCan't open %s to read letter statistics\n", statfname);
X exit(0);
X }
X load_1stats(inp);
X fclose(inp);
X}
X
END_OF_stats.c
if test 18796 -ne `wc -c <stats.c`; then
echo shar: \"stats.c\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f terminal.c -a "${1}" != "-c" ; then
echo shar: Will not over-write existing file \"terminal.c\"
else
echo shar: Extracting \"terminal.c\" \(19285 characters\)
sed "s/^X//" >terminal.c <<'END_OF_terminal.c'
X/* Terminal dependent routines (well, most of them ...)
X *
X * Author: Bob Baldwin, October 1986.
X */
X
X/*
X * Routines in terminal abstraction:
X *
X * set_term()
X * Initialize terminal, clear the screen.
X *
X * unset_term()
X * Return terminal to state before set_term().
X *
X * char2sym(char)
X * Return the symbol used to display the given char in the
X * decryption window.
X *
X * putsym(symbol)
X * Displays the given symbol on the terminal. Handles
X * entering and exiting graphics mode.
X *
X * getcmd()
X * Reads stdin for a keystroke and returns
X * a command integer.
X *
X * beep()
X * Cause the terminal to beep or flash.
X */
X
X/* The trick to device independence is to clearly separate
X * internal and external representations. On the inbound
X * side, we separate KEYSTOKES which generate a sequence
X * of ascii characters (one per keystroke in the simple case),
X * from COMMAND-KEYS such as move-cursor-up. On the outbound
X * side we separate SYMBOLS from the sequence of ascii characters,
X * called GRAPHICS, used to display the symbol. Each symbol
X * has a single use by the code, though two symbols might
X * appear the same on the user's terminal.
X */
X
X/* Symbols are represented by integers. If the integer is
X * greater than 256, then it denotes one of the symbols defined
X * in terminal.h.
X *
X * Commands are represented by two byte integers. The high byte
X * describes the command (see terminal.h), the low byte is the
X * argument to the command. For example, the insert char command
X * sets the low byte to the character to be inserted.
X */
X
X/* INTERNALS: symbols and graphics
X *
X * The terminal is assumed to be in one of three modes: normal, graphics,
X * or standout (inverse video). This abstraction takes care of all the
X * switching between modes and tries to avoid sending redundant escape
X * sequences to the terminal.
X *
X * Part of the terminal initialization is to build a table that describes
X * how to display each symbol. Ascii characters (including control chars)
X * pass through unchanged, but symbols are looked up in graphtab.
X * Each entry in the graphics table specifies a terminal mode and a
X * string to print that will display that symbol in the given mode.
X * If necessary a mode switch sequence will be sent before printing the
X * string.
X *
X * The graphics corresponding to symbols can be set by the shell variable
X * 'GRAHPICS' using a format similar to the termcap format. The two
X * character names of the symbols are defined in the table symnames[],
X * which can be indexed by the symbol codes (see terminal.h).
X * The mapping from symbols to graphics is first set to a default value,
X * and then updated by the values found in the shell variable (if any).
X *
X * The GRAPHICS string consists of a number of entries separated by colon
X * characters. Each entry have the form xx=\Mccc, where xx is the name
X * of a symbol (see symnames[] below), \M indicates the mode for displaying
X * this symbol (it must be one of \N (for normal), \G (for graphics), or \S
X * (for standout or inverse video)), and ccc is a sequence of characters
X * to send. To include a colon character in the ccc portion, preceed it
X * by a backslash (i.e., '\'). If the \M is ommitted, it defaults to normal.
X */
X
X/* INTERNALS: keystrokes and commands
X *
X * The table keycmdtab is used to convert a sequence of ascii characters
X * into a command integer. The table specifies the escape sequences that
X * might be generated by the different command keys. The getcmd routine
X * will read characters from stdin until it has uniquely identified a
X * command, or decided that there is no match. If no match is found,
X * the terminal is beeped and matching is restarted with the next character.
X * By default keystrokes turn into self-insert commands.
X *
X * In general the last character of the sequence is returned as the arg.
X * This helps windows assign different meanings to keystokes. For example
X * The return key can be either an insert-return command or an execute-
X * command-line command.
X */
X
X
X#include <curses.h>
X#include <sgtty.h>
X#include <strings.h>
X#include "window.h"
X#include "terminal.h"
X#include "specs.h"
X
X
X/* Routines from termcap library.
X */
Xextern char *getenv(), *tgetstr(), *tgoto();
X
X
X/* Screen control strings from termcap entry.
X */
Xchar *term_is; /* Terminal initializaion string. */
Xchar *erase_eol; /* Erase to end of line. */
Xchar *erase_eos; /* Erase to end of screen. */
Xchar *erase_scr; /* Erase whole screen. */
Xchar *cm; /* Cursor motion. */
Xchar *start_kp; /* Start graphics mode. */
Xchar *end_kp; /* End graphics mode. */
Xchar *start_alt; /* Start graphics mode. */
Xchar *end_alt; /* End graphics mode. */
Xchar *start_so; /* Start standout mode. */
Xchar *end_so; /* End standout mode. */
X
X/* Keymap strings from termcap file.
X */
Xchar *term_f1; /* The f1 key. */
Xchar *term_f2; /* The f2 key. */
Xchar *term_f3; /* The f3 key. */
Xchar *term_f4; /* The f4 key. */
Xchar *term_up; /* Up arrow. */
Xchar *term_down; /* Down arrow. */
Xchar *term_left; /* Left arrow. */
Xchar *term_right; /* Right arrow. */
X
X
X/* Symbol names for the shell variable 'GRAPHICS'
X * The values must be and'ed with SYMBOLM to make
X * suitable indices for graphtab[].
X */
Xlabelv symnames[NSYMC + 1] = {
X {"tb", STAB}, /* Tab */
X {"na", SNOTASCII}, /* Not ascii */
X {"lf", SLINEFEED}, /* Linefeed */
X {"cr", SCARETURN}, /* Carriage return */
X {"ff", SFORMFEED}, /* Formfeed */
X {"cc", SCONTCODE}, /* Other control characters */
X {"uk", SUNKNOWN}, /* Plaintext unknown */
X {"ul", SUNDERLINE}, /* Pseudo underline char */
X {"hb", SHORZBAR}, /* Horizontal bar */
X {"vb", SVERTBAR}, /* Vertical bar */
X {"ll", SLLCORNER}, /* Lower left corner */
X {NULL, 0}, /* End flag. */
X};
X
X/* Table of graphics characters initialized for ordinary CRT.
X */
Xsymgraph graphtab[NSYMC];
X
X
X/* Symbol names for the shell variable KEYMAPVAR
X * A command's index in this table should be one less
X * than the command code.
X */
Xlabelv cmdnames[] = {
X {"up", CGO_UP},
X {"do", CGO_DOWN},
X {"le", CGO_LEFT},
X {"ri", CGO_RIGHT},
X {"re", CREFRESH},
X {"un", CUNDO},
X {"cl", CCLRLINE},
X {"ws", CWRDSRCH},
X {"df", CDELF},
X {"db", CDELB},
X {"pr", CPREVBLOCK},
X {"ne", CNEXTBLOCK},
X {"ac", CACCEPT},
X {"ex", CEXECUTE},
X {"--", CINSERT}, /* Should not be in keymap var. */
X {"ta", CTRYALL},
X {"jc", CJUMPCMD},
X {NULL, 0},
X};
X
X/* Table of keystroke commands.
X * Self-insert commands are the default.
X * There can be several keystrokes that generate the same command.
X * To insert control chars, they must be quoted, see QUOTEC.
X * The table is terminated by an entry with c_seq == NULL.
X */
X#define QUOTEC (CNTRL & 'Q')
Xkeycmd keycmdtab[100];
Xkeycmd *keycmdp; /* Pointer to next free entry. */
X
X
X
X/* Saved process control characters.
X */
Xstruct tchars saved_tchars;
X
X
X/* Buffer for termcap entry. */
X#define TBUFSIZ 1024
Xchar buf[TBUFSIZ];
Xchar free_buf[1000], *fr; /* Must be global, see tgetstr() */
X
X
X/* Current terminal mode. */
Xint termmode = -1;
X
X
X/* Set up the terminal. This package now makes calls to both curses
X * and termcap subroutine packages, although the old code is used
X * for screen refresh.
X */
Xset_term()
X{
X printf("\n\nInitializing terminal ...");
X fflush(stdout);
X
X get_termstrs();
X get_genv();
X get_kenv();
X savetty();
X crmode();
X noecho();
X noflow();
X Puts(term_is);
X Puts(start_kp);
X enter_mode(SMNORMAL);
X
X printf(" done.\n");
X
X clrscreen();
X}
X
X
X/* Get keystroke characters, build keymap.
X * The earlier entries have priority, so fill them in from
X * the shell var, then add defaults from a string then termcap.
X */
Xget_kenv()
X{
X char *kenv;
X char tcapstr[1000];
X
X keycmdp = keycmdtab;
X kenv = getenv(KEYMAPVAR);
X if (kenv != NULL)
X read_keymap(kenv);
X kenv_termcap(tcapstr);
X read_keymap(tcapstr);
X read_keymap(DKEYMAP);
X}
X
X
X/* Build a keymap string in the given buffer from the info
X * in the termcap file.
X * The string format is like: "up=\Eu:do=\033d".
X */
Xkenv_termcap(str)
Xchar *str;
X{
X *str = NULL;
X
X if (term_up != NULL) {
X strcat(str, cmdnames[CGO_UP - 1].label);
X strcat(str, "=");
X strcat(str, term_up);
X strcat(str, ":");
X }
X if (term_down != NULL) {
X strcat(str, cmdnames[CGO_DOWN - 1].label);
X strcat(str, "=");
X strcat(str, term_down);
X strcat(str, ":");
X }
X if (term_left != NULL) {
X strcat(str, cmdnames[CGO_LEFT - 1].label);
X strcat(str, "=");
X strcat(str, term_left);
X strcat(str, ":");
X }
X if (term_right != NULL) {
X strcat(str, cmdnames[CGO_RIGHT - 1].label);
X strcat(str, "=");
X strcat(str, term_right);
X strcat(str, ":");
X }
X if (term_f1 != NULL) {
X strcat(str, cmdnames[CPREVBLOCK - 1].label);
X strcat(str, "=");
X strcat(str, term_f1);
X strcat(str, ":");
X }
X if (term_f2 != NULL) {
X strcat(str, cmdnames[CNEXTBLOCK - 1].label);
X strcat(str, "=");
X strcat(str, term_f2);
X strcat(str, ":");
X }
X if (term_f3 != NULL) {
X strcat(str, cmdnames[CACCEPT - 1].label);
X strcat(str, "=");
X strcat(str, term_f3);
X strcat(str, ":");
X }
X if (term_f4 != NULL) {
X strcat(str, cmdnames[CJUMPCMD - 1].label);
X strcat(str, "=");
X strcat(str, term_f4);
X strcat(str, ":");
X }
X}
X
X
X
X/* Add key bindings from the given string to the keycmdtab.
X */
Xread_keymap(var)
Xchar *var;
X{
X int cmd_code;
X
X while (*var != NULL) {
X if (! read_varlabel(&var, cmdnames, &cmd_code)) {
X if (*var == NULL)
X break;
X disperr("Can't parse label in read_keymap.");
X exit(1);
X }
X
X keycmdp->c_code = cmd_code;
X if (! read_varval(&var, &(keycmdp->c_seq))) {
X disperr("keymap value has bad format.");
X exit(1);
X }
X keycmdp++;
X }
X}
X
X
X/* Get graphics characters.
X * Set to defaults, then read changes from shell var (if any).
X */
Xget_genv()
X{
X char *genv;
X
X read_graphics(DGRAPHICS);
X genv = getenv(GRAPHICSVAR);
X if (genv == NULL)
X return;
X read_graphics(genv);
X}
X
X
X/* Read graphics map from the given string.
X */
Xread_graphics(var)
Xchar *var;
X{
X int sym_idx;
X
X while (*var != NULL) {
X if (! read_varlabel(&var, symnames, &sym_idx)) {
X if (*var == NULL)
X break;
X disperr("Can't parse label in GRAPHICSMAP.");
X exit(1);
X }
X
X if ((var[0] != '\\') || (index(GVARMODES, var[1]) == 0)) {
X disperr("GRAPHICSMAP value has bad mode.");
X exit(1);
X }
X sym_idx &= SYMBOLM;
X graphtab[sym_idx].s_mode = var[1] & CHARM;
X var++, var++;
X
X if (! read_varval(&var, &(graphtab[sym_idx].s_seq))) {
X disperr("GRAPHICSMAP val has bad format.");
X exit(1);
X }
X }
X}
X
X
X/* Advance to the next label in strp, look the label up in the
X * labeltab, and set *valp to the value in the labeltab.
X * Return with *strp pointing after '=' that follows the label.
X * Return TRUE is parses ok, else return FALSE.
X * If string empty, return false and set *strp to point to a NULL.
X */
Xint read_varlabel(strp, labeltab, valp)
Xchar **strp;
Xlabelv *labeltab;
Xint *valp;
X{
X char *str;
X labelv *lp;
X
X for (str = *strp ; *str && index(VARSEP, *str) != 0 ; str++ );
X
X for (lp = labeltab ; lp->label != NULL ; lp++) {
X if (substrp(str, lp->label)) {
X *valp = lp->value;
X str = index(str, '=');
X if (str == NULL)
X return(FALSE);
X str++;
X *strp = str;
X return(TRUE);
X }
X }
X *strp = str;
X return(FALSE);
X}
X
X
X/* Read a string value from a shell var string.
X * Return with *strp pointing after the string,
X * fill in valp with a pointer to a copy of the value string
X * on the heap.
X * Return TRUE if things go ok.
X */
Xint read_varval(strp, valp)
Xchar **strp;
Xchar **valp;
X{
X char buf[100], *bp;
X char *var;
X
X var = *strp;
X bp = buf;
X while ((*var != NULL) && (index(VARTERM, *var) == NULL)) {
X *bp = read_slashed(&var);
X bp++;
X }
X *bp = 0;
X *valp = savestr(buf);
X *strp = var;
X return (TRUE);
X}
X
X
X/* Read the given (slashified) string and return a character.
X * Advance *strp over chars read.
X * Handle \\, \001, \n, \t, \r.
X * The symbol \E maps to escape (\033).
X * An unexpected char after the slash is just returned.
X */
Xint read_slashed(strp)
Xchar **strp;
X{
X char *str;
X char c;
X
X str = *strp;
X if (*str != '\\') {
X *strp = (*strp) + 1;
X return (*str);
X }
X str++;
X c = *str;
X switch (c) {
X default:
X if (index(DIGITS, c) == NULL)
X break;
X c -= '0';
X if (index(DIGITS, str[1]) == NULL)
X break;
X str++;
X c = (c * 8) + (*str - '0');
X if (index(DIGITS, str[1]) == NULL)
X break;
X str++;
X c = (c * 8) + (*str - '0');
X break;
X
X case 'n':
X c = '\n';
X break;
X
X case 't':
X c = '\t';
X break;
X
X case 'E':
X c = '\033';
X break;
X
X case 'f':
X c = '\f';
X break;
X
X case 'r':
X c = '\r';
X break;
X }
X *strp = str + 1;
X return (c);
X}
X
X
X/* Turn off flow control characters.
X */
Xnoflow()
X{
X struct tchars new_tchars;
X
X /* Turn off C-Q, C-S flow control. */
X if (ioctl(0, TIOCGETC, &saved_tchars) < 0) {
X perror("noflow iocl get");
X exit(1);
X }
X new_tchars = saved_tchars;
X new_tchars.t_stopc = -1;
X new_tchars.t_startc = -1;
X if (ioctl(0, TIOCSETC, &new_tchars) < 0) {
X perror("noflow iocl set");
X exit(1);
X }
X}
X
X
X/* Restore the flow control characters.
X */
Xrestore_flow()
X{
X if (ioctl(0, TIOCSETC, &saved_tchars) < 0) {
X perror("restore_flow iocl set");
X exit(1);
X }
X}
X
X
X/* Read in the termcap strings.
X */
Xget_termstrs()
X{
X char *term;
X int res;
X
X fr=free_buf;
X term = getenv("TERM");
X if (term == NULL) {
X disperr("The shell variable TERM is not defined.");
X exit(1);
X }
X res = tgetent(buf,term);
X switch (res) {
X case -1:
X disperr("Can't open termcap file.");
X exit(1);
X
X case 0:
X disperr("No termcap entry for your terminal.");
X exit(1);
X
X default:
X break;
X }
X
X term_is = tgetstr("is", &fr);
X if (term_is == NULL)
X term_is = "";
X erase_eol = tgetstr("ce", &fr);
X erase_eos = tgetstr("cd", &fr);
X erase_scr = tgetstr("cl", &fr);
X start_so = tgetstr("so", &fr);
X end_so = tgetstr("se", &fr);
X start_alt = tgetstr("as", &fr);
X end_alt = tgetstr("ae", &fr);
X if (start_alt == 0 || end_alt == 0) {
X start_alt = "\033F"; /* VT100 default. */
X end_alt = "\033G";
X }
X cm = tgetstr("cm", &fr); /* for cursor positioning */
X start_kp = tgetstr("ks", &fr);
X end_kp = tgetstr("ke", &fr);
X
X if ((term_is == NULL) || (erase_eol == NULL) ||
X (erase_eos == NULL) || (erase_scr == NULL) ||
X (cm == NULL) ||
X (end_kp == NULL) || (start_kp == NULL) ||
X (end_alt == NULL) || (start_alt == NULL) ||
X (end_so == NULL) || (start_so == NULL) ) {
X disperr("A required termcap capability is missing.");
X disperr("\t one of: ce, cd, cl, so, se, cm, ks, ke.");
X exit(1);
X }
X
X /* Now get entries for keymap, NULL means no such entry.
X */
X term_f1 = tgetstr("k1", &fr);
X term_f2 = tgetstr("k2", &fr);
X term_f3 = tgetstr("k3", &fr);
X term_f4 = tgetstr("k4", &fr);
X term_up = tgetstr("ku", &fr);
X term_down = tgetstr("kd", &fr);
X term_left = tgetstr("kl", &fr);
X term_right = tgetstr("kr", &fr);
X}
X
X
X/* Restore the terminal to its original mode.
X */
Xunset_term()
X{
X enter_mode(SMNORMAL);
X Puts(end_kp); /* Can't tell if this is original. */
X fflush(stdout);
X nocrmode();
X echo();
X restore_flow();
X resetty();
X}
X
X
X/* Return the symbol used to display the given char in the
X * decryption window.
X */
Xint char2sym(pchar)
Xint pchar;
X{
X int gchar;
X
X if (printable(pchar)) {gchar = pchar;}
X else if (pchar == -1) {gchar = SUNKNOWN;}
X else if (notascii(pchar)) {gchar = SNOTASCII;}
X else if (pchar == '\n') {gchar = SLINEFEED;}
X else if (pchar == '\r') {gchar = SCARETURN;}
X else if (pchar == '\f') {gchar = SFORMFEED;}
X else if (pchar == '\t') {gchar = STAB;}
X else {gchar = SCONTCODE;}
X return (gchar);
X}
X
X
X/* Displays the given symbol on the terminal. Handles
X * entering and exiting graphics or standout mode.
X */
Xputsym(symbol)
Xint symbol;
X{
X int symcode;
X symgraph *gp;
X
X if (! graphic(symbol)) {
X enter_mode(SMNORMAL);
X putchar(symbol & CHARM);
X return;
X }
X symcode = symbol & SYMBOLM;
X if (symcode >= NSYMC) {
X disperr("Bad symbol code in putsym.");
X return;
X }
X gp = &graphtab[symcode];
X enter_mode(gp->s_mode);
X Puts(gp->s_seq);
X}
X
X
X/* Enter a particular mode. If necessary send escape sequence
X * to the terminal. Handle terminating the previous mode.
X */
Xenter_mode(mode)
Xint mode;
X{
X if (termmode == mode)
X return;
X
X switch (termmode) {
X case SMNORMAL:
X break;
X
X case SMGRAPHIC:
X Puts(end_alt);
X break;
X
X case SMSTANDOUT:
X Puts(end_so);
X break;
X
X default:
X Puts(end_so);
X Puts(end_alt);
X break;
X }
X
X termmode = mode;
X
X switch (termmode) {
X case SMNORMAL:
X break;
X
X case SMGRAPHIC:
X Puts(start_alt);
X break;
X
X case SMSTANDOUT:
X Puts(start_so);
X break;
X
X default:
X disperr("Bad terminal mode.");
X break;
X }
X}
X
X
X/* Return values from srch_ktab().
X */
X#define SK_SUBSTR -1
X#define SK_NOMATCH -2
X
X
X/* Search the key command table for the given keystroke.
X * If no match, return SK_NOMATCH (which is < 0).
X * If the keystroke is the prefix for one or more commands,
X * return SK_SUBSTR (which is < 0).
X * If an exact match is found, return the index ( >= 0) of
X * the entry that matched.
X */
Xint srch_ktab(ktab, stroke)
Xkeycmd ktab[];
Xchar *stroke;
X{
X int i;
X int nsubstr = 0; /* Number of close entries */
X
X for (i = 0 ; ktab[i].c_seq != NULL ; i++) {
X if (strcmp(ktab[i].c_seq, stroke) == 0)
X return(i);
X if (substrp(ktab[i].c_seq, stroke))
X nsubstr++;
X }
X if (nsubstr > 0)
X return (SK_SUBSTR);
X return (SK_NOMATCH);
X}
X
X
X/* Return TRUE if the model string starts with the given string.
X * Return false if strlen(given) > strlen(model).
X */
Xint substrp(model, given)
Xchar *model;
Xchar *given;
X{
X for ( ; (*model != 0) && (*given != 0) ; model++, given++) {
X if (*model != *given)
X return (FALSE);
X }
X if (*given == 0)
X return (TRUE);
X return (FALSE);
X}
X
X
X/* Read a keystroke from stdin and return the command for it.
X *
X * Single character keystrokes not found in the table generate
X * self-insert commands.
X * Control characters other than \n, and \t must be quoted in order
X * to generate self-insert commands. To quote a char, preceed it
X * by the QUOTEC character.
X * Multi character keystrokes should end in an exact match. If not,
X * throw away the char that caused no matches in the keycmdtab,
X * beep the terminal, and start over.
X */
Xint getcmd()
X{
X char keystroke[10]; /* Chars in keystroke. */
X int nchars; /* Length of keystroke[]. */
X int c;
X int index; /* Cmd index in keycmdtab. */
X int code; /* Cmd code. */
X
X start_over:
X nchars = 0;
X keystroke[0] = 0;
X
X while (TRUE) {
X c = getchar();
X keystroke[nchars++] = c;
X keystroke[nchars] = 0;
X index = srch_ktab(keycmdtab, keystroke);
X switch (index) {
X case SK_SUBSTR:
X continue;
X
X case SK_NOMATCH:
X if (nchars != 1) {
X beep();
X goto start_over;
X }
X code = CINSERT;
X if (c == QUOTEC) {
X c = getchar();
X break;
X }
X else if (printable(c)) {
X break;
X }
X else if ((c != LINEFEED) && (c != TAB)) {
X beep();
X goto start_over;
X }
X break;
X
X default:
X if (index < 0) {
X disperr("Bad keycmdtab index.");
X }
X code = keycmdtab[index].c_code;
X break;
X }
X return ((code << CMDSHIFT) | (c & CHARM));
X }
X}
X
X
X/* Cause the terminal to beep.
X */
Xbeep()
X{
X Puts("\007"); /* C-G */
X}
X
X
X/* Save a copy of the given string on the heap.
X * Return pointer to copy.
X */
Xchar *savestr(s)
Xregister char *s;
X{
X char *p;
X register char *t;
X
X if (s == NULL)
X return( NULL );
X t = p = (char*) calloc(strlen(s) + 1, 1);
X if (t == NULL)
X return(NULL);
X while (*t++ = *s++);
X return(p);
X}
END_OF_terminal.c
if test 19285 -ne `wc -c <terminal.c`; then
echo shar: \"terminal.c\" unpacked with wrong size!
fi
# end of overwriting check
fi
echo shar: End of archive 8 \(of 11\).
cp /dev/null ark8isdone
MISSING=""
for I in 1 2 3 4 5 6 7 8 9 10 11 ; do
if test ! -f ark${I}isdone ; then
MISSING="${MISSING} ${I}"
fi
done
if test "${MISSING}" = "" ; then
echo You have unpacked all 11 archives.
rm -f ark[1-9]isdone ark[1-9][0-9]isdone
else
echo You still need to unpack the following archives:
echo " " ${MISSING}
fi
## End of shell archive.
exit 0