home *** CD-ROM | disk | FTP | other *** search
/ CP/M / CPM_CDROM.iso / cpm / utils / squsq / fcrnch11.lbr / CRN.MZC / CRN.MAC
Text File  |  1986-12-24  |  35KB  |  1,111 lines

  1. title    'CRN v2.5 compressor, 8080 code.'
  2. ;
  3. ; Copyright (c) 1986 Nov. 30 by:
  4. ;    C.B. Falconer, 680 Hartford Tpk, Hamden, Conn.  (203) 281-1438
  5. ; all rights reserved.
  6. ;
  7. ; The original Z80 version from which this was developed is
  8. ; Copyright (c) 1986, Steven Greenberg            (201) 670-8724
  9. ;
  10. ; v25    Added trap for output sequence '0D,40,0D' (with possible hi
  11. ;    bits set) for comm. program compatibility. Injects nop.
  12. ;                    cbf (86/12/19)
  13. ;
  14. ; This program may be copied and used freely for non-profit purposes.
  15. ; It may not be sold nor included in packages for sale without the
  16. ; express written consent of C.B. Falconer.
  17. ;
  18. ; NOTE: 8086 versions of CRN and UNC are under development.  Contact
  19. ; C.B. Falconer.  These will be available in .OBJ (linkable) form,
  20. ; and will be data compatible with 8080 CRN/UNC versions.
  21. ;
  22. ; Adaptation of Steve Greenberg's Crunch algorithm/code to a separate
  23. ; module.  This also enables the use of the algorithm on 8080s.  The
  24. ; adaptive reset criterion has to be different, since this module
  25. ; knows nothing about records, just byte streams.  A further differ-
  26. ; ence is that a 9 bit reset code is always the first thing emitted.
  27. ; I have tried to define a flexible set of interface conventions,
  28. ; with attention to efficiency and clarity.
  29. ;
  30. ; With the clutter of the file system and user interface removed, the
  31. ; elegant simplicity of Mr. Greenbergs algorithm becomes visible.
  32. ;
  33. ; All registers available to these externals.  They may implement
  34. ; any multi-processing desired, abort, monitor, etc.
  35.     extrn    getchr;        next input char to (a). Cy for EOF
  36.     extrn    outbyt;        output char (a). Every 256th output
  37. ;                call has Z flag set, for monitoring.
  38. ;
  39. ; Library linkages. Available in BUFFLIB, (see BUFFERS.LBR), or can
  40. ; be coded separately.  Unsigned arithmetic.
  41.     extrn    .idiv;        de := dehl/bc; hl := dehl MOD bc
  42.     extrn    .imul;        dehl := bc * de
  43. ;
  44. ; This entry is organized as an analogy to UNC. See below for parm.
  45.     entry    crn;        a is parm, hl points to storage.
  46. ;
  47. ; Allow main program to monitor the status.
  48.     entry    incnt, outcnt;    Can monitor counts.
  49.     entry    nxtcod, ttotal;    Can monitor codes/reassignments
  50. ;
  51. rev    equ    25h;    Program revision level
  52. sigrev    equ    20h;    "significant" rev. lvl (compatibility)
  53. ;
  54. ; Bits in input argument "parm", strategy etc.
  55. allfile    equ    080h;    Do not check for pre-squeezed/crunched. This
  56. ;            allows any bit pattern to be processed.
  57. stkset    equ    040h;    Using incoming SP as memtop, no stack switch
  58. csfield    equ    030h;    0..3, value of output checksum flag
  59. ;            0 = normal usage, as crunch/uncr. MOD 65k.
  60. ;            1 = CRC16 checksum, using BUFFLIB routine
  61. ;            2, 3.  Unassigned values.
  62. ;            NOTE: Existing systems will ignore chksums
  63. ;            for all non-zero arguments.
  64. rafield    equ    0ch;    }
  65. lghfld    equ    03h;    } Criteria for adaptive reset triggers
  66. ; The useful discrete values in the low 4 bits, and their effects
  67. ; value    lgh fld    ra fld    reset allowed when
  68. ; -----    -------    ------    ------------------
  69. ; 0    0    0    codlen reaches 10
  70. ; 1    1=11    0    codlen reaches 11
  71. ; 2    2=12    0    codlen reaches 12
  72. ; 3    3=13    0    No adaptive resets allowed
  73. ; 4    0    1    table full and  1024 reassignments
  74. ; 8    0    2      "    "    2048    "
  75. ; 12    0    3      "    "    3072    "
  76. ; 14            Any time
  77. ; 15            Whenever table full
  78. ;
  79. ; Embedded in other programs.  Do not change.  Note that both
  80. ; "impred" and "nopred" include the "used" bit in their values.
  81. nopred    equ    0ffffh;    "no predecessor"
  82. impred    equ    07fffh;    Pred that can't be matched or bumped
  83. tblsize    equ    5003
  84. mincod    equ    9;    minimum bits per code
  85. maxcod    equ    12;    maximum bits per code
  86. crsqhd    equ    076h;    header byte, crunched/squeezed files
  87. crhdr    equ    0feh;    2nd byte, for crunched files
  88. sqhdr    equ    0ffh;    2nd byte, for squeezed files
  89. vacant    equ    080h;    marker for vacant table entries
  90. used    equ    020h;    "used" marker bit, in high order code.
  91. escape    equ    090h;    Repeat encoding marker.
  92. ;
  93. ; Installation configurable values
  94. slop    equ    11;    pages, 8 for CCP allowance
  95. stksize    equ    0;    pages of 256 bytes, when assigned locally
  96. ;            (spare above last table area suffices)
  97. @memtop    equ    6;    Where CPM keeps max memory pointer
  98. ;
  99. ; If both are set to 0 no overhead code is generated. 7 sig. bits.
  100. trp1st    equ    0dh;  (0dh)     Sequence on which to inject nulcod
  101. trp2nd    equ    040h; (040h)    2nd char of sequence.
  102. ;
  103. ; Calculated values
  104. tblroom    equ    (tblsize + 255) AND 0ff00h;    Round up to pages
  105. trapit    equ    (trp1st OR trp2nd) ne 0
  106. ;
  107. ; allowance for 5 column table and stack
  108. pages    equ    (tblroom shr 8) * 5 + stksize
  109.  
  110. ; reserved codes - DO NOT CHANGE
  111. eofcod    equ    100h;    EOF code
  112. rstcod    equ    101h;    Adaptive reset code
  113. nulcod    equ    102h;    Null code
  114. sprcod    equ    103h;    Spare code
  115. ;
  116. ; Error codes.  Nothing sacred here
  117. err1    equ    1;    input file empty
  118. err2    equ    2;    input already squeezed/crunched
  119. err3    equ    3;    memory/stack overflow
  120. ;
  121. spare    equ    5;    filler for "spare" header byte
  122. ;
  123. ; Macro for "horizontal" movement through the table.
  124. ; See "Table structure" comment near "initbl" for more information.
  125. ;
  126. ; move "right" one column (same row)
  127. right1    macro
  128.      mov    a,h
  129.      adi    tblroom shr 8
  130.      mov    h,a
  131.     endm
  132. ;
  133. ;    ----------
  134. ;
  135. ; Relocatable code module begins here.
  136.     cseg
  137. ;
  138.     db    rev;        At crn-1, for reference
  139. ;
  140. ; The caller has already emitted the header word, file name, stamp, 0
  141. ; (needed for crunched format files, optional for other applications).
  142. ; At entry a contains "stategy" byte, hl points to memory area (25k+).
  143. ; At exit the caller must output the checksum field (needed for files,
  144. ; optional for other applications, e.g. communication systems).
  145. ; a,f,b,c,d,e,h,l
  146. crn:    sta    arg;        input arg, for strategy etc.
  147.     xchg;             (crunched checks, adaptive reset)
  148.     lxi    h,0
  149.     dad    sp
  150.     shld    spsave;        for aborts, exit etc.
  151.     call    malloc;        allocate memory
  152.     mvi    a,err3
  153.     rc;            memory overflow, stack not switched
  154.     sphl;            nullop if stkset true
  155.     lxi    h,zerobgn
  156.     lxi    b,zeroend-zerobgn
  157.     call    fillz;        Initialize this data area to 0
  158.     mvi    a,rev;        Output rev level of this program
  159.     call    outb
  160.     mvi    a,sigrev;    Output "significant revision" level
  161.     call    outb
  162.     lda    arg
  163.     ani    csfield;    Mask out checksum control field
  164.     rar;            and reposition as 0..3
  165.     rar
  166.     rar
  167.     rar
  168.     call    outb;        to output stream
  169.     mvi    a,spare
  170.     call    outb;        Output a spare byte of "5"
  171.     call    getc
  172.     jc    xempty;        Input file is empty
  173.     push    psw
  174.     call    getc;        initializes "lastch"
  175.     jc    xempty;        1 byte only, treat as empty
  176.     pop    h;        1st byte to h
  177.     mov    l,a;        2nd byte to l
  178.     lda    arg
  179.     ani    allfile
  180.     jnz    crn1;        omit pre-squeezed/crunched check
  181.     mov    a,h
  182.     cpi    crsqhd
  183.     jnz    crn1;        not squeezed/crunched
  184.     mov    a,l
  185.     cpi    crhdr
  186.     jz    xsqzcr;        already crunched
  187.     cpi    sqhdr
  188.     jz    xsqzcr;        already squeezed
  189. crn1:    mvi    a,01h
  190.     sta    csave;        init "putcd" machine
  191.     mvi    a,mincod
  192.     sta    codlgh;        (crnch inits codlen)
  193.     mov    a,h;        first byte (2nd in lastch)
  194.     lxi    h,normal;    initial state for "nxtch"
  195.     shld    istate
  196.     call    crnch;        Initial reset, with 1st char in a
  197. ;    "    "
  198. ; If no error, checksum still to be output and files closed etc.
  199. ; Enter here with carry for error, a holding error code
  200. exit:    lhld    spsave
  201.     sphl
  202.     ret
  203. ;
  204. ; Error connectors
  205. xsqzcr:    mvi    a,err2;        already squeezed/crunched
  206.     jmp    xexit
  207. xempty:    mvi    a,err1;        "Input file empty"
  208. xexit:    stc
  209.     jmp    exit
  210. ;
  211. ; perform an adaptive reset and crunch the remaining input.
  212. ; Initial byte in (a).
  213. ; Unlike the original, this version always emits an initial "reset".
  214. ; I was going to suppress this, but on reconsideration this is
  215. ; probably useful to synchronize the uncruncher state.
  216. crnch:    push    psw;        Save suffix which has yet to be output
  217.     mvi    a,mincod;    Reset the code length
  218.     sta    codlen;        (codlgh updated by putcd)
  219.     lxi    h,rstcod;    Send (otherwise disallowed) reset code
  220.     call    putcd
  221.     xra    a
  222.     sta    fulflg;        Clear the adaptive reset flag.
  223.     mov    h,a
  224.     mov    l,a;        hl := 0
  225.     shld    nxtcod;        Reset entry # prior to table re-init.
  226.     shld    ttotal;        Reset "codes reassigned"
  227.     mvi    a,1 shl (mincod-8);    Reset target mask value.
  228.     sta    trgmsk
  229.     call    initbl;        Re-initialize the entire LZW table
  230.     mvi    a,0ffh;        Init target compression ratio to max
  231.     sta    lowper;        Goes there
  232.     pop    psw;        Restore suffix char, patiently waiting
  233. ;    "    "
  234. ; *** Main encoding loop ***
  235. ;    "    "
  236. crnch1:    lxi    h,nopred;    Beginning of string
  237. ;    "    "
  238. ; "Match" determines if the combination { <pred>, <suffix>  }, as
  239. ; supplied in { HL, A }, is already in the table.  If it is the
  240. ; matching index value is returned in DE.  If it isn't, it will be
  241. ; added to the table in an appropriate place (assuming the table is
  242. ; not yet filled).  If the table is filled, it may or may not still
  243. ; be added.  Carry flag set indicates NOT found.
  244. crnch2:    push    h
  245.     call    match;        Is { pred, suffix } in the table?
  246.     pop    h
  247.     jnc    crnch4;        found, try to extend string
  248. crnch3:    call    putcd;        not found, send pred (a whole string)
  249.     jnc    crnch1;        start a new string unless
  250.     jmp    crnch;        adaptive reset requested, start over
  251. ;                (assumed to break any bad sequence)
  252. crnch4:    xchg;            Match, discard old pred, replace w/new
  253.     call    nxtch;        A := next byte from "logical" input
  254.     jnc    crnch2;        not EOF
  255. ;    "    "
  256. ; *** End of main encoding loop ***
  257. ;    "    "
  258. ; end of input, flush everything
  259.     call    putcd;        Output the "leftover" code
  260.     lxi    h,eofcod;    Send (otherwise disallowed) "EOF" code
  261.     call    putcd
  262.     lda    csave;        Flush any remaining output
  263.     cpi    01h;        The 1 in 8 chance we're on byte bndry
  264.     mvi    a,0;        last 8 bits of EOF code are 0
  265.     cnz    outb;        If output was not on byte boundary
  266.     xra    a;        no error
  267.     ret
  268. ;    ________________________
  269. ;
  270. ; Initialize the table to contain the 256 "atomic" entries-
  271. ; { "NOPRED", <char> },  for all values of <char> from 0 thru 255
  272. initbl:    call    preset;        "pre-init" the table (mostly zeroes)
  273.     xra    a;        Start with 0
  274. initlp:    push    psw
  275.     lxi    h,nopred;    Use this value for all 256 loops
  276.     call    match;        Make the entry  { hl, a }
  277.     pop    psw;        (incrementing nxtcod for each)
  278.     inr    a
  279.     jnz    initlp;        Next suffix
  280. ;    "    "
  281. ; Reserve entries 100h thru 103h (EOF, RESET, NULL, & SPARE)
  282.     call    resrv2
  283. resrv2:    call    resrv;        (not bumpable or matchable)
  284. ;    "    "        (incrementing nxtcod for each)
  285. ; Reserve the next code (in nxtcod) by assigning with an impossible
  286. ; predecessor.  This makes it unmatchable & unbumpable (eof, etc)
  287. ; f,b,c,d,e,h,l
  288. resrv:    lxi    h,impred
  289. ;    "    "
  290. ; Find a match for { <pred> <suffix> }, as supplied in { HL, A }.
  291. ;  Does one of the following two things:
  292. ;    "    "
  293. ;  (1) Returns the index # of a match in DE, with carry clear
  294. ;  (2) Sets carry & adds new combo to approp. place in "table".
  295. ; f,b,c,d,e,h,l (preserve a)
  296. match:    mov    b,a;        b := suffix supplied
  297. ;    "    "
  298. ; When the table is full the first entry encountered which has been
  299. ; made, yet is still "available" (i.e. it has not been used since the
  300. ; entry was made, guaranteeing it is not referenced by another entry)
  301. ; is saved in "AVAIL".  So we initialize that [special value] zero,
  302. ; meaning "none".
  303.     xchg
  304.     lxi    h,0
  305.     shld    avail;        Mark no re-assignment candidate yet
  306.     push    d;        Save pred
  307.     xchg;            hl := pred for hash
  308. ;    "    "
  309.     call    hash;        hl := initial hash value
  310.     pop    d;        de := pred
  311. match1:    mov    c,h;        C  := extra copy of h
  312.     mov    a,m;        Check if any entry exists at that locn
  313.     cpi    vacant
  314.     jz    insrt;        Empty, use spot to create new entry
  315. ;    "    "
  316.     cpi    0ffh;        Check for a special "atomic" entry
  317.     jz    match2;        If so leave "FF" for matching process
  318.     ani    not used;    Else mask out used flag before match
  319. match2:    cmp    d;        Does entry match pred (hi)
  320.     jnz    match4;        No match here
  321.     right1;            pred (lo)
  322.     mov    a,e
  323.     cmp    m
  324.     jnz    match4;        no match
  325.     right1;            move to suffix
  326.     mov    a,b
  327.     cmp    m
  328.     jnz    match4;        no match
  329. ;    "    "
  330. ; We have a match! But there is one very important "but". If the table
  331. ; is full, and we are in "code reassignment" mode, we must pre-empt
  332. ; the possibility  of generating the WsWsW *** string here in the
  333. ; cruncher. This is because it is impossible to detect these in the
  334. ; uncruncher once all codes are defined.
  335.     lda    fulflg
  336.     ora    a
  337.     jz    match3;        Table not full, not "reassign" phase
  338. ;    "    "
  339.     lda    lpr;        If so, see if this pred/suffix combo
  340.     cmp    e;        - is identical to last one generated
  341.     jnz    match3;        Pred (lo) doesn't match, so all ok
  342.     lda    lsufx;        Check suffix. Order of these 3 checks
  343.     cmp    b;        - is intended to optimized speed (most
  344.     jnz    match3;        - likely "non-matches" first)
  345.     lda    lpr+1;        check pred (hi)
  346.     cmp    d
  347.     jz    match4;        Ugly situation - pretend no match
  348. ;    "    "
  349. ; <pred> <suffix> matched in table
  350. match3:    right1;            3rd right so far
  351.     mov    d,m;        Get entry #, hi byte, for return.
  352.     right1;            and lo byte
  353.     mov    e,m
  354.     mov    h,c;        Normalize. Cancel all those "right"'s
  355.     mov    a,m
  356.     ori    used;        flag entry as "referenced"
  357.     mov    m,a
  358.     mov    a,b;        Restore "a" to its value on entry
  359.     ana    a;        Clear cy flag (return status) & return
  360.     ret
  361. ;
  362. ; No match yet. Norm. to beg of entry.
  363. match4:    mov    h,c
  364.     lda    fulflg
  365.     ora    a
  366.     jz    match5;        Not in code reassignment mode
  367.     mov    a,m
  368.     ani    used
  369.     jnz    match5;        Entry not available for reassignment
  370.     lda    avail+1
  371.     ora    a;
  372.     jnz    match5;        Already have re-assignment candidate
  373.     shld    avail;        Else this physical loc is candidate
  374. ;    "    "
  375. ; Standard hash collision processing. Add "DISP", a variable displace-
  376. ; ment value, for the "secondary probe". DISP was precalculated at the
  377. ; time the original hash value was computed.
  378. ;    "    "
  379. ; Note that I (S.G) have implemented this secondary probe "backwards".
  380. ; Though identically effective (by symmetry), it has a number of speed
  381. ; advantages. When DISP is added, we are really subtracting (DISP was
  382. ; intentionally created to be "negative").  Not only is adding faster
  383. ; than subtracting, but the check for loop around (which is of course
  384. ; passing the beginning of the table) is a one-byte compare (table
  385. ; starts on a page boundary). Furthermore, when loop around occurs, we
  386. ; get to add once again instead of subtracting. (In fact, no subtract-
  387. ; ion is necessary for computing DISP either. See the "HASH" routine).
  388. ;    "    "
  389. match5:    push    d;        Process standard hash collision.
  390.     xchg
  391.     lhld    disp;        Get pre-computed displacement value
  392.     dad    d;        Add displacement to current phys loc
  393.     mov    a,h
  394.     push    h
  395.     lxi    h,@table+1;    table page
  396.     cmp    m;        And check for rollover to table beg
  397.     pop    h
  398.     jnc    match6;        no rollover
  399.     lxi    d,tblsize
  400.     dad    d;        Else tblsize for rollover
  401. match6:    pop    d
  402.     jmp    match1;        Repeat to see if this "link" matches
  403. ;
  404. ; Returns incremented "nxtcod" in de.
  405. ; Arms codlen/trgmsk as needed for any changes in output width.
  406. ; a,f,d,e
  407. nextcd:    xchg
  408.     lhld    nxtcod;        Pre-incr for next code.
  409.     inx    h
  410.     shld    nxtcod;        Save the new value
  411.     xchg
  412.     lda    trgmsk;        See if new code length is necessitated
  413.     cmp    d;        Check hi-byte against target value
  414.     rnz;            Simply return if not
  415.     add    a;        Yes, code length will change
  416.     sta    trgmsk;        Next target mask
  417.     lda    codlen;        Previous code length value (#of bits)
  418.     inr    a;        Increment code length
  419.     cpi    maxcod+1
  420.     jz    fullup;        Too long, table just filled.
  421.     sta    codlen;        Else record new length
  422.     ret
  423.  
  424. fullup:    mvi    a,0ffh;        Flag table full
  425.     sta    fulflg
  426.     ret;            don't update "CODLEN" past 12
  427. ;
  428. ; All "links" to the hashed entry have been checked and none have
  429. ; matched.  We therefore make a new entry if possible
  430. ; of pred de, suffix b.  Exit with a := b(entry)
  431. ; a,f,h,l
  432. insrt:    lda    fulflg;        Is the table full?
  433.     ora    a
  434.     jz    insrt1;        table not full
  435.     lhld    avail;        no empty space. Try for reassignment
  436.     mov    a,h
  437.     ora    l
  438.     jz    insrt2;        No reassignment candidate available
  439. ;    "    "
  440.     push    h
  441.     lhld    ttotal;        Advance "codes reassigned"
  442.     inx    h
  443.     shld    ttotal
  444.     xchg
  445.     shld    lpr;        Save last entry made for "ugly" detect
  446.     xchg
  447.     mov    a,b;        "LPR" <-- last pred,
  448.     sta    lsufx;         "LSUFX" <-- last suffix
  449.     pop    h
  450.     mov    m,d;        Re-assign entry. Leave it's # alone.
  451.     right1
  452.     mov    m,e;        Pred (lo)
  453.     right1
  454.     mov    m,b;        Suffix
  455.     stc
  456.     mov    a,b
  457.     ret
  458.  
  459. ; Make entry into table.
  460. insrt1:    mov    m,d;        Put in pred (hi)
  461.     right1
  462.     mov    m,e;        Pred (lo)
  463.     right1
  464.     mov    m,b;        Suffix
  465. ;    "    "
  466.     call    nextcd;        advance, returns NEXT code
  467.     dcx    d;        back to current entry
  468.     right1;            Move to entry# (lo) column
  469.     mov    m,d;        Put that in
  470.     right1
  471.     mov    m,e;        Likewise entry# (hi)
  472. ;    "    "
  473. insrt2:    stc;            cy indicates new entry (no match)
  474.     mov    a,b;        Return with cy set, "A" intact
  475.     ret
  476. ;
  477. ; Steve Greenbergs input state machine.
  478. ;
  479. ; This creates the "logical" input stream. It gets its data from the
  480. ; "physical" input stream, bet performs repeat byte encoding.  Each
  481. ; call supplies one logical character out. In general there is a one
  482. ; character delay; this character is kept in "lastch".
  483. ;
  484. ; This subroutine is a state machine, where one call defines the state
  485. ; for the following call. It does this by leaving the address of the
  486. ; proper section (which implements the next state) in "istate".
  487. ;
  488. ; This looks a little complicated, but any given call immediately
  489. ; jumps to the appropriate small block of code and does what it
  490. ; should. This routine acts like a filter, taking in bytes one at a
  491. ; time through calls to "getc", and outputting them one at a time via
  492. ; calls to it.
  493. ;
  494. ; TYPE
  495. ;   inputstate = (eofile,     normal,
  496. ;          duplicate,  repeating, dupsdone,
  497. ;          realescape, emitzero);
  498. ; VAR
  499. ;   istate     : inputstate;
  500. ;   lastch     : char;
  501. ;
  502. ; FUNCTION getc : char;
  503. ;
  504. ;   BEGIN (* getc *)
  505. ;   read(lastch); getc := lastch;
  506. ;   END; (* getc *)
  507. ;
  508. ; (* 1---------------1 *)
  509. ;
  510. ; FUNCTION nxtch : char;    (* using Pascal flavor EOF signal *)
  511. ;
  512. ;   VAR
  513. ;     ch1      : char;
  514. ;     count    : integer;
  515. ;
  516. ;   BEGIN (* nxtch *)
  517. ;   ch1 := lastch; nxtch := ch1;    (* defaults *)
  518. ;   CASE istate OF
  519. ; normal:
  520. ;     BEGIN                   (* all cases emit lastch *)
  521. ;     IF      eof        THEN istate := eofile
  522. ;     ELSE IF getc = escape THEN istate := realescape
  523. ;     ELSE IF lastch = ch1  THEN istate := duplicate;
  524. ;     END;
  525. ; duplicate:
  526. ;     BEGIN          (* first emitted already, most cases emit 2nd *)
  527. ;     IF      eof        THEN    istate := eofile
  528. ;     ELSE IF getc = escape THEN    istate := realescape
  529. ;     ELSE IF ch1 = lastch  THEN BEGIN
  530. ;    (* emit *) nxtch := escape; istate := repeating; END
  531. ;     ELSE  (* exactly 2 *)        istate := normal;
  532. ;     END;
  533. ; repeating:
  534. ;     BEGIN   (* 3 up encountered. char, escape emitted. count next *)
  535. ;     count := 3;
  536. ;     REPEAT
  537. ;    IF    eof          THEN istate := eofile
  538. ;    ELSE IF getc = escape THEN istate := realescape
  539. ;    ELSE IF lastch <> ch1 THEN istate := dupsdone
  540. ;       ELSE IF count = 255   THEN istate := dupsdone
  541. ;    ELSE count := succ(count);
  542. ;     UNTIL istate <> repeating;
  543. ;     nxtch := count;            (* emit the count *)
  544. ;     END;
  545. ; dupsdone:              (* after count, cannot start a repeat *)
  546. ;     BEGIN    (* this emits the lastch that terminated "repeating" *)
  547. ;     IF      eof        THEN istate := eofile
  548. ;     ELSE IF getc = escape THEN istate := realescape
  549. ;     ELSE             istate := normal;
  550. ;     END;
  551. ; realescape: (* applying principle of not making funny connections *)
  552. ;     BEGIN        (* thus don't jam lastch to 0 & do dupsdone *)
  553. ;     nxtch := escape; istate := emitzero;
  554. ;     END;
  555. ; emitzero:
  556. ;     BEGIN
  557. ;     nxtch := 0;
  558. ;     IF eof            THEN istate := eofile
  559. ;     ELSE IF getc = escape THEN istate := realescape
  560. ;     ELSE             istate := normal;
  561. ;     END;
  562. ; eofile:
  563. ;       nxtch := endfilemark;
  564. ;     END; (* case *)
  565. ;   END; (* nxtch *)
  566. ;
  567. ; Get next (repeat encoded) byte from input stream.
  568. ; Unlike the coding in SQZ/UNSQ the "number" of repeats is the
  569. ; total number, not just the number added to the initial one.
  570. ; The input char <escape> is represented as <escape> <0> and
  571. ; <ch> <escape> <n> represents n occurences of <ch> (3 <= n <= 255).
  572. ; The expander treats <escape> <1> and <escape> <2> correctly.
  573. ; a,f,d,e
  574. nxtch:    push    h
  575.     lda    lastch
  576.     mov    d,a;        save in d for all states
  577.     lhld    istate
  578.     call    xpchl;        Cases return next state in hl
  579.     shld    istate
  580.     pop    h
  581.     ret
  582. xpchl:    pchl;            implements "call (hl)"
  583. ;
  584. ; Normal state. hl contains "normal"
  585. normal:    call    getc;        Get next byte from phys input stream
  586.     jc    eof;        Br if no more data
  587.     cpi    escape
  588.     jz    escin
  589.     cmp    d;        Compare to last char
  590.     jnz    chgst;        chrs different, emit prev & continue
  591.     lxi    h,duplic;    Set next state to duplic.
  592. ;    "    "
  593. ; Change state to hl (may not be a change)
  594. chgst:    mov    a,d;        output previous lastch
  595.     ora    a;        Clear any carry, not eof
  596.     ret
  597. ;
  598. ; Special state change to delay EOF signal
  599. eof:    lxi    h,eofile;    next state is eofile
  600.     mov    a,d;        emit last char first
  601.     ora    a
  602.     ret
  603. ;
  604. ; A second occurrence of the same character has been detected.
  605. ; So far only one occurrence has been output.
  606. duplic:    call    getc;        Get new byte from input stream
  607.     jc    eof
  608.     cpi    escape;        (Repeats of 90H cannonot be packed)
  609.     jz    escin
  610.     cmp    d;        Another repeat (3rd contiguous)?
  611.     lxi    h,normal
  612.     jnz    chgst;        Only 2, back to normal
  613.     mvi    a,escape;    Jam output to escape
  614.     lxi    h,repeat;    Next state counts
  615.     ret;            cy is clear
  616. ;
  617. ; Three contiguous occurrences of a byte been detected. The byte
  618. ; itself and the escape have already been output. Now it is time to
  619. ; suck up characters (up to 255 of them).
  620. repeat:    mov    e,d;        Byte to be matched will be kept in e
  621.     mvi    d,3;        Init d, repeat byte counter, to 3
  622. rept1:    call    getc;        Get next byte
  623.     jc    eof
  624.     inr    d;        test max repeat byte counter
  625.     jz    rept2;        255 contiguous occurrences
  626.     dcr    d;        form the real count so far
  627.     cpi    escape;        *** watch order of events here! ***
  628.     jz    escin
  629.     inr    d;        finally count it, if still same
  630.     cmp    e
  631.     jz    rept1;        still same, test next input
  632. rept2:    dcr    d;        re-adjust count
  633. ;    "    "
  634. ; Transfer to non-repeatable emission state
  635. godun:    lxi    h,dupdun;    Change to dupdun (cleanup)
  636.     mov    a,d
  637.     ora    a
  638.     ret
  639. ;
  640. ; Like normal state, but don't look for a match. Terminates repeat.
  641. ; (because the last byte output was a count).
  642. dupdun:    call    getc;        Get next character
  643.     jc    eof
  644.     cpi    escape
  645.     jz    escin;        escape encountered
  646.     lxi    h,normal;    Next state
  647.     mov    a,d
  648.     ora    a
  649.     ret
  650. ;
  651. ; "escape" has been encountered, byte before it has been output.
  652. ; Now output escape, followed by output 00h.
  653. escout:    mvi    a,escape;    State doesn't get another phys char
  654.     lxi    h,emit00;    Next state will emit the 0
  655.     ora    a
  656.     ret
  657. ;
  658. ; escape has been encountered and output.  Now output "00h"
  659. emit00:    call    getc;        Get next physical char
  660.     mvi    d,0
  661.     jc    eof
  662.     cpi    escape
  663.     jnz    godun;        go emit the 0
  664. ;    "    "
  665. ; Escape char. appeared in input
  666. escin:    lxi    h,escout;    Set next state to escout
  667.     mov    a,d
  668.     ora    a
  669.     ret
  670. ;
  671. ; EOF has been encountered, and all bytes have been output.
  672. ; Set carry flag and return.
  673. eofile:    stc
  674.     ret
  675. ;
  676. ;    ------ END of state machine ------
  677. ;
  678.     if    trapit
  679. ; Emit a nul code.  prechk has stacked the actual output code.
  680. ; This assumes nulcode really does break up the evil sequence,
  681. ; which may not be warranted.  nulcode should have been 0, with
  682. ; a 2nd available nulcode of 0ffh to cater to all cases.
  683. sndnul:     lxi    h,trpflg
  684.      mvi    m,0;        reset the flag. Only 1 recursion
  685.      lxi    h,nulcod
  686.     endif
  687. ;     "    "
  688. ; Insert the pred now in HL into the output stream, length codlgh.
  689. ; This returns carry to indicate adaptive reset needed.  If trpflg
  690. ; is set to 01h on exit, the output sequence trp1st,trp2nd has been
  691. ; detected, and it is up to the cruncher to take steps.  Note that
  692. ; trpflg can take on other intermediate values (w/o ls bit set).
  693. ; Emits at least 1 (9 bit min codelgh), at most 2 (12 + 7 leftover)
  694. ; bytes of output code.  The 2 byte case creates problems, because
  695. ; an 'inject nulcode' signal reaches the main system too late. In
  696. ; this case the prewarning flag is already set
  697. ; f,b,c,h,l
  698. putcd:    ora    a;        clear any carry
  699.     push    psw
  700.     if    trapit
  701.      lda    trpflg
  702.      ora    a
  703.      cnz    prechk;        If 01 must inject null, else watch out
  704.      jnc    putcd1;        no carry from prechk
  705.      pop    psw
  706.      stc;            preserve reset flag
  707.      push    psw
  708. putcd1:
  709.     endif
  710.     call    setup
  711.     mov    b,c;        b := codlgh
  712. putcd2:    dad    h;        shift out bits
  713.     ral;            from hl into a
  714.     jnc    putcd4;        not yet time to dump the contents
  715.     if    trapit;        check for bad output sequence
  716.      mov    c,a
  717.      call    trpchk
  718.      mov    a,c
  719.     endif
  720.     call    outb;        Dump assembled byte
  721.     jnc    putcd3;        no reset signal found
  722.     pop    psw;        Set the reset flag
  723.     stc
  724.     push    psw
  725. putcd3:    mvi    a,01h;        re-init to flag bit only
  726. putcd4:    dcr    b
  727.     jnz    putcd2;        For as many bits as need to be output
  728.     sta    csave;        save any bits left over
  729.     lda    codlen;        "codlgh" always = "CODLEN" delayed
  730.     sta    codlgh;        -by one code output call. Update here.
  731.     pop    psw
  732.     ret
  733. ;
  734. ; Setup a & hl to form next output byte(s). Set c := codlgh, b := 0
  735. ; a,f,b,c,h,l
  736. setup:    lda    codlgh;        Compute number of pre-shifts
  737.     mov    c,a;        c := codlgh
  738.     cma
  739.     adi    4+(maxcod+1)
  740.     mov    b,a;        b := 16 - codlgh (4..7)
  741. setup1:    dad    h;        position code at left of word
  742.     dcr    b
  743.     jnz    setup1;
  744.     lda    csave;        Get "leftover" bits from last time
  745.     ret
  746. ;
  747.     if    trapit
  748. ; Check for anomolous sequence, next output in c (and a on entry)
  749. ; a,f (trpflg)
  750. trpchk:     ani    07fh
  751.      cpi    trp2nd
  752.      jnz    trpchk3;    not a bad sequence
  753.      lda    trpflg
  754.      ora    a
  755.      jp    trpchk3;    not bad sequence, reset flag
  756.      mvi    a,1;        trp1st,trp2nd detected
  757.      jmp    trpchk4;    set injection flag
  758. trpchk3: xra    a
  759.      sta    trpflg;        clear any previous flag
  760.      mov    a,c
  761.      ani    07fh
  762.      cpi    trp1st
  763.      rnz;            not a trap startup
  764.      mvi    a,080h
  765. trpchk4: sta    trpflg;        mark start of sequence
  766.      ret
  767. ;
  768. ; precheck.  trpflg in a, is non-zero.  New output code is in hl
  769. prechk:     push    h;        If minus then real precheck needed
  770.      jm    prechk1;    else trpflg=01, inject right now
  771.      call    sndnul;        which resets the flag
  772.      pop    h;        now this has to go
  773.      ret;            If cy, then need reset
  774.  
  775. prechk1: call    setup;        take advance look at the next code
  776. prechk2: dad    h;        form the next code to ship
  777.      ral;            by shifting out from hl into a
  778.      jnc    prechk2;    For as many bits as need to be output
  779. ;     "    "
  780. ; Now, if the look ahead byte a is 'trp2nd' must inject nulcode,
  781. ; else go back and let the main system proceed
  782.      ani    07fh
  783.      sui    trp2nd
  784.      ora    a;        make sure cy reset here
  785.      cz    sndnul;        which resets the flag, may return cy.
  786.      pop    h;        else leave it alone
  787.      ret;            and now do the real output operation
  788.     endif
  789. ;
  790. ; Output byte (a) to output stream.  Every 256th  call to
  791. ; "outbyt" is made with the Z flag set, to signal any monitors.
  792. ; a,f
  793. outb:    push    h
  794.     push    d
  795.     push    b
  796.     lxi    h,outcnt;    Minimize calls here
  797.     inr    m
  798.     cz    dincma;        count output bytes
  799.     call    outbyt
  800.     ora    a;        clear any carry (errors up to outbyt)
  801.     lxi    h,outct2
  802.     inr    m
  803.     cz    chkadp;        check adaptive reset
  804.     pop    b
  805.     pop    d
  806.     pop    h
  807.     ret
  808. ;
  809. ; Get byte (a) from the input stream.  Carry for eof
  810. ; a,f
  811. getc:    push    h
  812.     push    d
  813.     push    b
  814.     lxi    h,incnt
  815.     inr    m
  816.     cz    dincm;        Count input bytes
  817.     lxi    h,incnt2;    This one may start over.
  818.     inr    m
  819.     cz    dincm;        for adaptive operations
  820.     call    getchr;        Get a char into a
  821.     sta    lastch;        For encoder system
  822.     pop    b
  823.     pop    d
  824.     pop    h
  825.     ret;            With any carry from "getchr"
  826. ;
  827. ; Dincm with z flag preserved.  Allows monitor signal to main
  828. dincma:    push    psw
  829.     call    dincm
  830.     pop    psw
  831.     ret
  832. ;
  833. ; Increment 24 bit word hl+1^.
  834. ; f,h,l
  835. dincm:    inx    h
  836.     inr    m;        carry, 2nd byte
  837.     rnz
  838.     inx    h
  839.     inr    m;        carry, 3rd byte
  840.     rnz
  841.     inx    h
  842.     inr    m;        MS byte.  No more propagation
  843.     ret
  844. ;
  845. ;    ------     The following code may be reused in UNC    ------
  846. ;
  847. ; Clear the table
  848. ; a,f,b,c,e,h,l
  849. preset:    lhld    @table;        Beg of table (1st entry, first column)
  850.     mvi    e,vacant;    Init whole 1st column to empty flags
  851.     lxi    b,tblroom
  852.     call    fill
  853.     lxi    b,3*tblroom + tblsize;    Next 4 x 1400h locs all get 0s
  854. ;    "    "
  855. ; Fill hl^, length b, with 0
  856. ; a,f,b,c,e,h,l
  857. fillz:    mvi    e,0
  858. ;    "    "
  859. ; Fill hl^, length b, with e
  860. ; a,f,b,c,h,l
  861. fill:    mov    m,e
  862.     inx    h
  863.     dcx    b
  864.     mov    a,b
  865.     ora    c
  866.     jnz    fill
  867.     ret
  868. ;
  869. ; Notes about the hashing. The "open-addressing, double hashing"
  870. ; scheme used, where the actual codes output are the logical entry#,
  871. ; contained in the table along with the entry itself, would normally
  872. ; make the codes output independent of the exact hashing scheme used
  873. ; (codes are simply assigned in order - their physical location    is
  874. ; irrelevant).  However, with code reassignment implemented, the
  875. ; re-assignments are obviously not made in any particular order, and
  876. ; are hash function dependent. Thus hash function must not be changed.
  877. ;
  878. ; Called with pred in HL (3 nybble quantity) and suffix in A (2
  879. ; nybbles).  Exclusive or's the upper 2 nybbles of the pred with the
  880. ; suffix for the two ls nybbles of the result. The lower nybble of the
  881. ; pred becomes the highest of 3 nybble result. Adds one to that, as
  882. ; well as the table offset, resulting in a usable address, returned in
  883. ; HL.  Also compute "DIFF", the secondary hash displacement value, as
  884. ; a negative number.
  885. ;
  886. hash:    mov    e,l;        Save low nybble of pred, used below
  887.     dad    h
  888.     dad    h
  889.     dad    h
  890.     dad    h;        Shift whole pred value left 4 bits
  891.     xra    h;        Xor hi-byte of that with suffix
  892.     mov    l,a;        Goes there as lo-byte of result
  893.     mov    a,e;        Get pred(lo) saved above
  894.     ani    0fh;        Want only low nybble of that
  895.     push    h
  896.     lxi    h,@table+1
  897.     add    m;        Convenient time to add in table offset
  898.     pop    h
  899.     mov    h,a;        Goes here as hi-byte of result
  900.     inx    h;        Except add 1. Eliminates poss. of 0.
  901.     push    h;        Save hash val for return
  902.     xchg
  903.     lhld    delta;        holds -(5003 + (@table)).
  904.     dad    d;        de := tblsize - hash (no table offset)
  905.     shld    disp;        Secondary hash value, negative number.
  906.     pop    h;        Return orig hash address
  907.     ret
  908. ;
  909. ; Allocate memory, from de^ up. Minimum stk use (2 wds from call crn)
  910. ; to allow for main programs with insufficient stack allocation.
  911. ; Carry for any memory overflow.  Returns hl = new stack ptr
  912. ; a,f,b,c,d,e,h,l
  913. malloc:    lxi    h,255
  914.     dad    d
  915.     mvi    l,0;        Round up to page boundary
  916.     shld    @table
  917.     xchg
  918.     lxi    b,-tblsize
  919.     mov    a,c
  920.     sub    e
  921.     mov    l,a
  922.     mov    a,b
  923.     sbb    d
  924.     mov    h,a
  925.     shld    delta;        -(tblsize+(@table))
  926.     xchg;            hl := @table
  927.     lda    arg
  928.     ani    stkset
  929.     jz    mallo1;        Not using SP as memtop marker
  930. ;    "    "
  931. ; Ensure sufficient memory available, SP marks top available
  932.     mvi    a,5 * (tblroom shr 8)
  933.     add    h
  934.     mov    d,a;        top of table storage area + a bit
  935.     lxi    h,2
  936.     dad    sp;        to return stack ptr on "call malloc"
  937.     lda    spsave+1;    proposed should be below existing
  938.     sub    d;        carry if insufficient memory
  939.     ret;            (run time does dynamic checking)
  940. ;
  941. ; General purpose allocation, assign our own stack space
  942. mallo1:    mvi    a,pages;    for table and stack
  943.     add    h
  944.     mov    h,a;        proposed stack top
  945.     lda    @memtop+1;    top page of memory +1
  946.     sui    slop;        allow for CCP retention etc.
  947.     sub    h
  948.     rc;            Too big 
  949.     lda    spsave+1
  950.     sub    h
  951.     rnc;            original stack above our area, ok
  952.     xchg
  953.     lhld    spsave
  954.     xchg;            (@table was rounded up to fresh page)
  955.     lda    @table+1;    Ensure orig stack below table area
  956.     inr    d;        equal is not good enough
  957.     sub    d;        carry if insufficient memory
  958.     ret
  959. ;
  960. ;    --- END of reusable code area    ---
  961. ;
  962. ; This implements the adaptive reset criteria.
  963. ;
  964. ; Advance OUTCT2 = hl^ (adaptive counter for output).  Can check
  965. ; INCNT2 if desired, may zero both counters, and returns carry
  966. ; to request an adaptive reset.  Called ONLY from outbyt when
  967. ; the ls byte of outct2 has just rolled over (to 0).  The carry
  968. ; has NOT been propagated yet. Carry is clear on entry.
  969. ;
  970. ; Since called only once per 256 output bytes, this can afford
  971. ; to use the full multiply divide cycle (about 1.5 Millisec at
  972. ; 2 Mhz clocks).  The delays will be trivial, especially since
  973. ; this usually corresponds to about 512 input bytes.
  974. ;
  975. ; This is designed to allow "fooling around" with the stategy
  976. ; at a higher coding level, to optimize the squeezing ratios.
  977. ; The useful discrete values and their effects
  978. ; value    lgh fld    ra fld    reset allowed when
  979. ; -----    ------    -------    ------------------
  980. ; 0    0    0    codlen reaches 10
  981. ; 1    1=11    0    codlen reaches 11
  982. ; 2    2=12    0    codlen reaches 12
  983. ; 3    3=13    0    No adaptive resets allowed
  984. ; 4    0    1    table full and  1024 reassignments
  985. ; 8    0    2      "    "    2048    "
  986. ; 12    0    3      "    "    3072    "
  987. ; 14            Any time
  988. ; 15            Whenever table full
  989. ; a,f,b,c,d,e,h,l (available)
  990. chkadp:    call    dincm;        outct2 pointer passed in
  991.     lda    arg
  992.     ani    0fh;        Strategy field
  993.     cpi    0fh
  994.     jz    chka1;        0fh = original strategy
  995.     cpi    0eh
  996.     jz    chka2;        Whenever ratio allows
  997.     mov    b,a
  998.     ani    lghfld
  999.     adi    10;        Bits 0/1 are codelen field
  1000.     mov    c,a;        Range 10..13
  1001. ;    "    "
  1002. ; Use values 10..13 to inhibit reset unless (codlen >= value)
  1003. ; i.e. 10 allows reset when codlgh reaches 10, 13 inhibits any
  1004. ; resets being generated.
  1005. ; or up, etc.
  1006.     lda    codlen;        9..12
  1007.     sub    c
  1008.     cmc
  1009.     rnc;            length not above criteria
  1010. ;    "    "
  1011. ; Field values 0..3 specify the minimum (* 1 k) number of code
  1012. ; reassignments that must be performed before resetting.
  1013. ; A non-zero value here effectively forces the above codlen=12
  1014.     mov    a,b
  1015.     ani    rafield
  1016.     mov    c,a;        range 0, 4, 8, 12
  1017.     lda    ttotal+1;    high byte
  1018.     sub    c
  1019.     cmc
  1020.     rnc;            count not at criterion
  1021.     jmp    chka2;        bypass fulflg check
  1022.         
  1023. chka1:    lda    fulflg;        original algorithm
  1024.     ora    a
  1025.     rz;            Not full, no reset
  1026. ;    "    "
  1027. ; Check the squeezing efficiency, incremental since last reset.
  1028. chka2:    lhld    incnt2+1;    In 256 byte blocks (truncated)
  1029.     push    h
  1030.     lhld    outct2+1
  1031.     xchg
  1032.     lxi    b,100
  1033.     call    .imul
  1034.     pop    b
  1035.     call    .idiv;        de := percentage, assumed < 255
  1036.     mov    a,d
  1037.     ora    a
  1038.     jnz    chka6;        >255%, reset it
  1039.     lda    lowper
  1040.     cmp    e
  1041.     jnc    chka9;        ratio got smaller, record it
  1042.     inr    a
  1043.     sub    e
  1044.     rnc;            change <= 1%, not enough
  1045. chka6:    lxi    h,incnt2
  1046.     lxi    b,ctrspc
  1047.     call    fillz;        zero the incrmental counters
  1048.     stc
  1049.     ret;            carry signals reset needed
  1050.  
  1051. chka9:    mov    a,e
  1052.     sta    lowper;        ratio got smaller    
  1053.     ret
  1054. ;
  1055. ;    -----------
  1056. ;
  1057.     dseg;        DATA area.  Preserves any word alignment.
  1058. ;
  1059. zerobgn    equ    $;        BEGIN of initially zeroed area
  1060. ;
  1061. ; Output machine
  1062. codlen:    ds    1;    mincod..maxcod only; Lgh after current output.
  1063. codlgh:    ds    1;      "        "    ".    Current output length
  1064. csave:    ds    1;    Bits (& marker) not yet emitted
  1065. ;
  1066. ; Input machine
  1067. lastch:    ds    1;    input awaiting use
  1068. istate:    ds    2;    Current state, a routine pointer.
  1069. ;
  1070. ; Encoding variables
  1071. disp:    ds    2;    -ve displacement for rehashing
  1072. nxtcod:    ds    2;    (formerly 'entry').  Next to be assigned.
  1073. trgmsk:    ds    1;    target mask. When code hi byte = advance lgh.
  1074. fulflg:    ds    1;    set when table full. Marks reassignment phase
  1075. ;
  1076. ; Counters.  Allow for CPM3 files and communications systems.
  1077. ; Maintain the in/out order, may be used in code.
  1078. ttotal:    ds    2;    codes reassigned
  1079. incnt:    ds    4;    bytes received, total
  1080. outcnt:    ds    4;    bytes emitted, total
  1081. incnt2:    ds    4;    bytes received, incremental.
  1082. outct2:    ds    4;    bytes emitted, incremental.
  1083. ctrspc    equ    $-incnt2;    storage for incremental counters only
  1084. lowper:    ds    1;    For adaptive reset trigger calculations
  1085. ;
  1086. ; "Ugly" detection in re-assignment phase
  1087. lsufx:    ds    1;    last suffix
  1088. lpr:    ds    2;    last pred
  1089. ;
  1090. ; Communications. sequence trapping
  1091.     if    trapit;    also keep word aligned
  1092. trpflg:     ds    1;    zero if no 0dh output detected
  1093.      ds    1;    -ve if 0dh detected, but not trapped
  1094.     endif;        01  when sequence 0dh 040h detected
  1095. ;
  1096. zeroend    equ    $;        END of initially zeroed area
  1097. ;
  1098. ; table storage location related variables
  1099. @table:    ds    2;    location of master table
  1100. avail:    ds    2;    for reassignment. 0 if none. An address.
  1101. delta:    ds    2;    Precalculated displacement for hashing
  1102. ;            (-tblsize-table)
  1103. spsave:    ds    2;    entry sp
  1104. ;
  1105. ; Input argument, for strategy, allocation mechanism, etc.
  1106. arg:    ds    1;    0fh value is similar to CRUNCH23
  1107.     ds    1;    spare data byte, keep aligned.
  1108.     end
  1109. 6X